Skip to content

Commit ddea8e9

Browse files
committed
switch stop sequence in tool calls
1 parent c658229 commit ddea8e9

File tree

9 files changed

+92
-42
lines changed

9 files changed

+92
-42
lines changed

backend/src/__tests__/run-agent-step-tools.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ describe('runAgentStep - update_report tool', () => {
176176
message: 'Hi',
177177
},
178178
false
179-
) + getToolCallString('end_turn', {}, true)
179+
) +
180+
'\n\n' +
181+
getToolCallString('end_turn', {}, true)
180182

181183
spyOn(aisdk, 'promptAiSdkStream').mockImplementation(async function* () {
182184
yield mockResponse

backend/src/__tests__/xml-stream-parser.test.ts

Lines changed: 8 additions & 5 deletions
Large diffs are not rendered by default.

backend/src/prompt-agent-stream.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CoreMessage } from 'ai'
33

44
import { promptAiSdkStream } from './llm-apis/vercel-ai-sdk/ai-sdk'
55
import { AgentTemplate } from './templates/types'
6-
import { globalStopSequences } from './tools/constants'
6+
import { globalStopSequence } from './tools/constants'
77

88
export const getAgentStreamFromTemplate = (params: {
99
clientSessionId: string
@@ -26,7 +26,7 @@ export const getAgentStreamFromTemplate = (params: {
2626
const options: Parameters<typeof promptAiSdkStream>[0] = {
2727
messages,
2828
model,
29-
stopSequences: globalStopSequences,
29+
stopSequences: [globalStopSequence],
3030
clientSessionId,
3131
fingerprintId,
3232
userInputId,

backend/src/tools.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,14 @@ export const toolParams = Object.fromEntries(
7272
) as Record<ToolName, string[]>
7373

7474
function paramsSection(schema: z.ZodObject, endsAgentStep: boolean) {
75-
const jsonSchema = z.toJSONSchema(
76-
schema.extend({ [endsAgentStepParam]: z.literal(endsAgentStep) })
77-
)
75+
const schemaWithEndsAgentStepParam = endsAgentStep
76+
? schema.extend({
77+
[endsAgentStepParam]: z
78+
.literal(endsAgentStep)
79+
.describe('Easp flag must be set to true'),
80+
})
81+
: schema
82+
const jsonSchema = z.toJSONSchema(schemaWithEndsAgentStepParam)
7883
delete jsonSchema.description
7984
delete jsonSchema['$schema']
8085
const paramsDescription = Object.keys(jsonSchema.properties ?? {}).length

backend/src/tools/constants.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ import { handleUpdateSubgoal } from './handlers/update-subgoal'
4646
import { handleWebSearch } from './handlers/web-search'
4747
import { handleWriteFile } from './handlers/write-file'
4848

49-
export const globalStopSequences = [
50-
`${JSON.stringify(endsAgentStepParam)}:true`,
51-
`${JSON.stringify(endsAgentStepParam)}: true`,
52-
]
49+
export const globalStopSequence = `${JSON.stringify(endsAgentStepParam)}`
5350

5451
type Prettify<T> = { [K in keyof T]: T[K] } & {}
5552

backend/src/tools/tool-executor.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,17 @@ export function parseRawToolCall<T extends ToolName = ToolName>(
5858
codebuffToolDefs[validName].endsAgentStep
5959
}
6060

61-
const result = (
62-
codebuffToolDefs[validName].parameters satisfies z.ZodObject as z.ZodObject
63-
)
64-
.extend({
65-
[endsAgentStepParam]: z.literal(
66-
codebuffToolDefs[validName].endsAgentStep
67-
),
68-
})
69-
.safeParse(processedParameters)
61+
const paramsSchema = codebuffToolDefs[validName].endsAgentStep
62+
? (
63+
codebuffToolDefs[validName]
64+
.parameters satisfies z.ZodObject as z.ZodObject
65+
).extend({
66+
[endsAgentStepParam]: z.literal(
67+
codebuffToolDefs[validName].endsAgentStep
68+
),
69+
})
70+
: codebuffToolDefs[validName].parameters
71+
const result = paramsSchema.safeParse(processedParameters)
7072

7173
if (!result.success) {
7274
return {
@@ -81,7 +83,10 @@ export function parseRawToolCall<T extends ToolName = ToolName>(
8183
}
8284
}
8385

84-
delete result.data[endsAgentStepParam]
86+
if (endsAgentStepParam in result.data) {
87+
delete result.data[endsAgentStepParam]
88+
}
89+
8590
return {
8691
toolName: validName,
8792
args: result.data,

backend/src/xml-stream-parser.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { trackEvent } from '@codebuff/common/analytics'
2+
import { Model } from '@codebuff/common/constants'
3+
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
14
import {
25
endsAgentStepParam,
36
endToolTag,
@@ -37,7 +40,12 @@ export async function* processStreamWithTags(
3740
onTagEnd: (tagName: string, params: Record<string, any>) => void
3841
}
3942
>,
40-
onError: (tagName: string, errorMessage: string) => void
43+
onError: (tagName: string, errorMessage: string) => void,
44+
loggerOptions?: {
45+
userId?: string
46+
model?: Model
47+
agentName?: string
48+
}
4149
): AsyncGenerator<string> {
4250
let streamCompleted = false
4351
let buffer = ''
@@ -62,16 +70,49 @@ export async function* processStreamWithTags(
6270
try {
6371
parsedParams = JSON.parse(contents)
6472
} catch (error: any) {
65-
onError('parse_error', error.message)
73+
trackEvent(
74+
AnalyticsEvent.MALFORMED_TOOL_CALL_JSON,
75+
loggerOptions?.userId ?? '',
76+
{
77+
contents,
78+
model: loggerOptions?.model,
79+
agent: loggerOptions?.agentName,
80+
}
81+
)
82+
const shortenedContents =
83+
contents.length < 50
84+
? contents
85+
: contents.slice(0, 20) + '...' + contents.slice(-20)
86+
onError(
87+
'parse_error',
88+
`Invalid JSON: ${JSON.stringify(shortenedContents)}\nError: ${error.message}`
89+
)
6690
return
6791
}
6892

6993
const toolName = parsedParams[toolNameParam] as keyof typeof processors
7094
if (!processors[toolName]) {
95+
trackEvent(
96+
AnalyticsEvent.UNKNOWN_TOOL_CALL,
97+
loggerOptions?.userId ?? '',
98+
{
99+
contents,
100+
toolName,
101+
model: loggerOptions?.model,
102+
agent: loggerOptions?.agentName,
103+
}
104+
)
71105
onError(toolName, `Tool not found: ${toolName}`)
72106
return
73107
}
74108

109+
trackEvent(AnalyticsEvent.TOOL_USE, loggerOptions?.userId ?? '', {
110+
toolName,
111+
contents,
112+
parsedParams,
113+
model: loggerOptions?.model,
114+
agent: loggerOptions?.agentName,
115+
})
75116
delete parsedParams[toolNameParam]
76117

77118
processors[toolName].onTagStart(toolName, {})

common/src/constants/analytics-events.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ export enum AnalyticsEvent {
2525
UPDATE_CODEBUFF_FAILED = 'cli.update_codebuff_failed',
2626

2727
// Backend
28-
USER_INPUT = 'backend.user_input',
2928
AGENT_STEP = 'backend.agent_step',
3029
CREDIT_GRANT = 'backend.credit_grant',
3130
CREDIT_CONSUMED = 'backend.credit_consumed',
31+
MALFORMED_TOOL_CALL_JSON = 'backend.malformed_tool_call_json',
3232
TOOL_USE = 'backend.tool_use',
33+
UNKNOWN_TOOL_CALL = 'backend.unknown_tool_call',
34+
USER_INPUT = 'backend.user_input',
3335

3436
// Web
3537
SIGNUP = 'web.signup',

common/src/constants/tools.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ToolResultPart } from 'ai'
22
import { closeXml } from '../util/xml'
33

44
export const toolNameParam = 'codebuff_tool_name'
5-
export const endsAgentStepParam = 'codebuff_end_step'
5+
export const endsAgentStepParam = 'codebuff_easp'
66
export const toolXmlName = 'codebuff_tool_call'
77
export const startToolTag = `<${toolXmlName}>\n`
88
export const endToolTag = `\n</${toolXmlName}>`
@@ -81,19 +81,14 @@ export const getToolCallString = (
8181
params: Record<string, any>,
8282
endsAgentStep: boolean
8383
) => {
84-
return [
85-
startToolTag,
86-
JSON.stringify(
87-
{
88-
[toolNameParam]: toolName,
89-
...params,
90-
[endsAgentStepParam]: endsAgentStep,
91-
},
92-
null,
93-
2
94-
),
95-
endToolTag,
96-
].join('')
84+
const obj: Record<string, any> = {
85+
[toolNameParam]: toolName,
86+
...params,
87+
}
88+
if (endsAgentStep) {
89+
obj[endsAgentStepParam] = true
90+
}
91+
return [startToolTag, JSON.stringify(obj, null, 2), endToolTag].join('')
9792
}
9893

9994
export type StringToolResultPart = Omit<ToolResultPart, 'type'> & {

0 commit comments

Comments
 (0)