”我“:Java/Go后端开发者、有点时间想自己琢磨,想入门Agent但不想堆砌框架、希望理解底层原理的研发
一、前言:为什么放弃框架,从零手搓Agent?
作为常年写Java、Go的后端工程师,近期开始学习Agent智能体开发。
最开始直接上手LangChain、LangGraph、Dify,踩了大量典型坑:
- 框架封装度极高,ReAct循环、工具调用、记忆管理全部黑盒封装,出现异常无从断点排查;
- 框架自定义语法、内置类繁多,底层HTTP请求、Prompt拼接、输出解析逻辑完全被掩盖;
- 想要自定义工具、改造调度逻辑时,多层抽象层限制扩展,修改一处需要联动大量组件。
想要真正吃透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)
四、运行效果说明
-
启动
main.py后,大模型自动输出Thought推理过程,Action指定calculator工具,Params携带表达式;
-
代码解析工具名称,执行计算器逻辑,将计算结果以system消息存入记忆;
-
进入第二轮循环,大模型读取工具返回结果,确认任务完成后Action输出FINISH,循环终止;
-
全程无任何框架介入,每一步调度、解析、存储逻辑完全可控,可断点追踪每一轮交互。
五、后续进阶拓展方案(逐渐迭代,看后续文章)
基础版完成后,可依次实现以下进阶功能,贴合后端工程化落地需求:
拓展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/Gothis- ABC抽象基类 = Java Interface / Go interface
- Pydantic BaseModel = Java POJO / Go Struct
if __name__ == "__main__"= Java main 入口函数- venv虚拟环境 = Maven/Gradle依赖隔离、Go mod
6.2 开发核心建议
- 先手搓底层,再学框架
框架是业务加速工具,学习原理必须从零实现;吃透循环、工具、记忆三大组件后,再阅读LangGraph、Dify源码可以一眼看懂底层实现。 - 安全红线不可忽视
示例中eval仅用于学习演示,线上环境严格禁止;所有工具入参必须增加参数校验、沙箱隔离、文件访问白名单。 - 工程化扩展方向
- 封装Agent调度为独立Service层;
- 增加全局日志、异常捕获、超时熔断机制;
- 封装HTTP接口,搭配前端页面实现全栈AI应用;
- 性能瓶颈可将核心调度逻辑重构为Go高性能服务。
七、总结
本文实现的无框架原生ReAct Agent代码简洁、分层清晰,完整覆盖智能体三大核心能力:工具调用、上下文记忆、循环推理。
对于后端研发,这套手搓Demo是打通LLM到大模型智能体认知的关键一步。理解底层逻辑后,无论二次开发开源Agent平台,还是自研企业内部AI助手,都不会停留在只会调用API的浅层阶段。
完整代码无复杂依赖,复制到PyCharm填入密钥即可直接运行。
331

被折叠的 条评论
为什么被折叠?



