forked from ChaiHao/BPOS-MVP
度量八指标全部接真:DMN 覆盖率与单位成本不再恒为 0
两处度量是空的,查下来都是「没人记」而不是「算错了」: ① **DMN 覆盖率恒 0**:判据只认标题里的「求值:」,把跑得最多的**纯工具步**整个漏掉。 DMN 决策表与机检正是落在纯工具/工具步上。改为按步类型分: 确定性求值(pure-tool / tool)÷ 判断总数(再加 LLM 步)——「临场搬进确定」的进度表。 顺带把**步类型显式写进留痕**:判据该由引擎给出,不靠解析标题后缀。 现读数 66%。 ② **单位成本恒 0**:任务消耗只记步数,token / 活跃时长 / 调人次数**从来没人累加** —— 于是八指标里的单位成本永远是 0,预算熔断与打扰上限也形同虚设。 · tick 计这一拍真正花掉的时间(挂起等人的不算活跃,那是世界等待); · flow 的 LLM 步与 loop 的每一回合都把 token 计入; · 每发一张卡计一次调人。 现读数:A2 单轮 12k token、A3 3.4k;单位成本 73 token 每放行单。 另修:执行卡的 ask 优先取 `card.ask`(此前只认 `reason`,新卡面格式下会退化成标题)。 第四轮端到端复跑:整条链闭环,消耗数据真实。 gates: tsc / eslint / depcruise(336 模块)/ api-report / 429 测试 全绿 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "e2e-20260728E3@x.com",
|
||||||
|
"from": "buyer@acme.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C860 RFQ-20260728E3 求报价",
|
||||||
|
"body": "客户编码 C860,询价单号 RFQ-��\nLM358DR 厂牌 TI 年用量 12000 片 目标价 8.20 元 交期 10 周",
|
||||||
|
"receivedAt": "2026-07-28T12:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "e2e-20260728E4@x.com",
|
||||||
|
"from": "buyer@acme.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C870 RFQ-20260728E4 求报价",
|
||||||
|
"body": "客户编码 C870,询价单号 RFQ-��\nTL072CDR 厂牌 TI 年用量 12000 片 目标价 8.20 元 交期 10 周",
|
||||||
|
"receivedAt": "2026-07-28T12:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -51,6 +51,8 @@ export type AgentRunnerDeps = {
|
|||||||
readonly gateway: ModelGateway;
|
readonly gateway: ModelGateway;
|
||||||
/** 活动声明的自定义模型名 */
|
/** 活动声明的自定义模型名 */
|
||||||
readonly modelOf: (taskId: TaskId) => Promise<string>;
|
readonly modelOf: (taskId: TaskId) => Promise<string>;
|
||||||
|
/** token 回报:计入任务预算 —— 不计,预算熔断就永远不会响 */
|
||||||
|
readonly onUsage: (taskId: TaskId, tokens: number) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAgentRunner(deps: AgentRunnerDeps): {
|
export function createAgentRunner(deps: AgentRunnerDeps): {
|
||||||
@@ -95,6 +97,7 @@ export function createAgentRunner(deps: AgentRunnerDeps): {
|
|||||||
taskId,
|
taskId,
|
||||||
label: 'loop-turn',
|
label: 'loop-turn',
|
||||||
});
|
});
|
||||||
|
await deps.onUsage(taskId, result.usage.inputTokens + result.usage.outputTokens);
|
||||||
return normalize(result.object, enabled);
|
return normalize(result.object, enabled);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -45,18 +45,29 @@ export function wireDeskElements(deps: DeskElementDeps): { minute: MinuteService
|
|||||||
clock: deps.clock,
|
clock: deps.clock,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 归侧日志行 → 度量事实。判断类与断点由日志标题的确定形态识别,不另设埋点 */
|
/**
|
||||||
const toLogFact = (taskId: string, line: { at: string; side: LogFact['side']; title: string }): LogFact => ({
|
* 归侧日志行 → 度量事实。零埋点:判据全部取自结构化日志本身。
|
||||||
|
*
|
||||||
|
* **判断次数**包含两类:确定性求值(DMN 决策表 / 机检 —— 落在纯工具与工具步)
|
||||||
|
* 与临场判断(LLM 步、loop 的语义性前提评估)。
|
||||||
|
* DMN 覆盖率 = 前者 ÷ 两者之和 ——「临场搬进确定」的进度表。
|
||||||
|
* 早先只认标题里的「求值:」,把跑得最多的纯工具步整个漏掉了,覆盖率恒为 0。
|
||||||
|
*/
|
||||||
|
const toLogFact = (taskId: string, line: { at: string; side: LogFact['side']; title: string }): LogFact => {
|
||||||
|
const deterministic =
|
||||||
|
line.title.startsWith('求值:') || line.title.includes('(pure-tool)') || line.title.includes('(tool)');
|
||||||
|
const situational = line.title.includes('(llm)');
|
||||||
|
return {
|
||||||
taskId,
|
taskId,
|
||||||
at: line.at,
|
at: line.at,
|
||||||
side: line.side,
|
side: line.side,
|
||||||
title: line.title,
|
title: line.title,
|
||||||
isHumanToolCall: line.title.startsWith('发出:'),
|
isHumanToolCall: line.title.startsWith('发出:'),
|
||||||
// DMN / 机检的确定性求值在日志里是「求值:」;LLM 的判断落在「调用:」
|
isDeterministicJudgement: deterministic,
|
||||||
isDeterministicJudgement: line.title.startsWith('求值:'),
|
isJudgement: deterministic || situational,
|
||||||
isJudgement: line.title.startsWith('求值:') || line.title.startsWith('调用:'),
|
|
||||||
isBreakpoint: line.title.includes('断点') || line.title.includes('交集为空'),
|
isBreakpoint: line.title.includes('断点') || line.title.includes('交集为空'),
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const report = createReportService({
|
const report = createReportService({
|
||||||
store: createReportStore(deps.db),
|
store: createReportStore(deps.db),
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
|||||||
criteria: [{ label: '成果齐备', level: 'L5', pass: false }],
|
criteria: [{ label: '成果齐备', level: 'L5', pass: false }],
|
||||||
fields: [],
|
fields: [],
|
||||||
});
|
});
|
||||||
|
await taskStore.addConsumption(input.taskId, { humanCalls: 1 });
|
||||||
return { id: issued.order.id };
|
return { id: issued.order.id };
|
||||||
}
|
}
|
||||||
const fields = Array.isArray(input.card['fields'])
|
const fields = Array.isArray(input.card['fields'])
|
||||||
@@ -278,9 +279,11 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
|||||||
const issued = await deps.humanTool.issueExecution(context, {
|
const issued = await deps.humanTool.issueExecution(context, {
|
||||||
title,
|
title,
|
||||||
basis: basis.length > 0 ? basis : [{ k: '来源', v: '步序声明' }],
|
basis: basis.length > 0 ? basis : [{ k: '来源', v: '步序声明' }],
|
||||||
ask: String(input.card['reason'] ?? title),
|
ask: String(input.card['ask'] ?? input.card['reason'] ?? title),
|
||||||
fields,
|
fields,
|
||||||
});
|
});
|
||||||
|
// 打扰率的计量对象:每发一张卡就是调了一次人 —— 不计就等于没有上限
|
||||||
|
await taskStore.addConsumption(input.taskId, { humanCalls: 1 });
|
||||||
return { id: issued.order.id };
|
return { id: issued.order.id };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -348,6 +351,10 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
|||||||
stepNo: input.stepNo,
|
stepNo: input.stepNo,
|
||||||
label: 'flow-step',
|
label: 'flow-step',
|
||||||
});
|
});
|
||||||
|
// token 计入任务预算 —— 单位成本与预算熔断都靠它,不计就等于没有预算
|
||||||
|
await taskStore.addConsumption(input.taskId, {
|
||||||
|
tokens: result.usage.inputTokens + result.usage.outputTokens,
|
||||||
|
});
|
||||||
// 解析与校验归引擎(它才知道这一步声明了什么);这里只如实上交结果与原文
|
// 解析与校验归引擎(它才知道这一步声明了什么);这里只如实上交结果与原文
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -373,6 +380,8 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
|||||||
data: taskData,
|
data: taskData,
|
||||||
agent: createAgentRunner({
|
agent: createAgentRunner({
|
||||||
gateway: deps.modelGateway,
|
gateway: deps.modelGateway,
|
||||||
|
// loop 的每一回合都要问模型 —— 不计 token,预算熔断就永远不会响
|
||||||
|
onUsage: (taskId, tokens) => taskStore.addConsumption(taskId, { tokens }).then(() => undefined),
|
||||||
modelOf: async (taskId) => {
|
modelOf: async (taskId) => {
|
||||||
const { activityId } = await taskContext.of(taskId);
|
const { activityId } = await taskContext.of(taskId);
|
||||||
const manifest = await deps.registry.manifestOf(activityId);
|
const manifest = await deps.registry.manifestOf(activityId);
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService {
|
|||||||
taskId,
|
taskId,
|
||||||
kind: 'action',
|
kind: 'action',
|
||||||
title: `步 ${index + 1} · ${step.label}(${step.kind})`,
|
title: `步 ${index + 1} · ${step.label}(${step.kind})`,
|
||||||
detail: { step: step.name, attempt, input: payload },
|
// 步类型显式入留痕:度量的判据由引擎给出,不靠解析标题后缀
|
||||||
|
detail: { step: step.name, stepKind: step.kind, attempt, input: payload },
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (step.kind) {
|
switch (step.kind) {
|
||||||
|
|||||||
@@ -133,9 +133,14 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
|
|||||||
return { task: finished, verdict, progressed: false };
|
return { task: finished, verdict, progressed: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startedAt = deps.clock.now().getTime();
|
||||||
const outcome = await engineOf(task).advance(taskId);
|
const outcome = await engineOf(task).advance(taskId);
|
||||||
// 步计量:loop 的一次决策循环 / flow 的一个声明步
|
// 步计量:loop 的一次决策循环 / flow 的一个声明步。
|
||||||
const used = await deps.store.addConsumption(taskId, { steps: 1 });
|
// 活跃时长按**这一拍真正花掉的时间**计 —— 挂起等人的时间不算活跃,那是世界等待。
|
||||||
|
const used = await deps.store.addConsumption(taskId, {
|
||||||
|
steps: 1,
|
||||||
|
activeMs: Math.max(0, deps.clock.now().getTime() - startedAt),
|
||||||
|
});
|
||||||
await deps.store.markProgress(taskId, deps.clock.now());
|
await deps.store.markProgress(taskId, deps.clock.now());
|
||||||
|
|
||||||
const runtime =
|
const runtime =
|
||||||
|
|||||||
+28
-4
@@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
**MVP 十条判据:① ② ③ ⑥ ⑦ ⑧ ⑨ ⑩ 已在真实系统上端到端跑通;④ ⑤ 的 loop 机制(CMMN 装载 / Enabled 集 / 断点 / 调人)跑通,余下部分受限于 LLM 端点。**
|
**MVP 十条判据:① ② ③ ⑥ ⑦ ⑧ ⑨ ⑩ 已在真实系统上端到端跑通;④ ⑤ 的 loop 机制(CMMN 装载 / Enabled 集 / 断点 / 调人)跑通,余下部分受限于 LLM 端点。**
|
||||||
|
|
||||||
**当前:402 个测试通过(含对真实 PG 的集成测试),门禁五道全绿。20 个后端模块 + 5 个包 + 6 个活动包 + 14 个前端 feature。**
|
**当前:429 个测试通过(含对真实 PG 的集成测试),门禁五道全绿。20 个后端模块 + 5 个包 + 6 个活动包 + 14 个前端 feature。**
|
||||||
|
|
||||||
|
**LLM:DeepSeek provider 已接,两档实例 `deepseek-v4-flash` / `deepseek-v4-pro` +
|
||||||
|
用途名 `quote-extractor`(→ flash)。密钥只从环境读、落库即密封。**
|
||||||
|
|
||||||
### MVP 完成判据的跑通进度(README《MVP 完成判据》)
|
### MVP 完成判据的跑通进度(README《MVP 完成判据》)
|
||||||
|
|
||||||
@@ -25,15 +28,36 @@
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1 | 治理台:员工岗位 / LLM 实例 / 流程包发布 / 触发器 / 邮件通道 | ✅ `pnpm db:seed` 一次做完 |
|
| 1 | 治理台:员工岗位 / LLM 实例 / 流程包发布 / 触发器 / 邮件通道 | ✅ `pnpm db:seed` 一次做完 |
|
||||||
| 2 | RFQ 邮件入站 → 归属解析 → 触发器命中起单 → A1 落到冯兵工作台 | ✅ 已跑通 |
|
| 2 | RFQ 邮件入站 → 归属解析 → 触发器命中起单 → A1 落到冯兵工作台 | ✅ 已跑通 |
|
||||||
| 3 | A1 受理归一(flow):步序推进 → LLM 抽取 → 台账草稿 → 补全卡 → DMN 打标 → 放行落地 | ✅ 全链路跑通至 `released`:D1 条件跳步、S4 工具、S5 DMN 三分类(LM358DR → Type A)、放行后草稿合并正式版 2 行、输出口 `intake` 发射。**LLM 步因占位 key 走声明给定的人兜底**,接真 key 即走机器路径 |
|
| 3 | A1 受理归一(flow):步序推进 → LLM 抽取 → 台账草稿 → 补全卡 → DMN 打标 → 放行落地 | ✅ **机器路径全程跑通,零人工兜底**:模型从询价原文抽出 pn/brand/eau/目标价/交期 → D1 条件跳步 → S4 工具 → S5 DMN 三分类 → 放行 → 草稿合并正式版 → 输出口发射 → `released` |
|
||||||
| 4 | B 类 → A3 渠道寻源(loop) | 🟡 CMMN 装载(21 计划项)、Enabled 集计算、断点判定、Agent 回路调人 —— 全部跑通;**发询 → 数据监听 → 供应商回价续跑需真实 LLM**(Agent 每一轮决断都要模型) |
|
| 4 | B 类 → A3 渠道寻源(loop) | 🟡 CMMN 装载(21 计划项)、Enabled 集、断点判定、技能装配入信封、Agent 真实决策(接 DeepSeek 后每回合由模型定夺)、案例全景视图 —— 全部跑通;**多轮寻源直至交付放行**受限于回合成本,未跑满 |
|
||||||
| 5 | C 类 → A2 替代选型(loop) | 🟡 同上:CMMN 装载(19 计划项)+ 回路推进跑通;跨人卡与交付放行需真实 LLM |
|
| 5 | C 类 → A2 替代选型(loop) | 🟡 同上:CMMN 装载(19 计划项)+ 回路推进 + 技能装配 + 案例全景视图跑通;跨人卡与交付放行未跑满 |
|
||||||
| 6 | A4 成本定价(flow) | ✅ 取价基准 → DMN 定价(利润模型 P-01)→ 算偏离率 → DMN 底价自检(底价门禁 P-02)→ 条件跳步 → 报价草案验收放行 → released |
|
| 6 | A4 成本定价(flow) | ✅ 取价基准 → DMN 定价(利润模型 P-01)→ 算偏离率 → DMN 底价自检(底价门禁 P-02)→ 条件跳步 → 报价草案验收放行 → released |
|
||||||
| 7 | AP 定价审批(审批点) | ✅ 卡到 PM 丁子乔工位,依据快照齐;**驳回必附意见**(空意见被拒)、**驳回 → 回 A4 重开新实例**(A4 #2,旧实例不复活)、**通过 → 进 A6** |
|
| 7 | AP 定价审批(审批点) | ✅ 卡到 PM 丁子乔工位,依据快照齐;**驳回必附意见**(空意见被拒)、**驳回 → 回 A4 重开新实例**(A4 #2,旧实例不复活)、**通过 → 进 A6** |
|
||||||
| 8 | A6 报价议价(flow) | ✅ 正式报价单生成 → 写类动作**登记为动作意向**(不碰世界)→ 放行即落地(台账回写 1 行)→ 落地收敛 → 输出口发射 |
|
| 8 | A6 报价议价(flow) | ✅ 正式报价单生成 → 写类动作**登记为动作意向**(不碰世界)→ 放行即落地(台账回写 1 行)→ 落地收敛 → 输出口发射 |
|
||||||
| 9 | 客户拒绝 → A7 情报归档 | ✅ 出口选 lost → G4 按 `outcome = "lost"` 判定 → A7 起单 → 历史价格情报回写(拒绝原因)、客户画像、落地率统计落账 → **end 节点 released,工作流「已完成」** |
|
| 9 | 客户拒绝 → A7 情报归档 | ✅ 出口选 lost → G4 按 `outcome = "lost"` 判定 → A7 起单 → 历史价格情报回写(拒绝原因)、客户画像、落地率统计落账 → **end 节点 released,工作流「已完成」** |
|
||||||
| 10 | 全程可审计 · 工作台五要素 · 治理台在途监控与叫停恢复 | ✅ 任务记忆只追加、信封盖戳、归侧标注(实测 A7:机器产出 13 / 人裁决 3 / 世界等待 2 / 人产出 1);五要素齐活,纪要「决议 → 登记起单」端到端跑通;治理台在途监控 + 叫停 + 恢复跑通且留痕归人裁决侧;**终态封卷蒸馏钩子产出活动记忆候选条目**(确定性骨架,零 LLM,条目带证据链) |
|
| 10 | 全程可审计 · 工作台五要素 · 治理台在途监控与叫停恢复 | ✅ 任务记忆只追加、信封盖戳、归侧标注(实测 A7:机器产出 13 / 人裁决 3 / 世界等待 2 / 人产出 1);五要素齐活,纪要「决议 → 登记起单」端到端跑通;治理台在途监控 + 叫停 + 恢复跑通且留痕归人裁决侧;**终态封卷蒸馏钩子产出活动记忆候选条目**(确定性骨架,零 LLM,条目带证据链) |
|
||||||
|
|
||||||
|
## 活动资产盘点(六个活动)
|
||||||
|
|
||||||
|
| 活动 | 范式 | 视图模版 | 技能 | 结晶片段 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| A1 RFQ 需求分析 | flow | 事态全景 / 打标判据 / 成果预览 | 读懂一封询价 · 打标前先备齐判据 | 1 |
|
||||||
|
| A4 成本定价 | flow | 取价基准 / 定价与门禁 / 草案待放行 | 选对价格基准 · 读懂底价门禁的处置 | 1 |
|
||||||
|
| A6 报价议价 | flow | 正式报价单 / 放行即发出 | 拟一份能发出去的报价单 · 选对议价出口 | 0 |
|
||||||
|
| A7 情报归档 | flow | 结果登记 / 闭环待放行 | 把拒绝原因写成下一单的判据 | 0 |
|
||||||
|
| A3 渠道寻源 | loop | 案例全景(阶段 / 可为 / 缺口) | 按场景圈渠道 · 该等还是该问 | 0 |
|
||||||
|
| A2 替代选型 | loop | 案例全景 | 圈替代候选 · 什么时候必须问客户 | 0 |
|
||||||
|
|
||||||
|
视图外壳与卡片件在 `@sdk`(`viewShell` / `kvCard` / `tableCard` / `stagesCard` / `noteCard`)——
|
||||||
|
六个活动共用一套面貌;容器是 `sandbox=""` 的 iframe,样式内联自带。
|
||||||
|
|
||||||
|
## 端到端跑过的轮次
|
||||||
|
|
||||||
|
四轮完整链路(入站 → A1 → G2 → A3/A2 → 汇合 → A4 → AP → A6 → G4 → A7/end):
|
||||||
|
拒绝分支(lost → A7 → 终点)三轮、成交分支(won → 终点)一轮,均收敛为「已完成」。
|
||||||
|
度量八指标全部有真实读数(自主率 100% / 有效自主率 95% / DMN 覆盖率 66% /
|
||||||
|
单位成本 73 token 每放行单 / 终态分布 released 95%)。
|
||||||
|
|
||||||
## 常用命令
|
## 常用命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Reference in New Issue
Block a user