Skip to content

Commit 41da2f3

Browse files
authored
feat: Improved the Architecture Modification Agent
2 parents a745f38 + 666e0b2 commit 41da2f3

11 files changed

Lines changed: 506 additions & 342 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v0.5.1
4+
### Added
5+
- Structured architecture update flow with a user-facing update action
6+
- Tool-driven agent flow for architecture modification
7+
- Token-usage based credit accounting for architecture-related AI operations
8+
9+
### Improved
10+
- Architecture modification dialog and decision logic for clearer tool selection
11+
- More concise and deterministic agent responses
12+
- Chat interface now includes per-message inline options for quicker actions
13+
- Responses include termination metadata
14+
- Support for partial-result recovery when errors occur
15+
316
## v0.5.0
417
### Added
518
- Tool-driven chat agent with structured interview flow and clearer multi-stage responses

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</p>
1010

1111
<p align="center">
12-
<img src="https://img.shields.io/badge/version-0.5.0-blue" alt="Version 0.5.0" />
12+
<img src="https://img.shields.io/badge/version-0.5.1-blue" alt="Version 0.5.1" />
1313
<img src="https://img.shields.io/badge/status-experimental-orange" alt="Status: Experimental" />
1414
<a href="https://opensource.org/licenses/Apache-2.0">
1515
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0" />

actions/agentsFlow.ts

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
"use server";
22
import { ChatOpenAI } from "@langchain/openai";
3-
import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, PromptTemplate } from "@langchain/core/prompts";
4-
import { architectureModificationPrompt, BASE_DEVILDEV_AGENT_PROMPT, DevilDevAgentAfterInterviewRole, DevilDevAgentBeforeInterviewRole } from "../prompts/dev/Chatbot";
3+
import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
4+
import { architectureModificationAgentPrompt, BASE_DEVILDEV_AGENT_PROMPT, DevilDevAgentAfterInterviewRole, DevilDevAgentBeforeInterviewRole } from "../prompts/dev/Chatbot";
55
import { deductCredits, getCredits } from "./credits";
66
import { minSoulsToSendMessage } from "../Limits";
7-
import { extractTextContent } from "@/lib/ai/extractTextContent";
87
import { interviewTool } from "../tools/ptoA-tools/interview";
98
import { generalResTool } from "../tools/ptoA-tools/general_res";
109
import { AgentExecutor, createToolCallingAgent } from "langchain/agents";
1110
import { TokenUsageCallbackHandler } from "../common/TokenUsageHandler";
1211
import { TERMINATING_TOOLS, TerminatingTools } from "../types/pToA/tools";
1312
import { tier1Tool } from "../tools/ptoA-tools/tier-1";
1413
import { tier2Tool } from "../tools/ptoA-tools/tier-2";
14+
import { updateArchitectureTool } from "../tools/ptoA-tools/update_architecture";
15+
import { DynamicStructuredTool } from "langchain/tools";
16+
import { z } from "zod";
1517

1618
// Return type for agent flow functions
1719
export type AgentFlowResult = {
@@ -132,49 +134,96 @@ export async function chatbot(userInput: string, conversationHistory: any[] = []
132134

133135
export async function architectureModificationBot(userInput: string, conversationHistory: any[] = [], architectureData: any, userId: string | null = null) {
134136
// Check credits before running the agent
135-
if (userId) {
137+
if (userId) {
136138
const creditsResult = await getCredits(userId);
137139
if (creditsResult.success && creditsResult.credits !== undefined && creditsResult.credits < minSoulsToSendMessage) {
138140
return { error: 'INSUFFICIENT_CREDITS', remainingCredits: creditsResult.credits };
139141
}
140142
}
141143

142-
const template = architectureModificationPrompt;
143-
144144
// Format conversation history for the prompt
145-
const formattedHistory = conversationHistory.map(msg =>
145+
const formattedHistory = conversationHistory.map(msg =>
146146
`${msg.type === 'user' ? 'User' : 'Assistant'}: ${msg.content}`
147147
).join('\n');
148148

149-
const prompt = PromptTemplate.fromTemplate(template);
150-
const chain = prompt.pipe(llm);
151-
const result = await chain.invoke({
152-
userInput: userInput,
153-
conversationHistory: formattedHistory,
154-
architecture_data: JSON.stringify(architectureData)
149+
const get_architecture = new DynamicStructuredTool({
150+
name: "get_architecture",
151+
description:
152+
"Use this when you need to inspect the current architecture. It returns the full architecture JSON as a string. This does NOT close the agent.",
153+
schema: z.object({}),
154+
func: async (): Promise<string> => {
155+
return JSON.stringify(architectureData);
156+
},
155157
});
156-
157-
// Extract text content - handle both string and complex content types
158-
const textContent = extractTextContent(result.content);
159-
160-
// Deduct credits if userId is provided
158+
159+
const tools = [get_architecture, generalResTool, interviewTool, updateArchitectureTool];
160+
161+
const prompt = ChatPromptTemplate.fromMessages([
162+
["system", architectureModificationAgentPrompt],
163+
HumanMessagePromptTemplate.fromTemplate("{userInput}"),
164+
new MessagesPlaceholder("agent_scratchpad"),
165+
]);
166+
167+
const agent = await createToolCallingAgent({ llm, tools, prompt });
168+
169+
const agentExecutor = new AgentExecutor({
170+
agent,
171+
tools,
172+
verbose: true,
173+
maxIterations: 10,
174+
});
175+
176+
const inputs = {
177+
userInput,
178+
conversationHistory: formattedHistory,
179+
};
180+
181+
const tokenUsageHandler = new TokenUsageCallbackHandler();
182+
183+
const stream = (agentExecutor as any)._streamIterator(inputs, { callbacks: [tokenUsageHandler] });
184+
185+
let result: { output?: string; terminatingTool?: TerminatingTools } | undefined;
186+
187+
for await (const step of stream) {
188+
if (!step) continue;
189+
190+
if (step.output !== undefined) {
191+
result = { output: step.output, terminatingTool: step.terminatingTool };
192+
break;
193+
}
194+
195+
const steps = step.intermediateSteps as Array<{ action: { tool: string }; observation: string }> | undefined;
196+
if (steps?.length) {
197+
const last = steps[steps.length - 1];
198+
const toolName = last?.action?.tool?.toLowerCase();
199+
if (toolName && TERMINATING_TOOLS.has(toolName)) {
200+
result = { output: last.observation, terminatingTool: toolName as TerminatingTools };
201+
break;
202+
}
203+
}
204+
}
205+
206+
console.log("architectureModificationBot result:", result);
207+
const tokenUsage = tokenUsageHandler.getUsage();
208+
161209
if (userId) {
162-
const inputTokens = result.usage_metadata?.input_tokens ?? 0;
163-
const outputTokens = result.usage_metadata?.output_tokens ?? 0;
164-
165-
const creditResult = await deductCredits(userId, inputTokens, outputTokens);
210+
const inputTokens = tokenUsage.inputTokens;
211+
const outputTokens = tokenUsage.outputTokens;
212+
213+
const creditResult = await deductCredits(userId, inputTokens, outputTokens, true);
166214
if (!creditResult.success) {
167215
console.error("Failed to deduct credits:", creditResult.error);
168-
return { textContent };
216+
return { response: result?.output, terminatingTool: result?.terminatingTool };
169217
} else {
170218
console.log(`Credits deducted: ${creditResult.deducted}, remaining: ${creditResult.remaining}`);
171219
return {
172-
textContent,
220+
response: result?.output,
221+
terminatingTool: result?.terminatingTool,
173222
remainingCredits: creditResult.remaining,
174223
deducted: creditResult.deducted
175224
};
176225
}
177226
}
178-
179-
return { textContent };
227+
228+
return { response: result?.output, terminatingTool: result?.terminatingTool };
180229
}

actions/architecture.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { minSoulsToGenArch } from "../Limits";
1818
import { TokenUsageCallbackHandler } from "../common/TokenUsageHandler";
1919
import { db } from "@/lib/db";
2020
import { createParallelWebSearchTool } from "../tools/parallel";
21-
import { ARCHITECTURE_GENERATION_PROMPT } from "../prompts/dev/architecture";
21+
import { ARCHITECTURE_GENERATION_PROMPT, ARCHITECTURE_UPDATE_PROMPT } from "../prompts/dev/architecture";
2222
const { inngest } = await import('../src/inngest/client');
2323

2424

@@ -490,3 +490,85 @@ export async function generateMainArchitecture({
490490
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
491491
}
492492
}
493+
494+
export async function updateArchitecture({
495+
changeRequirement,
496+
chatId,
497+
stackId,
498+
architecture_data,
499+
userId,
500+
}: {
501+
changeRequirement: string;
502+
chatId: string;
503+
stackId?: string;
504+
architecture_data: any;
505+
userId: string;
506+
}) {
507+
const creditsResult = await getCredits(userId);
508+
if (creditsResult.success && creditsResult.credits !== undefined && creditsResult.credits < minSoulsToGenArch) {
509+
return { success: false, error: 'INSUFFICIENT_CREDITS', remainingCredits: creditsResult.credits };
510+
}
511+
512+
const tokenUsageHandler = new TokenUsageCallbackHandler();
513+
514+
try {
515+
const llm = new ChatOpenAI({ openAIApiKey: process.env.OPENAI_API_KEY, model: "gpt-5-mini-2025-08-07" });
516+
517+
const prompt = PromptTemplate.fromTemplate(ARCHITECTURE_UPDATE_PROMPT);
518+
519+
const structuredLlm = llm.withStructuredOutput(architectureJsonSchemaWithPrd);
520+
const chain = prompt.pipe(structuredLlm);
521+
522+
const updatedArchitecture = await chain.invoke(
523+
{
524+
changeRequirement,
525+
currentArchitecture: JSON.stringify(architecture_data),
526+
},
527+
{ callbacks: [tokenUsageHandler] }
528+
);
529+
530+
const dbOps: any[] = [
531+
db.architecture.create({
532+
data: {
533+
chatId,
534+
stackId,
535+
requirement: changeRequirement,
536+
architectureRationale: updatedArchitecture.architectureRationale,
537+
components: updatedArchitecture.components,
538+
connectionLabels: updatedArchitecture.connectionLabels || {},
539+
componentPositions: {},
540+
},
541+
}),
542+
];
543+
544+
if (stackId) {
545+
dbOps.push(
546+
db.stack.update({
547+
where: { id: stackId },
548+
data: { prd: updatedArchitecture.prd },
549+
})
550+
);
551+
}
552+
553+
const [newArchitecture] = await db.$transaction(dbOps);
554+
555+
const usage = tokenUsageHandler.getUsage();
556+
const creditResult = await deductCredits(userId, usage.inputTokens, usage.outputTokens);
557+
if (!creditResult.success) {
558+
console.error("Failed to deduct credits:", creditResult.error);
559+
} else {
560+
console.log(`Credits deducted: ${creditResult.deducted}, remaining: ${creditResult.remaining}`);
561+
}
562+
563+
return {
564+
success: true,
565+
architecture: newArchitecture,
566+
prd: updatedArchitecture.prd,
567+
creditsRemaining: creditResult.remaining,
568+
};
569+
570+
} catch (error) {
571+
console.error("Error in updateArchitecture:", error);
572+
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
573+
}
574+
}

actions/chat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface ChatMessage {
1515
interviewAnswers?: InterviewAnswer[];
1616
prompt?: string;
1717
tier2Context?: string;
18+
changeRequirement?: string;
1819
}
1920

2021
// Create a new chat with a specific ID (for localStorage flow)

0 commit comments

Comments
 (0)