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.1s3471
行为线索选项1.5s119

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

效果展示

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

初稿与话术并排

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

预测与真实回复

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

行为线索与盲点

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

档案

完整流程:

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

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

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

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
      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.1s3471
      行为线索选项1.5s119

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

      效果展示

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

      初稿与话术并排

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

      预测与真实回复

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

      行为线索与盲点

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

      档案

      完整流程:

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

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

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

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' 【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
          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.1s3471
          行为线索选项1.5s119

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

          效果展示

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

          初稿与话术并排

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

          预测与真实回复

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

          行为线索与盲点

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

          档案

          完整流程:

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

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

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

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' 【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
              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.1s3471
              行为线索选项1.5s119

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

              效果展示

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

              初稿与话术并排

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

              预测与真实回复

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

              行为线索与盲点

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

              档案

              完整流程:

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

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

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

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' 【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
                  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.1s3471
                  行为线索选项1.5s119

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

                  效果展示

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

                  初稿与话术并排

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

                  预测与真实回复

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

                  行为线索与盲点

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

                  档案

                  完整流程:

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

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

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

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' 【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
                      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.1s3471
                      行为线索选项1.5s119

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

                      效果展示

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

                      初稿与话术并排

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

                      预测与真实回复

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

                      行为线索与盲点

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

                      档案

                      完整流程:

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

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

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

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' 【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
                          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.1s3471
                          行为线索选项1.5s119

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

                          效果展示

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

                          初稿与话术并排

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

                          预测与真实回复

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

                          行为线索与盲点

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

                          档案

                          完整流程:

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

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

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

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); 【外滩大会2026】 对齐(duiqi)—— 不用每次重新解释一遍你和他的关系 · Issue #87 · modelstudioai/modelstudioai.github.io · GitHub
                              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.1s3471
                              行为线索选项1.5s119

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

                              效果展示

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

                              初稿与话术并排

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

                              预测与真实回复

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

                              行为线索与盲点

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

                              档案

                              完整流程:

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

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

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