ReAct 介绍
运作流程
用一句话简单描述:ReAct 智能体是基于推理和行动反馈的框架,通过循环调用 LLM 思考得到概率纠正结果。
理论学习参考:www.ibm.com/cn-zh/think...
arduino
// 伪代码
def ReAct(query) {
call: Agent
Thought
call: Tool
Action
if Action === 'Finish'
Answer
else
loop: ReAct
}
ReAct Agent 实现要点
此例子来自于 hello_agents 学习文档,有兴趣的同学也可以阅读
主要是思维变化:面向场景选择合适模型->模型友好交互方式->得到合理答案,在 ReAct 模式中主要是 Prompt 策略和上下文的丰富度会影响调用成本和结果。
- Prompt 规划和约束
- 明确约定思考和行动标记:用于组合 LLM 思维链
- 明确推理结束标记:用于优化 Prompt 内容
- 明确推理记忆标记:用于丰富 Prompt 内容
- 明确推理外部调用能力描述:用于提升 LLM 处理准确性
- 灵活切换模型、Prompt 策略优化、工具增强,提升循环调用效果(这个对企业成本比较重要)
完整例子实现
整体基于 OpenRouter 调用,有兴趣的同学可以选个免费模型跑一下。
- LLM Client
typescript
import OpenAI from "openai";
export class LLM {
private client: OpenAI;
private modelId: string;
constructor(apiKey: string, modelId: string = "mistralai/devstral-2512:free") {
this.client = new OpenAI({
apiKey: apiKey,
baseURL: "https://openrouter.ai/api/v1",
dangerouslyAllowBrowser: true,
});
this.modelId = modelId;
}
setModel(modelId: string) {
this.modelId = modelId;
}
getModel() {
return this.modelId;
}
async chat(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]) {
return this.client.chat.completions.create({
model: this.modelId,
messages: messages,
});
}
async stream(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]) {
return this.client.chat.completions.create({
model: this.modelId,
messages: messages,
stream: true,
});
}
}
- ReActAgent
typescript
import type {LLM} from "./LLM.ts";
import {ToolExecutor} from "./ToolExecutor.ts";
type PromptTplProps = {
question: string;
history: string;
tools: string[];
}
const getPromptTpl = (props: PromptTplProps) => {
const {
tools,
question,
history,
} = props;
return `
请注意,你是一个有能力调用外部工具的智能助手。
能够使用的工具如下:
${tools.join('\n')}
请严格按照下面的格式进行回复:
Thought: 你的思考过程用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行为,必须是以下格式之一:
- {toolName}[{toolInput}]: 调用一个可用工具。
- Finish[最终答案]: 当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在Action:字段后使用 finish(answer="...") 来输出最终答案。
现在,请开始解决以下问题:
Question: ${question}
History: ${history}
`;
};
export class ReActAgent {
private history: string[];
constructor(
// 调用的 llm
private readonly llm: LLM,
// 最多循环思考几次
private readonly maxSteps: number = 3,
private readonly toolExecutor: ToolExecutor,
) {
this.history = [];
}
async run(question: string) {
let currentStep = 0;
while (currentStep < this.maxSteps) {
currentStep++;
console.log(`-----第${currentStep}步骤-------`);
const history = this.history.join('\n');
const tools = this.toolExecutor.getAvailableTools();
const prompt = getPromptTpl({
tools,
question,
history,
});
console.log('prompt:', prompt);
try {
const responseText = await this.llm.chat([{
role: 'user',
content: prompt,
}]);
console.log('大模型返回:', responseText);
const {thought, action} = this.parseOutput(responseText.choices[0].message.content ?? '');
if (thought) {
console.log('Thought:', thought);
}
if (!action) {
console.warn('模型没有返回具体的 Action,流程终止');
break;
}
console.log('Action:', action);
if (action.startsWith('Finish')) {
const answer = this.parseFinishAnswer(action);
console.log('最终答案: ', answer);
return answer;
}
const [toolName, toolInput] = this.parseAction(action);
if (!toolName || !toolInput) {
console.warn('无法解析 Action:', action);
continue;
}
const tool = this.toolExecutor.getTool(toolName);
let observation = '';
if (tool) {
observation = tool.fn(toolInput) as string;
}
console.log('Observation:', observation);
this.history.push(`Action:${action}`);
this.history.push(`Observation:${observation}`)
} catch (err) {
console.error(err);
}
}
console.log('已达到最大步数');
}
parseFinishAnswer(action: string) {
const answer = action.match(/Finish[(.*)]/);
if (answer && answer.length > 1) {
return answer[1];
}
return '-';
}
parseOutput(str: string) {
const thought = str.match(/Thought:(.*)/);
const action = str.match(/Action:(.*)/);
type Result = {
thought: string;
action: string;
};
const result: Result = {
thought: '',
action: '',
};
if (thought && thought.length > 1) {
result.thought = thought[1].trim();
}
if (action && action.length > 1) {
result.action = action[1].trim();
}
return result;
}
parseAction(str: string) {
const info = str.match(/(\w+)[(.*)]/);
if (info && info.length > 2) {
// info[0] is the full match, e.g., "calc_sum[1,1]"
// info[1] is the first capture group (the tool name), e.g., "calc_sum"
// info[2] is the second capture group (the tool input), e.g., "1,1"
return [info[1], info[2]];
}
return [];
}
}
- 工具注册和执行
typescript
type Tool = {
desc: string;
fn: (arg: string) => unknown;
}
export class ToolExecutor {
private tools: Map<string, Tool>;
constructor() {
this.tools = new Map();
}
register(name: string, desc: string, fn: (arg: string) => unknown) {
console.log(`您注册的工具:${name}, ${desc}`);
this.tools.set(name, {
desc,
fn,
} as Tool);
}
getTool(toolName: string): Tool | undefined {
if (!this.tools.has(toolName)) {
return;
}
return this.tools.get(toolName);
}
getAvailableTools() {
const result: string[] = [];
this.tools.forEach((tool, name) => {
result.push(`${name}: ${tool.desc}`);
});
return result;
}
}