AgentLoop 通过 Recall→Plan→Research→Reflect→Synthesize 六步循环实现真正的自判断 Agent,遇到知识缺口会主动返航重搜,而非一次性输出。
大多数 AI 项目都是这样的:
User Input → LLM → Output
这不是 agent,只是带 UI 的自动补全。
AgentLoop 不一样。给它一个研究主题,它会:
检查是否已经研究过类似内容(长期记忆)
把主题拆解成有针对性的子问题
逐个判断每个子问题是否需要调用实时网络搜索工具
重读自己的笔记,如果发现缺口就折返继续研究
只有完成以上步骤后才撰写结构化、带来源的报告
将本次运行保存到记忆,供下次使用
中间那个循环——agent 评判自身输出并决定是否继续——才是它真正具备 agent 特性的地方。不是 LLM 本身,而是围绕它所做的决策。
在线演示:agentloop.streamlit.app GitHub:github.com/ayush-s-tomar/agentloop
输入一个研究主题,agent 就会运行一个 6 节点管道:
[Recall] → [Plan] → [Research] → [Reflect] → [Synthesize] → [Persist]
↑ |
└───────────────┘
(发现缺口时折返)
Recall — 检查 SQLite 长期记忆中相关的过往研究。如果找到相关内容,会在规划前将这些笔记作为上下文加载进来。避免在同一个方向上重复研究。
Plan — LLM 将主题拆解成 3–5 个具体的子问题。不是"告诉我关于 X 的一切",而是真正有针对性的提问,比如"哪些公司在生产环境中部署了 X?"以及"X 的失败模式是什么?"
Research — 对于每个子问题,LLM 决定是否调用 Tavily 网络搜索工具。不是每个子问题都需要搜索——有时候答案可以从之前的笔记中推导出来。这是真正的工具调用,而不是把每次都硬编码为搜索。
Reflect — 这是我最引以为傲的节点。agent 重读所有收集到的内容,然后问自己:这些完整吗?有没有缺口?如果有,就折返进入 Research 再跑一轮。如果没有,就继续前进。最多迭代 3 次,防止无限循环。
Synthesize — 从所有收集的笔记和搜索结果中撰写结构化的 markdown 报告,包含引用。
Persist — 将完整运行记录保存到 SQLite:主题、子问题、来源、报告。下次运行时可被 Recall 调用。
6 节点图,包括 reflect → research 的循环回退。

主题输入、实时追踪和生成的报告——全部在一个 Streamlit 视图中。

技术栈:LangGraph · Streamlit · Groq (llama-3.1-8b-instant) · Tavily · SQLite · Streamlit Cloud
这就是让 AgentLoop 区别于线性管道的设计决策。
Research 运行完成后,agent 不是立即进入合成,而是先到 Reflect 节点:
def reflect_node(state: AgentState) -> AgentState:
notes = "\n".join(state["notes"])
sub_questions = "\n".join(state["sub_questions"])
prompt = f"""You researched these questions:
{sub_questions}
Here are your notes so far:
{notes}
Are there significant gaps? Answer YES or NO, then explain."""
response = llm.chat([{"role": "user", "content": prompt}], system="Be critical.")
has_gaps = response.strip().upper().startswith("YES")
iterations = state.get("reflect_iterations", 0)
return {
**state,
"needs_more_research": has_gaps and iterations < 3,
"reflect_iterations": iterations + 1
}
LangGraph 的条件边根据 needs_more_research 来路由:
graph.add_conditional_edges(
"reflect",
lambda state: "research" if state["needs_more_research"] else "synthesize"
)
迭代上限(iterations < 3)是不可妥协的。没有它,一个喜欢循环的 LLM 会在模糊主题上永不停歇。有了它,最坏情况也只是 3 轮研究——仍然比一次搞定要彻底得多。
短期(单次运行内):state["notes"] —— 一个列表,跨所有 Research 迭代持续积累。每次工具调用都会追加其发现。Reflect 和 Synthesize 节点看到的是完整的累积视图,而不是最后一次搜索的结果。
长期(跨运行):SQLite,采用简洁的 schema —— topic、sub_questions、notes、report、timestamp。Recall 节点在每次运行开始时通过关键词相似度查询。不是向量搜索(那是下一步)—— 只是 SQL LIKE 匹配,对于一个作品集项目来说够用了,而且逻辑简单易于推理。
def recall_node(state: AgentState) -> AgentState:
topic = state["topic"]
words = [w for w in topic.lower().split() if len(w) > 4]
past_runs = []
for word in words[:3]: # top 3 keywords
results = db.search_by_keyword(word)
past_runs.extend(results)
context = format_past_runs(past_runs[:2]) # most recent 2 matches
return {**state, "memory_context": context}
LLM 不会盲目调用网络搜索。它接收工具 schema,然后逐个子问题做出决定:
TOOLS = [{
"name": "web_search",
"description": "Search the live web for current information",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
}
}]
对于涉及当前事件的实事性子问题,它调用工具。对于能从现有笔记中推理出来的子问题,它不调用。这才是真正的 agent 与搜索封装器的区别。
最初架构是 FastAPI + React,通过 Server-Sent Events 将实时追踪流式传输到前端——每个节点完成时触发一个 SSE 事件,UI 实时更新。
本地运行完美。在 Render 的免费层级上,SSE 在 30 秒后就死了—— Render 会在免费计划中关闭长连接,而一个 6 节点 agent 多次网络搜索耗时会超过这个时间。我用后台任务 + 轮询来打补丁(前端改为每 2 秒轮询一次 /api/research/status/{job_id} 而不是保持连接打开),这解决了超时问题。
但更深层的问题是平台本身。Render 的免费 Web 服务会因不活动而停止,并在每月被暂停,因此在线演示链接在访客间隔期间会一直变冷——这不是能靠补丁解决的问题,这就是该层级的实际模式。所以我完全从 FastAPI + React 迁移到了单文件 Streamlit 应用,复用了原有的 agent/ 和 memory/ 模块,部署到 Streamlit Community Cloud。没有要暂停的后端进程,没有 SSE 与轮询之间的权衡—— Streamlit 在每次交互时直接重新运行脚本并渲染状态。
教训:为部署环境而设计,而不仅仅是本地环境——有时候正确的修复不是更聪明的变通方案,而是选择一个免费层模型真正符合项目使用场景的主机(偶尔的演示点击,而不是需要保持活跃的服务)。
Groq 在开发过程中多次弃用模型:
llama-3.3-70b-versatile → 已弃用
llama3-70b-8192 → 已下线
llama3-groq-70b-8192-tool-use-preview → 工具调用损坏
每一次要么静默失败,要么报 cryptic 错误。最后用上了 llama-3.1-8b-instant——更小,但稳定且有活跃维护。
教训:永远不要硬编码模型字符串。它应该放在环境变量里:
MODEL = os.getenv("GROQ_MODEL", "llama-3.1-8b-instant")
一个环境变量的改动,下次再遇到弃用时无需重新部署。
Groq 的免费层级是每天 100k tokens。在开发测试、调试和演示运行之间,我在录制 LinkedIn 截图当天耗尽了配额。等待重置损失了两小时。
教训:为演示准备一个独立的 Groq API key。永远不要用开发 key 做生产用途。
在还是 FastAPI 在 Render 上的时代,pydantic-core 没有 Python 3.14 的 wheel——静默构建失败,没有清晰的错误信息,就是部署挂了。修复方法是一个环境变量(PYTHON_VERSION = 3.11.9)显式固定运行时,而不是信任主机默认值。
现在 app 已经是纯 Streamlit,依赖面小得多,这个问题已经无关紧要了,但底层教训在迁移时一并带了过来:在任何主机上都要显式固定 Python 版本,不要信任默认值。
用向量搜索替换 SQL LIKE 匹配。目前 Recall 节点通过关键词找过往运行——措辞不同的语义相似研究就会漏掉。ChromaDB 或 Supabase pgvector 能解决这个问题。这是本项目最有意义的升级。
添加第二个工具。目前唯一的工具是 web_search。一个计算器或结构化数据查询能展示 LLM 真正在工具之间做选择——不只是"搜索还是不搜索"。这是更强的工具调用故事。
为 reflect 循环添加监控。我没有记录 agent 实际折返的频率与直接进入合成的频率对比。这个指标能告诉我 reflect 节点是在产生价值还是只是在增加延迟。
在第一天就定义 LLM 接口契约。我一半的调试时间都花在了 graph.py 和 llm.py 对函数签名和返回类型做了不同假设上。在任何 agent 逻辑之前写一份带类型的契约文件,就能 catch 所有这类问题。
在线演示:agentloop.streamlit.app GitHub:github.com/ayush-s-tomar/agentloop
(如果你收藏了旧的 agentloop.onrender.com 链接,它已经废弃了—— app 现在托管在 Streamlit Cloud,原因见上文。)
输入任意研究主题,然后看着管道一步步运行——追踪面板展示每个执行中的节点,这样你就能看到 agent 何时决定折返。
如果你正在做类似的东西,或者对记忆架构有想法,欢迎在 LinkedIn 上联系我。