线上报了一个 Bug,它该归哪个团队?这个看似简单的问题,在大型组织里往往是耗时最长的环节之一——路由错了,工单在团队间来回踢皮球,修复周期被拉到数天甚至数周。Miro 用 Amazon Bedrock 上的大模型重构了整个 Bug 路由流程,结果:团队重分配次数降了 6 倍,平均修复时间缩短 5 倍,从"天"级别直接压到"小时"级别。
这篇文章拆解他们的架构思路,并给出一个你可以直接改造上线的最小实现。
Bug 路由到底难在哪
传统路由依赖两个信号:报错组件和人工经验。问题在于——
- 用户提交的 Bug 描述往往模糊,"页面崩了"不等于"渲染引擎崩了",也可能是 API 返回了异常数据。
- 组织架构持续变动,人工维护的路由规则很快过时。
- 微服务架构下,一个表面 Bug 可能涉及 3-4 个团队的代码边界,初级支持人员很难判断根因。
Miro 之前的流程是:支持团队先手动分类 → 分给某个工程团队 → 工程团队发现不对 → 重新分配 → 循环往复。每次重分配意味着等待、上下文丢失和时间浪费。
核心架构:让大模型做"初筛裁判"
Miro 的方案并不复杂,但每个环节的设计都有讲究。整体流程可以概括为四步:
- 信号收集——把 Bug 报告、错误日志、堆栈信息、用户环境等原始信号聚合为结构化输入。
- 上下文增强——用内部服务目录(哪个团队负责哪个服务)和近期路由历史作为辅助上下文,注入 Prompt。
- 模型推理——调用 Amazon Bedrock 上的大模型,让它基于上下文输出路由建议及置信度。
- 后处理与兜底——置信度低于阈值时回退到人工路由;置信度足够则自动分配,同时记录决策日志供后续审计和模型微调。
关键设计决策:不让模型直接决定最终分配,而是输出建议 + 置信度,由规则层做最终裁决。这把模型的"幻觉风险"控制在可接受范围内。
Prompt 设计是真正的工程活
Miro 的经验表明,Prompt 不是写一段自然语言就完事,而是要像设计 API 一样严谨。几个实操要点:
- 服务目录作为硬上下文:把团队-服务映射表直接放进 Prompt,模型不需要"猜"组织架构。
- 结构化输出格式:要求模型返回 JSON,包含
team_suggestion、confidence、reasoning三个字段,方便下游程序解析。 - 多信号融合:不只是 Bug 描述,堆栈里出现的模块名、错误码、最近变更的 PR 作者都可以作为路由线索。
下面是一个你可以直接改造的 Prompt 模板和调用示例。
最小可运行实现
以下示例用 Amazon Bedrock Python SDK 调用 Claude 模型做 Bug 路由。你需要一个 AWS 账号、启用 Bedrock 的权限,以及 boto3。
import boto3
import json
# --- 配置 ---
MODEL_ID = "anthropic.claude-3-5-sonnet-20241022-v2:0"
REGION = "us-east-1"
CONFIDENCE_THRESHOLD = 0.7 # 低于此值回退人工
# 内部服务目录(实际应从 CMDB 或服务注册中心动态拉取)
SERVICE_DIRECTORY = """
| 服务名 | 超级团队 |
|-------------------|---------------|
| canvas-renderer | Rendering |
| api-gateway | Platform |
| auth-service | Security |
| collaboration-sync| Realtime |
| billing-api | Finance |
"""
def build_prompt(bug_report: dict) -> str:
"""构造路由 Prompt,包含 Bug 信号 + 服务目录"""
return f"""你是一个 Bug 路由助手。根据下面的 Bug 报告和服务目录,判断该 Bug 应路由到哪个团队。
## 服务目录
{SERVICE_DIRECTORY}
## Bug 报告
- 标题:{bug_report['title']}
- 描述:{bug_report['description']}
- 错误码:{bug_report.get('error_code', 'N/A')}
- 堆栈摘要:{bug_report.get('stack_summary', 'N/A')}
- 用户环境:{bug_report.get('user_env', 'N/A')}
## 输出要求
严格返回以下 JSON,不要添加任何其他内容:
{
"team_suggestion": "<团队名>",
"confidence": <0.0-1.0 的浮点数>,
"reasoning": "<简短理由,不超过两句话>"
}"""
def route_bug(bug_report: dict) -> dict:
"""调用 Bedrock 进行路由推理"""
client = boto3.client("bedrock-runtime", region_name=REGION)
prompt = build_prompt(bug_report)
# Bedrock Claude 的请求格式
body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 300,
"temperature": 0.2, # 低温度,路由任务不需要创造性
"messages": [
{"role": "user", "content": prompt}
]
}
response = client.invoke_model(
modelId=MODEL_ID,
body=json.dumps(body),
contentType="application/json",
accept="application/json"
)
result = json.loads(response["body"].read())
# Claude 返回的 content 是一个列表
raw_output = result["content"][0]["text"]
# 解析模型输出的 JSON
route_result = json.loads(raw_output)
# 置信度兜底逻辑
if route_result["confidence"] < CONFIDENCE_THRESHOLD:
route_result["action"] = "manual_review"
route_result["note"] = f"置信度 {route_result['confidence']} 低于阈值 {CONFIDENCE_THRESHOLD},建议人工复核"
else:
route_result["action"] = "auto_assign"
return route_result
# --- 演示运行 ---
if __name__ == "__main__":
bug = {
"title": "画板在 Safari 上频繁崩溃",
"description": "用户在 Safari 17 上打开大画板时,页面白屏并出现 JS 错误",
"error_code": "ERR_WEBGL_CONTEXT_LOST",
"stack_summary": "canvas-renderer > WebGLPipeline > drawFrame",
"user_env": "Safari 17.2 / macOS 14.1 / M2 MacBook"
}
result = route_bug(bug)
print(json.dumps(result, indent=2, ensure_ascii=False))
运行前确保:
# 安装依赖
pip install boto3
# 配置 AWS 凭证(已有 CLI 配置的可跳过)
aws configure
# 确认 Bedrock 模型访问已启用
aws bedrock list-models --region us-east-1
预期输出类似:
{
"team_suggestion": "Rendering",
"confidence": 0.92,
"reasoning": "堆栈指向 canvas-renderer 的 WebGLPipeline,错误码为 WebGL 上下文丢失,属于 Rendering 团队职责范围。",
"action": "auto_assign"
}
几个容易被忽略的工程细节
温度参数要压低。 路由是分类任务,不是创意写作。Miro 选择了低温度(接近 0)以获得稳定、可复现的输出。上面示例中 temperature=0.2 就是这个思路。
服务目录必须动态更新。 静态硬编码的目录一周就会过时。实际部署中,这个表应该从服务注册中心(如 Backstage、内部 CMDB)自动拉取,每天刷新一次缓存。
置信度阈值不是一劳永逸的。 初期可以把阈值设高(0.8),让更多工单走人工复核,同时收集模型判断 vs 实际结果的偏差数据。模型稳定后逐步下调阈值,扩大自动路由比例。
决策日志是后续优化的金矿。 每次路由结果——包括模型输出、最终决策、是否被人工修正——都应写入数据库。这些数据是未来微调模型、调整 Prompt、甚至训练专用分类器的训练集。
从试点到全面铺开的路线建议
如果你打算在自己的组织复现这套思路,可以按这个节奏推进:
- 冷启动期(1-2 周):选一个 Bug 量最大的团队做试点,用影子模式运行——模型给出建议但不自动分配,人工对照模型建议做决策,收集准确率数据。
- 调优期(2-4 周):根据影子模式数据调整 Prompt、服务目录格式、置信度阈值。准确率稳定在 85% 以上再考虑自动分配。
- 灰度期(1-2 周):置信度高的工单自动分配,低的仍走人工。监控重分配率是否下降。
- 全面上线:逐步扩大自动路由范围,保留人工兜底通道。
Miro 的数据已经证明了效果:6 倍减少重分配意味着工单不再在团队间流浪,5 倍缩短修复时间意味着用户更快看到问题解决。核心不是"用了大模型"这个标签,而是把大模型放在一个有约束、有兜底、有反馈的工程流程里——这才是从 demo 走到生产的关键一步。