diff --git a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts index b38298c..dc2bfdb 100644 --- a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts +++ b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts @@ -51,6 +51,49 @@ function installMockChatCompletions(sequence: ReadonlyArray): Response { + return jsonResponse({ + choices: [ + { + message: { + tool_calls: [ + { + id: "tc_extract_1", + type: "function", + function: { + name: "extract", + arguments: JSON.stringify(args), + }, + }, + ], + }, + }, + ], + }); +} + +function installMockToolCallCompletions(sequence: ReadonlyArray>): () => void { + const origFetch = globalThis.fetch; + let i = 0; + const mockFetch = async ( + _input: Parameters[0], + _init?: RequestInit, + ): Promise => { + const args = sequence[i] ?? sequence[sequence.length - 1]; + if (args === undefined) { + throw new Error("installMockToolCallCompletions: empty sequence"); + } + i += 1; + return buildToolCallResponse(args); + }; + globalThis.fetch = Object.assign(mockFetch, { + preconnect: origFetch.preconnect.bind(origFetch), + }) as typeof fetch; + return () => { + globalThis.fetch = origFetch; + }; +} + function makeStart(maxRounds: number): ModeratorContext["start"] { return { role: START, @@ -233,6 +276,43 @@ describe("solveIssueWorkflowDefinition + createWorkflow", () => { expect(first.value.meta).toEqual(EXPECT_PREPARER_META); }); + test("structured extraction also accepts tool_calls extraction path", async () => { + const EXPECT_PREPARER_META: PreparerMeta = { + repoPath: "/home/user/repos/tool-call", + defaultBranch: "main", + conventions: null, + toolchain: { + packageManager: "bun", + testCommand: "bun test", + lintCommand: null, + buildCommand: "bun run build", + }, + }; + restoreFetch = installMockToolCallCompletions([EXPECT_PREPARER_META]); + + casDir = await mkdtemp(join(tmpdir(), "solve-issue-cas-")); + const cas = createCasStore(casDir); + + const run = createWorkflow(solveIssueWorkflowDefinition, { + agent: async () => "", + overrides: { developer: async () => "stub-root-hash" }, + }); + const gen = run( + makeThread("task"), + { + cas, + extract: stubExtract, + }, + ); + const first = await gen.next(); + expect(first.done).toBe(false); + if (first.done) { + throw new Error("expected yield"); + } + expect(first.value.role).toBe("preparer"); + expect(first.value.meta).toEqual(EXPECT_PREPARER_META); + }); + test("per-role agent overrides default", async () => { const PREPARER_META: PreparerMeta = { repoPath: "/tmp/r", diff --git a/packages/workflow/src/extract/index.ts b/packages/workflow/src/extract/index.ts index 40b650d..cf502d8 100644 --- a/packages/workflow/src/extract/index.ts +++ b/packages/workflow/src/extract/index.ts @@ -6,7 +6,6 @@ export { extractFunctionToolFromZodSchema, llmErrorToCause, llmExtract, - llmExtractWithRetry, } from "./llm-extract.js"; export { reactExtract } from "./react-extract.js"; export type { diff --git a/packages/workflow/src/extract/llm-extract.ts b/packages/workflow/src/extract/llm-extract.ts index ca9a4f3..0e2dc50 100644 --- a/packages/workflow/src/extract/llm-extract.ts +++ b/packages/workflow/src/extract/llm-extract.ts @@ -92,20 +92,6 @@ function readToolArgumentsJson(parsed: unknown, previewSource: string): Result( export async function llmExtract(options: LlmExtractArgs): Promise> { return performLlmExtract({ ...options, userContent: options.text }); } - -/** - * Runs extract up to two times: on the first schema/tool-args parse failure, resends the agent - * output plus the error so the model can correct the tool call. - */ -export async function llmExtractWithRetry( - options: LlmExtractArgs, -): Promise> { - const first = await performLlmExtract({ - ...options, - userContent: options.text, - }); - if (first.ok) { - return first; - } - if (!isRetryableExtractError(first.error)) { - return first; - } - - const hint = describeRetryHint(first.error); - const correction = `The previous extraction attempt failed. - -${hint} - -Respond again with a single tool call whose \`arguments\` JSON strictly matches the schema.`; - - const secondContent = `${options.text} - ---- - -${correction}`; - - return performLlmExtract({ - ...options, - userContent: secondContent, - }); -} diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 685791e..70897a1 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -59,7 +59,6 @@ export { type LlmError, llmErrorToCause, llmExtract, - llmExtractWithRetry, type ReactExtractArgs, reactExtract, } from "./extract/index.js";