Skip to content

Commit e81afa5

Browse files
authored
added cost tracking for router & evaluator blocks, consolidated model information into a single file, hosted keys for evaluator & router, parallelized unit tests (#516)
1 parent 537e17d commit e81afa5

File tree

32 files changed

+1658
-629
lines changed

32 files changed

+1658
-629
lines changed

apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
1414
import type { OAuthProvider } from '@/lib/oauth/oauth'
1515
import { cn } from '@/lib/utils'
1616
import { getAllBlocks } from '@/blocks'
17-
import { supportsToolUsageControl } from '@/providers/model-capabilities'
18-
import { getProviderFromModel } from '@/providers/utils'
17+
import { getProviderFromModel, supportsToolUsageControl } from '@/providers/utils'
1918
import { useCustomToolsStore } from '@/stores/custom-tools/store'
2019
import { useGeneralStore } from '@/stores/settings/general/store'
2120
import { useSubBlockStore } from '@/stores/workflows/subblock/store'

apps/sim/app/w/[id]/hooks/use-workflow-execution.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,13 @@ export function useWorkflowExecution() {
9292
const streamingBlockId = (result.metadata as any)?.streamingBlockId || null
9393

9494
for (const log of enrichedResult.logs) {
95-
// Only update the specific agent block that was streamed
95+
// Only update the specific LLM block (agent/router) that was streamed
9696
const isStreamingBlock = streamingBlockId && log.blockId === streamingBlockId
97-
if (isStreamingBlock && log.blockType === 'agent' && log.output?.response) {
97+
if (
98+
isStreamingBlock &&
99+
(log.blockType === 'agent' || log.blockType === 'router') &&
100+
log.output?.response
101+
) {
98102
log.output.response.content = streamContent
99103
}
100104
}

apps/sim/app/w/logs/components/sidebar/sidebar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,8 @@ export function Sidebar({
556556
{isWorkflowWithCost && (
557557
<div className='border-t bg-muted p-3 text-muted-foreground text-xs'>
558558
<p>
559-
This is the total cost for all agent blocks in this workflow execution.
559+
This is the total cost for all LLM-based blocks in this workflow
560+
execution.
560561
</p>
561562
</div>
562563
)}

apps/sim/blocks/blocks/agent.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { AgentIcon } from '@/components/icons'
22
import { isHosted } from '@/lib/environment'
33
import { createLogger } from '@/lib/logs/console-logger'
4-
import { MODELS_TEMP_RANGE_0_1, MODELS_TEMP_RANGE_0_2 } from '@/providers/model-capabilities'
5-
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
4+
import {
5+
getAllModelProviders,
6+
getBaseModelProviders,
7+
getHostedModels,
8+
MODELS_TEMP_RANGE_0_1,
9+
MODELS_TEMP_RANGE_0_2,
10+
providers,
11+
} from '@/providers/utils'
612
import { useOllamaStore } from '@/stores/ollama/store'
713
import type { ToolResponse } from '@/tools/types'
814
import type { BlockConfig } from '../types'
@@ -121,29 +127,11 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
121127
placeholder: 'Enter your API key',
122128
password: true,
123129
connectionDroppable: false,
124-
// Hide API key for all OpenAI and Claude models when running on hosted version
130+
// Hide API key for all hosted models when running on hosted version
125131
condition: isHosted
126132
? {
127133
field: 'model',
128-
// Include all OpenAI models and Claude models for which we don't show the API key field
129-
value: [
130-
// OpenAI models
131-
'gpt-4o',
132-
'o1',
133-
'o1-mini',
134-
'o1-preview',
135-
'o3',
136-
'o3-preview',
137-
'o4-mini',
138-
'gpt-4.1',
139-
'gpt-4.1-nano',
140-
'gpt-4.1-mini',
141-
// Claude models
142-
'claude-sonnet-4-0',
143-
'claude-opus-4-0',
144-
'claude-3-7-sonnet-latest',
145-
'claude-3-5-sonnet-latest',
146-
],
134+
value: getHostedModels(),
147135
not: true, // Show for all models EXCEPT those listed
148136
}
149137
: undefined, // Show for all models in non-hosted environments
@@ -158,7 +146,7 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
158146
connectionDroppable: false,
159147
condition: {
160148
field: 'model',
161-
value: ['azure/gpt-4o', 'azure/o3', 'azure/o4-mini', 'azure/gpt-4.1', 'azure/model-router'],
149+
value: providers['azure-openai'].models,
162150
},
163151
},
164152
{
@@ -170,7 +158,7 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
170158
connectionDroppable: false,
171159
condition: {
172160
field: 'model',
173-
value: ['azure/gpt-4o', 'azure/o3', 'azure/o4-mini', 'azure/gpt-4.1', 'azure/model-router'],
161+
value: providers['azure-openai'].models,
174162
},
175163
},
176164
{

apps/sim/blocks/blocks/evaluator.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ChartBarIcon } from '@/components/icons'
2+
import { isHosted } from '@/lib/environment'
23
import { createLogger } from '@/lib/logs/console-logger'
34
import type { ProviderId } from '@/providers/types'
4-
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
5+
import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils'
56
import { useOllamaStore } from '@/stores/ollama/store'
67
import type { ToolResponse } from '@/tools/types'
78
import type { BlockConfig, ParamType } from '../types'
@@ -26,6 +27,11 @@ interface EvaluatorResponse extends ToolResponse {
2627
completion?: number
2728
total?: number
2829
}
30+
cost?: {
31+
input: number
32+
output: number
33+
total: number
34+
}
2935
[metricName: string]: any // Allow dynamic metric fields
3036
}
3137
}
@@ -181,6 +187,13 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
181187
placeholder: 'Enter your API key',
182188
password: true,
183189
connectionDroppable: false,
190+
condition: isHosted
191+
? {
192+
field: 'model',
193+
value: getHostedModels(),
194+
not: true,
195+
}
196+
: undefined,
184197
},
185198
{
186199
id: 'systemPrompt',
@@ -299,6 +312,7 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
299312
content: 'string',
300313
model: 'string',
301314
tokens: 'any',
315+
cost: 'any',
302316
},
303317
dependsOn: {
304318
subBlockId: 'metrics',
@@ -307,6 +321,7 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
307321
content: 'string',
308322
model: 'string',
309323
tokens: 'any',
324+
cost: 'any',
310325
},
311326
whenFilled: 'json',
312327
},

apps/sim/blocks/blocks/router.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ConnectIcon } from '@/components/icons'
2+
import { isHosted } from '@/lib/environment'
23
import type { ProviderId } from '@/providers/types'
3-
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
4+
import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils'
45
import { useOllamaStore } from '@/stores/ollama/store'
56
import type { ToolResponse } from '@/tools/types'
67
import type { BlockConfig } from '../types'
@@ -14,6 +15,11 @@ interface RouterResponse extends ToolResponse {
1415
completion?: number
1516
total?: number
1617
}
18+
cost?: {
19+
input: number
20+
output: number
21+
total: number
22+
}
1723
selectedPath: {
1824
blockId: string
1925
blockType: string
@@ -125,6 +131,14 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
125131
placeholder: 'Enter your API key',
126132
password: true,
127133
connectionDroppable: false,
134+
// Hide API key for all hosted models when running on hosted version
135+
condition: isHosted
136+
? {
137+
field: 'model',
138+
value: getHostedModels(),
139+
not: true, // Show for all models EXCEPT those listed
140+
}
141+
: undefined, // Show for all models in non-hosted environments
128142
},
129143
{
130144
id: 'systemPrompt',
@@ -171,6 +185,7 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
171185
content: 'string',
172186
model: 'string',
173187
tokens: 'any',
188+
cost: 'any',
174189
selectedPath: 'json',
175190
},
176191
},

apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ describe('EvaluatorBlockHandler', () => {
9191
content: 'This is the content to evaluate.',
9292
model: 'mock-model',
9393
tokens: { prompt: 50, completion: 10, total: 60 },
94+
cost: {
95+
input: 0,
96+
output: 0,
97+
total: 0,
98+
},
9499
score1: 5,
95100
score2: 8,
96101
},

apps/sim/executor/handlers/evaluator/evaluator-handler.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { env } from '@/lib/env'
22
import { createLogger } from '@/lib/logs/console-logger'
33
import type { BlockOutput } from '@/blocks/types'
4-
import { getProviderFromModel } from '@/providers/utils'
4+
import { calculateCost, getProviderFromModel } from '@/providers/utils'
55
import type { SerializedBlock } from '@/serializer/types'
66
import type { BlockHandler, ExecutionContext } from '../../types'
77

@@ -241,6 +241,14 @@ export class EvaluatorBlockHandler implements BlockHandler {
241241
logger.error('Error extracting metric scores:', e)
242242
}
243243

244+
// Calculate cost based on token usage, similar to how providers do it
245+
const costCalculation = calculateCost(
246+
result.model,
247+
result.tokens?.prompt || 0,
248+
result.tokens?.completion || 0,
249+
false // Evaluator blocks don't typically use cached input
250+
)
251+
244252
// Create result with metrics as direct fields for easy access
245253
const outputResult = {
246254
response: {
@@ -251,6 +259,11 @@ export class EvaluatorBlockHandler implements BlockHandler {
251259
completion: result.tokens?.completion || 0,
252260
total: result.tokens?.total || 0,
253261
},
262+
cost: {
263+
input: costCalculation.input,
264+
output: costCalculation.output,
265+
total: costCalculation.total,
266+
},
254267
...metricScores,
255268
},
256269
}

apps/sim/executor/handlers/router/router-handler.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ describe('RouterBlockHandler', () => {
152152
content: 'Choose the best option.',
153153
model: 'mock-model',
154154
tokens: { prompt: 100, completion: 5, total: 105 },
155+
cost: {
156+
input: 0,
157+
output: 0,
158+
total: 0,
159+
},
155160
selectedPath: {
156161
blockId: 'target-block-1',
157162
blockType: 'target',

apps/sim/executor/handlers/router/router-handler.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { env } from '@/lib/env'
22
import { createLogger } from '@/lib/logs/console-logger'
33
import { generateRouterPrompt } from '@/blocks/blocks/router'
44
import type { BlockOutput } from '@/blocks/types'
5-
import { getProviderFromModel } from '@/providers/utils'
5+
import { calculateCost, getProviderFromModel } from '@/providers/utils'
66
import type { SerializedBlock } from '@/serializer/types'
77
import type { PathTracker } from '../../path'
88
import type { BlockHandler, ExecutionContext } from '../../types'
@@ -92,6 +92,14 @@ export class RouterBlockHandler implements BlockHandler {
9292

9393
const tokens = result.tokens || { prompt: 0, completion: 0, total: 0 }
9494

95+
// Calculate cost based on token usage, similar to how providers do it
96+
const cost = calculateCost(
97+
result.model,
98+
tokens.prompt || 0,
99+
tokens.completion || 0,
100+
false // Router blocks don't typically use cached input
101+
)
102+
95103
return {
96104
response: {
97105
content: inputs.prompt,
@@ -101,6 +109,11 @@ export class RouterBlockHandler implements BlockHandler {
101109
completion: tokens.completion || 0,
102110
total: tokens.total || 0,
103111
},
112+
cost: {
113+
input: cost.input,
114+
output: cost.output,
115+
total: cost.total,
116+
},
104117
selectedPath: {
105118
blockId: chosenBlock.id,
106119
blockType: chosenBlock.type || 'unknown',

0 commit comments

Comments
 (0)