mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +00:00
feat: enhance suggestion generation with structured output and increased max turns
- Updated MAX_TURNS to allow for more iterations in suggestion generation: quick (5 to 50), standard (20 to 100), and extended (50 to 250). - Introduced a JSON schema for structured output in suggestions, improving the format and consistency of generated suggestions. - Modified the generateSuggestions function to utilize structured output when available, with a fallback to text parsing for compatibility. This enhances the suggestion generation process, allowing for more thorough exploration and better output formatting.
This commit is contained in:
@@ -58,13 +58,13 @@ export const TOOL_PRESETS = {
|
|||||||
*/
|
*/
|
||||||
export const MAX_TURNS = {
|
export const MAX_TURNS = {
|
||||||
/** Quick operations that shouldn't need many iterations */
|
/** Quick operations that shouldn't need many iterations */
|
||||||
quick: 5,
|
quick: 50,
|
||||||
|
|
||||||
/** Standard operations */
|
/** Standard operations */
|
||||||
standard: 20,
|
standard: 100,
|
||||||
|
|
||||||
/** Long-running operations like full spec generation */
|
/** Long-running operations like full spec generation */
|
||||||
extended: 50,
|
extended: 250,
|
||||||
|
|
||||||
/** Very long operations that may require extensive exploration */
|
/** Very long operations that may require extensive exploration */
|
||||||
maximum: 1000,
|
maximum: 1000,
|
||||||
@@ -143,6 +143,12 @@ export interface CreateSdkOptionsConfig {
|
|||||||
|
|
||||||
/** Optional abort controller for cancellation */
|
/** Optional abort controller for cancellation */
|
||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
|
|
||||||
|
/** Optional output format for structured outputs */
|
||||||
|
outputFormat?: {
|
||||||
|
type: "json_schema";
|
||||||
|
schema: Record<string, unknown>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,7 +200,7 @@ export function createFeatureGenerationOptions(
|
|||||||
*
|
*
|
||||||
* Configuration:
|
* Configuration:
|
||||||
* - Uses read-only tools for analysis
|
* - Uses read-only tools for analysis
|
||||||
* - Quick turns for focused suggestions
|
* - Standard turns to allow thorough codebase exploration and structured output generation
|
||||||
* - Opus model by default for thorough analysis
|
* - Opus model by default for thorough analysis
|
||||||
*/
|
*/
|
||||||
export function createSuggestionsOptions(
|
export function createSuggestionsOptions(
|
||||||
@@ -203,11 +209,12 @@ export function createSuggestionsOptions(
|
|||||||
return {
|
return {
|
||||||
...getBaseOptions(),
|
...getBaseOptions(),
|
||||||
model: getModelForUseCase("suggestions", config.model),
|
model: getModelForUseCase("suggestions", config.model),
|
||||||
maxTurns: MAX_TURNS.quick,
|
maxTurns: MAX_TURNS.extended, // Increased from quick (5) to standard (20) to allow codebase exploration + structured output
|
||||||
cwd: config.cwd,
|
cwd: config.cwd,
|
||||||
allowedTools: [...TOOL_PRESETS.readOnly],
|
allowedTools: [...TOOL_PRESETS.readOnly],
|
||||||
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
|
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
|
||||||
...(config.abortController && { abortController: config.abortController }),
|
...(config.abortController && { abortController: config.abortController }),
|
||||||
|
...(config.outputFormat && { outputFormat: config.outputFormat }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,39 @@ import { createSuggestionsOptions } from "../../lib/sdk-options.js";
|
|||||||
|
|
||||||
const logger = createLogger("Suggestions");
|
const logger = createLogger("Suggestions");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Schema for suggestions output
|
||||||
|
*/
|
||||||
|
const suggestionsSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
suggestions: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string" },
|
||||||
|
category: { type: "string" },
|
||||||
|
description: { type: "string" },
|
||||||
|
steps: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
},
|
||||||
|
priority: {
|
||||||
|
type: "number",
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 3,
|
||||||
|
},
|
||||||
|
reasoning: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["category", "description", "steps", "priority", "reasoning"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["suggestions"],
|
||||||
|
additionalProperties: false,
|
||||||
|
};
|
||||||
|
|
||||||
export async function generateSuggestions(
|
export async function generateSuggestions(
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
suggestionType: string,
|
suggestionType: string,
|
||||||
@@ -36,19 +69,7 @@ For each suggestion, provide:
|
|||||||
4. Priority (1=high, 2=medium, 3=low)
|
4. Priority (1=high, 2=medium, 3=low)
|
||||||
5. Brief reasoning for why this would help
|
5. Brief reasoning for why this would help
|
||||||
|
|
||||||
Format your response as JSON:
|
The response will be automatically formatted as structured JSON.`;
|
||||||
{
|
|
||||||
"suggestions": [
|
|
||||||
{
|
|
||||||
"id": "suggestion-123",
|
|
||||||
"category": "Category",
|
|
||||||
"description": "What to implement",
|
|
||||||
"steps": ["Step 1", "Step 2"],
|
|
||||||
"priority": 1,
|
|
||||||
"reasoning": "Why this helps"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`;
|
|
||||||
|
|
||||||
events.emit("suggestions:event", {
|
events.emit("suggestions:event", {
|
||||||
type: "suggestions_progress",
|
type: "suggestions_progress",
|
||||||
@@ -58,16 +79,21 @@ Format your response as JSON:
|
|||||||
const options = createSuggestionsOptions({
|
const options = createSuggestionsOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
|
outputFormat: {
|
||||||
|
type: "json_schema",
|
||||||
|
schema: suggestionsSchema,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const stream = query({ prompt, options });
|
const stream = query({ prompt, options });
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
|
let structuredOutput: { suggestions: Array<Record<string, unknown>> } | null = null;
|
||||||
|
|
||||||
for await (const msg of stream) {
|
for await (const msg of stream) {
|
||||||
if (msg.type === "assistant" && msg.message.content) {
|
if (msg.type === "assistant" && msg.message.content) {
|
||||||
for (const block of msg.message.content) {
|
for (const block of msg.message.content) {
|
||||||
if (block.type === "text") {
|
if (block.type === "text") {
|
||||||
responseText = block.text;
|
responseText += block.text;
|
||||||
events.emit("suggestions:event", {
|
events.emit("suggestions:event", {
|
||||||
type: "suggestions_progress",
|
type: "suggestions_progress",
|
||||||
content: block.text,
|
content: block.text,
|
||||||
@@ -81,18 +107,34 @@ Format your response as JSON:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.type === "result" && msg.subtype === "success") {
|
} else if (msg.type === "result" && msg.subtype === "success") {
|
||||||
responseText = msg.result || responseText;
|
// Check for structured output
|
||||||
|
const resultMsg = msg as any;
|
||||||
|
if (resultMsg.structured_output) {
|
||||||
|
structuredOutput = resultMsg.structured_output as {
|
||||||
|
suggestions: Array<Record<string, unknown>>;
|
||||||
|
};
|
||||||
|
logger.debug("Received structured output:", structuredOutput);
|
||||||
|
}
|
||||||
|
} else if (msg.type === "result") {
|
||||||
|
const resultMsg = msg as any;
|
||||||
|
if (resultMsg.subtype === "error_max_structured_output_retries") {
|
||||||
|
logger.error("Failed to produce valid structured output after retries");
|
||||||
|
throw new Error("Could not produce valid suggestions output");
|
||||||
|
} else if (resultMsg.subtype === "error_max_turns") {
|
||||||
|
logger.error("Hit max turns limit before completing suggestions generation");
|
||||||
|
logger.warn(`Response text length: ${responseText.length} chars`);
|
||||||
|
// Still try to parse what we have
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse suggestions from response
|
// Use structured output if available, otherwise fall back to parsing text
|
||||||
try {
|
try {
|
||||||
const jsonMatch = responseText.match(/\{[\s\S]*"suggestions"[\s\S]*\}/);
|
if (structuredOutput && structuredOutput.suggestions) {
|
||||||
if (jsonMatch) {
|
// Use structured output directly
|
||||||
const parsed = JSON.parse(jsonMatch[0]);
|
|
||||||
events.emit("suggestions:event", {
|
events.emit("suggestions:event", {
|
||||||
type: "suggestions_complete",
|
type: "suggestions_complete",
|
||||||
suggestions: parsed.suggestions.map(
|
suggestions: structuredOutput.suggestions.map(
|
||||||
(s: Record<string, unknown>, i: number) => ({
|
(s: Record<string, unknown>, i: number) => ({
|
||||||
...s,
|
...s,
|
||||||
id: s.id || `suggestion-${Date.now()}-${i}`,
|
id: s.id || `suggestion-${Date.now()}-${i}`,
|
||||||
@@ -100,7 +142,23 @@ Format your response as JSON:
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error("No valid JSON found in response");
|
// Fallback: try to parse from text (for backwards compatibility)
|
||||||
|
logger.warn("No structured output received, attempting to parse from text");
|
||||||
|
const jsonMatch = responseText.match(/\{[\s\S]*"suggestions"[\s\S]*\}/);
|
||||||
|
if (jsonMatch) {
|
||||||
|
const parsed = JSON.parse(jsonMatch[0]);
|
||||||
|
events.emit("suggestions:event", {
|
||||||
|
type: "suggestions_complete",
|
||||||
|
suggestions: parsed.suggestions.map(
|
||||||
|
(s: Record<string, unknown>, i: number) => ({
|
||||||
|
...s,
|
||||||
|
id: s.id || `suggestion-${Date.now()}-${i}`,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("No valid JSON found in response");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the parsing error for debugging
|
// Log the parsing error for debugging
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ describe("sdk-options.ts", () => {
|
|||||||
describe("MAX_TURNS", () => {
|
describe("MAX_TURNS", () => {
|
||||||
it("should export turn presets", async () => {
|
it("should export turn presets", async () => {
|
||||||
const { MAX_TURNS } = await import("@/lib/sdk-options.js");
|
const { MAX_TURNS } = await import("@/lib/sdk-options.js");
|
||||||
expect(MAX_TURNS.quick).toBe(5);
|
expect(MAX_TURNS.quick).toBe(50);
|
||||||
expect(MAX_TURNS.standard).toBe(20);
|
expect(MAX_TURNS.standard).toBe(100);
|
||||||
expect(MAX_TURNS.extended).toBe(50);
|
expect(MAX_TURNS.extended).toBe(250);
|
||||||
expect(MAX_TURNS.maximum).toBe(1000);
|
expect(MAX_TURNS.maximum).toBe(1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -141,7 +141,7 @@ describe("sdk-options.ts", () => {
|
|||||||
const options = createSuggestionsOptions({ cwd: "/test/path" });
|
const options = createSuggestionsOptions({ cwd: "/test/path" });
|
||||||
|
|
||||||
expect(options.cwd).toBe("/test/path");
|
expect(options.cwd).toBe("/test/path");
|
||||||
expect(options.maxTurns).toBe(MAX_TURNS.quick);
|
expect(options.maxTurns).toBe(MAX_TURNS.extended);
|
||||||
expect(options.allowedTools).toEqual([...TOOL_PRESETS.readOnly]);
|
expect(options.allowedTools).toEqual([...TOOL_PRESETS.readOnly]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user