ai-chatbot/lib/ai/auto-agent-graph.ts
Your Name 023c2fb754
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
增强Agent真实分析对话并减少硬编码兜底
2026-02-08 22:29:15 +08:00

246 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Annotation, StateGraph, START, END } from "@langchain/langgraph";
import { generateText } from "ai";
import type { AnalysisResult } from "@/lib/ai/smart-analyzer";
import type { RouteResult } from "@/lib/ai/model-router";
interface HistoryMessage {
role: "user" | "assistant";
content: string;
}
export interface AutoAgentInput {
text: string;
hasImage: boolean;
imageUrls?: string[];
imageFromHistory?: boolean;
historyMessages?: HistoryMessage[];
context?: string;
}
export interface AutoAgentResult {
analysis?: AnalysisResult;
friendlyMessage?: string;
optimizedPrompt?: string;
route?: RouteResult;
}
const AutoAgentState = Annotation.Root({
text: Annotation<string>,
hasImage: Annotation<boolean>,
imageUrls: Annotation<string[]>,
imageFromHistory: Annotation<boolean>,
historyMessages: Annotation<HistoryMessage[]>,
context: Annotation<string | null>,
analysis: Annotation<AnalysisResult | null>,
friendlyMessage: Annotation<string | null>,
optimizedPrompt: Annotation<string | null>,
route: Annotation<RouteResult | null>,
});
type AutoAgentStateType = typeof AutoAgentState.State;
function compactText(text: string, max = 48) {
const normalized = text.replace(/\s+/g, " ").trim();
if (normalized.length <= max) return normalized;
return `${normalized.slice(0, max)}...`;
}
function buildStrategicFriendlyMessage(
analysis: AnalysisResult,
state: AutoAgentStateType,
): Promise<string | null> {
const fallbackQuestion = analysis.clarifyingQuestions?.[0];
if (analysis.needsClarification) {
return Promise.resolve(
analysis.conversationalReply || fallbackQuestion || "我先确认一下,你希望我优先调整哪一部分?"
);
}
return generateAgentConversation(analysis, state);
}
async function generateAgentConversation(
analysis: AnalysisResult,
state: AutoAgentStateType
): Promise<string | null> {
try {
const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries");
const { getLanguageModelById } = await import("./dynamic-provider");
const allModels = await getEnabledAiModelsWithProvider();
const candidateModel = state.hasImage
? allModels.find((m) => m.model.supportsVision && m.model.isEnabled)
: allModels.find((m) => m.model.isEnabled && !m.model.isSubModel);
if (!candidateModel) {
return analysis.conversationalReply || null;
}
const model = await getLanguageModelById(candidateModel.model.id);
if (!model) {
return analysis.conversationalReply || null;
}
const history = state.historyMessages
.slice(-6)
.map((msg) => `${msg.role === "user" ? "用户" : "助手"}: ${compactText(msg.content, 120)}`)
.join("\n");
const imageInfo = state.hasImage
? `用户本轮可用图片数量:${state.imageUrls.length || 1}。图片来源:${state.imageFromHistory ? "历史消息复用" : "本轮上传"}。`
: "用户本轮没有上传图片。";
const systemPrompt = `你是一个真实执行中的 AI 生图/修图 Agent。
请根据用户需求和分析结果,给出“正在执行前”的简短汇报。
要求:
1) 不要说“我无法看到图片”;按系统提供的信息做合理分析。
2) 必须看起来是本轮真实分析结果,避免空泛套话。
3) 不暴露内部实现、模型路由、算法细节。
4) 输出 4-7 行 Markdown,语气专业自然。
5) 如果是图像任务,包含以下小节标题:需求理解、分析结论、执行计划、交付说明。
6) 每一行尽量具体,不要重复。`;
const prompt = [
`【用户原始需求】${state.text || "(无文本)"}`,
`【意图分析】intent=${analysis.intent}; subIntent=${analysis.subIntent || "none"}; confidence=${analysis.confidence}`,
`【图片信息】${imageInfo}`,
state.context ? `【画布上下文】${compactText(state.context, 300)}` : "",
history ? `【最近对话】\n${history}` : "",
"请输出本轮执行前汇报:",
]
.filter(Boolean)
.join("\n\n");
const { text } = await generateText({
model,
system: systemPrompt,
prompt,
temperature: 0.35,
maxTokens: 420,
} as any);
const cleaned = text.trim();
if (cleaned.length < 10) {
return analysis.conversationalReply || null;
}
return cleaned;
} catch (error) {
return analysis.conversationalReply || null;
}
}
const analyzeNode = async (state: AutoAgentStateType) => {
const { analyzeIntent } = await import("./smart-analyzer");
const analysis = await analyzeIntent({
text: state.text,
hasImage: state.hasImage,
imageUrls: state.imageUrls,
imageFromHistory: state.imageFromHistory,
historyMessages: state.historyMessages,
context: state.context ?? undefined,
});
const fallbackQuestion = analysis.needsClarification
? analysis.clarifyingQuestions?.[0]
: undefined;
const strategicMessage = await buildStrategicFriendlyMessage(analysis, state);
return {
analysis,
friendlyMessage: strategicMessage || analysis.conversationalReply || fallbackQuestion || null,
};
};
const shouldOptimize = (state: AutoAgentStateType) => {
if (!state.analysis) return "route";
if (state.analysis.needsClarification) return "route";
if (state.analysis.intent === "image_gen") {
return "optimize";
}
if (state.analysis.intent === "image_edit" && state.analysis.subIntent) {
return "optimize";
}
return "route";
};
const optimizeNode = async (state: AutoAgentStateType) => {
if (!state.analysis) {
return {};
}
if (state.analysis.intent === "image_edit" && !state.analysis.subIntent) {
return {};
}
if (state.analysis.intent !== "image_edit" && state.analysis.intent !== "image_gen") {
return {};
}
const { optimizePromptWithLLM } = await import("./prompt-optimizer");
const optimized = await optimizePromptWithLLM({
originalPrompt: state.text,
intent: state.analysis.intent,
subIntent: state.analysis.subIntent,
historyMessages: state.historyMessages,
context: state.context ?? undefined,
});
return { optimizedPrompt: optimized.optimizedPrompt };
};
const routeNode = async (state: AutoAgentStateType) => {
const { routeToModel } = await import("./model-router");
const route = await routeToModel({
intent: state.analysis?.intent || "chat",
subIntent: state.analysis?.subIntent,
hasImage: state.hasImage,
});
return { route };
};
let compiledGraph: ReturnType<typeof buildGraph> | null = null;
function buildGraph() {
return new StateGraph(AutoAgentState)
.addNode("analyze", analyzeNode)
.addNode("optimize", optimizeNode)
.addNode("route", routeNode)
.addEdge(START, "analyze")
.addConditionalEdges("analyze", shouldOptimize, {
optimize: "optimize",
route: "route",
})
.addEdge("optimize", "route")
.addEdge("route", END)
.compile();
}
function getGraph() {
if (!compiledGraph) {
compiledGraph = buildGraph();
}
return compiledGraph;
}
export async function runAutoAgentGraph(params: AutoAgentInput): Promise<AutoAgentResult> {
const graph = getGraph();
const result = await graph.invoke({
text: params.text,
hasImage: params.hasImage,
imageUrls: params.imageUrls ?? [],
imageFromHistory: params.imageFromHistory ?? false,
historyMessages: params.historyMessages ?? [],
context: params.context ?? null,
analysis: null,
friendlyMessage: null,
optimizedPrompt: null,
route: null,
});
return {
analysis: result.analysis ?? undefined,
friendlyMessage: result.friendlyMessage ?? undefined,
optimizedPrompt: result.optimizedPrompt ?? undefined,
route: result.route ?? undefined,
};
}