如何在 Codex 上构建一个自我优化的外呼系统

@nifinet
英语2天前 · 2026年7月19日
227K
358
22
13
1.7K

TL;DR

Nicolas Finet 详细介绍了一个用于构建自我优化外呼销售系统的技术框架。该系统利用 AI Agents 分析回复率,并通过 Pull Request 提出信息优化建议。

今年早些时候,Andrej Karpathy(@karpathy)将一个 Agent 对准自己的训练代码,让它运行了两天。它跑了 700 次实验,保留了 20 次超过基准的结果,使模型训练速度提高了 11%。然后他说了一句很有意思的话:任何你能廉价评估的指标,都可以交给一队 Agent 来处理。

回复率就是一个能廉价评估的指标。此后我花了一些时间,研究将这个循环应用到对外联系场景会是什么样子。

我的构建方案:

Codex 读取上周的结果,修改对外联系系统运行时使用的评分文件和话术文件,运行一次测试,然后创建一个 pull request。它会将改动连同证据和评分一起提交到 playbook,然后等待人工批准。发送和合并操作被排除在循环之外。

这个第一循环我构建过几次:感知市场、给账号评分、根据信号撰写内容、检查消息、记录结果、从回复中学习。本文要讲的是第二循环——那个用来修改第一循环的循环。

这就是构建方案:将 GTM 当作版本化的代码,让市场驱动其改进。

Nicolas Finet - inline image

仓库

从文件夹开始。结构很重要,因为 Codex 只能改进它能读取和编辑的内容。

text
1codex-self-improving-outbound/
2 AGENTS.md
3 README.md
4 config/
5 scoring.yaml
6 plays.yaml
7 prompts/
8 improve_scoring.md
9 improve_prompt.md
10 pr_summary.md
11 memory/
12 outcomes.jsonl
13 evals/
14 fixtures.yaml
15 score.py
16 scripts/
17 append_outcome.py
18 run_codex_step.sh
19 propose_improvement.py
20 open_pr.sh
21 weekly_tune.sh
22 examples/
23 outcomes.sample.jsonl
24 weekly-pr.md

这个仓库设计得很简单。config/scoring.yaml 存放决定哪些信号重要的规则。prompts/ 存放用于撰写消息的话术。memory/outcomes.jsonl 存放市场的反馈数据。evals/score.py 是判断改动是否有效的关卡。AGENTS.md 是 Codex 在改动任何内容之前必须遵循的规则。

先离线运行第一个版本。不连接 CRM、不进行数据丰富、不接入投放系统。改进循环应该先在本地文件上证明自己,然后才能接近真正的对外发送系统。

第一步:先写规则

在编写评分文件、提示词文件之前,先写 AGENTS.md。这个文件能让 Agent 既有用又可控。

markdown
1# 自我改进对外联系规则
2
3你根据结果改进对外联系系统。
4
5硬性规则:
6- 永远不要发送消息。
7- 永远不要抓取或丰富真实用户数据。
8- 永远不要自行合并。
9- 只编辑本仓库中的文件。
10- 一次只改动一个概念。
11- 每次改动都要引用 memory/outcomes.jsonl 中的结果。
12- 在改动成为 PR 之前,先改进 evals/score.py。
13- 如果评估没有改进,则撤销你的编辑并停止。
14
15允许的编辑范围:
16- config/scoring.yaml
17- config/plays.yaml
18- prompts/*.md
19
20必须输出的内容:
21- 修改过的文件
22- 每次改动的原因
23- 改动前的评分
24- 改动后的评分
25- pull request 摘要

规则的唯一用途:缩小工作范围。没有规则,Codex 就会试图通过扩大范围来帮忙。它会增加更多数据、触及更多文件、调用更多工具,或者自动化一个本应由人类控制的步骤。在这里,任务更小:读取结果,提议改动一个文件,证明改动有效,然后等待。

好的表现是什么样。 你可以在批准 PR 之前阅读规则,并确切知道 Codex 被允许做什么。

哪里会出问题。 规则变成了一份合规文档。如果 AGENTS.md 需要目录,那已经太大了。保持可操作性。

第二步:将判断逻辑移到配置中

大多数对外联系的判断逻辑存在于人的头脑中。然后团队购买软件,期望软件能改进它看不见的决策。

将判断逻辑移到一个文件中。

yaml
1signals:
2 competitor_comparison:
3 weight: 8
4 reason: "买家正在比较替代方案"
5 implementation_page_visit:
6 weight: 6
7 reason: "买家在核实是否能安装此方案"
8 job_repost:
9 weight: 5
10 reason: "职位仍然开放且紧急"
11 funding_event:
12 weight: 5
13 reason: "预算或授权可能已变更"
14 generic_download:
15 weight: 1
16 reason: "内容兴趣,购买意图较弱"
17
18thresholds:
19 draft: 6
20 human_review: 10
21
22negative_signals:
23 student_research: -8
24 vendor_pitch: -6
25 competitor: -10

这个文件最初是一个可见的假设。如果某个通用下载应该算作零分,团队可以指向具体行并修改它。如果实现页面访问的信号强度比预想的更强,Codex 可以提议改动并展示为其辩护的结果行。

不要将这些逻辑埋藏在 Python 函数中。如果规则可见,团队就能审查它、讨论它、改进它,而无需将销售判断变成一次工程重构。

好的表现是什么样。 文件足够小,可以展开讨论。五个信号是一个不错的第一版。

哪里会出问题。 评分文件变成了杂物抽屉。二十个信号、六个阈值以及针对每个边缘情况的例外规则会导致改进器过拟合。从小处开始,让结果告诉你下一个调节旋钮应该放在哪里。

第三步:将结果作为记忆写入

最重要的文件是 memory/outcomes.jsonl。

每次接触一行,在结果已知时写入:

javascript
1{"date":"2026-07-01","account":"Northwind Finance","signal":"competitor_comparison","play":"migration_note","score":8,"outcome":"reply","reason":"asked for migration notes"}
2{"date":"2026-07-01","account":"Bluepeak Studio","signal":"generic_download","play":"resource_followup","score":1,"outcome":"no_reply","reason":"content-only intent"}
3{"date":"2026-07-02","account":"KiteOps","signal":"implementation_page_visit","play":"implementation_angle","score":6,"outcome":"reply","reason":"asked about implementation timeline"}
4{"date":"2026-07-02","account":"Atlas Recruiting","signal":"job_repost","play":"hiring_angle","score":5,"outcome":"bad_fit","reason":"student research request"}

reason 字段是整个关键。no_reply 几乎不能告诉你任何信息。content-only intent 则告诉下一次运行该信号可能不值得撰写草稿。bad_fit 只有在你解释了原因时才有用。asked about implementation timeline 这类细节足以改变一个权重。

在构建改进器之前先构建验证器:

text
1构建 scripts/append_outcome.py。
2
3它接受:
4- date
5- account
6- signal
7- play
8- score
9- outcome: reply | meeting | no_reply | bad_fit | bounced
10- reason
11
12它拒绝:
13- 缺少字段
14- 未知的 outcome
15- 空的 reason
16- 未来的日期
17
18将有效的行追加到 memory/outcomes.jsonl。
19打印追加的行。

这就是复利效应开始的地方。仪表盘可以告诉你某个活动效果不佳,而清晰的结果日志可以告诉 Codex 在下一次运行之前应该改变哪个信号、话术或短语。

好的表现是什么样。 一周后,一个陌生人可以读取该文件,并判断哪些信号产生了回复、哪些话术产生了不匹配的对话、以及市场忽略了哪些内部偏爱的选项。

哪里会出问题。 团队在周五凭记忆回填结果。胜利被保留,不匹配的原因变得模糊,系统从虚构中学习。在结果落地时立即写入该行。

第四步:构建评估关卡

在 Codex 编辑任何内容之前,它需要一个无法通过解释绕过的测试。

创建 evals/fixtures.yaml:

yaml
1cases:
2 - account: Northwind Finance
3 signals: [competitor_comparison, implementation_page_visit]
4 expected: human_review
5 note: "一个账号有两个强信号"
6
7 - account: Bluepeak Studio
8 signals: [generic_download]
9 expected: ignore
10 note: "纯内容意图"
11
12 - account: KiteOps
13 signals: [implementation_page_visit]
14 expected: draft
15 note: "实现意图应超过草稿阈值"
16
17 - account: Atlas Recruiting
18 signals: [job_repost, student_research]
19 expected: ignore
20 note: "不匹配标记抵消了信号"

然后创建 evals/score.py:

text
1构建 evals/score.py。
2
3读取 config/scoring.yaml 和 evals/fixtures.yaml。
4
5对每个案例:
61. 对每个信号累加权重。
72. 加上负面信号的惩罚。
83. 对账号进行路由:
9 - score >= thresholds.human_review => human_review
10 - score >= thresholds.draft => draft
11 - otherwise => ignore
124. 将路由结果与期望结果比较。
13
14打印每个预测结果。
15打印最终准确率,格式为 score=0.00 到 score=1.00。
16仅在准确率为 1.00 时退出 0。

第一个关卡应该足够小以便理解,足够敏锐以捕捉真实的偏差。在我的第一次运行中,基准未能通过一个案例:

text
1Northwind Finance: predicted=human_review expected=human_review
2Bluepeak Studio: predicted=ignore expected=ignore
3KiteOps: predicted=ignore expected=draft
4Atlas Recruiting: predicted=ignore expected=ignore
5score=0.75

这很好。系统将实现意图置于草稿阈值以下,因此它忽略了一个测试用例认为应该发送消息的账号。最好在测试中捕捉到这一点,而不是在一个月后才发现丢失了大量账号。

好的表现是什么样。 一个命令给出一个数字,每个失败的案例都易于检查。

哪里会出问题。 测试用例只包含明显的成功案例。于是任何鲁莽的改动都能通过。在关卡中加入棘手的案例:弱意图、不匹配、无回复、过时信号,以及你希望系统当初跳过的账号。

第五步:让 Codex 提议一次评分改动

现在 Codex 可以编辑了。

创建 prompts/improve_scoring.md:

markdown
1你改进对外联系评分系统。
2
3阅读:
4- AGENTS.md
5- config/scoring.yaml
6- memory/outcomes.jsonl
7- evals/fixtures.yaml
8
9你的任务:
101. 找出一条应该改变的评分规则。
112. 理由必须引用 memory/outcomes.jsonl。
123. 只修改 config/scoring.yaml。
134. 运行 python3 evals/score.py。
145. 如果评分提高,保留改动。
156. 如果评分持平或下降,撤销你的编辑并停止。
16
17输出:
18- 具体改动的行
19- 导致该改动的结果行
20- 改动前的评分
21- 改动后的评分
22- 该改动是否应该成为 PR
23
24不要编辑 prompts。
25不要添加新信号。
26不要触碰发送逻辑。

通过仓库封装运行:

bash
1scripts/run_codex_step.sh improve_scoring

我的改进器第一个版本犯了一个有用的错误。它追逐了看起来最干净的回复信号。competitor_comparison 在微小的结果日志中拥有最高的回复率,因此改进器想要增加它的权重。评估结果保持在 0.75,因此改动被拒绝了。

这正是关卡存在的理由。一个较弱的系统会因为听起来合理而接受这个说法。而这个系统提出了一个更好的问题:改动是否修复了已知的遗漏?

第二次尝试找到了能起作用的最小改动:

text
1- implementation_page_visit: 4
2+ implementation_page_visit: 6

评估通过了:

text
1Northwind Finance: predicted=human_review expected=human_review
2Bluepeak Studio: predicted=ignore expected=ignore
3KiteOps: predicted=draft expected=draft
4Atlas Recruiting: predicted=ignore expected=ignore
5score=1.00

这就是循环变得有用的时刻。它为了一个原因改变了一条规则,并通过对测试用例的证明验证了改动。

Nicolas Finet - inline image

好的表现是什么样。 提议的 diff 枯燥且可追溯:改动了一行,附上了一条基于结果的理由,一个评估得到了改进。

哪里会出问题。 Codex 同时改变了三个权重和两个提示词。现在没人能说出哪个改动起了作用。保持规则严格:每个提案只针对一个概念。

第六步:分别改进提示词文件

评分只是系统的一半。消息模板也会退化。

上个月有效的行开始变得千篇一律。在一个细分领域中能获得回复的问题,在另一个领域中却无人理会。感觉犀利的措辞,到了市场上却遭到惩罚。将提示词改进视为独立的轨道,这样 Codex 就不会在同一个 PR 中混合评分和文案。

创建 config/plays.yaml:

yaml
1plays:
2 migration_note:
3 prompt_file: prompts/plays/migration_note.md
4 use_when:
5 - competitor_comparison
6 banned_lines:
7 - "thought this might be relevant"
8 - "quick question"
9
10 implementation_angle:
11 prompt_file: prompts/plays/implementation_angle.md
12 use_when:
13 - implementation_page_visit
14 banned_lines:
15 - "checking out our solution"
16 - "would love to chat"

然后创建 prompts/improve_prompt.md:

markdown
1你改进一个对外联系话术。
2
3阅读:
4- AGENTS.md
5- config/plays.yaml
6- memory/outcomes.jsonl
7- 所选话术的提示词文件
8
9选择一个至少有 10 个结果的话术。
10
11找出:
12- 在积极结果中出现的行或结构
13- 在 no_reply 或 bad_fit 结果中出现的行或结构
14- 任何应该被禁止的短语
15
16对该话术的提示词做一次小编辑。
17
18规则:
19- 不要修改评分。
20- 不要创建新的话术。
21- 不要添加新的渠道。
22- 引用结果行。
23- 写出修改前后的指令。
24
25然后运行文案评估(如果有)。
26如果没有文案评估,则将 PR 标记为 review_required。

有些改进可以自动评分。其他的仍然需要判断力。如果没有文案评估,Codex 可以提议提示词编辑,但它应该将 PR 标记为需要审查,而不是假装编辑已经得到验证。

好的表现是什么样。 Codex 说:“这个短语出现在七个 no_reply 结果中,所以我将其添加到了 banned_lines”,或者“积极回复引用了第一句中的实现细节,所以我收紧了话术要求。”

哪里会出问题。 改进器因为一条消息获得了回复就重写了整个语气。提示词编辑应该比你本能设想的更小。

第七步:以 pull request 形式发布改动

这是控制层。Codex 编辑文件,运行评估,编写 PR 摘要。人工审查并合并。

Nicolas Finet - inline image

创建 prompts/pr_summary.md:

markdown
1为这次对外联系改进编写一个 pull request 摘要。
2
3包含:
41. 改了什么。
52. 为什么改,引用结果行。
63. 改动前的评分。
74. 改动后的评分。
85. 修改了哪些文件。
96. 风险。
107. 人工审查者应该检查什么。
11
12保持简短。
13不要声称改动已上线。

创建 scripts/open_pr.sh

bash
1#!/usr/bin/env bash
2set -euo pipefail
3
4branch="codex/weekly-tune-$(date +%Y-%m-%d)"
5
6git checkout -b "$branch"
7git add config prompts evals memory
8git commit -m "Codex weekly outbound tune"
9
10body="$(cat outputs/pr-summary.md)"
11
12python3 scripts/create_pr.py \
13 "$branch" \
14 "Codex weekly outbound tune" \
15 "$body"

PR 应该看起来像是团队成员写的:

text
1Changed:
2- Raised implementation_page_visit from 4 to 6.
3
4Why:
5- KiteOps had implementation-page intent and replied with implementation timing.
6- The previous score routed this account to ignore.
7
8Before:
9- eval score 0.75
10
11After:
12- eval score 1.00
13
14Reviewer check:
15- Make sure implementation intent is specific enough.
16- Keep generic downloads low.
17- Merge only if this matches actual sales judgment.

这就是安全系统。Codex 做繁琐的工作。操作员坚持标准。

好的表现是什么样。 每周一个 PR,小的 diff,清晰的理由,通过的评估。

哪里会出问题。 有人因为审查感觉麻烦而允许 Codex 自行合并。那一分钟将系统从“改进”变成了“偏离”。

第八步:设定节奏

不要在每次回复后都运行。那样系统会过度拟合到某个突出的账号。

让一周过去,让结果积累,然后调优。

Nicolas Finet - inline image

创建 scripts/weekly_tune.sh:

bash
1#!/usr/bin/env bash
2set -euo pipefail
3
4cd "$(dirname "$0")/.."
5
6python3 evals/score.py || true
7scripts/run_codex_step.sh improve_scoring
8python3 evals/score.py
9scripts/run_codex_step.sh pr_summary > outputs/pr-summary.md
10scripts/open_pr.sh

然后设置 cron:

bash
10 8 * * MON cd ~/codex-self-improving-outbound && scripts/weekly_tune.sh

如果你使用 GitHub Actions,保持相同的形式:

yaml
1name: weekly-outbound-tune
2
3on:
4 schedule:
5 - cron: "0 8 * * 1"
6 workflow_dispatch:
7
8jobs:
9 tune:
10 runs-on: ubuntu-latest
11 steps:
12 - uses: actions/checkout@v4
13 - uses: actions/setup-python@v5
14 with:
15 python-version: "3.11"
16 - run: pip install -r requirements.txt
17 - run: python3 evals/score.py || true
18 - run: scripts/weekly_tune.sh

前两次调优手动运行。阅读每个 diff。观察当样本量较小的时候 Codex 试图改变什么。一旦提案变得枯燥,就将其设置为定时任务。

好的表现是什么样。 每周一个 PR 出现,附带证据、diff 和评估结果。你可以合并、编辑或关闭它。

哪里会出问题。 任务运行了,但没人审查,PR 堆积如山。一个自我改进的系统仍然需要一个人为习惯:阅读 diff。

克隆即运行版本

仓库应该附带四个命令:

bash
1git clone <repo>
2cd codex-self-improving-outbound
3python3 -m venv .venv
4source .venv/bin/activate
5pip install -r requirements.txt
6python3 evals/score.py
7python3 scripts/propose_improvement.py

预期首次运行结果:

text
1score=0.75
2changed config/scoring.yaml
3implementation_page_visit: 4 -> 6
4score=1.00
5open PR for human review

离线演示证明了文件合约。Codex 运行证明了编辑循环。之后,将示例结果替换为你自己的结果,重命名信号,添加你的话术,并构建一个能反映你希望系统当初不同处理的账号的测试用例。

不要从连接发送系统开始。从证明改进循环开始。

完整版本:max

这个仓库是手动层。它从文件、公开信号和你的 Codex 方案运行。它通过暴露每条规则来展示结构。

yourmax.ai 是同一个系统,但接缝被隐藏了起来。

max 不是你自行组合的仓库,而是你直接使用的 Agent。它能检测市场动态,判断谁值得联系以及为什么现在联系,在邮件和 LinkedIn 上草拟对外消息供你审批,并持续从结果中改进。

这个仓库展示了大多数团队从未构建过的自我调优层:结果变成提议的规则改动;提议的规则改动通过一个关卡;人工合并决定什么变为实际运行。max 将相同的运行逻辑作为托管系统来运行。

如果你想要完整的仓库,可以告诉我,我会发给你。

二次创作

使用 YouMind 创作爆款文章

收集素材、拆解爆点、生成视觉资产、撰写内容,并在一个 AI 工作空间里完成分发。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章