这是一个独立实验 project:不再把“单首词”当作进化个体,而是让“诗人角色(带记忆)”在多轮写作-评审-更新中持续进化。
固定一个 cipai + title,循环 N 轮:
- 写作:诗人根据当前记忆生成
k个候选词作(默认k=3)。 - 评审:对每个候选同时打三类分:
- LLM 评审(两种模式):
absolute:对每首给信息量informativeness+ 艺术性aesthetic(1–10)+ 理由relative(默认):同轮k个候选做相对排序(rank),再映射回 1–10(缓解打分饱和)
- 格律信号:结构匹配(True/False)+ 平仄匹配率
tonal_score(0–1) - 押韵信号:按词牌韵位的一致性评分
rhyme_score(0–1) - 用典信号(可选):
allusion_count(严格 form 词表匹配命中数量)+ 命中列表
- LLM 评审(两种模式):
- 择优:先算
fitness = info_component + aes_component + tonal_score + rhyme_score,再按selection_score选本轮最佳候选:selection_score = fitness + allusion_weight * ac_factor + rhyme_weight * rhyme_scoreac_factor = -distance(allusion_count, [2,3])(命中 2~3 时为 0,偏离越大越负)absolute/relative:都使用 judge 给出的 1~10 分,component = score / 10relative中的 rank 仅保留为同轮排序信息,不直接参与 fitness
- 更新记忆:把评审理由 + 格律问题,转成“可执行写作规则”的 JSON patch,更新诗人记忆。
- 落盘:每轮保存候选、分数、选中项、记忆快照,便于回放和分析。
- 写作代理:
src/poet_role_evolve/agent.py - 评审器:
src/poet_role_evolve/judge.py - 记忆 schema + patch 应用:
src/poet_role_evolve/memory.py - 主流程编排(数据流核心):
src/poet_role_evolve/runner.py - LLM 访问层(含 Mock):
src/poet_role_evolve/llm_provider.py - KG 典故检索(可选):
src/poet_role_evolve/kg/ - 用典统计(严格 form 匹配):
src/poet_role_evolve/allusion_match.py - 词牌/平仄校验(自包含拷贝并调整 import):
src/poet_role_evolve/cipai_utils.py、src/poet_role_evolve/poem.py、src/poet_role_evolve/xinyun/ - CLI 入口:
scripts/run.py
输入:
- 词牌模板数据:
data/cipai2info.json(本 project 内自包含) .env:API Key / Base URL(默认读取CHATANY_API_KEY/CHATANY_BASE_URL,也兼容OPENAI_API_KEY/OPENAI_BASE_URL)- (可选)KG 典故检索:
NEO4J_URI/NEO4J_USER/NEO4J_PASSWORD
输出(每次运行一个独立目录):
results/poet_role_evolve/<run_id>/config.json:完整参数与模型名results/poet_role_evolve/<run_id>/kg_allusions.json:本次 run 的关键词与 Top-K 典故池(KG 不可用则为 error + 空列表)results/poet_role_evolve/<run_id>/intention.json:prompt v2的创作意图(若启用)results/poet_role_evolve/<run_id>/memory_init.json:初始记忆results/poet_role_evolve/<run_id>/rounds.jsonl:每轮所有候选 + 分数 + 选中项results/poet_role_evolve/<run_id>/memory_snapshots.jsonl:每轮 patch + patch_raw + 更新后的记忆results/poet_role_evolve/<run_id>/best.json:全程 top5 候选(先按历史selection_score取 top5,再做一次 relative rank 复评,并按overall_score做最终排序;保留 top1 兼容字段)
cd poet_role_evolve
pip install -r requirements.txt
cp config.example.yaml config.yaml
cp .env.example .env # 填写 key/base_url(推荐把 key 放 env,不要写进 config.yaml)如需启用 KG 典故检索(可选),在 .env 里补齐 NEO4J_URI/NEO4J_USER/NEO4J_PASSWORD,并在 config.yaml 中设置 kg.enabled: true。
config.yaml 可切换:
prompts:
profile: v2 # v1 | v2
intention:
enabled: true
seed: ""
max_chars: 600
last_round_poem:
enabled: false # true 时:把上一轮入选作品全文 + 结构/平仄/押韵评估写入记忆并注入下一轮写作 prompt
rhyme_memory:
enabled: false # true 时:在长期记忆中注入“同韵/换韵策略 + 韵脚一致性约束”
memory_settings:
max_inspirations: 20
max_revision_checks: 20
max_avoid_patterns: 20
max_rhyme_lessons: 20
max_good_phrases: 10
max_bad_cases: 10v1:保留原始写作 prompt(向后兼容)。v2:加入“硬约束优先 + intention + 词牌分型冷启动记忆”。
v2 中,intention 默认在每次 run 冷启动时生成一次并落盘,不参与每轮 patch 更新;在写作 prompt 中以中文“意图要点”呈现(非 JSON,不含用典/意象策略这类硬约束项)。
若 rhyme_memory.enabled: true,会根据词牌规则生成“押韵要点”,并写入 intention.json 的 rhyme_guidance 及初始记忆。
memory_settings 的 canonical 容量键也已切到新 schema;旧的 max_principles / max_checklist 等名称目前仍可读取,但只作为兼容别名。
你可以把配置理解成两层:
- 供应商(vendor/provider):一套
base_url + api_key(可能是 OpenAI 官方,也可能是任何 OpenAI-compatible 代理/平台)。 - 模型 ID(model):该供应商下你要调用的具体模型名(例如
deepseek-v3.2/gpt-5.1/kimi-k2等)。
本项目在 config.yaml 里用:
providers.<name>定义供应商(base_url / api_key)llm.<role>.provider_ref选择用哪个供应商llm.<role>.model选择用该供应商下的哪个模型 ID
示例(一个供应商):
providers:
vendor_a:
provider: openai
api_key_env: CHATANY_API_KEY
base_url_env: CHATANY_BASE_URL
llm:
poet: { provider_ref: vendor_a, model: deepseek-v3.2 }
judge: { provider_ref: vendor_a, model: gpt-5.1 }
memory: { provider_ref: vendor_a, model: gpt-5.1 }示例(两个供应商:写作走 A,评审走 B):
providers:
vendor_a: { provider: openai, api_key_env: A_KEY, base_url_env: A_URL }
vendor_b: { provider: openai, api_key_env: B_KEY, base_url_env: B_URL }
llm:
poet: { provider_ref: vendor_a, model: deepseek-v3.2 }
judge: { provider_ref: vendor_b, model: gemini-3-pro-preview }
memory: { provider_ref: vendor_a, model: gpt-5.1 }如果你想用 3 个不同评审模型来降低偏差,可以在 llm 里配置 judges 列表:
llm:
judge:
provider_ref: vendor_a # 作为 judges 的默认供应商(可选)
judges:
- { model: gemini-3-pro-preview }
- { model: kimi-k2-0905-preview }
- { model: doubao-seed-1-6-251015 }运行时每个 candidate 会被这三个 judge 各评一次,最后 informativeness/aesthetic 取平均(只对成功解析的 judge 求平均;若全失败则为 0)。
在 run.judge_mode 中选择:
absolute:每首单独打 1–10 分(旧行为)relative(默认):同轮k个候选做 rank,再线性映射回 1–10(rank=1→10,rank=k→1),更不容易出现“全是 9/10”饱和。
当相对排序输出无法解析/不满足全排列约束时,会自动回退到 absolute 逐个打分,保证流程不中断。
kg.enabled: true时,会用title作为关键词,从 Neo4j 聚合 Top-K 典故条目(典故名 + 1–2 个常见 form + 出处古籍(若有)),写入初始记忆并注入写作 prompt。- 每个候选会统计
allusion_count:对“典故 form 词表”做严格子串匹配(便于自动统计与人工核验),并输出命中 forms 列表。 allusion_count不参与fitness,但会通过ac_factor影响selection_score(更偏好 2~3 的用典密度)。- prompt 展示层可在
memory_settings控制条目数量,尤其是prompt_allusion_forms_per_item(每个典故展示几个 form)。
运行(真实 API):
cd poet_role_evolve
python scripts/run.py --config config.yaml批量运行 ACL 实验(读取 data/acl_experiment/experiment_config_8x6.json,输出到 results/acl_exp):
cd poet_role_evolve
python scripts/run_acl_experiment.py --config config.yaml --out-dir results/acl_exp干跑(不调用 API,用于快速验证流程):
cd poet_role_evolve
python scripts/run.py --config config.yaml --mock测试:
cd poet_role_evolve
python -m pytest -q- 记忆更新使用 patch:LLM 只输出严格 JSON patch;代码确定性应用(去重/裁剪/版本号+1)。
- 记忆编辑器是上一轮提炼器:输入是整轮
candidates证据 JSON,专门提炼good_phrases/inspirations/bad_cases/avoid_patterns/rhyme_lessons/revision_checks。 - patch 失败不让流程中断:解析失败会用 fallback patch(确定性规则)继续跑,并在
memory_snapshots.jsonl记录错误。 - prompt profile 可切换:
run期间统一使用config.prompts.profile指定的写作/记忆更新模板。 - v2 意图建模可回退:intention JSON 解析失败时自动回退默认意图,并在
intention.json标记 source/error。 - 记忆支持增删:patch 可对
inspirations/revision_checks/avoid_patterns/rhyme_lessons/good_phrases/bad_cases做 add/remove。 - 好句与反例都单句化:
add_good_phrases/add_bad_cases若传多行文本会被拒收,避免整首作品写入长期记忆。 - 短期信息会被拦截:标题、创作意图、上一轮作品、评分/rank/候选编号等运行时信息不会进入长期记忆。
- 押韵诊断可解释:Judge 除
rhyme_score外,还会输出rhyme_feedback与结构化rhyme_diagnostics,供筛选、落盘与记忆提炼复用。 - Runner 中创建
CiPoem时默认auto_split_que=False:避免模型输出行分割与模板阕结构不一致时抛异常;格律检查仍以default_auditor的模板匹配为准。 - 相对排序评审输出为严格 JSON;解析失败会回退到绝对分评审,保证流程不中断。