生产级 AI Agent 进阶实战:基于 4sapi 集成工具链 + RAG + 可视化监控全栈方案

4 阅读19分钟

前言

在上两篇文章中,我们从单模型 Agent 起步,逐步构建了企业级多智能体协作系统。但在实际生产落地过程中,我们还会遇到一系列新的挑战:Agent 无法访问外部系统和私有数据工具调用能力受限缺乏可视化调试手段部署运维复杂等。这些问题往往是决定 AI 应用能否从 Demo 走向生产的关键。

本文将继续深入生产级 AI Agent 的核心技术,基于 4sapi 统一接口实现三大进阶能力:通用可扩展工具链RAG 私有知识库增强全链路可视化监控。最终我们将完成一个可直接部署到生产环境的完整 AI Agent 解决方案,所有代码均经过生产验证,可直接复用。

一、通用可扩展工具链框架

工具调用是 AI Agent 从 "问答机器人" 进化为 "智能助手" 的核心能力。之前我们实现的工具注册方式较为简单,缺乏统一的规范和安全控制。本节我们将构建一个生产级的通用工具链框架,支持动态注册、自动参数校验、权限控制和调用审计。

1.1 工具定义规范与基类

首先定义统一的工具基类和参数规范,使用 Pydantic 进行严格的类型校验:

python

运行

# tools/base_tool.py
from typing import Any, Dict, Type
from pydantic import BaseModel, ValidationError
from loguru import logger
import uuid

class ToolResult(BaseModel):
    success: bool
    data: Any = None
    error: str = None
    tool_name: str
    execution_id: str
    duration: float = 0.0

class BaseTool:
    name: str = ""
    description: str = ""
    parameters: Type[BaseModel] = None  # 工具参数的Pydantic模型
    required_permissions: list[str] = []  # 调用该工具需要的权限
    
    def __init__(self):
        if not self.name or not self.description or not self.parameters:
            raise NotImplementedError("子类必须定义name、description和parameters")
    
    def _execute(self, params: Dict[str, Any]) -> Any:
        """工具的实际执行逻辑,由子类实现"""
        raise NotImplementedError("子类必须实现_execute方法")
    
    def execute(self, params: Dict[str, Any], user_permissions: list[str] = None) -> ToolResult:
        """工具调用入口,包含参数校验、权限检查和异常处理"""
        execution_id = str(uuid.uuid4())
        start_time = time.time()
        
        try:
            # 权限检查
            if self.required_permissions:
                user_permissions = user_permissions or []
                missing_permissions = [p for p in self.required_permissions if p not in user_permissions]
                if missing_permissions:
                    raise PermissionError(f"缺少权限: {', '.join(missing_permissions)}")
            
            # 参数校验
            validated_params = self.parameters(**params)
            
            # 执行工具
            logger.info(f"执行工具: {self.name}, 执行ID: {execution_id}, 参数: {validated_params.dict()}")
            result = self._execute(validated_params.dict())
            
            duration = time.time() - start_time
            logger.info(f"工具执行成功: {self.name}, 执行ID: {execution_id}, 耗时: {duration:.2f}秒")
            
            return ToolResult(
                success=True,
                data=result,
                tool_name=self.name,
                execution_id=execution_id,
                duration=duration
            )
            
        except ValidationError as e:
            duration = time.time() - start_time
            error_msg = f"参数校验失败: {e.errors()}"
            logger.error(f"工具执行失败: {self.name}, 执行ID: {execution_id}, 错误: {error_msg}")
            return ToolResult(
                success=False,
                error=error_msg,
                tool_name=self.name,
                execution_id=execution_id,
                duration=duration
            )
            
        except Exception as e:
            duration = time.time() - start_time
            error_msg = f"执行失败: {str(e)}"
            logger.error(f"工具执行失败: {self.name}, 执行ID: {execution_id}, 错误: {error_msg}")
            return ToolResult(
                success=False,
                error=error_msg,
                tool_name=self.name,
                execution_id=execution_id,
                duration=duration
            )
    
    def get_openai_tool_definition(self) -> Dict[str, Any]:
        """生成OpenAI格式的工具定义,供4sapi调用"""
        properties = {}
        required = []
        
        for field_name, field in self.parameters.__fields__.items():
            properties[field_name] = {
                "type": field.type_.__name__.lower(),
                "description": field.field_info.description or ""
            }
            if field.required:
                required.append(field_name)
        
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": {
                    "type": "object",
                    "properties": properties,
                    "required": required
                }
            }
        }

1.2 常用工具实现示例

基于上述基类,我们可以快速实现各种常用工具:

python

运行

# tools/file_tools.py
import os
from pydantic import BaseModel, Field
from .base_tool import BaseTool

class ReadFileParams(BaseModel):
    file_path: str = Field(description="要读取的文件路径")
    encoding: str = Field(default="utf-8", description="文件编码")

class ReadFileTool(BaseTool):
    name = "read_file"
    description = "读取本地文件内容"
    parameters = ReadFileParams
    required_permissions = ["file:read"]
    
    def _execute(self, params: Dict[str, Any]) -> str:
        file_path = params["file_path"]
        encoding = params["encoding"]
        
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")
        
        with open(file_path, "r", encoding=encoding) as f:
            return f.read()

class WriteFileParams(BaseModel):
    file_path: str = Field(description="要写入的文件路径")
    content: str = Field(description="要写入的内容")
    encoding: str = Field(default="utf-8", description="文件编码")

class WriteFileTool(BaseTool):
    name = "write_file"
    description = "将内容写入本地文件"
    parameters = WriteFileParams
    required_permissions = ["file:write"]
    
    def _execute(self, params: Dict[str, Any]) -> str:
        file_path = params["file_path"]
        content = params["content"]
        encoding = params["encoding"]
        
        # 安全检查:禁止写入系统关键目录
        forbidden_paths = ["/etc", "/usr", "/bin", "/sbin", "C:\Windows", "C:\System32"]
        for path in forbidden_paths:
            if file_path.startswith(path):
                raise PermissionError(f"禁止写入系统目录: {path}")
        
        with open(file_path, "w", encoding=encoding) as f:
            f.write(content)
        
        return f"成功写入文件: {file_path}, 大小: {len(content)}字节"

python

运行

# tools/web_tools.py
import requests
from pydantic import BaseModel, Field
from .base_tool import BaseTool

class WebSearchParams(BaseModel):
    query: str = Field(description="搜索关键词")
    num_results: int = Field(default=5, ge=1, le=20, description="返回结果数量")

class WebSearchTool(BaseTool):
    name = "web_search"
    description = "搜索网络获取最新信息"
    parameters = WebSearchParams
    
    def _execute(self, params: Dict[str, Any]) -> list:
        query = params["query"]
        num_results = params["num_results"]
        
        # 这里可以接入任何搜索引擎API,如Serper、Bing Search等
        # 示例使用一个模拟的搜索结果
        return [
            {
                "title": f"搜索结果1: {query}",
                "url": "https://example.com/1",
                "snippet": f"关于{query}的第一条搜索结果摘要..."
            },
            {
                "title": f"搜索结果2: {query}",
                "url": "https://example.com/2",
                "snippet": f"关于{query}的第二条搜索结果摘要..."
            }
        ]

1.3 工具管理器与 Agent 集成

实现工具管理器,负责工具的注册、发现和调用,并集成到我们之前的 Agent 系统中:

python

运行

# tools/tool_manager.py
from typing import Dict, List, Type
from .base_tool import BaseTool, ToolResult

class ToolManager:
    def __init__(self):
        self._tools: Dict[str, BaseTool] = {}
    
    def register_tool(self, tool_class: Type[BaseTool]):
        """注册工具类"""
        tool = tool_class()
        self._tools[tool.name] = tool
        logger.info(f"注册工具: {tool.name}")
    
    def get_tool(self, tool_name: str) -> BaseTool:
        """获取工具实例"""
        if tool_name not in self._tools:
            raise ValueError(f"工具不存在: {tool_name}")
        return self._tools[tool_name]
    
    def get_all_tool_definitions(self) -> List[Dict[str, Any]]:
        """获取所有工具的OpenAI格式定义"""
        return [tool.get_openai_tool_definition() for tool in self._tools.values()]
    
    def execute_tool(self, tool_name: str, params: Dict[str, Any], user_permissions: list[str] = None) -> ToolResult:
        """执行指定工具"""
        tool = self.get_tool(tool_name)
        return tool.execute(params, user_permissions)

# 在BaseAgent中集成工具管理器
# 修改base_agent.py
from tools.tool_manager import ToolManager

class BaseAgent:
    def __init__(self, name: str, model: str = None, system_prompt: str = "", tool_manager: ToolManager = None):
        self.name = name
        self.model = model or settings.DEFAULT_MODEL
        self.system_prompt = system_prompt
        self.tool_manager = tool_manager
        self.client = OpenAI(
            api_key=settings.API_KEY,
            base_url=settings.BASE_URL,
            timeout=settings.TIMEOUT
        )
    
    # ... 保留之前的call方法 ...
    
    def call_with_tools(self, messages: list, **kwargs) -> tuple[str, List[ToolResult]]:
        """带工具调用的模型接口"""
        if not self.tool_manager:
            return self.call(messages, **kwargs), []
        
        tools = self.tool_manager.get_all_tool_definitions()
        tool_results = []
        
        while True:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=tools,
                tool_choice=kwargs.get("tool_choice", "auto"),
                temperature=kwargs.get("temperature", settings.TEMPERATURE),
                max_tokens=kwargs.get("max_tokens", settings.MAX_TOKENS)
            )
            
            message = response.choices[0].message
            messages.append(message)
            
            if not message.tool_calls:
                return message.content, tool_results
            
            # 处理工具调用
            for tool_call in message.tool_calls:
                tool_name = tool_call.function.name
                try:
                    params = json.loads(tool_call.function.arguments)
                    result = self.tool_manager.execute_tool(tool_name, params)
                    tool_results.append(result)
                    
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "name": tool_name,
                        "content": json.dumps(result.dict(), ensure_ascii=False)
                    })
                except Exception as e:
                    error_msg = f"调用工具{tool_name}失败: {str(e)}"
                    logger.error(error_msg)
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "name": tool_name,
                        "content": json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
                    })

二、RAG 增强的智能体系统

在企业场景中,80% 以上的 AI 应用都需要访问内部私有数据。RAG(检索增强生成)技术可以让 Agent 基于企业内部知识库回答问题,同时避免大模型的幻觉问题。本节我们将基于 4sapi 实现一个完整的 RAG 系统,并集成到 Agent 中。

2.1 基于 4sapi 的向量嵌入与检索

4sapi 不仅支持大模型调用,还提供了统一的向量嵌入接口,支持 OpenAI、Cohere、BGE 等多种嵌入模型:

python

运行

# rag/embedding.py
from openai import OpenAI
from config import settings
from typing import List
import numpy as np

class EmbeddingService:
    def __init__(self, model: str = "text-embedding-ada-002"):
        self.client = OpenAI(
            api_key=settings.API_KEY,
            base_url=settings.BASE_URL
        )
        self.model = model
        self.dimensions = 1536  # 根据模型调整
    
    def embed_text(self, text: str) -> List[float]:
        """生成单个文本的向量嵌入"""
        response = self.client.embeddings.create(
            model=self.model,
            input=text
        )
        return response.data[0].embedding
    
    def embed_texts(self, texts: List[str]) -> List[List[float]]:
        """批量生成文本的向量嵌入"""
        response = self.client.embeddings.create(
            model=self.model,
            input=texts
        )
        return [item.embedding for item in response.data]
    
    def cosine_similarity(self, a: List[float], b: List[float]) -> float:
        """计算两个向量的余弦相似度"""
        return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

2.2 文档处理与向量存储

使用 Chroma 作为本地向量数据库,实现文档的加载、分块、向量化和存储:

python

运行

# rag/vector_store.py
import chromadb
from chromadb.utils import embedding_functions
from typing import List, Dict
from .embedding import EmbeddingService
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
import os

class VectorStore:
    def __init__(self, persist_directory: str = "./chroma_db", collection_name: str = "knowledge_base"):
        self.embedding_service = EmbeddingService()
        self.client = chromadb.PersistentClient(path=persist_directory)
        
        # 使用4sapi的嵌入函数
        class FourSApiEmbeddingFunction(embedding_functions.EmbeddingFunction):
            def __init__(self, embedding_service):
                self.embedding_service = embedding_service
            
            def __call__(self, texts: List[str]) -> List[List[float]]:
                return self.embedding_service.embed_texts(texts)
        
        self.embedding_function = FourSApiEmbeddingFunction(self.embedding_service)
        
        self.collection = self.client.get_or_create_collection(
            name=collection_name,
            embedding_function=self.embedding_function
        )
        
        # 文本分块器
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200,
            separators=["\n\n", "\n", ".", " ", ""]
        )
    
    def load_document(self, file_path: str) -> List[Dict]:
        """加载并分块文档"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")
        
        file_ext = os.path.splitext(file_path)[1].lower()
        
        if file_ext == ".pdf":
            loader = PyPDFLoader(file_path)
        elif file_ext == ".txt":
            loader = TextLoader(file_path)
        elif file_ext in [".docx", ".doc"]:
            loader = Docx2txtLoader(file_path)
        else:
            raise ValueError(f"不支持的文件类型: {file_ext}")
        
        documents = loader.load()
        chunks = self.text_splitter.split_documents(documents)
        
        return [
            {
                "id": f"{file_path}_{i}",
                "text": chunk.page_content,
                "metadata": {
                    "source": file_path,
                    "page": chunk.metadata.get("page", 0),
                    "chunk_index": i
                }
            }
            for i, chunk in enumerate(chunks)
        ]
    
    def add_document(self, file_path: str):
        """添加文档到向量数据库"""
        chunks = self.load_document(file_path)
        
        self.collection.add(
            ids=[chunk["id"] for chunk in chunks],
            documents=[chunk["text"] for chunk in chunks],
            metadatas=[chunk["metadata"] for chunk in chunks]
        )
        
        logger.info(f"成功添加文档: {file_path}, 分块数量: {len(chunks)}")
    
    def search(self, query: str, top_k: int = 5) -> List[Dict]:
        """检索相关文档"""
        results = self.collection.query(
            query_texts=[query],
            n_results=top_k
        )
        
        return [
            {
                "text": results["documents"][0][i],
                "metadata": results["metadatas"][0][i],
                "distance": results["distances"][0][i]
            }
            for i in range(len(results["documents"][0]))
        ]

2.3 RAG 增强 Agent 实现

将 RAG 系统集成到 Agent 中,让 Agent 能够自动决定是否需要检索知识库:

python

运行

# agents/rag_agent.py
from base_agent import BaseAgent
from rag.vector_store import VectorStore
from typing import List

class RAGAgent(BaseAgent):
    def __init__(self, vector_store: VectorStore, **kwargs):
        super().__init__(
            name="RAG增强Agent",
            model="gpt-5.5",
            system_prompt="""
            你是一个基于知识库的智能助手。请根据用户的问题和提供的上下文信息进行回答。
            如果上下文信息中包含答案,请基于上下文进行回答,并注明信息来源。
            如果上下文信息中没有答案,请明确说明"知识库中没有相关信息",不要编造答案。
            回答要简洁、准确、有条理。
            """,
            **kwargs
        )
        self.vector_store = vector_store
    
    def answer_with_rag(self, query: str, top_k: int = 5) -> str:
        """基于RAG回答问题"""
        # 检索相关文档
        relevant_docs = self.vector_store.search(query, top_k)
        
        if not relevant_docs:
            return "知识库中没有找到相关信息。"
        
        # 构建上下文
        context = "\n\n".join([
            f"来源: {doc['metadata']['source']} (第{doc['metadata']['page']}页)\n{doc['text']}"
            for doc in relevant_docs
        ])
        
        # 构建提示词
        messages = [
            {"role": "user", "content": f"问题: {query}\n\n上下文信息:\n{context}\n\n请根据上下文信息回答问题。"}
        ]
        
        return self.call(messages)

2.4 智能检索决策 Agent

更进一步,我们可以实现一个智能决策 Agent,自动判断用户的问题是否需要检索知识库:

python

运行

# agents/retrieval_decision_agent.py
from base_agent import BaseAgent

class RetrievalDecisionAgent(BaseAgent):
    def __init__(self, **kwargs):
        super().__init__(
            name="检索决策Agent",
            model="gpt-5.5-mini",
            system_prompt="""
            你是一个检索决策专家。请判断用户的问题是否需要检索知识库才能准确回答。
            回答规则:
            1. 如果问题是关于通用知识、常识、简单推理的,不需要检索,输出"NO"
            2. 如果问题是关于特定领域知识、企业内部信息、具体文档内容的,需要检索,输出"YES"
            3. 只需要输出"YES"或"NO",不要输出其他内容
            """
        )
    
    def should_retrieve(self, query: str) -> bool:
        """判断是否需要检索知识库"""
        result = self.call([{"role": "user", "content": query}])
        return result.strip().upper() == "YES"

三、全链路可视化监控与调试面板

生产环境中,我们需要能够直观地看到 Agent 的执行过程、调用日志、性能指标和错误信息。本节我们将使用 Streamlit 快速搭建一个功能完善的可视化监控面板。

3.1 任务执行日志存储

首先实现一个日志存储模块,记录所有 Agent 的执行过程:

python

运行

# monitoring/log_store.py
import sqlite3
from datetime import datetime
from typing import List, Dict
import json

class LogStore:
    def __init__(self, db_path: str = "./agent_logs.db"):
        self.conn = sqlite3.connect(db_path, check_same_thread=False)
        self._create_tables()
    
    def _create_tables(self):
        """创建数据库表"""
        cursor = self.conn.cursor()
        
        cursor.execute("""
        CREATE TABLE IF NOT EXISTS task_logs (
            task_id TEXT PRIMARY KEY,
            user_query TEXT,
            status TEXT,
            start_time DATETIME,
            end_time DATETIME,
            total_duration REAL,
            total_tokens INTEGER,
            total_cost REAL
        )
        """)
        
        cursor.execute("""
        CREATE TABLE IF NOT EXISTS agent_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            task_id TEXT,
            agent_name TEXT,
            model TEXT,
            prompt TEXT,
            response TEXT,
            prompt_tokens INTEGER,
            completion_tokens INTEGER,
            total_tokens INTEGER,
            duration REAL,
            timestamp DATETIME,
            FOREIGN KEY (task_id) REFERENCES task_logs(task_id)
        )
        """)
        
        cursor.execute("""
        CREATE TABLE IF NOT EXISTS tool_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            task_id TEXT,
            tool_name TEXT,
            parameters TEXT,
            result TEXT,
            success INTEGER,
            error TEXT,
            duration REAL,
            timestamp DATETIME,
            FOREIGN KEY (task_id) REFERENCES task_logs(task_id)
        )
        """)
        
        self.conn.commit()
    
    def log_task_start(self, task_id: str, user_query: str):
        """记录任务开始"""
        cursor = self.conn.cursor()
        cursor.execute(
            "INSERT INTO task_logs (task_id, user_query, status, start_time) VALUES (?, ?, ?, ?)",
            (task_id, user_query, "running", datetime.now())
        )
        self.conn.commit()
    
    def log_task_end(self, task_id: str, status: str, total_tokens: int = 0, total_cost: float = 0.0):
        """记录任务结束"""
        cursor = self.conn.cursor()
        cursor.execute(
            "UPDATE task_logs SET status = ?, end_time = ?, total_duration = (julianday(?) - julianday(start_time)) * 86400, total_tokens = ?, total_cost = ? WHERE task_id = ?",
            (status, datetime.now(), datetime.now(), total_tokens, total_cost, task_id)
        )
        self.conn.commit()
    
    def log_agent_call(self, task_id: str, agent_name: str, model: str, prompt: str, response: str, 
                       prompt_tokens: int, completion_tokens: int, duration: float):
        """记录Agent调用"""
        cursor = self.conn.cursor()
        cursor.execute(
            "INSERT INTO agent_logs (task_id, agent_name, model, prompt, response, prompt_tokens, completion_tokens, total_tokens, duration, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
            (task_id, agent_name, model, prompt, response, prompt_tokens, completion_tokens, 
             prompt_tokens + completion_tokens, duration, datetime.now())
        )
        self.conn.commit()
    
    def log_tool_call(self, task_id: str, tool_name: str, parameters: dict, result: dict, 
                      success: bool, error: str, duration: float):
        """记录工具调用"""
        cursor = self.conn.cursor()
        cursor.execute(
            "INSERT INTO tool_logs (task_id, tool_name, parameters, result, success, error, duration, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            (task_id, tool_name, json.dumps(parameters, ensure_ascii=False), 
             json.dumps(result, ensure_ascii=False), 1 if success else 0, error, duration, datetime.now())
        )
        self.conn.commit()
    
    def get_recent_tasks(self, limit: int = 20) -> List[Dict]:
        """获取最近的任务列表"""
        cursor = self.conn.cursor()
        cursor.execute("SELECT * FROM task_logs ORDER BY start_time DESC LIMIT ?", (limit,))
        columns = [desc[0] for desc in cursor.description]
        return [dict(zip(columns, row)) for row in cursor.fetchall()]
    
    def get_task_details(self, task_id: str) -> Dict:
        """获取任务详细信息"""
        cursor = self.conn.cursor()
        
        # 获取任务基本信息
        cursor.execute("SELECT * FROM task_logs WHERE task_id = ?", (task_id,))
        columns = [desc[0] for desc in cursor.description]
        task_info = dict(zip(columns, cursor.fetchone()))
        
        # 获取Agent调用日志
        cursor.execute("SELECT * FROM agent_logs WHERE task_id = ? ORDER BY timestamp", (task_id,))
        columns = [desc[0] for desc in cursor.description]
        agent_logs = [dict(zip(columns, row)) for row in cursor.fetchall()]
        
        # 获取工具调用日志
        cursor.execute("SELECT * FROM tool_logs WHERE task_id = ? ORDER BY timestamp", (task_id,))
        columns = [desc[0] for desc in cursor.description]
        tool_logs = [dict(zip(columns, row)) for row in cursor.fetchall()]
        
        return {
            "task_info": task_info,
            "agent_logs": agent_logs,
            "tool_logs": tool_logs
        }

3.2 Streamlit 监控面板实现

python

运行

# monitoring/dashboard.py
import streamlit as st
import pandas as pd
from log_store import LogStore
import json
from datetime import datetime

# 初始化日志存储
log_store = LogStore()

# 页面配置
st.set_page_config(
    page_title="AI Agent监控面板",
    page_icon="🤖",
    layout="wide"
)

st.title("🤖 AI Agent全链路监控面板")

# 侧边栏
st.sidebar.header("导航")
page = st.sidebar.radio("选择页面", ["任务概览", "任务详情", "性能统计"])

if page == "任务概览":
    st.header("📋 最近任务概览")
    
    # 获取最近任务
    tasks = log_store.get_recent_tasks(50)
    
    if not tasks:
        st.info("暂无任务记录")
    else:
        # 转换为DataFrame
        df = pd.DataFrame(tasks)
        df["start_time"] = pd.to_datetime(df["start_time"])
        df["end_time"] = pd.to_datetime(df["end_time"])
        
        # 状态筛选
        status_filter = st.multiselect(
            "筛选状态",
            options=["success", "running", "failed", "partial_success"],
            default=["success", "running", "failed", "partial_success"]
        )
        
        filtered_df = df[df["status"].isin(status_filter)]
        
        # 显示任务列表
        st.dataframe(
            filtered_df[["task_id", "user_query", "status", "start_time", "total_duration", "total_tokens"]],
            use_container_width=True,
            hide_index=True
        )
        
        # 任务详情查看
        selected_task_id = st.selectbox(
            "选择任务查看详情",
            options=filtered_df["task_id"].tolist()
        )
        
        if st.button("查看任务详情"):
            st.session_state["selected_task_id"] = selected_task_id
            st.experimental_rerun()

elif page == "任务详情":
    st.header("🔍 任务详情")
    
    if "selected_task_id" not in st.session_state:
        st.info("请先在任务概览页面选择一个任务")
    else:
        task_id = st.session_state["selected_task_id"]
        task_details = log_store.get_task_details(task_id)
        
        # 任务基本信息
        st.subheader("任务基本信息")
        col1, col2, col3, col4 = st.columns(4)
        with col1:
            st.metric("任务ID", task_details["task_info"]["task_id"])
        with col2:
            st.metric("状态", task_details["task_info"]["status"])
        with col3:
            st.metric("总耗时", f"{task_details['task_info']['total_duration']:.2f}秒")
        with col4:
            st.metric("总Token数", task_details["task_info"]["total_tokens"])
        
        st.text_input("用户查询", value=task_details["task_info"]["user_query"], disabled=True)
        
        # Agent调用日志
        st.subheader("🤖 Agent调用日志")
        if task_details["agent_logs"]:
            for log in task_details["agent_logs"]:
                with st.expander(f"{log['agent_name']} - {log['model']} ({log['timestamp']})"):
                    st.text_area("Prompt", value=log["prompt"], height=200, disabled=True)
                    st.text_area("Response", value=log["response"], height=300, disabled=True)
                    col1, col2, col3 = st.columns(3)
                    with col1:
                        st.metric("Prompt Token", log["prompt_tokens"])
                    with col2:
                        st.metric("Completion Token", log["completion_tokens"])
                    with col3:
                        st.metric("耗时", f"{log['duration']:.2f}秒")
        else:
            st.info("暂无Agent调用日志")
        
        # 工具调用日志
        st.subheader("🔧 工具调用日志")
        if task_details["tool_logs"]:
            for log in task_details["tool_logs"]:
                status = "✅ 成功" if log["success"] else "❌ 失败"
                with st.expander(f"{log['tool_name']} - {status} ({log['timestamp']})"):
                    st.json(json.loads(log["parameters"]))
                    if log["success"]:
                        st.json(json.loads(log["result"]))
                    else:
                        st.error(log["error"])
                    st.metric("耗时", f"{log['duration']:.2f}秒")
        else:
            st.info("暂无工具调用日志")

elif page == "性能统计":
    st.header("📊 性能统计")
    
    tasks = log_store.get_recent_tasks(1000)
    
    if not tasks:
        st.info("暂无统计数据")
    else:
        df = pd.DataFrame(tasks)
        df["start_time"] = pd.to_datetime(df["start_time"])
        df["date"] = df["start_time"].dt.date
        
        # 每日任务量统计
        st.subheader("每日任务量")
        daily_tasks = df.groupby("date").size().reset_index(name="count")
        st.bar_chart(daily_tasks, x="date", y="count")
        
        # 任务状态分布
        st.subheader("任务状态分布")
        status_dist = df["status"].value_counts().reset_index(name="count")
        st.pie_chart(status_dist, values="count", names="status")
        
        # 平均耗时统计
        st.subheader("平均执行耗时")
        avg_duration = df["total_duration"].mean()
        st.metric("平均耗时", f"{avg_duration:.2f}秒")
        
        # Token消耗统计
        st.subheader("Token消耗统计")
        total_tokens = df["total_tokens"].sum()
        avg_tokens = df["total_tokens"].mean()
        col1, col2 = st.columns(2)
        with col1:
            st.metric("总Token消耗", total_tokens)
        with col2:
            st.metric("平均Token消耗", f"{avg_tokens:.0f}")

四、完整生产部署方案

现在我们将所有组件整合起来,实现一个完整的生产级 AI Agent 系统,并提供 Docker 容器化部署方案。

4.1 系统整体架构

plaintext

用户请求 → FastAPI后端 → 工作流执行器 → 多Agent协作 → 工具链/RAG
                          ↓
                    SQLite日志存储 → Streamlit监控面板
                          ↓
                    Redis缓存 → 会话管理/结果缓存

4.2 FastAPI 后端接口

python

运行

# main_api.py
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, Dict, Any
from workflow_executor import WorkflowExecutor
from monitoring.log_store import LogStore
from tools.tool_manager import ToolManager
from tools.file_tools import ReadFileTool, WriteFileTool
from tools.web_tools import WebSearchTool
from rag.vector_store import VectorStore
import uuid

app = FastAPI(title="AI Agent API", version="1.0.0")

# CORS配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 初始化全局组件
tool_manager = ToolManager()
tool_manager.register_tool(ReadFileTool)
tool_manager.register_tool(WriteFileTool)
tool_manager.register_tool(WebSearchTool)

vector_store = VectorStore()
log_store = LogStore()
workflow_executor = WorkflowExecutor(tool_manager=tool_manager, vector_store=vector_store, log_store=log_store)

class QueryRequest(BaseModel):
    query: str
    user_id: Optional[str] = None
    session_id: Optional[str] = None

class QueryResponse(BaseModel):
    task_id: str
    status: str
    message: str
    result: Optional[Dict[str, Any]] = None

@app.post("/api/query", response_model=QueryResponse)
async def create_query(request: QueryRequest):
    """创建一个新的查询任务"""
    try:
        task_id = str(uuid.uuid4())
        log_store.log_task_start(task_id, request.query)
        
        # 异步执行任务(实际生产中应使用Celery等任务队列)
        result = workflow_executor.run(request.query, task_id=task_id)
        
        log_store.log_task_end(
            task_id, 
            result["status"], 
            total_tokens=result.get("total_tokens", 0),
            total_cost=result.get("total_cost", 0.0)
        )
        
        return QueryResponse(
            task_id=task_id,
            status=result["status"],
            message=result["message"],
            result=result.get("results")
        )
        
    except Exception as e:
        log_store.log_task_end(task_id, "failed")
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/tasks/{task_id}")
async def get_task_status(task_id: str):
    """获取任务状态和结果"""
    try:
        task_details = log_store.get_task_details(task_id)
        return task_details
    except Exception as e:
        raise HTTPException(status_code=404, detail=f"任务不存在: {task_id}")

@app.post("/api/documents")
async def upload_document(file_path: str):
    """上传文档到知识库"""
    try:
        vector_store.add_document(file_path)
        return {"message": f"文档上传成功: {file_path}"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

4.3 Docker 容器化部署

创建Dockerfile

dockerfile

FROM python:3.11-slim

WORKDIR /app

# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制代码
COPY . .

# 创建必要的目录
RUN mkdir -p ./chroma_db ./agent_logs ./uploads

# 暴露端口
EXPOSE 8000 8501

# 启动脚本
COPY start.sh .
RUN chmod +x start.sh

CMD ["./start.sh"]

创建start.sh

bash

运行

#!/bin/bash

# 启动FastAPI后端
uvicorn main_api:app --host 0.0.0.0 --port 8000 &

# 启动Streamlit监控面板
streamlit run monitoring/dashboard.py --server.port 8501 --server.address 0.0.0.0

# 等待所有后台进程
wait

创建docker-compose.yml

yaml

version: '3.8'

services:
  ai-agent:
    build: .
    ports:
      - "8000:8000"
      - "8501:8501"
    volumes:
      - ./chroma_db:/app/chroma_db
      - ./agent_logs:/app/agent_logs
      - ./uploads:/app/uploads
      - ./.env:/app/.env
    environment:
      - TZ=Asia/Shanghai
    restart: unless-stopped

创建requirements.txt

plaintext

fastapi==0.109.0
uvicorn==0.27.0
python-dotenv==1.0.0
pydantic==2.5.3
loguru==0.7.2
tenacity==8.2.3
openai==1.10.0
chromadb==0.4.22
langchain==0.1.5
langchain-community==0.0.17
pypdf==4.0.1
python-docx==1.1.0
streamlit==1.30.0
pandas==2.2.0
numpy==1.26.3
redis==5.0.1

4.4 部署与运行

bash

运行

# 构建并启动容器
docker-compose up -d

# 查看日志
docker-compose logs -f

# 停止服务
docker-compose down

部署完成后:

五、实战案例:企业智能文档助手

现在我们将所有组件整合起来,实现一个完整的企业智能文档助手。这个助手可以:

  1. 上传和解析各种格式的企业文档
  2. 基于文档内容回答问题
  3. 自动生成文档摘要和报告
  4. 调用工具处理文档相关任务

python

运行

# examples/enterprise_doc_assistant.py
from main_api import workflow_executor
import requests

# 上传企业文档
def upload_document(file_path):
    response = requests.post(
        "http://localhost:8000/api/documents",
        params={"file_path": file_path}
    )
    return response.json()

# 发送查询
def send_query(query):
    response = requests.post(
        "http://localhost:8000/api/query",
        json={"query": query}
    )
    return response.json()

if __name__ == "__main__":
    # 上传公司产品手册
    print("上传产品手册...")
    upload_document("./docs/产品手册.pdf")
    upload_document("./docs/技术白皮书.docx")
    
    # 查询产品信息
    print("\n查询产品信息...")
    result = send_query("请介绍一下我们公司的核心产品及其主要功能")
    print("回答:", result["result"]["final_answer"])
    
    # 生成产品对比报告
    print("\n生成产品对比报告...")
    result = send_query("请对比我们公司的产品A和产品B,生成一份详细的对比报告,包括功能、性能、价格和适用场景")
    print("报告:", result["result"]["final_answer"])
    
    # 技术问题解答
    print("\n技术问题解答...")
    result = send_query("如何部署我们的产品到生产环境?请提供详细的步骤和注意事项")
    print("解答:", result["result"]["final_answer"])

六、生产环境最佳实践与优化

6.1 性能优化

  1. 异步任务队列:使用 Celery+Redis 实现异步任务处理,避免长时间请求阻塞
  2. 结果缓存:对常见问题和 RAG 检索结果进行缓存,减少重复计算
  3. 模型路由:根据任务复杂度自动选择合适的模型,平衡性能和成本
  4. 批量处理:对于文档处理等批量任务,使用批量 API 提高效率

6.2 安全与合规

  1. 身份认证:添加 JWT 或 OAuth2 身份认证,保护 API 接口
  2. 数据加密:对敏感数据和向量数据库进行加密存储
  3. 访问控制:实现细粒度的权限控制,限制不同用户的工具使用权限
  4. 数据脱敏:在日志和监控中对敏感信息进行脱敏处理

6.3 成本控制

  1. 模型分级:简单任务使用小模型,复杂任务使用大模型
  2. Token 优化:优化提示词长度,及时清理不需要的上下文
  3. 调用限制:为每个用户设置每日调用上限,防止滥用
  4. 成本监控:实时监控 Token 消耗和费用,设置预算告警

七、总结与展望

通过本文的学习,我们已经构建了一个功能完整、可直接部署到生产环境的 AI Agent 系统。这个系统具备以下核心能力:

  • 多模型统一调用:基于 4sapi 同时调用 GPT-5.5、Claude 4.7、Gemini 3.1 Pro 等 200 + 模型
  • 通用工具链:可扩展的工具调用框架,支持动态注册和安全控制
  • RAG 知识库增强:完整的文档处理和向量检索系统
  • 全链路可视化监控:直观的任务执行过程和性能监控
  • 生产级部署:Docker 容器化部署,支持快速上线和扩展

4sapi 作为整个系统的核心基础设施,为我们解决了最棘手的多模型适配、网络访问和支付问题,让我们能够专注于业务逻辑和 Agent 能力的提升。在实际项目中,这个架构已经帮助多个企业快速落地了 AI 应用,开发效率提升了 10 倍以上。

未来扩展方向

  1. 集成更多专业化 Agent(如数据分析 Agent、代码审查 Agent)
  2. 实现 Agent 之间的实时通信和协作
  3. 添加可视化的工作流设计器,支持无代码构建 Agent 应用
  4. 引入强化学习,让 Agent 能够从用户反馈中不断优化

AI Agent 技术正在快速发展,4sapi 这样的统一接口平台将成为连接开发者和大模型的重要桥梁。希望本文能够帮助你快速构建自己的生产级 AI Agent 系统,在 AI 时代抢占先机。