一、后端在 Agent 开发中的角色全景
text
┌─────────────────────────────────────────────────────────────────────┐
│ Agent 系统全景 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 前端/交互层 │ │
│ │ (Web UI / 小程序 / 企业微信) │ │
│ └─────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────┴────────────────────────────────┐ │
│ │ Agent 编排层(后端核心) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ Agent │ │ 记忆管理 │ │ 上下文 │ │ 多Agent协调 │ │ │
│ │ │ 路由器 │ │ Session │ │ 窗口管理 │ │ (Supervisor) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └─────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────┴────────────────────────────────┐ │
│ │ 能力层(后端提供) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 工具开发 │ │ RAG服务 │ │ MCP服务 │ │ 工作流 │ │ │
│ │ │ Tool │ │ 检索增强 │ │ Server │ │ 引擎 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────┴────────────────────────────────┐ │
│ │ 数据层 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 向量DB │ │ 关系DB │ │ 缓存Redis│ │ 消息队列 │ │ │
│ │ │(Milvus等)│ │(Pg/MySQL)│ │ │ │ (Kafka) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
二、工具开发(Tool Development)
2.1 什么是工具?
工具是 Agent 可以调用的后端函数/API,让 Agent 能够执行实际操作。
python
工具的本质:一个可被 LLM 调用的函数
@tool
def get_weather(city: str, unit: str = "celsius") -> dict:
"""查询指定城市的天气"""
后端逻辑:调用天气API、查数据库、计算...
return {"temperature": 25, "condition": "sunny"}
2.2 工具开发的核心要素
| 要素 | 说明 | 示例 |
|---|---|---|
| 函数签名 | 明确的输入输出类型 | (city: str) -> dict |
| 描述文档 | LLM 理解工具用途的关键 | "查询城市天气,返回温度和天气状况" |
| 参数 Schema | 结构化参数定义 | JSON Schema 格式 |
| 错误处理 | 优雅处理异常 | API 超时、数据不存在等 |
| 幂等性 | 重复调用不产生副作用 | 查询类工具天然幂等 |
2.3 工具开发实战(Python + FastAPI + LangChain)
bash
python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
import asyncio
from langchain.tools import StructuredTool
\# ============ 1\. 定义输入输出模型 ============
class WeatherInput(BaseModel):
city: str = Field(description="城市名称,如'Beijing'")
unit: Optional\[str\] = Field(default="celsius", description="温度单位:celsius/fahrenheit")
class WeatherOutput(BaseModel):
city: str
temperature: float
condition: str
humidity: int
timestamp: str
\# ============ 2\. 实现核心逻辑 ============
class WeatherService:
def \_\_init\_\_(self):
self._cache = {} \# 简单缓存
self.\_api\_key = "your\_api\_key"
async def query(self, city: str, unit: str) -> Dict\[str, Any\]:
\# 1\. 检查缓存
cache_key = f"{city}:{unit}"
if cache_key in self._cache:
return self._cache\[cache_key\]
\# 2\. 调用外部API
try:
async with aiohttp.ClientSession() as session:
params = {"q": city, "units": unit, "appid": self.\_api\_key}
async with session.get("https://api.openweathermap.org/data/2.5/weather",
params=params) as resp:
if resp.status != 200:
raise HTTPException(status_code=resp.status,
detail=f"Weather API error: {await resp.text()}")
data = await resp.json()
except asyncio.TimeoutError:
raise HTTPException(status_code=504, detail="Weather API timeout")
\# 3\. 转换格式
result = {
"city": city,
"temperature": data\["main"\]\["temp"\],
"condition": data\["weather"\]\[0\]\["description"\],
"humidity": data\["main"\]\["humidity"\],
"timestamp": data\["dt"\]
}
\# 4\. 缓存(5分钟)
self._cache\[cache_key\] = result
return result
\# ============ 3\. 封装为 LangChain Tool ============
weather_service = WeatherService()
def get_weather(city: str, unit: str = "celsius") -> str:
"""查询城市天气,返回人类可读的字符串"""
result = await weather_service.query(city, unit)
return f"{city}天气:{result\['condition'\]},温度{result\['temperature'\]}°{unit\[:1\]},湿度{result\['humidity'\]}%"
weather_tool = StructuredTool.from_function(
func=get_weather,
name="get_weather",
description="查询指定城市的实时天气信息",
args_schema=WeatherInput
)
\# ============ 4\. 暴露为 REST API ============
app = FastAPI()
@app.post("/tools/weather", response_model=WeatherOutput)
async def weather_endpoint(input: WeatherInput):
"""供 Agent 调用的 HTTP 端点"""
return await weather_service.query(input.city, input.unit)
@app.get("/tools/health")
async def health_check():
return {"status": "healthy", "tools": \["weather"\]}
2.4 工具开发的常见模式
| 模式 | 描述 | 适用场景 |
|---|---|---|
| 同步工具 | 立即返回结果 | 简单查询、计算 |
| 异步工具 | 需要等待外部响应 | API调用、数据库查询 |
| 流式工具 | 分批返回数据 | 文件下载、日志流 |
| 审批工具 | 需要人工确认 | 发送邮件、执行操作 |
| 批处理工具 | 处理大量数据 | 数据清洗、报表生成 |
2.5 工具开发最佳实践
python
python
\# 1\. 工具描述要清晰(LLM 理解的关键)
"""
获取用户订单列表
Args:
user_id: 用户ID(从会话上下文获取)
status: 订单状态(pending/shipped/delivered/cancelled)
limit: 返回数量(默认10,最大100)
offset: 分页偏移(默认0)
Returns:
{
"orders": \[...\],
"total": 123,
"has_more": true
}
Raises:
PermissionError: 当用户无权查看该订单时
"""
\# 2\. 实现重试和降级
@retry(max_attempts=3, delay=1, backoff=2)
async def call\_external\_api(url: str):
try:
return await http_client.get(url)
except NetworkError:
return await fallback_cache.get(url) \# 降级到缓存
\# 3\. 结构化日志
logger.info("tool_call", extra={
"tool_name": "get_weather",
"parameters": {"city": city},
"duration_ms": elapsed,
"success": True
})
三、上下文管理(Context Management)
3.1 什么是上下文管理?
管理 Agent 与用户对话中的状态、记忆、历史 ,解决 LLM 的无状态性 和上下文窗口限制。
3.2 上下文管理的核心维度
text
┌─────────────────────────────────────────────────────────┐
│ 上下文管理全景 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 短期记忆 │ │ 长期记忆 │ │ 工作记忆 │ │
│ │ (会话内) │ │ (跨会话) │ │ (当前任务) │ │
│ │ │ │ │ │ │ │
│ │ • 对话历史 │ │ • 用户偏好 │ │ • 中间结果 │ │
│ │ • 工具调用 │ │ • 历史行为 │ │ • 待办事项 │ │
│ │ • 临时状态 │ │ • 知识图谱 │ │ • 执行计划 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 上下文窗口管理 │ │
│ │ • 滑动窗口 • 摘要压缩 • 关键信息提取 │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
3.3 上下文管理架构设计
python
python
from typing import List, Dict, Any, Optional
from dataclasses import dataclass, field
from datetime import datetime
import redis
import json
\# ============ 1\. 数据结构定义 ============
@dataclass
class Message:
role: str \# "user" / "assistant" / "tool"
content: str
timestamp: datetime
metadata: Dict\[str, Any\] = field(default_factory=dict)
@dataclass
class Session:
session_id: str
user_id: str
messages: List\[Message\]
context_window: int = 4096 \# token 限制
created_at: datetime
updated_at: datetime
metadata: Dict\[str, Any\] = field(default_factory=dict)
\# ============ 2\. 会话管理器 ============
class SessionManager:
def \_\_init\_\_(self, redis_client: redis.Redis, ttl: int = 3600):
self.redis = redis_client
self.ttl = ttl \# 会话过期时间(秒)
async def create_session(self, user_id: str) -> Session:
session_id = f"session:{user_id}:{datetime.now().timestamp()}"
session = Session(
session_id=session_id,
user_id=user_id,
messages=\[\],
created_at=datetime.now(),
updated_at=datetime.now()
)
await self._save(session)
return session
async def get_session(self, session_id: str) -> Optional\[Session\]:
data = await self.redis.get(session_id)
if data:
return Session(**json.loads(data))
return None
async def add_message(self, session_id: str, message: Message):
session = await self.get_session(session_id)
if not session:
raise ValueError(f"Session {session_id} not found")
session.messages.append(message)
session.updated_at = datetime.now()
\# 上下文窗口管理
session = await self.\_manage\_context_window(session)
await self._save(session)
return session
async def \_manage\_context_window(self, session: Session) -> Session:
"""管理上下文窗口,防止超出 token 限制"""
\# 计算当前 token 数(简化版,实际用 tiktoken)
current_tokens = sum(len(m.content) // 4 for m in session.messages)
if current_tokens <= session.context_window:
return session
\# 策略1:滑动窗口 - 保留最新的消息
while current_tokens > session.context_window and len(session.messages) > 1:
removed = session.messages.pop(0) \# 移除最早的消息
current_tokens -= len(removed.content) // 4
\# 策略2:摘要压缩(可选)
if current_tokens > session.context_window * 1.2:
session = await self.\_compress\_with_summary(session)
return session
async def \_compress\_with_summary(self, session: Session) -> Session:
"""使用 LLM 压缩历史消息为摘要"""
\# 调用 LLM 生成摘要
summary = await self.\_call\_llm\_for\_summary(session.messages\[:-10\])
\# 保留最近10条消息 + 摘要
recent_messages = session.messages\[-10:\]
session.messages = \[
Message(role="system", content=f"历史摘要:{summary}", timestamp=datetime.now())
\] + recent_messages
return session
async def _save(self, session: Session):
await self.redis.setex(
session.session_id,
self.ttl,
json.dumps(session, default=str)
)
\# ============ 3\. 上下文注入器 ============
class ContextInjector:
"""将上下文注入到 LLM 请求中"""
def \_\_init\_\_(self, session_manager: SessionManager):
self.session_manager = session_manager
async def build_prompt(self, session_id: str, user_input: str) -> str:
session = await self.session_manager.get_session(session_id)
prompt_parts = \[\]
\# 1\. 系统提示词
prompt_parts.append("你是一个智能助手...")
\# 2\. 长期记忆(用户偏好)
user_profile = await self.\_get\_user_profile(session.user_id)
if user_profile:
prompt_parts.append(f"## 用户偏好\\n{user_profile}")
\# 3\. 对话历史
if session and session.messages:
history = "\\n".join(\[
f"{m.role}: {m.content}" for m in session.messages\[-20:\] \# 最近20条
\])
prompt_parts.append(f"## 对话历史\\n{history}")
\# 4\. 当前输入
prompt_parts.append(f"## 用户输入\\n{user_input}")
return "\\n\\n".join(prompt_parts)
3.4 上下文管理的核心策略
| 策略 | 原理 | 适用场景 | 信息损失 |
|---|---|---|---|
| 滑动窗口 | 保留最近 N 条消息 | 多轮对话 | 丢失早期信息 |
| 重要性评分 | 根据重要性保留 | 重要信息优先 | 可配置 |
| 摘要压缩 | LLM 生成历史摘要 | 长对话 | 中等 |
| 向量检索 | 从向量库检索相关历史 | 跨会话记忆 | 低 |
| 分层记忆 | 短期+中期+长期 | 复杂应用 | 可配置 |
四、RAG 服务开发(Retrieval-Augmented Generation)
4.1 RAG 的核心流程
text
用户问题: "什么是Transformer的注意力机制?"
↓
┌─────────────────────────────────────────────────────────────┐
│ 1. Query 处理 │
│ • Query 改写 • HyDE • Multi-query │
└─────────────────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 向量检索 │
│ • 向量数据库 (Milvus/Pinecone/Qdrant) │
│ • 相似度计算 (cosine/IP/L2) │
│ • Top-K 检索 (k=5~10) │
└─────────────────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 重排序 (Rerank) │
│ • Cross-Encoder 模型 │
│ • 相关性精排 │
└─────────────────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. 上下文构建 │
│ • 合并相关片段 │
│ • 截断到上下文窗口 │
│ • 添加元数据(来源、时间) │
└─────────────────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. LLM 生成 │
│ • Prompt: 检索内容 + 用户问题 │
│ • 生成最终回答 + 引用来源 │
└─────────────────────────────────────────────────────────────┘
4.2 RAG 后端服务实现
python
powershell
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
import numpy as np
from pymilvus import Collection, connections
import openai
\# ============ 1\. 数据模型 ============
@dataclass
class Document:
id: str
content: str
metadata: Dict\[str, Any\]
embedding: Optional\[List\[float\]\] = None
@dataclass
class SearchResult:
document: Document
score: float
rerank_score: Optional\[float\] = None
\# ============ 2\. 向量化服务 ============
class EmbeddingService:
def \_\_init\_\_(self, model_name: str = "text-embedding-3-small"):
self.model_name = model_name
self.client = openai.AsyncOpenAI()
async def embed(self, text: str) -> List\[float\]:
"""将文本转换为向量"""
response = await self.client.embeddings.create(
model=self.model_name,
input=text
)
return response.data\[0\].embedding
async def embed_batch(self, texts: List\[str\]) -> List\[List\[float\]\]:
"""批量向量化"""
response = await self.client.embeddings.create(
model=self.model_name,
input=texts
)
return \[data.embedding for data in response.data\]
\# ============ 3\. 向量检索服务 ============
class VectorSearchService:
def \_\_init\_\_(self, collection_name: str = "knowledge_base"):
self.collection_name = collection_name
self.\_init\_milvus()
def \_init\_milvus(self):
connections.connect(host='localhost', port='19530')
self.collection = Collection(self.collection_name)
self.collection.load()
async def search(
self,
query_vector: List\[float\],
top_k: int = 10,
filter_expr: Optional\[str\] = None
) -> List\[SearchResult\]:
"""向量相似度检索"""
search_params = {
"metric_type": "IP", \# 内积相似度
"params": {"nprobe": 10}
}
results = self.collection.search(
data=\[query_vector\],
anns_field="embedding",
param=search_params,
limit=top_k,
expr=filter_expr,
output_fields=\["content", "metadata"\]
)
return \[
SearchResult(
document=Document(
id=hit.id,
content=hit.entity.get('content'),
metadata=hit.entity.get('metadata')
),
score=hit.score
)
for hit in results\[0\]
\]
\# ============ 4\. 重排序服务 ============
class RerankService:
"""使用 Cross-Encoder 模型重排序"""
def \_\_init\_\_(self, model_name: str = "cross-encoder/ms-marco-MiniLM-L-6-v2"):
\# 实际使用时加载模型
self.model = None
async def rerank(
self,
query: str,
results: List\[SearchResult\],
top_k: int = 5
) -> List\[SearchResult\]:
"""重排序,返回精排后的 top_k"""
if not results:
return \[\]
\# 计算相关性分数
pairs = \[(query, r.document.content) for r in results\]
scores = await self._predict(pairs)
\# 更新分数并排序
for result, score in zip(results, scores):
result.rerank_score = score
results.sort(key=lambda x: x.rerank_score, reverse=True)
return results\[:top_k\]
\# ============ 5\. RAG 编排服务 ============
class RAGService:
def \_\_init\_\_(
self,
embedding_service: EmbeddingService,
search_service: VectorSearchService,
rerank_service: RerankService,
llm_client: openai.AsyncOpenAI
):
self.embedding = embedding_service
self.search = search_service
self.rerank = rerank_service
self.llm = llm_client
async def query(
self,
question: str,
top_k: int = 10,
rerank\_top\_k: int = 5,
include_sources: bool = True
) -> Dict\[str, Any\]:
"""RAG 完整流程"""
\# 1\. 向量化问题
query_vector = await self.embedding.embed(question)
\# 2\. 向量检索
initial_results = await self.search.search(query_vector, top_k=top_k)
\# 3\. 重排序
reranked_results = await self.rerank.rerank(
question, initial_results, top_k=rerank\_top\_k
)
\# 4\. 构建上下文
context = self.\_build\_context(reranked_results)
\# 5\. 生成回答
answer = await self.\_generate\_answer(question, context)
\# 6\. 返回结果
response = {
"answer": answer,
"question": question
}
if include_sources:
response\["sources"\] = \[
{
"content": r.document.content,
"score": r.rerank_score or r.score,
"metadata": r.document.metadata
}
for r in reranked_results
\]
return response
def \_build\_context(self, results: List\[SearchResult\]) -> str:
"""将检索结果构建为 LLM 上下文"""
context_parts = \[\]
for i, result in enumerate(results, 1):
context_parts.append(
f"\[{i}\] {result.document.content}\\n"
f"(来源: {result.document.metadata.get('source', 'unknown')})"
)
return "\\n\\n".join(context_parts)
async def \_generate\_answer(self, question: str, context: str) -> str:
"""调用 LLM 生成最终答案"""
prompt = f"""基于以下参考资料回答用户问题。如果参考资料中没有相关信息,请如实告知。
\## 参考资料
{context}
\## 用户问题
{question}
\## 回答(请注明信息来源编号)"""
response = await self.llm.chat.completions.create(
model="gpt-4",
messages=\[{"role": "user", "content": prompt}\],
temperature=0.3
)
return response.choices\[0\].message.content
4.3 RAG 高级优化技术
| 技术 | 说明 | 实现方式 |
|---|---|---|
| HyDE | 用 LLM 生成假设文档再检索 | 假设文档 = LLM(问题) → 向量化假设文档 |
| Multi-query | 生成多个查询变体 | LLM生成N个改写问题 → 并行检索 → 合并结果 |
| Self-RAG | 检索+生成交替进行 | 检索 → LLM判断是否需要更多检索 → 循环 |
| CRAG | 带纠错的检索增强 | 检索 → 评估相关性 → 低分时触发web搜索 |
| RAPTOR | 递归摘要树 | 文档聚类 → 生成摘要 → 递归构建树 → 分层检索 |
4.4 索引构建(离线流程)
python
python
class IndexBuilder:
"""离线文档索引构建"""
def \_\_init\_\_(self, embedding_service: EmbeddingService):
self.embedding = embedding_service
async def build_index(
self,
documents: List\[Document\],
chunk_size: int = 512,
chunk_overlap: int = 50
):
"""构建向量索引"""
\# 1\. 文档分块
chunks = \[\]
for doc in documents:
doc_chunks = self.\_chunk\_document(doc.content, chunk_size, chunk_overlap)
for i, chunk in enumerate(doc_chunks):
chunks.append(Document(
id=f"{doc.id}\_chunk\_{i}",
content=chunk,
metadata={**doc.metadata, "chunk_index": i, "parent_id": doc.id}
))
\# 2\. 批量向量化
texts = \[chunk.content for chunk in chunks\]
embeddings = await self.embedding.embed_batch(texts)
\# 3\. 存入向量数据库
for chunk, embedding in zip(chunks, embeddings):
chunk.embedding = embedding
await self.\_insert\_to_milvus(chunk)
return len(chunks)
def \_chunk\_document(self, text: str, chunk_size: int, overlap: int) -> List\[str\]:
"""智能分块"""
\# 1\. 按段落分
paragraphs = text.split('\\n\\n')
\# 2\. 合并短段落
chunks = \[\]
current_chunk = \[\]
current_length = 0
for para in paragraphs:
para_len = len(para)
if current_length + para_len <= chunk_size:
current_chunk.append(para)
current_length += para_len
else:
\# 保存当前块
if current_chunk:
chunks.append('\\n\\n'.join(current_chunk))
\# 重叠处理
overlap_text = current_chunk\[-1\] if current_chunk else ""
current_chunk = \[overlap_text, para\] if overlap_text else \[para\]
current_length = len(overlap_text) + para_len
if current_chunk:
chunks.append('\\n\\n'.join(current_chunk))
return chunks
五、后端开发最佳实践总结
5.1 架构设计原则
| 原则 | 说明 | 实践 |
|---|---|---|
| 解耦 | Agent逻辑与工具实现分离 | 使用 MCP 协议 |
| 可观测 | 全链路追踪 | OpenTelemetry + Jaeger |
| 弹性 | 应对 LLM 高延迟 | 异步、流式、超时控制 |
| 安全 | 防止 Prompt 注入 | 输入验证、权限检查 |
| 成本控制 | Token 消耗优化 | 缓存、摘要、滑动窗口 |
5.2 技术栈推荐
| 领域 | 推荐技术 | 说明 |
|---|---|---|
| Agent 框架 | LangGraph, AutoGen, Semantic Kernel | 工作流编排 |
| MCP 实现 | mcp SDK (Python/TypeScript) | 官方协议实现 |
| 向量数据库 | Milvus, Qdrant, Pinecone | RAG 检索 |
| 会话存储 | Redis, Valkey | 高性能内存存储 |
| 消息队列 | Kafka, RabbitMQ | 异步任务处理 |
| 监控 | OpenTelemetry, LangSmith | 可观测性 |
5.3 生产环境 Checklist
text
□ 工具注册中心(服务发现)
□ 统一的认证鉴权(API Key / OAuth)
□ 速率限制和配额管理
□ 多模型支持(GPT-4, Claude, 本地模型)
□ 流式响应(SSE / WebSocket)
□ 会话持久化和恢复
□ 成本监控和预算告警
□ A/B 测试框架
□ 降级和熔断机制