后端工程师纯原生手搓Agent,吃透ReAct底层循环(完整可运行代码01,持续更新中)

”我“:Java/Go后端开发者、有点时间想自己琢磨,想入门Agent但不想堆砌框架、希望理解底层原理的研发

一、前言:为什么放弃框架,从零手搓Agent?

作为常年写Java、Go的后端工程师,近期开始学习Agent智能体开发。
最开始直接上手LangChain、LangGraph、Dify,踩了大量典型坑:

  1. 框架封装度极高,ReAct循环、工具调用、记忆管理全部黑盒封装,出现异常无从断点排查;
  2. 框架自定义语法、内置类繁多,底层HTTP请求、Prompt拼接、输出解析逻辑完全被掩盖;
  3. 想要自定义工具、改造调度逻辑时,多层抽象层限制扩展,修改一处需要联动大量组件。

想要真正吃透Agent核心运行逻辑,最优路径:不引入任何Agent专用框架,仅使用requests、pydantic等基础库,纯原生实现一套标准ReAct智能体

本文全部代码无LangChain、无AgentScope、无任何智能体封装库,分层设计贴合后端面向接口、分层开发思维,Java/Go开发者极低学习门槛,复制到PyCharm配置密钥即可运行。手搓完之后再逐步升级迭代为工程化实践。
在这里插入图片描述

二、环境与项目结构

2.1 编辑器推荐

PyCharm 社区免费版:断点调试循环逻辑、虚拟环境管理、多模块分层管理体验最优,适合完整工程开发。

2.2 项目目录

native_agent/
├── .env                # 密钥配置,禁止提交仓库
├── .gitignore          # 过滤缓存、环境、向量库文件
├── requirements.txt    # 最小依赖清单
├── env_loader.py       # 统一读取环境配置(配置层)
├── llm_client.py       # LLM原生HTTP请求客户端(客户端层)
├── main.py             # Agent程序入口,主循环调度
├── agent/
│   ├── __init__.py
│   ├── memory.py       # 短期对话上下文记忆
│   └── parser.py       # ReAct输出文本解析器
└── tools/
    ├── __init__.py
    ├── base_tool.py    # 工具抽象基类(对应Java Interface/Go interface)
    └── calculator.py  # 示例工具:数学计算器

2.3 依赖文件 requirements.txt

仅基础工具,无重型Agent框架

requests==2.31.0
python-dotenv==1.0.0
pydantic==2.8.2
chromadb==0.5.5

终端执行安装命令

# 初始化虚拟环境
python -m venv venv
# Windows激活环境
venv\Scripts\activate
# Mac/Linux激活环境
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt

2.4 配置文件 .env

兼容通义千问、DeepSeek、智谱等所有OpenAI兼容接口

LLM_API_KEY=替换成你的大模型密钥
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1

2.5 .gitignore 规范

venv/
.env
*.chroma
__pycache__/
*.pyc
*.log

三、分层完整代码实现

3.1 配置加载层 env_loader.py

对标Java配置工具类、Go全局Config结构体,统一管理环境变量

from dotenv import load_dotenv
import os

# 加载根目录.env配置文件
load_dotenv()

class LLMConfig:
    """大模型全局配置类"""
    API_KEY = os.getenv("LLM_API_KEY")
    BASE_URL = os.getenv("LLM_BASE_URL")

3.2 LLM HTTP客户端 llm_client.py

不使用厂商SDK,原生发送POST请求,直观看懂Function Calling底层报文,对标OkHttp/Go http.Client封装

import requests
from env_loader import LLMConfig

def chat_completion(messages: list) -> str:
    """
    原生调用大模型对话接口
    :param messages: 标准对话消息列表
    :return: 大模型原始返回文本
    """
    headers = {
        "Authorization": f"Bearer {LLMConfig.API_KEY}",
        "Content-Type": "application/json"
    }
    request_body = {
        "model": "qwen-turbo",
        "messages": messages,
        "temperature": 0.1
    }
    # 发送HTTP请求
    resp = requests.post(
        url=f"{LLMConfig.BASE_URL}/chat/completions",
        headers=headers,
        json=request_body,
        timeout=30
    )
    # 抛出请求异常
    resp.raise_for_status()
    # 提取返回内容
    return resp.json()["choices"][0]["message"]["content"]

# 单独测试客户端入口
if __name__ == "__main__":
    result = chat_completion([{"role": "user", "content": "你好"}])
    print("大模型返回:", result)

3.3 工具抽象层(面向接口编程)

3.3.1 工具基类 tools/base_tool.py

Python抽象基类ABC,等价Java Interface、Go隐式接口,统一所有工具执行规范

from abc import ABC, abstractmethod

class BaseTool(ABC):
    """所有工具的顶层抽象接口"""

    @property
    @abstractmethod
    def name(self) -> str:
        """工具唯一标识名称,LLM通过该名称调用"""
        pass

    @property
    @abstractmethod
    def desc(self) -> str:
        """工具描述,送入Prompt供大模型理解工具能力"""
        pass

    @abstractmethod
    def run(self, params: dict) -> str:
        """
        执行工具逻辑
        :param params: LLM传入的参数字典
        :return: 工具执行结果字符串,回传给大模型
        """
        pass
3.3.2 计算器工具 tools/calculator.py

第一个可运行业务工具,实现抽象基类全部方法

from tools.base_tool import BaseTool

class CalcTool(BaseTool):
    @property
    def name(self) -> str:
        return "calculator"

    @property
    def desc(self) -> str:
        return "数学计算器,入参expr为数学表达式,例如 (10+20)*5"

    def run(self, params: dict) -> str:
        expr = params["expr"]
        # 简易计算,生产环境必须使用沙箱隔离eval风险
        calc_result = eval(expr)
        return f"计算执行完成:{expr} = {calc_result}"

3.4 Agent核心支撑模块

3.4.1 短期记忆 agent/memory.py

管理对话上下文、用户提问、工具返回观测值,对标后端内存会话缓存

class ShortMemory:
    """短期上下文记忆,存储一轮Agent全部交互记录"""
    def __init__(self):
        self.history = []

    def add_user(self, content: str):
        """添加用户原始提问"""
        self.history.append({"role": "user", "content": content})

    def add_assistant(self, content: str):
        """添加大模型直接回答"""
        self.history.append({"role": "assistant", "content": content})

    def add_observation(self, content: str):
        """添加工具执行后的观测结果,作为系统消息送入下一轮Prompt"""
        self.history.append({"role": "system", "content": f"工具执行结果:{content}"})

    def get_all(self) -> list:
        """获取全部历史对话记录"""
        return self.history
3.4.2 ReAct输出解析器 agent/parser.py

手动解析大模型固定格式输出,提取思考、工具名、参数,彻底理解ReAct文本交互原理

import json
from typing import Optional, Dict

class ReActParser:
    @staticmethod
    def parse(llm_output: str) -> Optional[Dict]:
        """
        解析LLM返回的ReAct标准文本
        返回None代表无需调用工具,直接结束任务
        """
        thought = ""
        action_name = ""
        params = {}
        lines = llm_output.strip().split("\n")
        for line in lines:
            line = line.strip()
            if line.startswith("Thought:"):
                thought = line.replace("Thought:", "").strip()
            elif line.startswith("Action:"):
                action_name = line.replace("Action:", "").strip()
            elif line.startswith("Params:"):
                param_json_str = line.replace("Params:", "").strip()
                params = json.loads(param_json_str)

        # 无工具动作,直接返回空
        if not action_name or action_name == "FINISH":
            return None

        return {
            "thought": thought,
            "action": action_name,
            "params": params
        }

3.5 主程序入口 main.py|完整ReAct循环调度引擎

整合所有模块,实现Agent核心循环:思考→调用工具→获取观测→循环直至任务完成

from agent.memory import ShortMemory
from agent.parser import ReActParser
from llm_client import chat_completion
from tools.base_tool import BaseTool
from tools.calculator import CalcTool

# 1. 注册全部可用工具,构建工具映射(工厂模式)
tool_list: list[BaseTool] = [CalcTool()]
tool_name_map = {tool.name: tool for tool in tool_list}

# 2. 系统提示词:强制约束LLM输出ReAct固定格式
SYSTEM_PROMPT = """
你是一个具备工具调用能力的数学计算智能助手,仅可使用内置计算器工具完成用户数学问题。
## 可用工具说明
calculator:数学表达式计算器,入参expr为标准四则/括号数学表达式。

## 强制输出格式规范
每一轮推理仅输出三段固定内容,禁止多余文字、解释、代码块,严格分行:
Thought: 你的完整推理内容
Action: 工具标识,无需调用工具固定写 FINISH
Params: JSON对象,无参数填写 {}

## 硬性执行规则
1. 先读取完整对话历史,判断是否存在role为system的工具计算结果;
2. 若已存在对应表达式的system计算结果:禁止再次调用calculator,Action必须填FINISH;
3. 若没有任何计算结果,才允许填写Action: calculator并传入expr表达式;
4. 当Action为FINISH时,Thought必须整合system中的数值,生成面向用户的完整自然答案,不能仅说明“无需调用工具”;
5. Params永远是标准JSON,不能换行、不能加注释,无参数直接写{};
6. 禁止重复执行相同计算,禁止无限循环调用工具。

## 正确示例1(初次调用工具)
Thought: 用户需要计算(100+20)*5,我没有历史计算数据,需要调用计算器工具求解
Action: calculator
Params: {"expr":"(100+20)*5"}

## 正确示例2(已有结果,终止推理)
Thought: 根据工具返回结果,(100+20)*5的计算结果是600。先计算100加20得到120,再用120乘以5,最终结果为600。
Action: FINISH
Params: {}

## 错误示例(严禁出现)
Thought: 已有计算结果,不用调用工具
Action: FINISH
Params: {}
"""

def run_react_agent(user_query: str) -> str:
    """
    启动ReAct智能体主循环
    :param user_query: 用户原始需求
    :return: 最终回答文本
    """
    memory = ShortMemory()
    memory.add_user(user_query)
    max_loop_count = 5  # 限制最大循环次数,防止死循环

    for _ in range(max_loop_count):
        # 拼接完整Prompt:系统规则 + 全部历史上下文
        full_system_content = SYSTEM_PROMPT + "\n用户需求历史:" + str(memory.get_all())
        llm_text = chat_completion([{"role": "system", "content": full_system_content}])
        parse_result = ReActParser.parse(llm_text)

        # 分支1:无需调用工具,任务结束,返回最终答案
        if parse_result is None:
            memory.add_assistant(llm_text)
            return llm_text

        # 分支2:解析出工具,执行工具逻辑
        target_tool = tool_name_map.get(parse_result["action"])
        if target_tool is None:
            obs_info = f"错误:不存在名为 {parse_result['action']} 的工具"
        else:
            obs_info = target_tool.run(parse_result["params"])

        # 将工具结果存入记忆,进入下一轮思考循环
        memory.add_observation(obs_info)

    # 达到最大循环次数,强制终止
    return "已达到最大思考轮次,无法完成当前任务"

# 程序启动入口(等价Java main方法)
if __name__ == "__main__":
    # 测试提问:需要调用计算器工具
    answer = run_react_agent("计算 (100 + 20) * 5 再除以2")
    print("Agent最终回答:\n", answer)

四、运行效果说明

  1. 启动main.py后,大模型自动输出Thought推理过程,Action指定calculator工具,Params携带表达式;在这里插入图片描述

  2. 代码解析工具名称,执行计算器逻辑,将计算结果以system消息存入记忆;

  3. 进入第二轮循环,大模型读取工具返回结果,确认任务完成后Action输出FINISH,循环终止;

  4. 全程无任何框架介入,每一步调度、解析、存储逻辑完全可控,可断点追踪每一轮交互。

五、后续进阶拓展方案(逐渐迭代,看后续文章)

基础版完成后,可依次实现以下进阶功能,贴合后端工程化落地需求:

拓展1:Pydantic结构化输出,替代文本解析(对标标准Function Calling)

痛点:纯文本解析容错率低,LLM输出格式轻微错乱直接抛出JSON解析异常。
优化思路:使用Pydantic定义强类型输出结构体,强制模型返回标准JSON,彻底抛弃字符串切割逻辑,对齐各大厂商原生Function Calling机制。
适用场景:生产级Agent、多工具复杂调度场景。

拓展2:接入Chroma向量库,实现长期记忆RAG

现有ShortMemory仅保存单次对话上下文,程序重启记忆全部清空。
新增向量记忆模块:将历史任务、代码片段、文档存入Chroma向量库,新任务自动检索相关历史记录,实现长效记忆,可用于开发代码助手类Agent。

拓展3:新增文件读取工具,打造后端开发专用Agent

新增FileReadTool,实现本地项目文件读取、代码片段检索;同时配置文件访问白名单沙箱,限制Agent读取范围,补齐生产安全能力。
结合计算器工具,实现需求分析、代码查看、数值计算一体化开发助手。

六、Java/Go后端工程师快速接入

6.1 Python快速语法映射(静态语言开发者专用)

无需学习爬虫、数据分析、可视化,仅掌握后端相关语法即可:

  • self = Java/Go this
  • ABC抽象基类 = Java Interface / Go interface
  • Pydantic BaseModel = Java POJO / Go Struct
  • if __name__ == "__main__" = Java main 入口函数
  • venv虚拟环境 = Maven/Gradle依赖隔离、Go mod

6.2 开发核心建议

  1. 先手搓底层,再学框架
    框架是业务加速工具,学习原理必须从零实现;吃透循环、工具、记忆三大组件后,再阅读LangGraph、Dify源码可以一眼看懂底层实现。
  2. 安全红线不可忽视
    示例中eval仅用于学习演示,线上环境严格禁止;所有工具入参必须增加参数校验、沙箱隔离、文件访问白名单。
  3. 工程化扩展方向
  • 封装Agent调度为独立Service层;
  • 增加全局日志、异常捕获、超时熔断机制;
  • 封装HTTP接口,搭配前端页面实现全栈AI应用;
  • 性能瓶颈可将核心调度逻辑重构为Go高性能服务。

七、总结

本文实现的无框架原生ReAct Agent代码简洁、分层清晰,完整覆盖智能体三大核心能力:工具调用、上下文记忆、循环推理。
对于后端研发,这套手搓Demo是打通LLM到大模型智能体认知的关键一步。理解底层逻辑后,无论二次开发开源Agent平台,还是自研企业内部AI助手,都不会停留在只会调用API的浅层阶段。
完整代码无复杂依赖,复制到PyCharm填入密钥即可直接运行。

下一篇:Java/Go后端手撸原生Agent(第二篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值