# 分诊 Agent 什么时候可以自己创建 Jira issue？

> 打标签不难，关键是把置信度阈值和风险边界放在模型之外，拿不准的工单交给人。

原文: https://fdetimes.net/zh/guides/jira-ticket-triage-agent-human-review/

设想一个 Agent 自行在客户的 Jira 里创建 issue，却把一次严重的登录故障归为低优先级的“功能需求”。客户的支持团队很可能从此再也不信任这个 Agent。

所以在工单分诊中，最难的问题不是“模型标签打得准不准”，而是“什么时候允许代码自己动手”。

本文一步步搭建一个小型 Agent，它只做三件事：通过 schema 严格的工具对工单分类；足够有把握时创建 issue；置信度低或操作越过风险边界时，把工单交给审核人。

客户想“把 AI 引入支持流程”时，这是一个规模小、可衡量的问题，很适合作为第一步尝试。

## 需要准备什么？

你需要 Claude API 的访问权限、一个至少有一个项目的 Jira Cloud 测试实例、Python，以及几十张已经正确打好标签的历史工单，用作对照。下面的代码做了简化：认证、HTTP 调用和日志存储都换成了占位函数，方便你接到自己常用的库上。

## 第 1 步：工具描述就是你的分诊规则手册

一个工具由 `name`、`description`、`input_schema` 和可选的 `input_examples` 字段组成。功夫要下在描述上：讲清楚工具做什么、什么时候用、应该怎样表现。Anthropic 认为，详尽的描述是决定工具质量的最重要因素。

对分诊 Agent 来说，要在描述里给出每个标签和每个优先级的定义，而不只是罗列标签名。给工具加上服务前缀，比如 `jira_...`，这样 Agent 日后接入其他系统时，模型不容易混淆。

```json
{
  "name": "jira_triage_ticket",
  "description": "在创建 Jira issue 之前对支持工单分类。每个新工单都必须使用。category：'access' = 无法登录或缺少权限；'bug' = 功能表现与描述不符；'billing' = 发票、付款；'feature_request' = 新功能需求。priority：'critical' = 大量客户无法使用产品；'high' = 单个客户受阻且没有变通办法；'medium' = 有变通办法；'low' = 界面瑕疵或咨询问题。confidence：取值 0 到 1。evidence：工单中支持该判断的简短引文。",
  "strict": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "category": {"type": "string", "enum": ["access", "bug", "billing", "feature_request"]},
      "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
      "confidence": {"type": "number", "minimum": 0, "maximum": 1},
      "evidence": {"type": "string"}
    },
    "required": ["category", "priority", "confidence", "evidence"],
    "additionalProperties": false
  }
}
```

`strict: true` 会开启 schema 校验，返回的标签一定落在枚举范围内。`evidence` 字段只要求一句简短的引文作为依据，而不是逐步推理。审核人需要的是能快速核对的证据，而不是一段要读完的论证。

**检查：**跑五张样例工单。每个结果都必须包含全部四个字段，标签必须在枚举内，`confidence` 必须介于 0 和 1 之间。

## 第 2 步：强制调用工具，但要为做不到的情况留后路

`tool_choice` 参数设为 `tool` 类型时，模型必须且只能调用一个工具。但并非所有模型或配置都支持强制调用。Anthropic 文档明确写道，Sonnet 5.5 在使用 `any` 或 `tool` 时会返回 400 错误。

```python
# 简化版：只保留与工具相关的配置
request = {
    "tools": [TRIAGE_TOOL],
    "tool_choice": {"type": "tool", "name": "jira_triage_ticket"},  # 如果所用模型返回 400，删除此行
}
```

因此，在依赖强制调用之前，先确认所用的模型是否支持。如果无法强制，代码就必须把“响应中没有工具调用”视为转人工的信号，而不是试图从自由文本里猜标签。

## 第 3 步：升级逻辑必须放在模型之外

这是最重要的一步。不要让模型决定何时升级，因为它做不到前后一致。触发升级的必须是你的代码。

也不要只看置信度分数：dev.to 上一篇分析 Agent 升级机制的文章把这称为常见错误。要再加一个条件：只要计划中的操作越过风险边界，就升级。在这里，风险边界指的是 critical 工单。

```python
THRESHOLD = 0.85          # 起点，第 5 步再校准
RISKY_PRIORITIES = {"critical"}

def route(triage):
    if triage is None:
        return "human", "no_tool_call"
    if triage["priority"] in RISKY_PRIORITIES:
        return "human", "risk_boundary"
    if triage["confidence"] < THRESHOLD:
        return "human", "low_confidence"
    return "auto", "passed_checks"
```

看两张工单。工单 A 写的是“修改密码后，我们三家子公司都登录不了”，模型返回 `access`、`critical`，置信度 0.93。工单 B 写的是“导出按钮有点没对齐”，模型返回 `bug`、`low`，置信度 0.62。

如果只看置信度，工单 A 会直接进入 Jira，而它恰恰是客户希望先由人来看的那类工单。上面的 `route` 函数把两张工单都拦了下来，但原因不同。这个原因必须记录下来，第 5 步要用到。

**要点:** 模型负责建议标签；什么时候可以放手自动执行，由你的代码决定。

**检查：**在接入 Jira 之前，为三个升级分支都写好单元测试。

## 第 4 步：创建 issue，并记录每一个决定

在 Jira Cloud 中，向实例的 `/rest/api/3/issue` 端点发送 POST 请求即可创建 issue。响应会返回新 issue 的 id、key 和 REST 端点。把这三项都记下来，同时记录 `route()` 对每张工单给出的原因，没有生成 issue 的工单也要记。

```python
# 简化版：jira_post()、send_to_reviewer()、audit_log()、audit_update() 均为占位函数。
# 请对照客户的项目配置核对 payload 字段名。
def handle(ticket, triage):
    decision, reason = route(triage)
    record = {"ticket_id": ticket["id"], "triage": triage,
              "route": decision, "reason": reason,
              "issue_id": None, "issue_key": None, "issue_url": None,
              "reviewer_category": None, "reviewer_priority": None}
    if decision == "auto":
        payload = {"fields": {
            "project": {"key": "SUP"},
            "summary": ticket["title"],
            "issuetype": {"name": "Task"},
            "labels": [triage["category"], triage["priority"]],
        }}
        resp = jira_post("/rest/api/3/issue", payload)
        record.update(issue_id=resp["id"], issue_key=resp["key"], issue_url=resp["self"])
    else:
        send_to_reviewer(ticket, triage, reason)
    audit_log(record)

def on_review(ticket_id, category, priority):
    # 审核人确认某张工单的标签时调用
    audit_update(ticket_id, reviewer_category=category, reviewer_priority=priority)
```

客户问 issue SUP-… 为什么带某个标签时，打开记录即可：key、标签、置信度、引文证据和路由原因都在，几秒钟就能对清楚。两个 `reviewer_*` 字段则是下一步的原始数据。

**检查：**在测试项目里创建一个真实 issue，在 Jira 中打开返回的 key，把它的标签和日志对比。然后让一张工单走审核分支，调用 `on_review`，确认记录里已经存入审核人选择的标签。

## 第 5 步：阈值不是设一次就完事的数字

置信度阈值是一条分界线：高于它的决定自动处理，低于它的需要人来看。第 3 步里的 0.85 只是起点。

Galileo 的 Agent 监控指南强调，校准是持续性的工作，目标升级率必须来自你自己工单的真实分布，而不是通用的行业数字。

第 4 步的日志让这件事变得具体。一周后，把 `triage` 与 `reviewer_category`、`reviewer_priority` 做对比。你会看到哪些标签模型即使置信度很高也常出错，哪些标签即使置信度一般也几乎总是对的。这就是调高或调低 `THRESHOLD`、甚至为每个标签单独设阈值的依据。

把升级率作为系统的健康指标来跟踪，并按 `reason` 的每个取值拆开看。如果客户上线新产品后 `low_confidence` 的比例突然飙升，很可能是工具描述里缺了某个标签。

## 常见错误

第一个错误是把工作拆成太多小工具。当 Agent 还需要更新 issue 或添加评论时，应把相关操作合并成一个带 `action` 参数的工具，这样模型选错工具的情况会更少。

第二个错误是把模型自报的置信度当作经过校准的概率。它只是一个信号，需要用审核人的决定来检验。

第三个错误是隐藏升级原因。审核人需要知道工单被拦下是因为风险还是因为置信度低，两者需要投入的关注程度不同。

## 放到真实的客户项目里是什么样？

在客户现场，本文的代码大概不是最难的部分。更难的是和支持负责人坐下来，把他们自己对“critical”的定义写进描述，并商定由谁来当审核人。

团队还要明确什么算风险边界。第一件事是向客户要 50 张已经由人分诊过的历史工单，在碰真实 Jira 之前，先用它们评测 Agent。

如果你正在转向前线部署工程师（FDE）岗位，这是一个很有分量的作品集项目。简历上不要只写“搭建了工单分诊 Agent”，而要写明你把升级逻辑和模型分离开、阈值是怎么选的，以及校准后升级率发生了怎样的变化。

在职位描述里，“human-in-the-loop”“tool use”“与客户系统集成”这类说法，指的都是这项能力。

懂得何时停手的 Agent，往往比总是自作主张的 Agent 更快赢得客户信任。所以，花在 `route()` 函数上的时间，应该比花在提示词上的更多。

**本周动手试试:**

- 从 Jira 测试项目中取 50 张已关闭的工单，写好分诊工具描述，把模型给出的标签与真实标签对比。
- 写一个独立于模型调用的 route() 函数，并为三种情况编写单元测试：模型没有调用工具、优先级为 critical、置信度低于阈值。
- 记录 Agent 的每一个决定，包括 route() 返回的原因、issue key（如有）以及审核人选择的标签，以便下周计算升级率。

## 信源

- [Define tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools)

- [Creating a Jira Cloud issue in a single REST Call](https://www.atlassian.com/blog/developer/creating-a-jira-cloud-issue-in-a-single-rest-call)

- [How to Build Human-in-the-Loop Oversight for Production AI Agents](https://galileo.ai/blog/human-in-the-loop-agent-oversight)

- [Escalation Triggers for LLM Agents: Why Confidence Alone Is Not Enough](https://dev.to/dipbhi/escalation-triggers-for-llm-agents-why-confidence-alone-is-not-enough-3bj)
