【AI应用开发】用户会话多、并发高,Agent 服务如何做性能优化?缓存怎么做?

目录

  1. Agent 服务的性能瓶颈
  2. 缓存策略全览
  3. 异步与并发优化
  4. LLM 层优化
  5. 架构层优化
  6. 完整高并发架构

1. Agent 服务的性能瓶颈

一次 Agent 调用 = N 次 LLM API 调用 + M 次工具调用

典型耗时组成:
  LLM 推理:    1-3s × 2-5次 = 2-15s
  工具执行:    0.1-2s × 2-5次 = 0.2-10s
  RAG 检索:    0.2-0.5s × 1-3次 = 0.2-1.5s
  
  总延迟: 3-25 秒/次对话(用户感知非常慢)

高并发瓶颈:
  1. LLM API 限流: 多数 API 有 RPM/TPM 限制
  2. 长连接占用: Agent 执行时间长,连接不释放
  3. 内存膨胀: 每会话上下文消耗内存
  4. 无缓存: 同样问题每次重新完整执行

2. 缓存策略全览

2.1 多层缓存架构

L1: 精确匹配缓存 (Redis, TTL=5min)
    ↓ 未命中
L2: 语义相似缓存 (向量库, TTL=30min)
    ↓ 未命中  
L3: 部分结果缓存 (工具调用结果, TTL=10min)
    ↓ 未命中
L4: 实际执行

2.2 L1: 精确匹配缓存

import hashlib
import redis
import json

class ExactMatchCache:
    """精确匹配缓存 —— 完全相同的输入直接返回缓存"""
    
    def __init__(self, redis_client, ttl=300):
        self.redis = redis_client
        self.ttl = ttl
    
    def _normalize(self, text: str) -> str:
        """标准化输入"""
        # 去除多余空格、统一大小写(中文不需)、去除标点变化
        text = re.sub(r'\s+', ' ', text.strip())
        return text
    
    def get(self, user_input: str) -> Optional[str]:
        """获取缓存"""
        key = f"cache:exact:{hashlib.md5(self._normalize(user_input).encode()).hexdigest()}"
        cached = self.redis.get(key)
        return json.loads(cached) if cached else None
    
    def set(self, user_input: str, response: str):
        """写入缓存"""
        key = f"cache:exact:{hashlib.md5(self._normalize(user_input).encode()).hexdigest()}"
        self.redis.setex(key, self.ttl, json.dumps(response, ensure_ascii=False))
    
    def invalidate(self, pattern: str = "*"):
        """批量失效缓存"""
        keys = self.redis.keys(f"cache:exact:{pattern}")
        if keys:
            self.redis.delete(*keys)

# 不缓存的场景
NO_CACHE_PATTERNS = [
    r"我的.*订单",       # 个性化查询
    r"ORD-\d{4}-\d{5}",  # 具体订单号
    r"查询.*积分|余额",   # 实时数据
]

2.3 L2: 语义相似缓存

class SemanticCache:
    """语义相似缓存 —— 意思相同的不同问法命中缓存"""
    
    def __init__(self, vector_store, redis_client, 
                 similarity_threshold=0.92, ttl=1800):
        self.vector_store = vector_store
        self.redis = redis_client
        self.threshold = similarity_threshold
        self.ttl = ttl
    
    async def get(self, user_input: str, session_id: str) -> Optional[str]:
        """语义检索缓存"""
        
        query_embedding = await self._get_embedding(user_input)
        
        # 搜索相似的已缓存问题
        results = self.vector_store.search(
            query_embedding,
            k=1,
            filter={"type": "semantic_cache"}
        )
        
        if results and results[0]["score"] > self.threshold:
            cache_key = results[0]["metadata"]["cache_key"]
            cached = self.redis.get(cache_key)
            if cached:
                # 更新访问时间
                self.redis.expire(cache_key, self.ttl)
                return json.loads(cached)
        
        return None
    
    async def set(self, user_input: str, response: str):
        """写入语义缓存"""
        
        embedding = await self._get_embedding(user_input)
        cache_key = f"cache:semantic:{hashlib.md5(user_input.encode()).hexdigest()}"
        
        # 存储响应
        self.redis.setex(cache_key, self.ttl, json.dumps(response, ensure_ascii=False))
        
        # 存储向量用于语义检索
        self.vector_store.add(
            id=cache_key,
            vector=embedding,
            metadata={
                "type": "semantic_cache",
                "cache_key": cache_key,
                "original_query": user_input[:200],
                "timestamp": time.time()
            }
        )
    
    async def _get_embedding(self, text):
        # 实际的 embedding 调用
        return await self.embedding_client.encode(text)

2.4 L3: 工具调用结果缓存

class ToolResultCache:
    """工具调用结果缓存 —— 同一工具+参数的结果缓存"""
    
    def __init__(self, redis_client, default_ttl=600):
        self.redis = redis_client
        self.default_ttl = default_ttl
        
        # 不同工具的缓存 TTL
        self.ttl_map = {
            "search_knowledge_base": 1800,  # 知识库30分钟
            "query_product": 300,            # 商品信息5分钟
            "query_inventory": 60,           # 库存1分钟
            "query_order": 0,                # 订单不缓存(实时变化)
            "query_user": 0,                 # 用户信息不缓存
        }
    
    def get(self, tool_name: str, args: dict) -> Optional[dict]:
        """获取工具结果缓存"""
        
        ttl = self.ttl_map.get(tool_name, -1)
        if ttl == 0:
            return None  # 该工具不缓存
        
        # 构建缓存 key
        cache_key = self._build_key(tool_name, args)
        
        cached = self.redis.get(cache_key)
        return json.loads(cached) if cached else None
    
    def set(self, tool_name: str, args: dict, result: dict):
        """缓存工具结果"""
        ttl = self.ttl_map.get(tool_name, self.default_ttl)
        if ttl <= 0:
            return
        
        cache_key = self._build_key(tool_name, args)
        self.redis.setex(
            cache_key,
            ttl,
            json.dumps(result, ensure_ascii=False)
        )
    
    def _build_key(self, tool_name: str, args: dict) -> str:
        """构建缓存 key"""
        args_str = json.dumps(args, sort_keys=True, ensure_ascii=False)
        args_hash = hashlib.md5(args_str.encode()).hexdigest()
        return f"tool_cache:{tool_name}:{args_hash}"
    
    def invalidate_tool(self, tool_name: str):
        """使某个工具的所有缓存失效"""
        keys = self.redis.keys(f"tool_cache:{tool_name}:*")
        if keys:
            self.redis.delete(*keys)

2.5 缓存集成到 Agent

class CachedAgent:
    """集成缓存的 Agent"""
    
    def __init__(self, llm, tools):
        self.llm = llm
        self.tools = tools
        self.exact_cache = ExactMatchCache(redis_client)
        self.semantic_cache = SemanticCache(vector_store, redis_client)
        self.tool_cache = ToolResultCache(redis_client)
        
        self.stats = {"cache_hit": 0, "cache_miss": 0}
    
    async def run(self, user_input: str, session_id: str):
        # 1. 精确缓存
        exact = self.exact_cache.get(user_input)
        if exact:
            self.stats["cache_hit"] += 1
            return exact
        
        self.stats["cache_miss"] += 1
        
        # 2. 语义缓存
        semantic = await self.semantic_cache.get(user_input, session_id)
        if semantic:
            self.stats["cache_hit"] += 1
            return semantic
        
        # 3. 实际执行(带工具缓存)
        response = await self._execute_with_tool_cache(user_input)
        
        # 4. 写入缓存
        self.exact_cache.set(user_input, response)
        await self.semantic_cache.set(user_input, response)
        
        return response
    
    async def _execute_with_tool_cache(self, user_input):
        """带工具缓存的执行"""
        messages = [{"role": "user", "content": user_input}]
        
        for step in range(15):
            response = await self.llm.chat(messages, tools=self.tools)
            
            if response.finish_reason == "stop":
                return response.content
            
            for tc in response.tool_calls:
                # 检查工具缓存
                cached_result = self.tool_cache.get(tc.name, tc.args)
                
                if cached_result:
                    result = cached_result
                    logger.info(f"工具缓存命中: {tc.name}")
                else:
                    # 执行工具
                    result = await self._execute_tool(tc)
                    # 缓存结果
                    self.tool_cache.set(tc.name, tc.args, result)
                
                messages.append({
                    "role": "tool",
                    "content": json.dumps(result, ensure_ascii=False)
                })

3. 异步与并发优化

3.1 会话内工具并行

async def execute_parallel_tools(self, tool_calls: list) -> list:
    """并行执行互不依赖的工具调用"""
    
    # 检查工具之间有无依赖
    independent = self._filter_independent(tool_calls)
    
    if len(independent) > 1:
        # 并行执行
        tasks = [
            self._execute_tool(tc) for tc in independent
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        for tc, result in zip(independent, results):
            if isinstance(result, Exception):
                logger.error(f"工具 {tc.name} 执行失败: {result}")
        
        return results
    else:
        # 单个工具直接执行
        return [await self._execute_tool(tool_calls[0])]

def _filter_independent(self, tool_calls):
    """过滤出互不依赖的工具调用"""
    # 简单规则: 如果工具类别不同,就认为可并行
    # 实际可根据工具间数据依赖判断
    categories = [self.tools[tc.name].category for tc in tool_calls]
    if len(set(categories)) == len(categories):
        return tool_calls  # 全部可并行
    return tool_calls[:1]  # 保守:只并行第一个

3.2 跨会话异步处理

class AsyncAgentService:
    """异步 Agent 服务"""
    
    def __init__(self, max_concurrent=50):
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.agent_pool = AgentPool(min_size=5, max_size=20)
    
    async def chat(self, user_input: str, session_id: str):
        """异步聊天接口"""
        
        async with self.semaphore:  # 并发控制
            agent = await self.agent_pool.acquire()
            try:
                response = await agent.run(user_input, session_id)
                return response
            finally:
                await self.agent_pool.release(agent)

class AgentPool:
    """Agent 实例池(复用 LLM 连接)"""
    
    def __init__(self, min_size=5, max_size=20):
        self.pool = asyncio.Queue()
        self.min_size = min_size
        self.max_size = max_size
        self.current_size = 0
    
    async def acquire(self):
        """获取 Agent 实例"""
        try:
            return self.pool.get_nowait()
        except asyncio.QueueEmpty:
            if self.current_size < self.max_size:
                self.current_size += 1
                return self._create_agent()
            return await self.pool.get()
    
    async def release(self, agent):
        """归还 Agent 实例"""
        await self.pool.put(agent)

4. LLM 层优化

class LLMOptimizer:
    """LLM 层优化"""
    
    @staticmethod
    def use_small_model_for_simple_tasks(user_input, intent):
        """简单任务用小模型,省钱+快"""
        
        if intent in ["chitchat", "simple_fact"]:
            return "gpt-4o-mini"   # 快 5x,便宜 20x
        elif intent in ["procedure", "faq"]:
            return "gpt-4o"        # 平衡
        else:
            return "gpt-4o"        # 复杂任务才用最好的
    
    @staticmethod
    def streaming_response(generator):
        """流式输出 —— 用户更快看到第一个字"""
        for chunk in generator:
            yield chunk.content
    
    @staticmethod
    def prompt_compression(messages, max_tokens=8000):
        """压缩 Prompt —— 移除冗余内容"""
        # 保留 system prompt + 最近 N 轮 + 工具结果摘要
        compressed = [messages[0]]  # system prompt
        
        # 最近的消息
        recent = messages[-10:]
        
        # 压缩过长的工具返回
        for msg in recent:
            if msg["role"] == "tool" and len(str(msg["content"])) > 1000:
                msg["content"] = str(msg["content"])[:1000] + "...[已截断]"
            compressed.append(msg)
        
        return compressed
    
    @staticmethod
    def estimate_and_control_tokens(messages):
        """Token 预估与控制"""
        total = sum(len(str(m.get("content", ""))) // 4 for m in messages)
        
        if total > 12000:
            # 触发压缩
            return LLMOptimizer.prompt_compression(messages)
        
        return messages

5. 架构层优化

# ===== 推荐的高并发架构 =====

"""
              ┌──────────┐
              │  Nginx    │  (负载均衡 + 限流)
              └─────┬────┘
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   ┌─────────┐ ┌─────────┐ ┌─────────┐
   │Agent服务 │ │Agent服务 │ │Agent服务 │  (水平扩展)
   │ 实例 1  │ │ 实例 2  │ │ 实例 3  │
   └────┬────┘ └────┬────┘ └────┬────┘
        │           │           │
        └───────────┼───────────┘
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   ┌─────────┐ ┌─────────┐ ┌─────────┐
   │ Redis   │ │ Milvus  │ │ LLM API │
   │ (缓存)  │ │ (向量库)│ │ (多Key) │
   └─────────┘ └─────────┘ └─────────┘
"""

class ScaledAgentService:
    """水平扩展的 Agent 服务"""
    
    def __init__(self):
        # 多 LLM API Key 轮转(突破 RPM 限制)
        self.llm_keys = [
            "sk-key1", "sk-key2", "sk-key3"
        ]
        self.current_key_idx = 0
        
        # 限流器
        self.rate_limiter = TokenBucket(rate=100, capacity=200)
    
    def get_llm_client(self):
        """轮转 API Key"""
        key = self.llm_keys[self.current_key_idx]
        self.current_key_idx = (self.current_key_idx + 1) % len(self.llm_keys)
        return LLMClient(api_key=key)
    
    async def handle_request(self, user_input, session_id):
        """处理请求(带限流)"""
        
        if not self.rate_limiter.consume():
            return {"error": "服务繁忙,请稍后重试", "retry_after": 5}
        
        return await self.agent.chat(user_input, session_id)

class TokenBucket:
    """令牌桶限流器"""
    
    def __init__(self, rate: int, capacity: int):
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity
        self.last_refill = time.time()
    
    def consume(self, tokens=1):
        self._refill()
        if self.tokens >= tokens:
            self.tokens -= tokens
            return True
        return False
    
    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
        self.last_refill = now

6. 完整高并发架构

关键数值总结

优化手段 延迟降低 并发提升
精确缓存 100% (命中时) 极高
语义缓存 80-90% 极高
工具缓存 20-40%
异步并行 30-50% 50%
小模型分流 60-80% 100%
流式输出 感知 50% 不变
水平扩展 不变 N倍线性
API Key 轮转 不变 N倍

落地建议

第一阶段(立即可做):
  ✅ Redis 精确缓存 → 热点问题零延迟
  ✅ 异步并行工具调用 → 减少 30% 延迟
  ✅ 流式输出 → 用户感知更快

第二阶段(一周内):
  ✅ 工具结果缓存 → 减少重复 API 调用
  ✅ 语义缓存 → 覆盖相似问法
  ✅ 小模型分流 → 降低 LLM 成本

第三阶段(生产加固):
  ✅ 多 API Key 轮转 → 突破 RPM 限制
  ✅ 水平扩展 + 负载均衡
  ✅ Token 预算 + Prompt 压缩

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐