1. 从零构建Python版OnCall系统的完整历程
作为一名长期从事AI系统开发的工程师,我最近完成了一个极具挑战性的项目——用Python语言从零开始构建一个完整的OnCall智能助手系统。这个系统不仅具备基础的聊天功能,还整合了RAG知识库检索、多工具调用以及动态规划执行等高级特性。下面我将详细分享整个开发过程中的关键步骤和技术实现。
2. 项目架构与技术选型
2.1 基础框架选择
在项目启动之初,我选择了FastAPI作为后端框架。FastAPI的异步特性、自动生成的API文档以及出色的性能表现,使其成为构建AI服务的理想选择。同时,FastAPI对WebSocket和SSE(Server-Sent Events)的原生支持,也为后续实现流式响应打下了良好基础。
python复制# main.py - FastAPI应用入口
from fastapi import FastAPI
from app.api.routes import chat, chat_stream
app = FastAPI(title="Python OnCall System")
app.include_router(chat.router)
app.include_router(chat_stream.router)
2.2 核心模块划分
系统采用分层架构设计,主要分为以下几层:
- API路由层:处理HTTP请求和响应
- 服务层:核心业务逻辑实现
- 适配器层:对接外部服务(如LLM、向量数据库等)
- 基础设施层:提供会话存储、工具注册等基础能力
这种分层设计使得系统各组件职责清晰,便于后续扩展和维护。
3. 基础功能实现
3.1 最小可行系统搭建
第一步是构建一个最小可运行的系统骨架。我创建了以下基础目录结构:
code复制app/
├── api/
│ ├── routes/
│ │ ├── chat.py
│ │ └── chat_stream.py
├── schemas/
│ └── chat.py
├── services/
│ └── chat_service.py
└── infra/
└── memory/
└── session_store.py
这个阶段实现了两个核心接口:
/api/chat:普通聊天接口/api/chat_stream:流式聊天接口
虽然此时还是返回mock数据,但已经验证了基础通信链路是通的。
3.2 会话管理与状态维护
为了支持多轮对话,我实现了一个简单的SessionStore,用于保存会话历史:
python复制# session_store.py
from typing import Dict, List
from app.schemas.chat import Message
class MemorySessionStore:
def __init__(self):
self.sessions: Dict[str, List[Message]] = {}
def get_messages(self, session_id: str) -> List[Message]:
return self.sessions.get(session_id, [])
def add_message(self, session_id: str, message: Message):
if session_id not in self.sessions:
self.sessions[session_id] = []
self.sessions[session_id].append(message)
这个内存存储虽然简单,但已经能够满足初期开发需求。后续可以轻松替换为Redis等持久化存储方案。
4. 大模型能力集成
4.1 模型适配层设计
为了灵活支持不同的大模型服务,我设计了一个模型适配层:
python复制# openai_compatible.py
import os
from typing import AsyncGenerator
import openai
class OpenAIClient:
def __init__(self):
self.client = openai.AsyncOpenAI(
base_url=os.getenv("LLM_BASE_URL"),
api_key=os.getenv("LLM_API_KEY")
)
async def chat_completion(self, messages: List[Dict], model: str, stream: bool = False) -> AsyncGenerator:
response = await self.client.chat.completions.create(
model=model,
messages=messages,
stream=stream
)
return response
这种设计使得更换模型服务商时,只需修改适配层代码,业务逻辑无需变动。
4.2 流式响应实现
流式接口对于提升用户体验至关重要。我使用SSE技术实现了token-by-token的流式输出:
python复制# chat_stream.py
from fastapi import APIRouter, Request
from sse_starlette.sse import EventSourceResponse
router = APIRouter()
@router.post("/api/chat_stream")
async def chat_stream(request: Request):
async def event_generator():
async for token in chat_service.stream_response(session_id, user_input):
yield {"event": "token", "data": token}
yield {"event": "done", "data": ""}
return EventSourceResponse(event_generator())
5. RAG知识增强实现
5.1 知识处理流水线
为了实现知识检索增强(RAG),我构建了一个完整的知识处理流水线:
- 文档加载:支持PDF、Word、TXT等多种格式
- 文本分割:使用递归字符分割器处理长文档
- 向量化:通过嵌入模型将文本转换为向量
- 存储:使用Milvus向量数据库存储和检索
python复制# rag_service.py
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from pymilvus import connections, Collection
class RAGService:
def __init__(self):
self.splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
self.embedder = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-mpnet-base-v2"
)
connections.connect("default", host="localhost", port="19530")
self.collection = Collection("knowledge_base")
5.2 检索增强问答
在聊天流程中插入检索逻辑:
python复制async def generate_response(self, session_id: str, query: str, use_rag: bool):
messages = self.session_store.get_messages(session_id)
if use_rag:
query_embedding = self.embedder.embed_query(query)
results = self.collection.search(
data=[query_embedding],
anns_field="embedding",
param={"metric_type": "L2", "params": {"nprobe": 10}},
limit=3
)
context = "\n".join([hit.entity.get("text") for hit in results[0]])
messages.append({"role": "system", "content": f"Relevant context: {context}"})
return await self.llm_client.chat_completion(messages)
6. 工具调用能力建设
6.1 工具系统架构
为了实现模型调用外部工具的能力,我设计了以下组件:
- 工具注册中心:管理所有可用工具
- 工具协议:统一工具描述和调用规范
- 执行引擎:负责实际调用工具并处理结果
python复制# tool_registry.py
from typing import Dict, Type
from pydantic import BaseModel
from inspect import signature
class ToolRegistry:
def __init__(self):
self.tools: Dict[str, dict] = {}
def register(self, name: str, description: str, func: callable):
params = signature(func).parameters
schema = {p: str(param.annotation) for p, param in params.items()}
self.tools[name] = {
"description": description,
"func": func,
"schema": schema
}
6.2 工具调用循环
工具调用采用循环执行模式:
python复制async def tool_loop(self, messages: List[Dict], max_iter=3):
for _ in range(max_iter):
response = await self.llm_client.chat_completion(
messages=messages,
tools=self.tool_registry.get_tools_schema()
)
if not response.tool_calls:
return response
for tool_call in response.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
tool_result = self.tool_registry.execute(tool_name, tool_args)
messages.append({
"role": "tool",
"content": tool_result,
"tool_call_id": tool_call.id
})
return response
7. 真实业务系统集成
7.1 Prometheus告警接入
将告警工具从mock升级为真实Prometheus接口:
python复制# alerts_client.py
import httpx
from typing import List, Dict
class PrometheusClient:
def __init__(self, base_url: str):
self.base_url = base_url
async def query_alerts(self, service: str = None) -> List[Dict]:
params = {}
if service:
params["query"] = f'ALERTS{{service="{service}"}}'
else:
params["query"] = 'ALERTS{alertstate="firing"}'
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/api/v1/query",
params=params
)
return self._parse_response(response.json())
7.2 腾讯云CLS日志集成
通过MCP协议接入腾讯云日志服务:
python复制# cls_mcp_client.py
import json
import httpx
from sseclient import SSEClient
class CLSMCPClient:
def __init__(self, mcp_url: str):
self.mcp_url = mcp_url
self.tools = {}
async def initialize(self):
async with httpx.AsyncClient() as client:
response = await client.get(f"{self.mcp_url}/tools")
self.tools = response.json()
async def execute_tool(self, tool_name: str, params: dict):
messages = [{
"role": "user",
"content": json.dumps({"tool": tool_name, "params": params})
}]
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.mcp_url}/execute",
json={"messages": messages},
timeout=30
)
return response.json()
8. 高级调查能力实现
8.1 固定流程故障分析
实现/api/triage接口进行初步故障分析:
python复制# incident_analysis_service.py
from typing import Optional
from app.schemas.triage import TriageResult
class IncidentAnalysisService:
async def analyze(
self,
service: str,
symptom: str,
use_rag: bool
) -> TriageResult:
# 第一步:查询相关告警
alerts = await self._query_alerts_with_fallback(service)
# 第二步:基于症状查询日志
logs = await self.query_logs(service, self._infer_log_query(symptom))
# 第三步:检索相关知识
docs = await self.query_internal_docs(symptom) if use_rag else []
# 综合分析结果
summary = await self._generate_summary(alerts, logs, docs)
return TriageResult(
summary=summary,
alerts=alerts,
logs=logs,
docs=docs
)
8.2 动态规划调查器
实现Plan-Execute-Replan工作流:
python复制# plan_execute_service.py
from typing import List, Dict, Optional
from enum import Enum
class PlanDecision(Enum):
CONTINUE = "continue"
REPLAN = "replan"
FINISH = "finish"
class PlanExecuteService:
async def execute_plan(
self,
service: str,
symptom: str,
max_steps: int = 5
):
evidence = []
trace = []
# 初始规划
initial_plan = await self.planner.generate_initial_plan(service, symptom)
pending_steps = initial_plan.steps.copy()
trace.append({"phase": "plan", "plan": initial_plan.dict()})
while len(trace) < max_steps and pending_steps:
# 执行下一步
step = pending_steps.pop(0)
result = await self._execute_step(step)
evidence.append(result)
trace.append({"phase": "execute", "step": step, "result": result})
# 重规划决策
decision = await self.planner.decide_replan(
evidence=evidence,
remaining_steps=pending_steps
)
if decision == PlanDecision.FINISH:
break
elif decision == PlanDecision.REPLAN:
new_steps = await self.planner.generate_next_steps(
evidence=evidence,
remaining_steps=pending_steps
)
pending_steps = new_steps
trace.append({"phase": "replan", "new_steps": new_steps})
return {
"initial_plan": initial_plan,
"evidence": evidence,
"trace": trace,
"summary": await self._generate_summary(evidence)
}
9. 项目经验与最佳实践
9.1 开发过程中的关键决策
-
分层架构的价值:清晰的层级划分使得每项功能都有明确的位置,大大降低了维护成本。例如,当需要更换向量数据库时,只需修改infra层的少量代码。
-
配置与代码分离:所有环境相关的配置都通过.env文件管理,使得应用可以轻松在不同环境间迁移。
-
渐进式复杂度:从最简单的mock实现开始,逐步替换为真实组件,这种渐进方式降低了开发风险。
9.2 性能优化技巧
-
异步编程:全面采用async/await模式,显著提高了IO密集型操作的吞吐量。
-
批处理:对向量化等耗时操作采用批处理方式,减少网络往返。
-
缓存策略:对频繁访问的数据(如工具描述)实施缓存,减少重复计算。
9.3 常见问题排查
-
SSE连接中断:确保客户端正确处理了心跳机制,服务器端设置合理的keepalive间隔。
-
工具调用超时:为每个工具设置合理的超时时间,并实现重试机制。
-
模型响应格式错误:使用严格的输出约束和解析校验,确保即使模型输出不规范,系统也能优雅降级。
10. 项目演进与未来规划
当前系统已经实现了从基础聊天到复杂调查的全套能力,但仍有改进空间:
-
更强大的规划器:引入更复杂的规划算法,支持多步骤依赖关系的识别。
-
工具协同:实现工具间的数据传递和结果复用,减少重复查询。
-
可观测性增强:完善指标收集和日志记录,便于问题诊断和性能分析。
-
前端交互优化:提供更丰富的可视化手段展示调查过程和证据链。
这个项目的开发过程让我深刻体会到,构建一个实用的AI系统不仅需要强大的模型能力,更需要精心设计的工程架构和深思熟虑的交互流程。希望我的经验分享能为同样在AI工程化道路上探索的开发者提供一些有价值的参考。