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;
    }
}
相关推荐
大模型教程5 小时前
大模型LLM入门篇:小白入门一文快速了解大模型(附教程)
langchain·llm·agent
L.EscaRC7 小时前
【TextIn大模型加速器 + 火山引擎】通过COZE平台构建企业级数字投标专家Agent
agent·火山引擎·工作流
AI大模型7 小时前
别再被割韭菜!真正免费的Prompt学习路径,0基础也能抄
程序员·llm·agent
咚咚咚ddd8 小时前
AI 应用开发:Agent @在线文档功能 - 前端交互与设计
前端·aigc·agent
安思派Anspire9 小时前
Claude技能:真正解决实际问题的AI功能
aigc·openai·agent
用户377833043499 小时前
( 教学 )Agent 构建 Memory(提示词对话存储)1. ConversationBufferMemory(对话缓存存储, 版本>1.0和<1.0的区别
langchain·agent
大模型真好玩10 小时前
LangGraph1.0速通指南(三)—— LangGraph1.0 自动邮件处理智能体实战
人工智能·langchain·agent
熊猫钓鱼>_>11 小时前
多智能体协作:构建下一代高智能应用的技术范式
人工智能·ai·去中心化·wpf·agent·多智能体·multiagent