Agent 研发:ReAct Agent

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;
    }
}
相关推荐
laplace01231 小时前
智能体经典范式构建
算法·langchain·大模型·agent
金融RPA机器人丨实在智能1 小时前
通用Agent智能体有哪些?国内优选实在Agent、扣子,国际优选ChatGPT Agents、Microsoft Copilot
agent·实在智能·实在agent
草帽lufei14 小时前
Prompt Engineering基础实践:角色设定/约束条件等技巧
openai·agent
贾维思基14 小时前
告别RPA和脚本!视觉推理Agent,下一代自动化的暴力解法
人工智能·agent
程序猿DD19 小时前
Anthropic 如何评估 AI Agent
agent
饭勺oO20 小时前
AI 编程配置太头疼?ACP 帮你一键搞定,再也不用反复折腾!
ai·prompt·agent·acp·mcp·skills·agent skill
AGI杂货铺20 小时前
零基础也能快速搭建的Deep Agents
ai·langchain·llm·agent·deepagent
AlienZHOU20 小时前
MCP 是最大骗局?Skills 才是救星?
agent·mcp·vibecoding
Glink21 小时前
从零开始编写自己的AI账单Agent
前端·agent·ai编程
进阶的鱼21 小时前
一文助你了解Langchain
python·langchain·agent