1. 架构总览:agent_engine.py 在 Social Media Agent 中的职责
在我们的 social-media-agent 项目中,tools/x_tools.py 是纯粹的"被动函数库",而 agent_engine.py 才是整个系统的调度中枢(Brain & Pipeline)。
它负责三件事:
-
记忆维护(Memory Management) :维护一个全局或 Session 级别的
messages列表,存储所有的User提问、Thought思考、Action指令以及Observation返回值。 -
工具映射(Tool Dispatching) :把大模型输出的字符串函数名(如
"fetch_tweet_comments"),映射并执行x_tools.py中真正的 Python 函数。 -
ReAct 死循环拦截与收敛 :控制单次问题的最大思考轮数(如
max_turns=5),防止 Agent 陷入死循环暴刷 X API。
2. 核心代码解析:agent_engine.py 的完整实现
在 Alex 导师的指导下,我手写并调通了适用于本系统的 agent_engine.py:
import os
import json
from typing import List, Dict, Any
from openai import OpenAI
# 1. 导入昨天与今天在 tools/x_tools.py 中写好的工具
from tools.x_tools import fetch_x_user_recent_posts, fetch_tweet_comments
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# 2. 注册可用的工具函数字典
TOOL_REGISTRY = {
"fetch_x_user_recent_posts": fetch_x_user_recent_posts,
"fetch_tweet_comments": fetch_tweet_comments
}
# 3. 严格的 System Prompt 设定:注入多轮记忆与按次计费(Pay-per-use)的省钱规则
SYSTEM_PROMPT = """
你是一个顶级的 Social Media 智能分析 Agent,支持连接 X (Twitter) API 实时获取数据。
你必须严格遵循 ReAct (Thought -> Action -> Action Input -> Observation) 范式回答问题。
可用工具列表:
1. fetch_x_user_recent_posts(username: str, max_results: int = 5)
- 用途:查询某 X 用户的 ID、基本信息以及最近发布的原创推文。
2. fetch_tweet_comments(tweet_id: str, max_comments: int = 10)
- 用途:根据具体推文的 tweet_id,查询其下方的真实用户评论列表。
极重要规则(成本与记忆控制):
- 注意:X API 为按次计费模式!如果历史对话 (Memory) 中已经包含你需要的 tweet_id 或用户信息,直接从历史 Observation 中提取,严禁重复调用 fetch_x_user_recent_posts!
- 当需要分析某条推文的评论时,必须先检查历史消息里是否有对应的 tweet_id,将该 tweet_id 作为参数传给 fetch_tweet_comments。
输出格式规范:
Thought: 分析当前用户意图与历史上下文,说明下一步需要什么信息。
Action: 工具名称 (必须是上述工具之一)
Action Input: 工具参数的 JSON 字典,例如 {"username": "elonmusk"}
Observation: [由系统自动插入工具运行结果,你无需手动填写]
当获取到足够信息后,请输出:
Thought: 我已经获得了足够的信息。
Final Answer: 给出最终的总结回答。
"""
class SocialMediaAgentEngine:
def __init__(self):
# 初始化短期记忆上下文列表 (Short-term Memory Context)
self.conversation_history: List[Dict[str, str]] = [
{"role": "system", "content": SYSTEM_PROMPT}
]
def chat(self, user_input: str, max_turns: int = 5) -> str:
"""接收用户输入,发起多轮 ReAct 循环推理"""
# 将用户最新提问追加到历史 Memory 中
self.conversation_history.append({"role": "user", "content": f"Question: {user_input}"})
print(f"\n📥 收到用户输入: '{user_input}'")
for turn in range(max_turns):
print(f"--- 🔄 ReAct 循环第 {turn + 1} 轮 ---")
# 调用 LLM 进行推理
response = client.chat.completions.create(
model="gpt-4o",
messages=self.conversation_history,
temperature=0.1, # 低随机度保证工具调用的稳定性
stop=["Observation:"] # 强制拦截,避免 LLM 伪造 Observation
)
llm_text = response.choices[0].message.content.strip()
print(f"🧠 模型思考与决策:\n{llm_text}")
# 1. 存入 Memory:将模型的 Thought/Action 追加到对话历史
self.conversation_history.append({"role": "assistant", "content": llm_text})
# 2. 终止条件检查:如果生成了 Final Answer,返回答案并结束本次交互
if "Final Answer:" in llm_text:
final_answer = llm_text.split("Final Answer:")[-1].strip()
return final_answer
# 3. 解析 Action 并匹配工具执行
if "Action:" in llm_text and "Action Input:" in llm_text:
try:
# 提取 Action 与 Action Input
lines = llm_text.split("\n")
action_line = [l for l in lines if l.startswith("Action:")][0]
input_line = [l for l in lines if l.startswith("Action Input:")][0]
tool_name = action_line.replace("Action:", "").strip()
tool_args = json.loads(input_line.replace("Action Input:", "").strip())
# 校验工具是否存在并调用 tools/x_tools.py 中的实现
if tool_name in TOOL_REGISTRY:
print(f"⚙️ 正在分发执行工具 [{tool_name}],参数: {tool_args}...")
exec_result = TOOL_REGISTRY[tool_name](**tool_args)
else:
exec_result = {"status": "error", "message": f"未找到名为 {tool_name} 的工具"}
# 4. 组装 Observation 结果,喂回给 Memory
obs_str = f"Observation: {json.dumps(exec_result, ensure_ascii=False)}"
print(f"📡 工具返回 Observation 存入 Memory: {obs_str[:150]}...")
# 关键步骤:把工具运行的真实数据写入对话历史,作为下一轮推理的依据!
self.conversation_history.append({"role": "user", "content": obs_str})
except Exception as e:
error_obs = f"Observation: 解析或执行工具出错: {str(e)}"
self.conversation_history.append({"role": "user", "content": error_obs})
else:
# 如果既没有 Final Answer 也没有标准 Action,提示格式错误
self.conversation_history.append({
"role": "user",
"content": "Observation: 输出格式不符合要求,请确保输出 Action 和 Action Input,或给出 Final Answer。"
})
return "很抱歉,在限定的思考轮次内未能在 X 平台上完成分析。"
3. 链路闭环演示:Memory 如何跨轮次传递数据?
为了深刻理解 agent_engine.py 如何与 x_tools.py 协同工作,Alex 带我推演了当用户连续问第二句时,内存数据(self.conversation_history)的具体形态:
self.conversation_history 存储的完整上下文队列:
[消息 1] System Prompt (包含全局规则与工具列表)
[消息 2] User: "Question: 埃隆·马斯克(@elonmusk)最新的一条 X 说了啥?"
[消息 3] Assistant: "Thought: ... Action: fetch_x_user_recent_posts Action Input: {\"username\": \"elonmusk\"}"
[消息 4] User (Observation): "Observation: {\"status\": \"success\", \"tweets\": [{\"tweet_id\": \"1846001122334455667\", ...}]}"
[消息 5] Assistant: "Thought: ... Final Answer: 马斯克最新发布了星舰试飞成功的消息..."
--- 此时发起第二句提问 ---
[消息 6] User: "Question: 针对这一条,评论看用户的 sentiment 怎么样?"
当 agent_engine.py 将这一整串 conversation_history 扔给大模型时:
-
大模型看到了
[消息 6]的代词"这一条"。 -
大模型往上检索,在
[消息 4]的Observation字符串里抓到了tweet_id: 1846001122334455667。 -
大模型决定生成
Action: fetch_tweet_comments+Action Input: {"tweet_id": "1846001122334455667"}。 -
agent_engine.py解析该字符串,自动在TOOL_REGISTRY中匹配并触发tools/x_tools.py中的fetch_tweet_comments()函数!