参赛项目名称
对齐(duiqi)—— 不用每次重新解释一遍你和他的关系
团队 / 作者
Stellaw23(个人参赛)
我做了什么
把想说的话丢给任何一个大模型,都能换回一段更得体的话。这个项目在意的是另一件事:你写的那句话本身,可能就是问题所在。
「最近怎么都不太理我了,是我做错什么了吗」——这句话把解释权全交给了对方,还预设了一个指责结构,逼对方先处理"你的不安"而不是"你们的关系";而对方的沉默很可能跟你完全无关。 系统不整理你已经知道的事,它通过提问暴露你不知道自己不知道的那部分:初稿与话术 并排放,差的那部分就是你没想到的。
比落差更硬的一条分界线是下一轮。对方真回复了之后,系统拿真实回复去比对自己上一轮的预测——预测说他会顺势约下次,实际他只回了句"没有啊,最近有点忙",那么预测所依据的 档案条目就被降级(已确认 → 待确认,再被打脸一次转已否认)。系统自己不能往档案里加东西,它只能让已有条目降级。 关于某个人的一切都以带状态、带来源的条目存在,没有旁路。
全部 LLM 调用走百炼 DashScope 的 OpenAI 兼容端点。档案存在用户自己浏览器的 localStorage 里,用用户自己的 API Key,不提供在线服务——docker compose up 一条命令自己跑。这不是缺项:后端一旦公开部署,所有访问者就共用一个档案库,自托管才是这个架构正确的形态。
使用的工具
分流原则(以及为什么这条理由重要)
输出会被用户直接当作决策依据 → 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 形状,choices 为None、正文在顶层 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% 是降级触发率而不是降级准确率;耗时测于一台笔记本、一条家用网络、全部串行。
参赛项目名称
对齐(duiqi)—— 不用每次重新解释一遍你和他的关系
团队 / 作者
Stellaw23(个人参赛)
我做了什么
把想说的话丢给任何一个大模型,都能换回一段更得体的话。这个项目在意的是另一件事:你写的那句话本身,可能就是问题所在。
「最近怎么都不太理我了,是我做错什么了吗」——这句话把解释权全交给了对方,还预设了一个指责结构,逼对方先处理"你的不安"而不是"你们的关系";而对方的沉默很可能跟你完全无关。 系统不整理你已经知道的事,它通过提问暴露你不知道自己不知道的那部分:初稿与话术 并排放,差的那部分就是你没想到的。
比落差更硬的一条分界线是下一轮。对方真回复了之后,系统拿真实回复去比对自己上一轮的预测——预测说他会顺势约下次,实际他只回了句"没有啊,最近有点忙",那么预测所依据的 档案条目就被降级(已确认 → 待确认,再被打脸一次转已否认)。系统自己不能往档案里加东西,它只能让已有条目降级。 关于某个人的一切都以带状态、带来源的条目存在,没有旁路。
全部 LLM 调用走百炼 DashScope 的 OpenAI 兼容端点。档案存在用户自己浏览器的 localStorage 里,用用户自己的 API Key,不提供在线服务——docker compose up 一条命令自己跑。这不是缺项:后端一旦公开部署,所有访问者就共用一个档案库,自托管才是这个架构正确的形态。
使用的工具
OpenWork / 百炼 CLI:用 bl model list --model 实拉模型规格(上下文、 最大输出、是否支持 structured-outputs),不靠查文档或凭记忆。
百炼能力 / 模型:
Skill 名称:
其他:FastAPI + React + TypeScript,单进程同源,Docker 一键跑。
分流原则(以及为什么这条理由重要)
依据是错误代价不对称:一句"他其实在意的是预算"如果推断错了,用户会带着错误认知去跟真人说话;而一个略显笼统的备选项,用户不选就是了。
模型名只在代码里的
PLUS/FLASH两个常量各出现一次,分流写在MODEL_BY_TASK一张表里,调用点全部走model_for("<task>"),测试守着字面量不许散回调用点。思考链比换模型更管用
实测同一个 flash 模型,开不开思考链差 20 倍:
输出质量没有可见差别——列六条"他急了什么样"不需要一条推理链。判据和选模型是同一条: 这一步是在推断,还是在枚举?
效果展示
主图 · 初稿与话术并排,落差就是产品的全部
第二轮 · 真实回复撞上预测,档案条目降级
认下行为线索,答不上来的那一项被标成盲点
档案 · 每条带状态、带来源,被证伪过的会在来源栏留一笔沿革
完整流程:
实测数字(60 次真实百炼调用、6 段场景 × 2 轮,每个数标 n):
数字后面紧跟边界声明,完整版见docs/evidence.md。
项目链接
没有在线服务要注册。自己跑一份就是完整体验:
踩坑记录
1 · 端点是按旧流程的屏切的,流程一改就成了历史包袱
试过按向导每一屏切一个端点(
/api/briefing、/api/questions、/api/follow-up等五个)→ 流程改成单页面之后屏没了,端点边界失去依据 → 现在合并成一个/api/compose,一次调用同时产出话术、前提声明、预测和简报。2 · 慢,而且决定不装快
试过只记一个"~51s"的耗时 → 补测发现发起模式 38.9–59.5s、回应模式端到端 138s,把"最大的未解问题"标在了两个数里小的那一个 → 现在不做流式、不拆调用、不为缩短它换掉主交付物的模型;界面上只放一块真走的秒表。40 秒换一句要发给在意的人的话是划算的,慢本身就是"这一句值得想清楚"的信号。
3 · 跨代模型的返回形状不一样
试过换
qwen-max(2.5 代)提速 → 经兼容端点返回的不是 OpenAI 形状,choices为None、正文在顶层text字段,response.choices[0].message.content直接抛TypeError→ 现在明确写下:"换模型不用改解析代码"只在 3.7 家族内成立,跨代不成立。4 · 只降级不封顶,同一个错判会每轮重来
试过条目被证伪就降一级 → 模型下一轮拿着这条 pending 再推一次、再被打脸、再降一次,用户看着系统一遍遍犯同一个错 → 现在累计两次直接转已否认,进"不许再提"段。
5 · 填空检出漏了带空格的那种
提示词里明写"绝对不许留填空",代码再验一道正则 → 真实跑测里一条话术带着「X 月 X 号」整条溜过,因为正则要求 X 和量词紧挨着 → 现在正则容忍同行空格(不吃换行,免得跨句连出假占位符)。这条是靠那批真实调用的统计跑出来的,也照实写进了 evidence.md:修复前测到的"填空触发率 0%"是检出率的下界,不是"模型没留空"的证明。
边界声明
demo/seed.json里,截图脚本回放所以每次跑出来的图都一样,也不需要 API Key。这样做是因为模型的格式漂移和几十秒超时会在演示时翻车,而回放是可复现的,也允许如实声明它是录制。