refactor(workflow): remove dead extract retry export
Drop unused llmExtractWithRetry implementation and public exports. Add solve-issue template coverage for tool_calls extraction path. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -51,6 +51,49 @@ function installMockChatCompletions(sequence: ReadonlyArray<Record<string, unkno
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildToolCallResponse(args: Record<string, unknown>): Response {
|
||||||
|
return jsonResponse({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
id: "tc_extract_1",
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "extract",
|
||||||
|
arguments: JSON.stringify(args),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function installMockToolCallCompletions(sequence: ReadonlyArray<Record<string, unknown>>): () => void {
|
||||||
|
const origFetch = globalThis.fetch;
|
||||||
|
let i = 0;
|
||||||
|
const mockFetch = async (
|
||||||
|
_input: Parameters<typeof fetch>[0],
|
||||||
|
_init?: RequestInit,
|
||||||
|
): Promise<Response> => {
|
||||||
|
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<SolveIssueMeta>["start"] {
|
function makeStart(maxRounds: number): ModeratorContext<SolveIssueMeta>["start"] {
|
||||||
return {
|
return {
|
||||||
role: START,
|
role: START,
|
||||||
@@ -233,6 +276,43 @@ describe("solveIssueWorkflowDefinition + createWorkflow", () => {
|
|||||||
expect(first.value.meta).toEqual(EXPECT_PREPARER_META);
|
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 () => {
|
test("per-role agent overrides default", async () => {
|
||||||
const PREPARER_META: PreparerMeta = {
|
const PREPARER_META: PreparerMeta = {
|
||||||
repoPath: "/tmp/r",
|
repoPath: "/tmp/r",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export {
|
|||||||
extractFunctionToolFromZodSchema,
|
extractFunctionToolFromZodSchema,
|
||||||
llmErrorToCause,
|
llmErrorToCause,
|
||||||
llmExtract,
|
llmExtract,
|
||||||
llmExtractWithRetry,
|
|
||||||
} from "./llm-extract.js";
|
} from "./llm-extract.js";
|
||||||
export { reactExtract } from "./react-extract.js";
|
export { reactExtract } from "./react-extract.js";
|
||||||
export type {
|
export type {
|
||||||
|
|||||||
@@ -92,20 +92,6 @@ function readToolArgumentsJson(parsed: unknown, previewSource: string): Result<s
|
|||||||
return ok(argsRaw);
|
return ok(argsRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRetryableExtractError(error: LlmError): boolean {
|
|
||||||
return error.kind === "schema_validation_failed" || error.kind === "tool_arguments_invalid_json";
|
|
||||||
}
|
|
||||||
|
|
||||||
function describeRetryHint(error: LlmError): string {
|
|
||||||
if (error.kind === "schema_validation_failed") {
|
|
||||||
return `Schema validation failed: ${error.message}`;
|
|
||||||
}
|
|
||||||
if (error.kind === "tool_arguments_invalid_json") {
|
|
||||||
return `Tool arguments were not valid JSON: ${error.message}`;
|
|
||||||
}
|
|
||||||
return JSON.stringify(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function llmErrorToCause(error: LlmError): Error {
|
export function llmErrorToCause(error: LlmError): Error {
|
||||||
switch (error.kind) {
|
switch (error.kind) {
|
||||||
case "http_error":
|
case "http_error":
|
||||||
@@ -206,40 +192,3 @@ async function performLlmExtract<T>(
|
|||||||
export async function llmExtract<T>(options: LlmExtractArgs<T>): Promise<Result<T, LlmError>> {
|
export async function llmExtract<T>(options: LlmExtractArgs<T>): Promise<Result<T, LlmError>> {
|
||||||
return performLlmExtract({ ...options, userContent: options.text });
|
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<T>(
|
|
||||||
options: LlmExtractArgs<T>,
|
|
||||||
): Promise<Result<T, LlmError>> {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export {
|
|||||||
type LlmError,
|
type LlmError,
|
||||||
llmErrorToCause,
|
llmErrorToCause,
|
||||||
llmExtract,
|
llmExtract,
|
||||||
llmExtractWithRetry,
|
|
||||||
type ReactExtractArgs,
|
type ReactExtractArgs,
|
||||||
reactExtract,
|
reactExtract,
|
||||||
} from "./extract/index.js";
|
} from "./extract/index.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user