零基础 | 从零实现ReAct Agent:完整技术实现指南

文章目录

从零实现ReAct Agent:完整技术实现指南

手把手实现一个基于ReAct模式的智能Agent,包含完整代码和深度技术解析。

文档信息

前言

ReAct(Reasoning + Acting)是当前最流行的Agent推理模式之一。与传统大模型对话不同,ReAct通过"思考-行动-观察"的循环机制,让AI像人类一样工作:先分析问题,选择合适的工具执行,观察结果后继续推理。

这篇文章从零实现一个完整的ReAct Agent系统,代码简洁但功能完整,适合作为Agent开发入门项目。

项目结构

复制代码
从头实现一个Agent/
├── agent.py      # Agent核心框架
├── main.py       # ReAct执行引擎
└── tools.py      # 工具函数集合

一、Agent核心框架

Agent核心实现如下,负责管理对话历史和调用大模型API。

1.1 核心代码

python 复制代码
# agent.py
import os
import dotenv
from openai import OpenAI

# 加载环境变量
dotenv.load_dotenv()
client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_API_BASE")
)

DEFAULT_MODEL = os.getenv("AI_MODEL", "deepseek-chat")


class Agent:
    """Agent推理框架"""

    def __init__(self, system_prompt=""):
        self._messages = []
        if system_prompt:
            self._messages.append({"role": "system", "content": system_prompt})

    def invoke(self, query: str) -> str:
        """调用Agent进行推理"""
        self._messages.append({"role": "user", "content": query})
        result = self.exec()
        self._messages.append({"role": "assistant", "content": result})
        return result

    def exec(self) -> str:
        """执行推理,返回结果"""
        completion = client.chat.completions.create(
            model=DEFAULT_MODEL,
            messages=self._messages,
            temperature=0  # 推理场景需要确定性输出
        )
        return completion.choices[0].message.content

1.2 设计要点

为什么temperature设为0?

在推理场景中,我们希望Agent的输出更加确定和可控。temperature参数控制模型输出的随机性:

  • temperature=0:模型选择概率最高的token,输出最确定
  • temperature=0.7:增加一些随机性,适合创意任务
  • temperature=1.0+:高度随机,适合生成多样化内容

在ReAct Agent中,我们使用temperature=0,原因如下:

  1. 工具调用需要精确匹配:工具名称和参数必须完全正确
  2. 减少幻觉:避免Agent编造不存在的工具
  3. 提高稳定性:相同输入产生相同输出,便于调试

temperature选择指南

python 复制代码
# 推理任务(工具调用、逻辑推理)
temperature = 0

# 创意任务(故事写作、头脑风暴)
temperature = 0.7 - 1.0

# 平衡任务(需要一定创造性但也要准确)
temperature = 0.3 - 0.5

实际案例

我在项目中遇到过temperature设置不当的问题:

python 复制代码
# 错误示例:temperature=0.7
Question: Calculate 5 * 6
Thought: I need to multiply 5 by 6.
Action: calc: 5 * 6  # 工具名错误!应该是calculate
PAUSE

# 正确示例:temperature=0
Question: Calculate 5 * 6
Thought: I need to multiply 5 by 6.
Action: calculate: 5 * 6  # 工具名正确
PAUSE

消息列表的作用

_messages维护了完整的对话历史,包括:

  • system消息:定义Agent的角色和行为准则
  • user消息:用户的输入
  • assistant消息:Agent的回复

这个历史会在每次API调用时完整传递给大模型,让Agent能够理解上下文。

对话历史管理策略

随着对话进行,消息列表会越来越长,导致:

  1. API调用变慢
  2. Token成本增加
  3. 可能超出模型上下文限制

解决方案

python 复制代码
class Agent:
    def __init__(self, system_prompt="", max_history=10):
        self._messages = []
        self.max_history = max_history
        if system_prompt:
            self._messages.append({"role": "system", "content": system_prompt})

    def invoke(self, query: str) -> str:
        self._messages.append({"role": "user", "content": query})
        result = self.exec()
        self._messages.append({"role": "assistant", "content": result})

        # 压缩历史:保留system消息和最近的N条消息
        if len(self._messages) > self.max_history:
            self._messages = self._messages[:1] + self._messages[-self.max_history:]

        return result

max_history的选择

  • 简单任务:5-10条消息足够
  • 复杂任务:10-20条消息
  • 长期对话:考虑实现摘要机制

二、工具函数集合

Agent需要工具来执行具体操作。我们定义两个简单工具:计算器和水果价格查询。

python 复制代码
# tools.py
def calculate(expression: str) -> float:
    """计算数学表达式
    
    ⚠️ 警告:此实现使用eval(),仅用于演示目的。
    生产环境务必使用 ast.literal_eval() 或 numexpr 等安全方案!
    """
    return eval(expression)


def ask_fruit_unit_price(fruit: str) -> str:
    """查询水果单价"""
    if fruit.casefold() == "apple":
        return "Apple unit price is 10/kg"
    if fruit.casefold() == "banana":
        return "Banana unit price is 6/kg"
    return f"{fruit} unit price is 20/kg"

⚠️ 安全警告 :上述代码中的eval()存在代码注入风险,仅供学习演示。生产环境建议使用ast.literal_eval()或专门的数学表达式解析库(如numexprsympy)。

三、ReAct执行引擎

整个系统的核心部分,驱动"思考-行动-观察"循环。

3.1 Prompt设计

Prompt的质量直接决定Agent的表现。一个好的Prompt需要清晰定义执行流程、可用工具和输出格式。

Prompt设计核心原则

1. 明确执行流程

Agent需要知道每一步该做什么。我们使用"Thought → Action → PAUSE → Observation → Answer"的循环模式。

python 复制代码
react_prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer

Use Thought to describe your thoughts about the question.
Use Action to run one of the available actions - then return PAUSE.
Observation will be the result of running those actions.
""".strip()

为什么需要PAUSE?

PAUSE是一个明确的信号,告诉执行引擎"我已经完成思考,等待观察结果"。如果没有PAUSE,Agent可能会继续输出,导致解析失败。

2. 提供完整示例

示例比抽象描述更有效。一个好的示例应该展示完整的执行流程,包括思考、行动、观察和最终答案。

python 复制代码
example_session = """
Example session:

Question: What is the unit price of apple?
Thought: I need to ask the user for the price of an apple.
Action: ask_fruit_unit_price: apple
PAUSE

You will be called again with this:

Observation: Apple unit price is 10/kg

You then output:

Answer: The unit price of apple is 10 per kg.
"""

示例设计技巧

  1. 完整流程:展示从问题到答案的完整路径
  2. 真实场景:使用实际会遇到的场景
  3. 格式统一:确保示例格式与要求一致
  4. 多角度覆盖:提供不同类型的示例

3. 格式标准化

Action使用固定格式,便于正则匹配。我们选择"Action: 工具名: 参数"的格式。

python 复制代码
# Action指令正则匹配
action_re = re.compile(r'^Action: (\w+): (.*)$')

为什么选择这种格式?

  • 冒号分隔清晰易读
  • 工具名和参数分开,便于解析
  • 与其他输出格式区分明显

4. 工具描述清晰

每个工具都需要清晰的说明,包括功能、参数和返回值。

python 复制代码
tool_descriptions = """
Available actions:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

ask_fruit_unit_price:
e.g. ask_fruit_unit_price: apple
Asks for the price of a fruit
"""

工具描述要素

  1. 工具名称:清晰易懂的名称
  2. 使用示例:展示如何调用
  3. 功能说明:描述工具的作用
  4. 注意事项:特殊情况的说明
Prompt优化技巧

技巧1:Few-shot Learning

提供多个示例,覆盖不同场景。

python 复制代码
react_prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer

Available actions:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number

ask_fruit_unit_price:
e.g. ask_fruit_unit_price: apple
Asks for the price of a fruit

Example 1:
Question: What is the unit price of apple?
Thought: I need to ask for the price of an apple.
Action: ask_fruit_unit_price: apple
PAUSE

Observation: Apple unit price is 10/kg

Answer: The unit price of apple is 10 per kg.

Example 2:
Question: What is 5 * 6?
Thought: I need to calculate 5 times 6.
Action: calculate: 5 * 6
PAUSE

Observation: 30

Answer: 5 times 6 is 30.

Example 3:
Question: What is the total cost of 2kg apple at 10/kg?
Thought: First I need to calculate the cost: 2 * 10
Action: calculate: 2 * 10
PAUSE

Observation: 20

Answer: The total cost is 20.
""".strip()

技巧2:明确边界条件

告诉Agent在什么情况下停止推理。

python 复制代码
react_prompt += """

Important:
- Stop when you have enough information to answer the question
- If you don't know the answer after 3 actions, say "I don't have enough information"
- Always output Answer at the end, never leave the loop without an Answer
"""

技巧3:错误处理指导

告诉Agent遇到错误时如何处理。

python 复制代码
react_prompt += """

If a tool returns an error:
1. Read the error message carefully
2. Try to fix the issue (e.g., correct the calculation)
3. If you can't fix it, explain the error in your Answer
"""

技巧4:输出格式约束

明确答案的格式要求。

python 复制代码
react_prompt += """

Your Answer should:
- Be concise and direct
- Include the final result
- Show your work if it's a calculation
- Be in the same language as the question
"""
完整Prompt示例
python 复制代码
react_prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer

Use Thought to describe your thoughts about the question.
Use Action to run one of the available actions - then return PAUSE.
Observation will be the result of running those actions.

Available actions:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

ask_fruit_unit_price:
e.g. ask_fruit_unit_price: apple
Asks for the price of a fruit

Example session:

Question: What is the unit price of apple?
Thought: I need to ask the user for the price of an apple.
Action: ask_fruit_unit_price: apple
PAUSE

You will be called again with this:

Observation: Apple unit price is 10/kg

You then output:

Answer: The unit price of apple is 10 per kg.

Important:
- Stop when you have enough information to answer the question
- If a tool returns an error, try to fix it or explain the error
- Always output Answer at the end
- Be concise and direct in your Answer
""".strip()

3.2 执行引擎实现

python 复制代码
# main.py
import re
from agent import Agent
from tools import calculate, ask_fruit_unit_price

# 工具注册表
known_tools = {
    "calculate": calculate,
    "ask_fruit_unit_price": ask_fruit_unit_price,
}

# Action指令正则匹配
action_re = re.compile(r'^Action: (\w+): (.*)$')


def react(query: str, max_turns: int = 5) -> None:
    """ReAct执行引擎"""
    agent = Agent(system_prompt=react_prompt)
    question = query

    for turn in range(max_turns):
        print(f"Question: {question}")

        # 获取Agent的思考结果
        result = agent.invoke(question)

        # 解析Action指令
        actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]

        # 如果没有Action,说明推理结束
        if not actions:
            print("Finish")
            return

        # 执行工具调用
        tool, params = actions[0].groups()
        if tool not in known_tools:
            print(f"Unknown tool: {tool}")
            return

        print(f"call tool: {tool}, params: {params}")
        observation = known_tools[tool](params)
        print(f"observation: {observation}")

        # 将观察结果作为下一轮输入
        question = f"Observation: {observation}\n\n"


if __name__ == '__main__':
    react(query="What is the total price of 3kg of apple and 2kg of banana?", max_turns=10)

3.3 执行流程解析

用一个实际例子说明整个流程:

问题:计算3kg苹果和2kg香蕉的总价

第1轮

  • Agent思考:需要先查询苹果单价
  • Action: ask_fruit_unit_price: apple
  • Observation: Apple unit price is 10/kg

第2轮

  • Agent思考:现在需要查询香蕉单价
  • Action: ask_fruit_unit_price: banana
  • Observation: Banana unit price is 6/kg

第3轮

  • Agent思考:现在可以计算总价了
  • Action: calculate: 3 * 10 + 2 * 6
  • Observation: 42

第4轮

  • Agent思考:已经得到结果,输出答案
  • Answer: The total price is 42

完整输出:

bash 复制代码
Question: What is the total price of 3kg of apple and 2kg of banana?
call tool: ask_fruit_unit_price, params: apple
observation: Apple unit price is 10/kg

Question: Observation: Apple unit price is 10/kg

call tool: ask_fruit_unit_price, params: banana
observation: Banana unit price is 6/kg

Question: Observation: Banana unit price is 6/kg

call tool: calculate, params: 3 * 10 + 2 * 6
observation: 42

Question: Observation: 42

Finish

四、ReAct模式深度解析

4.1 为什么需要ReAct?

传统的AI对话是"一问一答"模式,大模型直接根据问题生成答案。这种方式在简单场景下够用,但在复杂任务中会遇到明显问题:

问题1:知识截止问题

问:"今天北京的天气怎么样?"

答:"我无法获取实时天气信息。"

大模型的知识截止到训练时间,无法获取最新数据。

问题2:计算不准确

问:"12345 × 67890 = ?"

答:"838102050"

实际答案是838102050,但大模型经常在复杂计算中出错。

ReAct如何解决这些问题?

ReAct通过"思考-行动-观察"的循环,让AI学会:

  1. 先思考需要什么信息
  2. 选择合适的工具获取信息
  3. 观察工具返回的结果
  4. 基于真实结果继续推理

这样,AI不再依赖"记忆"和"猜测",而是通过工具获取真实数据,大大提高了准确性。

4.2 ReAct与其他模式对比

模式 工作原理 优点 缺点 适用场景
直接回答 直接根据问题生成答案 简单快速,成本低 容易产生幻觉,无法获取实时信息 简单问答、创意写作
CoT(Chain of Thought) 让模型逐步思考推理过程 提高复杂问题的准确性 仍然依赖模型知识,无法获取外部信息 数学推理、逻辑分析
ReAct 思考→行动→观察的循环 可获取真实数据,可扩展性强 实现复杂,成本较高 需要外部信息的复杂任务
Plan-and-Solve 先制定计划,再逐步执行 结构清晰,可控性强 计划可能不完善,需要调整 多步骤任务、工作流自动化

实际选择建议

从我实际项目经验来看:

  • 简单问答:直接用大模型,ReAct反而增加复杂度
  • 需要外部信息:必须用ReAct,比如查询数据库、调用API
  • 复杂推理:ReAct + CoT组合效果最好
  • 实时任务:ReAct是唯一选择,比如监控告警、实时决策

4.3 ReAct模式的优势

相比直接让大模型回答问题,ReAct模式有几个明显优势:

  1. 可追溯性:每一步思考都明确记录,便于调试
  2. 可扩展性:通过添加工具扩展能力,无需重新训练模型
  3. 准确性:工具执行的结果是精确的,避免了幻觉问题
  4. 可控性:可以限制工具的使用范围,提高安全性

4.4 技术要点总结

正则表达式的作用

action_re = re.compile(r'^Action: (\w+): (.*)$')

这个正则负责从Agent的输出中提取Action指令。两个捕获组分别对应工具名称和参数。

max_turns的作用

限制最大推理轮次,防止Agent陷入无限循环。实际应用中可以根据任务复杂度调整。

为什么需要max_turns?

在实际应用中,Agent可能会遇到以下情况:

  1. 陷入死循环:反复调用同一个工具,无法获得有用信息
  2. 无法收敛:不断尝试不同的工具组合,但始终无法得到答案
  3. 成本失控:每轮调用都需要消耗API费用,无限循环会导致成本爆炸

max_turns的选择策略

python 复制代码
# 简单任务(单步或两步推理)
max_turns = 3

# 中等复杂度任务(需要查询多个信息)
max_turns = 5

# 复杂任务(多步骤推理)
max_turns = 10

# 超复杂任务(需要多次迭代)
max_turns = 20

实际案例

python 复制代码
# 案例1:简单任务
react(query="What is 5 * 6?", max_turns=3)
# 实际使用:1轮
# 结果:30

# 案例2:中等复杂度任务
react(query="What is the total price of 3kg apple and 2kg banana?", max_turns=5)
# 实际使用:3轮
# - 第1轮:查询apple价格
# - 第2轮:查询banana价格
# - 第3轮:计算总价
# 结果:42

# 案例3:复杂任务
react(query="Compare the total cost of buying 5kg apple vs 3kg banana + 2kg orange", max_turns=10)
# 实际使用:5轮
# - 第1轮:查询apple价格
# - 第2轮:查询banana价格
# - 第3轮:查询orange价格
# - 第4轮:计算apple总价
# - 第5轮:计算banana+orange总价
# 结果:Apple is cheaper

max_turns设置不当的后果

python 复制代码
# 设置过小
max_turns = 2
react(query="What is the total price of 3kg apple and 2kg banana?")
# 问题:只够查询apple价格,无法完成计算
# 结果:Agent无法给出完整答案

# 设置过大
max_turns = 100
react(query="What is 5 * 6?")
# 问题:浪费资源,实际只需要1轮
# 结果:成本增加,但结果相同

最佳实践

python 复制代码
def react(query: str, max_turns: int = 5, timeout: int = 60):
    """
    改进的ReAct执行引擎

    Args:
        query: 用户问题
        max_turns: 最大推理轮次
        timeout: 超时时间(秒)
    """
    agent = Agent(system_prompt=react_prompt)
    question = query

    import time
    start_time = time.time()

    for turn in range(max_turns):
        # 检查超时
        if time.time() - start_time > timeout:
            print("Timeout reached")
            return

        result = agent.invoke(question)
        actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]

        if not actions:
            print("Finish")
            return

        tool, params = actions[0].groups()
        if tool not in known_tools:
            print(f"Unknown tool: {tool}")
            return

        observation = known_tools[tool](params)
        question = f"Observation: {observation}\n\n"

    print(f"Reached max_turns ({max_turns}) without finishing")

max_turns vs 超时机制

两种保护机制各有优劣:

机制 优点 缺点 适用场景
max_turns 简单直接,易于理解 无法处理单轮耗时过长的情况 快速任务
超时机制 灵活,适应不同任务复杂度 实现复杂,可能中断正在执行的工具 慢速任务
两者结合 最全面的保护 增加代码复杂度 生产环境

建议:生产环境同时使用max_turns和timeout机制。

为什么需要PAUSE?

PAUSE作为明确的停止信号,告诉执行引擎"我已经完成思考,等待观察结果"。缺少这个信号,Agent可能会继续输出,导致解析失败。

4.4 常见问题

Q1:Agent不按格式输出怎么办?

A:优化Prompt,增加更多示例。也可以在代码中添加容错机制,处理格式不规范的情况。

Q2:工具调用失败如何处理?

A:在工具函数中添加try-except,返回友好的错误信息。Agent会根据错误信息调整策略。

Q3:如何添加新工具?

A:三步走:

  1. 在tools.py中定义工具函数
  2. 在known_tools中注册
  3. 在Prompt中添加工具说明

五、实战经验与踩坑记录

5.1 实际开发中的常见问题

问题1:Agent不按格式输出

现象:Agent输出"Action: calculate 3 * 5"而不是"Action: calculate: 3 * 5",导致正则匹配失败。

原因:Prompt不够明确,Agent没有严格遵循格式。

解决方案:

  1. 在Prompt中增加更多格式示例
  2. 使用few-shot learning,提供3-5个完整示例
  3. 在代码中添加容错机制,处理格式不规范的情况
python 复制代码
# 改进后的正则匹配,容错处理
def parse_action(result: str):
    patterns = [
        r'^Action: (\w+): (.*)$',  # 标准格式
        r'^Action: (\w+)\s+(.*)$',  # 容错格式
        r'^(\w+):\s*(.*)$',         # 简化格式
    ]
    for pattern in patterns:
        match = re.search(pattern, result, re.MULTILINE)
        if match:
            return match.groups()
    return None

问题2:工具调用失败导致无限循环

现象:Agent一直调用同一个工具,但工具返回错误,导致无限循环。

原因:缺少错误处理机制,Agent无法从错误中学习。

解决方案:

  1. 在工具函数中添加try-except
  2. 返回友好的错误信息,让Agent理解问题
  3. 设置max_turns,防止无限循环
python 复制代码
def calculate(expression: str) -> str:
    try:
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"Error: {str(e)}. Please check your expression."

问题3:温度设置不当导致工具调用失败

现象:temperature=0.7时,Agent经常输出错误的工具名称或参数。

解决方案:推理场景统一使用temperature=0,保证输出的确定性。关于temperature参数的详细说明见第一章「1.2 设计要点」。

问题4:对话历史过长导致性能下降

现象:随着推理轮次增加,API调用越来越慢,成本也越来越高。

解决方案:实现对话历史压缩,具体方案见第一章「对话历史管理策略」。

5.2 性能优化建议

优化1:工具调用缓存

对于重复的工具调用,可以缓存结果避免重复计算。

python 复制代码
from functools import lru_cache

@lru_cache(maxsize=100)
def calculate(expression: str) -> str:
    return str(eval(expression))

优化2:并行工具调用

如果多个工具调用之间没有依赖关系,可以并行执行。

python 复制代码
import concurrent.futures

def parallel_tool_calls(tools_and_params):
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = []
        for tool_name, params in tools_and_params:
            future = executor.submit(known_tools[tool_name], params)
            futures.append(future)

        results = []
        for future in concurrent.futures.as_completed(futures):
            results.append(future.result())
        return results

优化3:Prompt模板化

将Prompt中不变的部分提取为模板,减少每次构造Prompt的开销。

python 复制代码
from string import Template

REACT_TEMPLATE = Template("""
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer

Available actions:

$tools

Example session:

$example
""")

def build_prompt(tools, example):
    return REACT_TEMPLATE.substitute(
        tools="\n".join(tools),
        example=example
    )

5.3 调试技巧

技巧1:打印完整推理过程

在开发阶段,打印每一步的思考过程,便于调试。

python 复制代码
def react(query: str, max_turns: int = 5, debug=False):
    agent = Agent(system_prompt=react_prompt)
    question = query

    for turn in range(max_turns):
        result = agent.invoke(question)

        if debug:
            print(f"\n=== Turn {turn + 1} ===")
            print(f"Question: {question}")
            print(f"Agent Output:\n{result}")

        actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]
        if not actions:
            break

        tool, params = actions[0].groups()
        observation = known_tools[tool](params)

        if debug:
            print(f"Tool: {tool}")
            print(f"Params: {params}")
            print(f"Observation: {observation}")

        question = f"Observation: {observation}\n\n"

技巧2:保存对话历史

将推理过程保存到文件,便于后续分析。

python 复制代码
import json
from datetime import datetime

def save_conversation(messages, filename=None):
    if filename is None:
        filename = f"conversation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"

    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(messages, f, ensure_ascii=False, indent=2)

    print(f"Conversation saved to {filename}")

技巧3:使用日志系统

用logging替代print,便于管理日志级别。

python 复制代码
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def react(query: str, max_turns: int = 5):
    logger.info(f"Starting ReAct with query: {query}")

    for turn in range(max_turns):
        result = agent.invoke(question)
        logger.debug(f"Turn {turn}: {result}")

        actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]
        if not actions:
            logger.info("No more actions, finishing")
            break

        tool, params = actions[0].groups()
        logger.info(f"Calling tool: {tool} with params: {params}")
        observation = known_tools[tool](params)
        logger.info(f"Observation: {observation}")

        question = f"Observation: {observation}\n\n"

5.4 最佳实践

实践1:工具函数设计原则

  1. 单一职责:每个工具只做一件事
  2. 参数简单:避免复杂参数结构
  3. 返回明确:返回值要清晰易懂
  4. 错误处理:捕获异常并返回友好信息
  5. 文档完善:添加详细的docstring
python 复制代码
def search_database(query: str, limit: int = 10) -> str:
    """
    在数据库中搜索数据

    Args:
        query: 搜索关键词
        limit: 返回结果数量限制,默认10

    Returns:
        搜索结果的JSON字符串,格式为:
        {
            "total": 总数量,
            "results": [结果列表]
        }

    Raises:
        ValueError: 当query为空时
    """
    if not query:
        raise ValueError("Query cannot be empty")

    try:
        results = db.search(query, limit=limit)
        return json.dumps({
            "total": len(results),
            "results": results
        })
    except Exception as e:
        return json.dumps({
            "error": str(e),
            "message": "Database search failed"
        })

实践2:Prompt设计原则

  1. 明确指令:用祈使句,清晰表达期望
  2. 提供示例:用few-shot learning提高理解
  3. 格式统一:使用固定的输出格式
  4. 边界说明:明确工具的能力边界
  5. 错误提示:告诉Agent遇到错误怎么办

实践3:安全考虑

  1. 输入验证:验证工具参数的合法性
  2. 权限控制:限制工具的访问范围
  3. 审计日志:记录所有工具调用
  4. 沙箱执行:危险操作在隔离环境执行
python 复制代码
import subprocess

def execute_command(command: str) -> str:
    """
    执行系统命令(沙箱环境)

    Args:
        command: 要执行的命令

    Returns:
        命令执行结果

    Raises:
        ValueError: 当命令包含危险操作时
    """
    dangerous_commands = ['rm', 'format', 'del', 'shutdown']
    if any(cmd in command for cmd in dangerous_commands):
        raise ValueError("Dangerous command detected")

    try:
        result = subprocess.run(
            command,
            shell=True,
            capture_output=True,
            text=True,
            timeout=30  # 防止长时间运行
        )
        return result.stdout
    except subprocess.TimeoutExpired:
        return "Command execution timeout"

六、扩展方向

这个基础实现还有很多可以优化的地方:

  1. 工具描述优化:为每个工具添加详细的描述,帮助Agent更好地理解工具用途
  2. 多工具并行:支持同时调用多个工具,提高效率
  3. 记忆机制:添加长期记忆,跨会话保持信息
  4. 错误恢复:工具调用失败时的自动重试和降级策略
  5. 可视化:展示推理链路,方便调试和展示

七、环境配置

依赖安装

bash 复制代码
pip install openai python-dotenv

Python版本要求

  • Python >= 3.8

环境变量配置

在项目根目录创建.env文件:

env 复制代码
OPENAI_API_KEY=your_api_key
OPENAI_API_BASE=https://api.deepseek.com/v1
AI_MODEL=deepseek-chat

结语

抛开LangChain等框架,用更底层的方式实现了一个基于ReAct框架的Agent。整个过程中,我们看到了如何编写ReAct提示词,让大模型通过思考(Thought)、行动(Action)、观察(Observation)来完成更多的工作。

在这个实现中:

  • 规划:通过ReAct提示词完成
  • 记忆:通过聊天历史实现
  • 动作:通过一个一个的函数实现

如果这篇文章你只记住一件事,那请记住:Agent的执行过程本质上就是一个循环,由大模型引导着一次次地做着各种动作,以达成我们的目标。

后续可以在此基础上添加更多工具,比如网络搜索、文件操作、数据库查询等,逐步构建更强大的Agent应用。

参考资源


完整代码仓库项目地址

相关推荐
白柚Y8 小时前
react的hooks
前端·javascript·react.js
vueTmp8 小时前
个人开发者系列-上线即“爆火”?那些掏空你 Cloudflare 额度的虚假繁荣
前端·nuxt.js
i7i8i9com8 小时前
React 19+Vite+TS学习基础-1
前端·学习·react.js
CHANG_THE_WORLD8 小时前
switch case 二分搜索风格
前端·数据库
我的golang之路果然有问题8 小时前
实习中遇到的 CORS 同源策略自己的理解分析
前端·javascript·vue·reactjs·同源策略·cors
Pony_188 小时前
面试 - web ui 自动化
前端·ui·自动化
EndingCoder8 小时前
接口基础:定义对象形状
linux·运维·前端·javascript·typescript
passerma9 小时前
解决qiankun框架子应用打包后css里的图片加载404失败问题
前端·微前端·qiankun
Aliex_git9 小时前
性能优化 - Vue 日常实践优化
前端·javascript·vue.js·笔记·学习·性能优化