Skip to content

【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 #87

Description

@Stellaw23

参赛项目名称

对齐(duiqi)—— 不用每次重新解释一遍你和他的关系

团队 / 作者

Stellaw23(个人参赛)

我做了什么

把想说的话丢给任何一个大模型,都能换回一段更得体的话。这个项目在意的是另一件事:你写的那句话本身,可能就是问题所在。

「最近怎么都不太理我了,是我做错什么了吗」——这句话把解释权全交给了对方,还预设了一个指责结构,逼对方先处理"你的不安"而不是"你们的关系";而对方的沉默很可能跟你完全无关。 系统不整理你已经知道的事,它通过提问暴露你不知道自己不知道的那部分:初稿与话术 并排放,差的那部分就是你没想到的。

比落差更硬的一条分界线是下一轮。对方真回复了之后,系统拿真实回复去比对自己上一轮的预测——预测说他会顺势约下次,实际他只回了句"没有啊,最近有点忙",那么预测所依据的 档案条目就被降级(已确认 → 待确认,再被打脸一次转已否认)。系统自己不能往档案里加东西,它只能让已有条目降级。 关于某个人的一切都以带状态、带来源的条目存在,没有旁路。

全部 LLM 调用走百炼 DashScope 的 OpenAI 兼容端点。档案存在用户自己浏览器的 localStorage 里,用用户自己的 API Key,不提供在线服务——docker compose up 一条命令自己跑。这不是缺项:后端一旦公开部署,所有访问者就共用一个档案库,自托管才是这个架构正确的形态。

使用的工具

  • OpenWork / 百炼 CLI:用 bl model list --model 实拉模型规格(上下文、 最大输出、是否支持 structured-outputs),不靠查文档或凭记忆。

  • 百炼能力 / 模型:

    • qwen3.7-plus-2026-05-26 —— 生成话术 + 简报、回复的结构化分析、解读与预测比对、 条目内追问
    • qwen3.7-flash-2026-07-15 —— 追问生成、行为线索选项、话术填空修复
    • 能力:response_format={"type":"json_object"}(除条目对话外所有调用都要求严格 JSON)、extra_body={"enable_thinking": False}(按任务关思考链)
    • 用带日期的快照版本,避免模型悄悄换代导致输出漂移
  • Skill 名称:

    • Matt Pocock 的工程 Skill 套件(mattpocock/skills)——这套东西的开发流程 整个跑在它上面,痕迹都在仓库里可查:setup-matt-pocock-skills 建起本地 issue tracker 与 triage 标签(docs/agents/);domain-modeling / ubiquitous-language 产出 CONTEXT.md 的领域语言段和 docs/adr/ 下的 10 条架构决策;to-tickets / triage 把计划拆成一票一文件;implement + tdd 落实现(本次的证据文档与记账层 就是这么写的);code-review / diagnosing-bugs 收尾。
    • apple-design(Emil Kowalski,emilkowalski/skills,已 vendored 进仓库并由 skills-lock.json 锁哈希)——界面侧主要用的就是这一个:组件的手感、过渡与状态 反馈照它的判据来调,让"等四十秒"这段路走得不难受。同源套件里的其余几个 (emil-design-eng、improve-animations 等)一并装着,本项目没深用。
  • 其他:FastAPI + React + TypeScript,单进程同源,Docker 一键跑。

分流原则(以及为什么这条理由重要)

输出会被用户直接当作决策依据 → plus
输出是中间态、选项、分类 → flash

依据是错误代价不对称:一句"他其实在意的是预算"如果推断错了,用户会带着错误认知去跟真人说话;而一个略显笼统的备选项,用户不选就是了。

模型名只在代码里的 PLUS / FLASH 两个常量各出现一次,分流写在 MODEL_BY_TASK一张表里,调用点全部走 model_for("<task>"),测试守着字面量不许散回调用点。

思考链比换模型更管用

实测同一个 flash 模型,开不开思考链差 20 倍

任务 思考链 耗时 输出 token
行为线索选项 开(默认) 29.1s 3471
行为线索选项 1.5s 119

输出质量没有可见差别——列六条"他急了什么样"不需要一条推理链。判据和选模型是同一条: 这一步是在推断,还是在枚举?

效果展示

主图 · 初稿与话术并排,落差就是产品的全部

初稿与话术并排

第二轮 · 真实回复撞上预测,档案条目降级

预测与真实回复

认下行为线索,答不上来的那一项被标成盲点

行为线索与盲点

档案 · 每条带状态、带来源,被证伪过的会在来源栏留一笔沿革

档案

完整流程:

选「发起」或「回应」
      ↓
填背景(全部可跳过)
      ↓
系统给出行为线索选项 → 认下的成为档案条目(已确认,记来源)
                     → 明确答不上来的那一项成为「盲点」
      ↓
拿到话术 —— 一句可以直接复制发出去的话
  · 盲点作为前提声明写出来("这条建议假设了预算没变")
  · 同时给出对「他会怎么回」的预测,以及这条预测依据了档案里的哪几条
      ↓
对方真的回了 → 贴进来
  · 真实回复 vs 预测,落差高亮
  · 被打脸的档案条目降级;再被打脸一次进"不许再提"段

实测数字(60 次真实百炼调用、6 段场景 × 2 轮,每个数标 n):

指标 n
JSON 解析成功率 100% n=60
调用失败(超时 / 网络) 0 次 n=60
第二轮至少推翻一条预测依据 80% n=5
已确认条目被降为待确认 27%(4/15 条) n=15
端到端:发起 / 回应 中位 53.4s / 148.2s n=6 / n=6

数字后面紧跟边界声明,完整版见docs/evidence.md

项目链接

没有在线服务要注册。自己跑一份就是完整体验:

git clone https://github.com/Stellaw23/duiqi.git
cd duiqi && cp .env.example .env   # 填你自己的百炼 API Key
docker compose up

踩坑记录

1 · 端点是按旧流程的屏切的,流程一改就成了历史包袱
试过按向导每一屏切一个端点(/api/briefing/api/questions/api/follow-up 等五个)→ 流程改成单页面之后屏没了,端点边界失去依据 → 现在合并成一个 /api/compose,一次调用同时产出话术、前提声明、预测和简报。

2 · 慢,而且决定不装快
试过只记一个"~51s"的耗时 → 补测发现发起模式 38.9–59.5s、回应模式端到端 138s,把"最大的未解问题"标在了两个数里小的那一个 → 现在不做流式、不拆调用、不为缩短它换掉主交付物的模型;界面上只放一块真走的秒表。40 秒换一句要发给在意的人的话是划算的,慢本身就是"这一句值得想清楚"的信号。

3 · 跨代模型的返回形状不一样
试过换 qwen-max(2.5 代)提速 → 经兼容端点返回的不是 OpenAI 形状,choicesNone、正文在顶层 text 字段,response.choices[0].message.content 直接抛TypeError → 现在明确写下:"换模型不用改解析代码"只在 3.7 家族内成立,跨代不成立。

4 · 只降级不封顶,同一个错判会每轮重来
试过条目被证伪就降一级 → 模型下一轮拿着这条 pending 再推一次、再被打脸、再降一次,用户看着系统一遍遍犯同一个错 → 现在累计两次直接转已否认,进"不许再提"段。

5 · 填空检出漏了带空格的那种
提示词里明写"绝对不许留填空",代码再验一道正则 → 真实跑测里一条话术带着「X 月 X 号」整条溜过,因为正则要求 X 和量词紧挨着 → 现在正则容忍同行空格(不吃换行,免得跨句连出假占位符)。这条是靠那批真实调用的统计跑出来的,也照实写进了 evidence.md:修复前测到的"填空触发率 0%"是检出率的下界,不是"模型没留空"的证明。

边界声明

  • AI 的建议不构成沟通结论,话术需要用户自己判断后再决定发不发。
  • 对方画像是推断,不是事实。它从档案和当前背景现推出来,只描述"这一次"沟通里对方可能的状态,不积累、不进档案。
  • 截图里的两段对话是构造出来的,不是真人对话。
  • 演示为一次真实百炼调用的录制回放:模型输出固化在 demo/seed.json 里,截图脚本回放所以每次跑出来的图都一样,也不需要 API Key。这样做是因为模型的格式漂移和几十秒超时会在演示时翻车,而回放是可复现的,也允许如实声明它是录制。
  • 上面那批统计不代表生产准确率、SLA 或 ROI:样本是手写构造的六段场景,不是真实用户输入;没有独立人工金标——预测准不准、哪条该降级都是模型自己判的,所以 80% 是降级触发率而不是降级准确率;耗时测于一台笔记本、一条家用网络、全部串行。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions