import { chatCompletion, MAX_LOOPS } from './dashscope'
import { groupStepsIntoRounds, countToolCalls } from './rounds'
import { resolveThought } from './thought'
import { executeTool, TOOL_DEFINITIONS, TOOL_LABELS } from './tools'
import type { ChatMessage, ReActStep, RunResult } from '../types'
const SYSTEM_PROMPT = `你是 ReAct(Reasoning + Acting)模式的教学演示助手。
每轮严格遵循:
1. Thought:先分析用户问题,说明是否需要工具、为什么
2. Action:需要实时数据时,调用对应工具(每次一个)
3. Observation:等待工具返回,不要编造
4. 拿到 Observation 后,再 Thought 一次,然后给出最终 Answer
硬性规则:
- 天气、当前时间、大数计算、汇率 → 必须调用工具
- 最终回答必须引用工具返回的真实数字
- 不要跳过思考步骤,即使你会同时发起工具调用`
function uid(): string {
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
}
function toAssistantMessage(msg: Awaited<ReturnType<typeof chatCompletion>>): ChatMessage {
return {
role: 'assistant',
content: msg.content ?? '',
tool_calls: msg.tool_calls,
}
}
export async function runReAct(
apiKey: string,
userQuestion: string,
onStep?: (step: ReActStep) => void,
): Promise<RunResult> {
const steps: ReActStep[] = []
const messages: ChatMessage[] = [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: userQuestion },
]
const push = (step: ReActStep) => {
steps.push(step)
onStep?.(step)
}
push({
id: uid(),
type: 'input',
round: 0,
title: '用户提问',
content: userQuestion,
})
let loops = 0
let finalAnswer = ''
let hadToolCalls = false
while (loops < MAX_LOOPS) {
loops++
const reply = await chatCompletion(apiKey, messages, TOOL_DEFINITIONS)
messages.push(toAssistantMessage(reply))
if (!reply.tool_calls?.length) {
const thought = resolveThought({
userQuestion,
round: loops,
modelContent: reply.content,
modelReasoning: reply.reasoning_content,
phase: hadToolCalls ? 'synthesis_answer' : 'direct_answer',
hadToolCalls,
})
push({
id: uid(),
type: 'thought',
round: loops,
title: `第 ${loops} 轮 · 思考`,
content: thought,
})
finalAnswer = reply.content?.trim() || '(模型未返回内容)'
push({
id: uid(),
type: 'answer',
round: loops,
title: '最终回答',
content: finalAnswer,
})
break
}
const call = reply.tool_calls[0]
const toolName = call.function.name
const toolArgs = call.function.arguments
const label = TOOL_LABELS[toolName] ?? toolName
hadToolCalls = true
push({
id: uid(),
type: 'thought',
round: loops,
title: `第 ${loops} 轮 · 思考`,
content: resolveThought({
userQuestion,
round: loops,
modelContent: reply.content,
modelReasoning: reply.reasoning_content,
phase: 'before_tool',
toolName,
toolArgs,
}),
})
push({
id: uid(),
type: 'action',
round: loops,
title: `第 ${loops} 轮 · 行动`,
content: `调用工具:${label}(${toolName})`,
meta: { tool: toolName, arguments: toolArgs },
})
const result = await executeTool(toolName, toolArgs)
push({
id: uid(),
type: 'observation',
round: loops,
title: `第 ${loops} 轮 · 观察`,
content: result,
meta: { tool: toolName },
})
messages.push({
role: 'tool',
tool_call_id: call.id,
name: toolName,
content: result,
})
}
if (!finalAnswer && loops >= MAX_LOOPS) {
finalAnswer = '已达到最大推理轮次,请简化问题后重试。'
push({
id: uid(),
type: 'answer',
round: loops,
title: '最终回答',
content: finalAnswer,
})
}
return {
steps,
rounds: groupStepsIntoRounds(steps),
finalAnswer,
toolCallCount: countToolCalls(steps),
messages,
}
}