一、组合模式
用户消息
↓
┌─────────────────────────────┐
│ 第1层:规则(<1ms, 零成本) │ ← 拦截明确意图
│ 关键词 / 正则 / 前缀匹配 │
└──────┬──────────┬───────────┘
│命中 │未命中
↓ ↓
直接路由 ┌─────────────────────────────┐
│ 第2层:Embedding(~50ms) │ ← 拦截常见意图
│ 向量相似度匹配 │
└──────┬──────────┬───────────┘
│高置信度 │低置信度
↓ ↓
直接路由 ┌─────────────────────────────┐
│ 第3层:LLM FC(1-3s) │ ← 处理模糊/复杂意图
│ Function Calling │
└─────────────────────────────┘
每一层拦截一部分,漏下来的才到下一层。80% 的请求在前两层就结束了
二、具体实现
class IntentRouter:
"""三层意图路由"""
def init(self, embedding_model, tools, model_service):
self.rules = {} # 规则库
self.intent_examples = {} # Embedding 意图示例库
self.tools = tools # FC 工具定义
self.model_service = model_service
async def route(self, user_message: str) -> dict:
第1层:规则匹配(零延迟)
result = self._match_rules(user_message)
if result:
return {"source": "rule", **result}
第2层:Embedding 匹配(低延迟)
result = self._match_embedding(user_message)
if result and result"score" > 0.85:
return {"source": "embedding", **result}
第3层:LLM Function Calling(兜底)
return await self._llm_route(user_message
三、各层职责
┌───────────┬─────────────────────────
│ 层级 │ 拦截什么 │
├───────────┼─────────────────────────
│ 规则 │ 模式固定的高频意图 │
├───────────┼────────────────────────
│ Embedding │ 表达多样但语义明确的意图 │
├───────────┼────────────────────────
│ FC │ 模糊、复杂、多步骤意图 │
四、Embedding 意图路由实现
核心思路:每个意图准备几条示例句 → 算 embedding → 存起来 → 用户消息来了算相似度匹配。
- 数据准备
每个意图 5-10 条示例句,覆盖不同表达方式
INTENT_EXAMPLES = {
"ssh_command": [
"查看 nginx 日志",
"看看服务器磁盘使用情况",
"重启 docker 容器",
"检查进程是否在运行",
"瞅瞅服务器咋了",
"帮我看看内存占用",
],
"task_plan": [
"下载安装 nginx",
"帮我部署一个 Node.js 项目",
"按照这个文档配置环境",
"批量安装这几个依赖",
"把项目从源码编译安装",
],
"knowledge_query": [
"什么是微服务架构",
"解释一下 Docker 和虚拟机的区别",
"如何设计高并发系统",
"JWT 认证的原理是什么",
],
"normal_chat": [
"今天天气怎么样",
"帮我写一首诗",
"你好",
"谢谢",
],
}
- Embedding 服务
import httpx
import numpy as np
from typing import List, Dict
class EmbeddingService:
"""Embedding 服务 - 调用 embedding 模型 API"""
def init(self, base_url: str, api_key: str, model: str = "text-embedding-3-small"):
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.model = model
async def embed(self, texts: Liststr) -> ListList\[float]:
"""批量获取文本的 embedding 向量"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{self.base_url}/embeddings",
headers={"Authorization": f"Bearer {self.api_key}"},
json={"model": self.model, "input": texts}
)
data = resp.json()
按 index 排序,确保顺序和输入一致
sorted_data = sorted(data"data", key=lambda x: x"index")
return item\["embedding" for item in sorted_data]
async def embed_single(self, text: str) -> Listfloat:
"""获取单条文本的 embedding"""
result = await self.embed(text)
return result0
- 意图路由器
class EmbeddingIntentRouter:
"""基于 Embedding 的意图路由器"""
def init(self, embedding_service: EmbeddingService):
self.embedding_service = embedding_service
self.intent_vectors: Dictstr, List\[List\[float]] = {} # intent -> 向量列表
self.intent_labels: Liststr = \[\] # 展平的意图标签
self.all_vectors: np.ndarray = None # 展平的向量矩阵
async def build_index(self, intent_examples: Dictstr, List\[str]):
"""
构建意图索引
Args:
intent_examples: {"intent_name": "示例句1", "示例句2", ...}
"""
all_texts = \[\]
self.intent_labels = \[\]
for intent, examples in intent_examples.items():
for text in examples:
all_texts.append(text)
self.intent_labels.append(intent)
批量获取 embedding
vectors = await self.embedding_service.embed(all_texts)
self.all_vectors = np.array(vectors)
按意图分组存储
for intent, examples in intent_examples.items():
self.intent_vectorsintent = \[\]
print(f"EmbeddingRouter 索引构建完成: {len(all_texts)} 条示例, {len(intent_examples)} 个意图")
async def match(self, query: str, top_k: int = 3) -> Listdict:
"""
匹配用户查询最相似的意图
Args:
query: 用户消息
top_k: 返回前 k 个结果
Returns:
{"intent": "ssh_command", "score": 0.92, "matched_example": "查看 nginx 日志"}, ...
"""
if self.all_vectors is None:
raise ValueError("索引未构建,请先调用 build_index()")
获取查询的 embedding
query_vec = np.array(await self.embedding_service.embed_single(query))
计算余弦相似度
similarities = self._cosine_similarity(query_vec, self.all_vectors)
取 top_k
top_indices = np.argsort(similarities)::-1:top_k
results = \[\]
seen_intents = set()
for idx in top_indices:
intent = self.intent_labelsidx
每个意图只取最高分
if intent not in seen_intents:
seen_intents.add(intent)
results.append({
"intent": intent,
"score": float(similaritiesidx),
"matched_example": list(intent_examples.values())0 # 简化
})
return results
@staticmethod
def _cosine_similarity(a: np.ndarray, b: np.ndarray) -> np.ndarray:
"""计算余弦相似度"""
a_norm = a / np.linalg.norm(a)
b_norm = b / np.linalg.norm(b, axis=1, keepdims=True)
return np.dot(b_norm, a_norm)
- 接入三层路由
class IntentRouter:
def init(self, embedding_router: EmbeddingIntentRouter, tools, model_service):
self.embedding_router = embedding_router
self.tools = tools
self.model_service = model_service
规则层:高频固定模式
self.rules = [
(r"^(help|帮助|\\?)$", "help"),
(r"^(clear|清空|清除)$", "clear"),
(r"^/", "command"), # 斜杠命令
]
async def route(self, user_message: str, model_id: int) -> dict:
===== 第1层:规则(<1ms)=====
for pattern, intent in self.rules:
if re.match(pattern, user_message.strip()):
return {"intent": intent, "source": "rule", "confidence": 1.0}
===== 第2层:Embedding(~50ms)=====
matches = await self.embedding_router.match(user_message, top_k=1)
if matches and matches0"score" > 0.85:
return {
"intent": matches0"intent",
"source": "embedding",
"confidence": matches0"score"
}
===== 第3层:LLM FC(兜底)=====
return await self._llm_route(user_message, model_id)
- 启动时初始化
应用启动时
embedding_service = EmbeddingService(
base_url="http://localhost:11434", # 或 OpenAI API
api_key="your-key",
model="text-embedding-3-small"
)
embedding_router = EmbeddingIntentRouter(embedding_service)
await embedding_router.build_index(INTENT_EXAMPLES)
关键点
相似度阈值需要根据实际数据调:
> 0.90 非常确定,直接路由
0.80-0.90 比较确定,可以路由但记录日志
< 0.80 不确定,交给 LLM FC
意图示例的质量决定准确率:
-
每个意图 5-10 条,覆盖不同说法
-
包含口语化表达("瞅瞅"、"咋了")
-
定期根据 LLM FC 的日志补充新示例
模型选择:
-
OpenAI text-embedding-3-small - 效果好,成本低
-
本地 bge-large-zh - 中文效果好,零成本
-
Milvus 内置向量搜索 - 你项目已有 Milvus,可以直接