一个 for 循环搞定 AI Agent:Function Calling + ReAct 实战

一个 for 循环搞定 AI Agent:Function Calling + ReAct 实战

1.项目目的

一个能在终端里搜索网页、查天气、算数学、记住对话的研究助手。

2.最小原型

vbnet 复制代码
普通 LLM: "特斯拉股价?" → "抱歉,我不知道实时数据。"

Agent:    "特斯拉股价?"
          → LLM: 我需要 search_web 工具
          → 代码: 真的去搜网页
          → 结果喂回 LLM
          → "当前 313 美元,来源 TradingView"

Agent = LLM 决定做什么 + 你的代码真正去做。

3.核心代码

ReAct循环:Agent的核心

python 复制代码
for step in range(max_steps):
    # ---- Step 1: 调 LLM,告诉它有哪些工具可用 ----
    response = client.chat.completions.create(
        model = os.getenv("DEEPSEEK_MODEL","deepseek-v4-pro"),
        messages = self.messages,
        tools = [t.to_openai_schema() for t in self.tools.values()],  # 所有工具描述
        temperature = 0.3
    )
    
    msg = response.choices[0].message
    # model_dump(): ChatCompletionMessage 对象 → dict,否则不能下标访问
    self.messages.append(msg.model_dump())

    # ---- Step 2: LLM 没说要调工具 → 这就是最终回答 ----
    if not msg.tool_calls:
        # ---- 短期记忆清理: 只保留干净的对话历史 ----
        # 删掉 tool_calls 和 tool 结果等中间状态,
        # 只留 System Prompt + 用户消息 + 不含 tool_calls 的助手消息
        answer = msg.content or""
        clean = []
        for m in self.messages:
            if m["role"] == "system":
                clean.append(m)
            elif m["role"] == "user":
                clean.append(m)
            elif m["role"] == "assistant" and not m.get("tool_calls"):
                clean.append(m)
        clean.append({"role":"assistant","content":answer})
        self.messages = clean
        return answer

    # ---- Step 3: LLM 决定调工具 → 真正执行 ----
    for tc in msg.tool_calls:
        name = tc.function.name          # 工具名,如 "search_web"
        args = json.loads(tc.function.arguments)  # 参数 JSON → dict
        tool = self.tools.get(name)      # 找到对应的 Tool 对象

        # 防御性处理: 工具不存在 or 执行出错
        if not tool:
            result = f"工具 '{name}' 不存在"
        else:
            try:
                result = tool.func(**args)   # ← 真正执行函数!
            except Exception as e:
                result = f"工具执行失败: {e}"
            
        # 执行结果作为 Tool 消息喂回 LLM
        self.messages.append({
            "role":"tool",
            "tool_call_id":tc.id,
            "content":str(result),
        })

# 达到最大步数仍无最终回答 → 兜底返回
return "已达到最大步数,但仍未得到答案"

Tool类:让LLM读懂我的函数

python 复制代码
class Tool:
    """
    一个工具 = 名字 + 描述 + 参数规则 + 实际要调用的函数。
    LLM 通过名字和描述决定"该不该调这个工具",
    通过 parameters(JSON Schema 格式)知道"要传什么参数",
    你的代码通过 func 真正执行。
    """

    def __init__(self,name :str ,description : str,parameters :dict,func):
        self.name = name                # 工具名,如 "search_web"
        self.description = description  # 告诉 LLM 这个工具干什么
        self.parameters = parameters    # JSON Schema 格式的参数定义
        self.func = func                # 实际执行的 Python 函数

    def to_openai_schema(self)->dict:
        """转成 OpenAI/DeepSeek API 要求的 tools 参数格式。
        type: "function" + function: {name, description, parameters}"""
        return {
            "type":"function",
            "function":{
                "name":self.name,
                "description":self.description,
                "parameters":self.parameters,
            }
        }

4.自定义工具

工具 干什么 实现
search_web 搜索网页 Tavily API
calculate 算数学 eval()
get_weather 查真实天气 Open-Meteo API
save_to_memory 记住重要信息 ChromaDB
search_memory 回忆之前的话 ChromaDB

长期记忆直接复用了RAG项目的 ChromaDB------写一遍的代码换了个场景再用。

5. 三个最有价值的坑

坑 1:LLM 假装调用了工具

sql 复制代码
现象: LLM 回复"已保存!✅",但 save_to_memory 根本没被调用
学到: 没有硬约束时,LLM 会"表演"符合预期但没发生的回复。
      System Prompt 加"当用户说记住时,你必须调用工具"。

坑 2:Agent 无限循环

makefile 复制代码
现象: 搜了 5 次都不回答,每次都换关键词继续搜
学到: Prompt 工程 = Agent 的边界。加"最多搜 3 次,之后必须给出答案"。

坑 3:短期记忆清理

arduino 复制代码
现象: 第二轮对话时 LLM 看到上轮的 tool_calls 垃圾,行为异常
学到: 消息历史要区分"该留的"和"该删的"------tool_calls 是中间状态,最终回答才留

6.收尾

1. 项目结构

bash 复制代码
ai-research-agent/
├── .env              # DeepSeek + Tavily API Key
├── requirements.txt  # openai, tavily-python, chromadb, sentence-transformers...
└── src/
    └── agent.py      # 全部:Tool 类 + Agent 类 + 5 个工具 + CLI

2. 依赖关系

sql 复制代码
agent.py
  ├── DeepSeek     → 大脑(决策 + 工具选择)
  ├── Tavily       → 手(搜索)
  ├── Open-Meteo   → 手(天气)
  ├── ChromaDB     → 记忆(长期存储,复用 Week 2)
  └── sentence-transformers → 记忆的编码器

一个大脑 + 两只手 + 一个记忆库。


3. GitHub 链接

https://github.com/MRfyyt/ai-research-agent


4. 为什么不用 LangChain(整篇的落点)

"为什么不直接用 LangChain------它封装了 AgentExecutor,调两行 API 就行。但它的底层就是一个 for 循环:LLM 决定 → 执行工具 → 结果回传 → 循环。我用 30 行手写了一遍,现在看 LangChain 源码,每一行都能看懂它在干什么。先理解原理再学框架,比先学框架再理解原理快得多。 "

相关推荐
诺伦9 分钟前
AI Agent 架构实战:从零拆解多Agent协作营销系统的设计与实现
人工智能·架构
遥感知识服务11 分钟前
从局部阈值、双极化到暗地表剔除:NASA OPERA DSWx-S1全球动态水体算法拆解
大数据·人工智能·深度学习·神经网络·算法·机器学习
讲温控就好了12 分钟前
从医疗影像到能源存储——芯片冷却温控技术的跨行业赋能
人工智能·python·能源
ZJU_统一阿萨姆26 分钟前
【算子开发】算子融合与Softmax_LayerNorm实现
人工智能·算法·语言模型·硬件架构
chunmiao303237 分钟前
皮尤研究:ChatGPT 发布后,超三成新网页带 AI 创作痕迹
人工智能·机器学习·chatgpt
HIT_Weston42 分钟前
181、【Agent】【OpenCode】TuiThreadCmd(类型增长)(编译&运行时)
人工智能·agent·opencode
jeffsonfu1 小时前
循环神经网络(RNN)详解:处理序列数据的自然选择
人工智能·深度学习
蓝速科技2 小时前
蓝速科技信创落地:平衡安全合规与项目成本的实战方案
android·运维·人工智能·科技·安全·电脑·鸿蒙