d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
312 lines
9.6 KiB
TypeScript
312 lines
9.6 KiB
TypeScript
import { createLogger } from '@sim/logger'
|
|
import { ChartBarIcon } from '@/components/icons'
|
|
import type { BlockConfig, ParamType } from '@/blocks/types'
|
|
import {
|
|
getModelOptions,
|
|
getProviderCredentialSubBlocks,
|
|
PROVIDER_CREDENTIAL_INPUTS,
|
|
} from '@/blocks/utils'
|
|
import { getBaseModelProviders } from '@/providers/models'
|
|
import type { ProviderId } from '@/providers/types'
|
|
import type { ToolResponse } from '@/tools/types'
|
|
|
|
const logger = createLogger('EvaluatorBlock')
|
|
|
|
interface Metric {
|
|
name: string
|
|
description: string
|
|
range: {
|
|
min: number
|
|
max: number
|
|
}
|
|
}
|
|
|
|
interface EvaluatorResponse extends ToolResponse {
|
|
output: {
|
|
content: string
|
|
model: string
|
|
tokens?: {
|
|
prompt?: number
|
|
completion?: number
|
|
total?: number
|
|
}
|
|
cost?: {
|
|
input: number
|
|
output: number
|
|
total: number
|
|
}
|
|
[metricName: string]: any // Allow dynamic metric fields
|
|
}
|
|
}
|
|
|
|
export const generateEvaluatorPrompt = (metrics: Metric[], content: string): string => {
|
|
// Filter out invalid/incomplete metrics first
|
|
const validMetrics = metrics.filter((m) => m?.name && m.range)
|
|
|
|
// Create a clear metrics description with name, range, and description
|
|
const metricsDescription = validMetrics
|
|
.map(
|
|
(metric) =>
|
|
`"${metric.name}" (${metric.range.min}-${metric.range.max}): ${metric.description || ''}` // Handle potentially missing description
|
|
)
|
|
.join('\n')
|
|
|
|
// Format the content properly - try to detect and format JSON
|
|
let formattedContent = content
|
|
try {
|
|
// If content looks like JSON (starts with { or [)
|
|
if (
|
|
typeof content === 'string' &&
|
|
(content.trim().startsWith('{') || content.trim().startsWith('['))
|
|
) {
|
|
// Try to parse and pretty-print
|
|
const parsedContent = JSON.parse(content)
|
|
formattedContent = JSON.stringify(parsedContent, null, 2)
|
|
}
|
|
// If it's already an object (shouldn't happen here but just in case)
|
|
else if (typeof content === 'object') {
|
|
formattedContent = JSON.stringify(content, null, 2)
|
|
}
|
|
} catch (e) {
|
|
logger.warn('Warning: Content may not be valid JSON, using as-is', { e })
|
|
formattedContent = content
|
|
}
|
|
|
|
// Generate an example of the expected output format using only valid metrics
|
|
const exampleOutput = validMetrics.reduce(
|
|
(acc, metric) => {
|
|
// Ensure metric and name are valid before using them
|
|
if (metric?.name) {
|
|
acc[metric.name.toLowerCase()] = Math.floor((metric.range.min + metric.range.max) / 2) // Use middle of range as example
|
|
} else {
|
|
logger.warn('Skipping invalid metric during example generation:', metric)
|
|
}
|
|
return acc
|
|
},
|
|
{} as Record<string, number>
|
|
)
|
|
|
|
return `You are an objective evaluation agent. Analyze the content against the provided metrics and provide detailed scoring.
|
|
|
|
Evaluation Instructions:
|
|
- You MUST evaluate the content against each metric
|
|
- For each metric, provide a numeric score within the specified range
|
|
- Your response MUST be a valid JSON object with each metric name as a key and a numeric score as the value
|
|
- IMPORTANT: Use lowercase versions of the metric names as keys in your JSON response
|
|
- Follow the exact schema of the response format provided to you
|
|
- Do not include explanations in the JSON - only numeric scores
|
|
- Do not add any additional fields not specified in the schema
|
|
- Do not include ANY text before or after the JSON object
|
|
|
|
Metrics to evaluate:
|
|
${metricsDescription}
|
|
|
|
Content to evaluate:
|
|
${formattedContent}
|
|
|
|
Example of expected response format (with different scores):
|
|
${JSON.stringify(exampleOutput, null, 2)}
|
|
|
|
Remember: Your response MUST be a valid JSON object containing only the lowercase metric names as keys with their numeric scores as values. No text explanations.`
|
|
}
|
|
|
|
// Simplified response format generator that matches the agent block schema structure
|
|
const generateResponseFormat = (metrics: Metric[]) => {
|
|
// Filter out invalid/incomplete metrics first
|
|
const validMetrics = metrics.filter((m) => m?.name)
|
|
|
|
// Create properties for each metric
|
|
const properties: Record<string, any> = {}
|
|
|
|
// Add each metric as a property
|
|
validMetrics.forEach((metric) => {
|
|
// We've already filtered, but double-check just in case
|
|
if (metric?.name) {
|
|
properties[metric.name.toLowerCase()] = {
|
|
type: 'number',
|
|
description: `${metric.description || ''} (Score between ${metric.range?.min ?? 0}-${metric.range?.max ?? 'N/A'})`, // Safely access range
|
|
}
|
|
} else {
|
|
logger.warn('Skipping invalid metric during response format property generation:', metric)
|
|
}
|
|
})
|
|
|
|
// Return a proper JSON Schema format
|
|
return {
|
|
name: 'evaluation_response',
|
|
schema: {
|
|
type: 'object',
|
|
properties,
|
|
// Use only valid, lowercase metric names for the required array
|
|
required: validMetrics
|
|
.filter((metric) => metric?.name)
|
|
.map((metric) => metric.name.toLowerCase()),
|
|
additionalProperties: false,
|
|
},
|
|
strict: true,
|
|
}
|
|
}
|
|
|
|
export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
|
|
type: 'evaluator',
|
|
name: 'Evaluator',
|
|
description: 'Evaluate content',
|
|
longDescription:
|
|
'This is a core workflow block. Assess content quality using customizable evaluation metrics and scoring criteria. Create objective evaluation frameworks with numeric scoring to measure performance across multiple dimensions.',
|
|
docsLink: 'https://docs.sim.ai/workflows/blocks/evaluator',
|
|
category: 'blocks',
|
|
bgColor: '#4D5FFF',
|
|
icon: ChartBarIcon,
|
|
subBlocks: [
|
|
{
|
|
id: 'metrics',
|
|
title: 'Evaluation Metrics',
|
|
type: 'eval-input',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'content',
|
|
title: 'Content',
|
|
type: 'long-input',
|
|
placeholder: 'Enter the content to evaluate',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'model',
|
|
title: 'Model',
|
|
type: 'combobox',
|
|
placeholder: 'Type or select a model...',
|
|
required: true,
|
|
defaultValue: 'claude-sonnet-5',
|
|
options: getModelOptions,
|
|
},
|
|
...getProviderCredentialSubBlocks(),
|
|
{
|
|
id: 'temperature',
|
|
title: 'Temperature',
|
|
type: 'slider',
|
|
min: 0,
|
|
max: 2,
|
|
hidden: true,
|
|
},
|
|
{
|
|
id: 'systemPrompt',
|
|
title: 'System Prompt',
|
|
type: 'code',
|
|
hidden: true,
|
|
value: (params: Record<string, any>) => {
|
|
try {
|
|
const metrics = params.metrics || []
|
|
|
|
// Process content safely
|
|
let processedContent = ''
|
|
if (typeof params.content === 'object') {
|
|
processedContent = JSON.stringify(params.content, null, 2)
|
|
} else {
|
|
processedContent = String(params.content || '')
|
|
}
|
|
|
|
// Generate prompt and response format directly
|
|
const promptText = generateEvaluatorPrompt(metrics, processedContent)
|
|
const responseFormatObj = generateResponseFormat(metrics)
|
|
|
|
// Create a clean, simple JSON object
|
|
const result = {
|
|
systemPrompt: promptText,
|
|
responseFormat: responseFormatObj,
|
|
}
|
|
|
|
return JSON.stringify(result)
|
|
} catch (e) {
|
|
logger.error('Error in systemPrompt value function:', { e })
|
|
// Return a minimal valid JSON as fallback
|
|
return JSON.stringify({
|
|
systemPrompt: 'Evaluate the content and return a JSON with metric scores.',
|
|
responseFormat: {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {},
|
|
additionalProperties: true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
},
|
|
},
|
|
],
|
|
tools: {
|
|
access: [
|
|
'openai_chat',
|
|
'anthropic_chat',
|
|
'google_chat',
|
|
'xai_chat',
|
|
'deepseek_chat',
|
|
'deepseek_reasoner',
|
|
],
|
|
config: {
|
|
tool: (params: Record<string, any>) => {
|
|
const model = params.model || 'gpt-4o'
|
|
if (!model) {
|
|
throw new Error('No model selected')
|
|
}
|
|
const tool = getBaseModelProviders()[model as ProviderId]
|
|
if (!tool) {
|
|
throw new Error(`Invalid model selected: ${model}`)
|
|
}
|
|
return tool
|
|
},
|
|
},
|
|
},
|
|
inputs: {
|
|
metrics: {
|
|
type: 'json' as ParamType,
|
|
description: 'Evaluation metrics configuration',
|
|
schema: {
|
|
type: 'array',
|
|
properties: {},
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
description: 'Name of the metric',
|
|
},
|
|
description: {
|
|
type: 'string',
|
|
description: 'Description of what this metric measures',
|
|
},
|
|
range: {
|
|
type: 'object',
|
|
properties: {
|
|
min: {
|
|
type: 'number',
|
|
description: 'Minimum possible score',
|
|
},
|
|
max: {
|
|
type: 'number',
|
|
description: 'Maximum possible score',
|
|
},
|
|
},
|
|
required: ['min', 'max'],
|
|
},
|
|
},
|
|
required: ['name', 'description', 'range'],
|
|
},
|
|
},
|
|
},
|
|
model: { type: 'string' as ParamType, description: 'AI model to use' },
|
|
...PROVIDER_CREDENTIAL_INPUTS,
|
|
temperature: {
|
|
type: 'number' as ParamType,
|
|
description: 'Response randomness level (low for consistent evaluation)',
|
|
},
|
|
content: { type: 'string' as ParamType, description: 'Content to evaluate' },
|
|
},
|
|
outputs: {
|
|
content: { type: 'string', description: 'Evaluation results' },
|
|
model: { type: 'string', description: 'Model used' },
|
|
tokens: { type: 'json', description: 'Token usage' },
|
|
cost: { type: 'json', description: 'Cost information' },
|
|
} as any,
|
|
}
|