“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
ReActAgent 使用指南:构建会思考、能行动的 AI Agent
最近在折腾 AI Agent 相关的东西,发现很多人对 ReAct 模式感兴趣但又不太清楚怎么落地。正好我手头有个项目用到了 LangChain 的 ReActAgent,踩了不少坑也积累了点经验,干脆整理成一篇笔记分享出来。这篇文章不会只贴代码,我会把背后的原理、为什么这么设计、实际工程中要注意什么都说清楚。
先聊聊 ReAct 到底是什么
先说个背景。传统的 LLM 调用模式就是“你问一句,它答一句”,比如你问“今天北京天气怎么样”,它可能直接回答“抱歉,我无法获取实时数据”。这很尴尬——模型有知识但没法采取行动。
ReAct(Reason + Act)这个名字很直白:推理 + 行动。它的核心思路是:让 LLM 不只是输出文本,而是输出一个“思考链”(Reasoning),然后根据思考结果决定调用什么工具(Action),再根据工具返回的结果继续推理,直到得出最终答案。
打个比方:传统 LLM 像个只会背书的学生,你问他“食堂今天有什么菜”,他只能凭记忆瞎猜。ReAct 模式下的 LLM 则像个会办事的助理——他会先想“我需要查一下食堂菜单”,然后掏出手机打开食堂 App 查数据,看到结果后再告诉你“今天有红烧肉和清炒时蔬”。
ReActAgent 的核心组件
在 LangChain 里,ReActAgent 由三个关键部分组成:
- LLM:负责推理和决策的大脑
- 工具列表(Tools):Agent 可以调用的外部能力,比如搜索、计算、API 调用
- 提示词模板(Prompt Template):指导 LLM 如何输出思考过程和调用工具的指令
这三者缺一不可。LLM 再聪明,没有工具也只能纸上谈兵;工具再多,LLM 不会推理也只会乱用。
手把手搭建一个 ReActAgent
第一步:准备 LLM
我用的是 OpenAI 的模型,但理论上任何支持函数调用(function calling)或工具调用的模型都行。这里我选了 gpt-4o-mini,性价比不错,日常实验够用了。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0, # 做 Agent 时建议 temperature 设低一点,减少随机性
api_key="your-api-key" # 实际用环境变量管理
)
为什么 temperature 要设 0? 因为 Agent 需要稳定、可预测的推理路径,而不是创意发散。想象一下,如果助理每次思考都“天马行空”,那工具调用就会乱套。
第二步:定义工具
工具是 Agent 的“手脚”。我定义了两个最常用的工具:一个用于计算,一个用于获取当前时间。
from langchain_core.tools import tool
from datetime import datetime
import math
@tool
def calculator(expression: str) -> str:
"""计算数学表达式,支持加减乘除、幂运算等。输入应为合法的数学表达式字符串。"""
try:
# 安全起见,只允许数学运算,避免执行任意代码
allowed_names = {
"abs": abs, "round": round, "min": min, "max": max,
"pow": pow, "sqrt": math.sqrt, "pi": math.pi, "e": math.e
}
result = eval(expression, {"__builtins__": {}}, allowed_names)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
@tool
def get_current_time() -> str:
"""返回当前的日期和时间。不需要输入参数。"""
return f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
这里有个重要的设计细节:工具的文档字符串(docstring) 非常重要。LLM 会读取这个 docstring 来决定什么时候调用这个工具、传什么参数。所以描述要清晰、准确,最好包含示例。
比如 calculator 工具的 docstring 里我特意写了“支持加减乘除、幂运算等”,这样 LLM 就知道 2**10 这种表达式也能处理。
第三步:创建提示词模板
这是最容易翻车的地方。LangChain 的 ReActAgent 默认需要一个特定格式的提示词模板,里面要包含 {input}、{agent_scratchpad} 等占位符。
from langchain.agents import create_react_agent
from langchain_core.prompts import PromptTemplate
# 这个模板告诉 LLM 如何思考和行动
react_prompt = PromptTemplate.from_template(
"""你是一个智能助手,可以通过思考和调用工具来回答问题。
请按照以下格式输出:
问题:用户提出的问题
思考:你当前的想法和推理过程
行动:你要调用的工具名称
行动输入:传给工具的具体参数
观察:工具返回的结果
...(可以重复思考-行动-观察循环)
思考:我现在有了足够的信息
最终答案:对用户的最终回复
可用的工具:
{tools}
工具名称列表(用逗号分隔):
{tool_names}
开始!
问题:{input}
思考:{agent_scratchpad}"""
)
agent_scratchpad 是什么? 这是关键。它记录了 Agent 之前所有的思考-行动-观察历史。每次循环,新的思考结果都会被追加到这个变量里,让 LLM 知道“我已经做了什么、得到了什么结果”,从而做出下一步决策。
第四步:组装 Agent
工具和提示词都有了,接下来把它们组装起来。
from langchain.agents import AgentExecutor
# 先把工具列表准备好
tools = [calculator, get_current_time]
# 创建 ReAct Agent
agent = create_react_agent(
llm=llm,
tools=tools,
prompt=react_prompt
)
# 用 AgentExecutor 包装,让它能实际执行工具调用
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 打开详细日志,方便调试
handle_parsing_errors=True, # 如果 LLM 输出格式不对,自动重试
max_iterations=10 # 防止无限循环
)
verbose=True 在开发阶段特别有用,你能看到 Agent 每一步的思考过程和工具调用结果。handle_parsing_errors 是个保险——LLM 偶尔会输出格式不规范的 JSON 或文本,开启后 Agent 会自动重试。
第五步:跑起来看看效果
先试一个需要计算的问题:
response = agent_executor.invoke({"input": "2的10次方是多少?"})
print(response["output"])
输出日志(verbose=True 时你会看到):
> 进入 AgentExecutor 循环...
思考:用户问的是2的10次方,这是一个数学计算,我可以调用 calculator 工具。
行动:calculator
行动输入:2**10
观察:计算结果: 1024
思考:计算完成,现在可以给出最终答案。
最终答案:2的10次方是1024。
完美。再试试需要时间信息的问题:
response = agent_executor.invoke({"input": "现在是几点?"})
print(response["output"])
输出:
> 进入 AgentExecutor 循环...
思考:用户想知道当前时间,我应该调用 get_current_time 工具。
行动:get_current_time
行动输入:{}
观察:当前时间: 2025-03-15 14:32:18
思考:获取到了当前时间,可以回答用户了。
最终答案:现在是 2025年3月15日 14点32分。
注意 行动输入 是空字典 {},因为 get_current_time 不需要参数。
进阶:让 Agent 调用自定义 API
光有计算器和时间功能还不够,实际项目中我们经常需要 Agent 调用内部 API。比如,我想让 Agent 能查用户订单信息。
定义更复杂的工具
import requests
@tool
def get_order_status(order_id: str) -> str:
"""根据订单ID查询订单状态。order_id 是字符串类型的订单编号,例如 'ORD-2025-001'。"""
# 这里假装调了一个内部 API
# 实际项目中替换为真实的 API 调用
api_url = f"https://api.example.com/orders/{order_id}"
try:
# 模拟请求,实际使用时取消注释下面两行
# response = requests.get(api_url, timeout=5)
# return response.json().get("status", "未知状态")
# 模拟数据
mock_data = {
"ORD-2025-001": "已发货,预计3月18日到达",
"ORD-2025-002": "正在打包中",
"ORD-2025-003": "已取消"
}
return mock_data.get(order_id, "未找到该订单")
except Exception as e:
return f"查询失败: {str(e)}"
这里我故意用了 mock 数据,但结构跟真实 API 一致。实际项目中要注意:工具函数里尽量做异常处理,因为 LLM 可能会传入不存在的订单号或者格式错误的参数,工具应该优雅地返回错误信息,而不是直接崩溃。
测试复杂场景
tools = [calculator, get_current_time, get_order_status]
agent = create_react_agent(llm=llm, tools=tools, prompt=react_prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
max_iterations=10
)
response = agent_executor.invoke({"input": "我的订单ORD-2025-002现在什么状态?"})
print(response["output"])
输出日志:
> 进入 AgentExecutor 循环...
思考:用户想查询订单 ORD-2025-002 的状态,我需要调用 get_order_status 工具。
行动:get_order_status
行动输入:{"order_id": "ORD-2025-002"}
观察:正在打包中
思考:查询成功,订单状态是“正在打包中”。
最终答案:您的订单 ORD-2025-002 目前正在打包中,请耐心等待。
踩坑记录与最佳实践
1. 提示词模板的格式陷阱
ReAct 的提示词模板对格式要求很严格。{agent_scratchpad} 必须放在最后,而且前面要有明确的格式说明。我一开始没注意,把模板写成了:
问题:{input}
{agent_scratchpad}
思考:
结果 Agent 完全不按套路出牌,一直输出无关内容。后来查文档才发现,agent_scratchpad 前面必须有“思考:”这样的前缀引导,LLM 才知道怎么续写。
正确做法:严格按照 LangChain 官方示例的格式,在 {agent_scratchpad} 前面加上“思考:”前缀,让 LLM 从思考开始续写。
2. 工具命名要直观
工具的名字和 docstring 会直接影响 LLM 的调用决策。我试过给计算器工具起名叫 math_op,结果 LLM 有时候认不出来,宁愿自己瞎算也不调用。改成 calculator 之后准确率明显提升。
经验:工具名用通俗易懂的英文单词,docstring 里说清楚“什么时候用、怎么用、参数是什么”。
3. 控制循环次数
max_iterations 这个参数一定要设。我遇到过 Agent 陷入死循环的情况——它反复调用同一个工具,每次得到相同的结果,然后继续调用。设个 10 次的上限,既防止无限循环,也避免 token 浪费。
4. 错误处理不能少
LLM 生成的工具调用参数有时候是错的。比如它会传 order_id=123(数字)而不是字符串。工具函数里要做类型检查和异常捕获,返回友好的错误信息,这样 Agent 就能根据错误信息调整参数重试。
@tool
def get_order_status(order_id: str) -> str:
if not isinstance(order_id, str):
return "错误:订单ID必须是字符串格式"
# ... 后续逻辑
5. 日志是调试神器
verbose=True 能让你看到 Agent 的每一步思考。生产环境可以关掉,但开发阶段一定要开着。有一次 Agent 老是调用错误工具,我一看日志才发现是 docstring 写得太模糊,LLM 理解错了。
总结
ReActAgent 的本质是给 LLM 装上了“手脚”和“思考能力”。通过定义清晰的工具和提示词模板,我们可以让 AI 完成需要多步推理和外部数据获取的复杂任务。
这篇文章里我们只用了三个简单工具,但同样的模式可以扩展到调用数据库、发邮件、操作文件系统等任何你能想到的外部能力。关键是要掌握好提示词模板的格式、工具的定义规范,以及异常处理机制。
如果你正在构建自己的 AI Agent,建议先从最简单的场景开始,比如“计算+时间查询”,跑通流程后再逐步增加工具。别一上来就想搞个万能 Agent,那只会让你在调试中崩溃。
最后留个思考题:如果想让 Agent 能记住对话历史(比如用户说“刚才那个订单怎么样了”),你觉得应该在哪个环节做改造?欢迎在评论区讨论。