feat(api): 叙事 prompt、职业上下文、读路径章节、WS 解耦与错误脱敏
- 回忆录:事实边界补充允许清单;传记文体示例与 JSON 叙事要求对齐 - default 职业提示 occupation_context;cadre/military 退休语境 - GET 章节读路径零写入,prepare_chapter_read_view + markdown_for_response - 文本归一抽到 core/text_normalize;移除弃用 reply 策略与 recompose_chapters_for_story - ConversationService:WS 连接/用户段落/结束对话;对外错误固定文案 - 测试:HTTP 脱敏契约、章节读视图、occupation 与 background_voice
This commit is contained in:
@@ -68,6 +68,7 @@ class NarrativeAgent:
|
||||
birth_year: Optional[int] = None,
|
||||
llm: Any = None,
|
||||
background_voice: str = "default",
|
||||
occupation: str = "",
|
||||
) -> str:
|
||||
"""将新对话改写为叙述。若无 LLM 则直接拼接。
|
||||
|
||||
@@ -88,6 +89,7 @@ class NarrativeAgent:
|
||||
user_profile=user_profile,
|
||||
birth_year=birth_year,
|
||||
background_voice=background_voice,
|
||||
occupation=occupation,
|
||||
)
|
||||
max_tokens = 8192
|
||||
agent_name = "NarrativeAgent.generate_narrative_merge"
|
||||
@@ -100,6 +102,7 @@ class NarrativeAgent:
|
||||
user_profile=user_profile,
|
||||
birth_year=birth_year,
|
||||
background_voice=background_voice,
|
||||
occupation=occupation,
|
||||
)
|
||||
max_tokens = 4096
|
||||
agent_name = "NarrativeAgent.generate_narrative"
|
||||
|
||||
@@ -7,6 +7,7 @@ import re
|
||||
from typing import Optional
|
||||
|
||||
from app.agents.chat.background_voice import get_background_voice_narrative_block
|
||||
from app.agents.chat.occupation_context import get_occupation_narrative_hint
|
||||
from app.features.memory.evidence_format import (
|
||||
dedupe_evidence_chunk_rows,
|
||||
format_evidence_chunks_for_prompt,
|
||||
@@ -142,7 +143,14 @@ def _memoir_fidelity_core_rules() -> str:
|
||||
1. **正文只能展开「本段用户口述」区块中的内容**。若输入中有「相关记忆摘录」等参考区,其中信息**不得**写成本人本轮亲口经历的细节;最多用一两句作主题衔接,且不得引入摘录里才有的具体人名、地点、时间、对话、数字。
|
||||
2. **禁止编造**:不得新增用户未提及的具体人物姓名、对话原文、地点、时间、事件经过、因果、数字;不得推断性心理描写或「典型年代场景」填充。**口述未明确结果、结局或对方最终决定时**,不得用常识补全为确定断言(例如未清楚表达落选、未通过、被拒绝等,则不得写「未能被选中」「最终没有录用」等);只写已明确的过程与事实,不确定处宁可略写或使用中性表述。
|
||||
3. **禁止为凑字数扩写**:材料短则输出短;段落数量与长度随材料而定。
|
||||
4. 允许:去除口语赘词与寒暄、调整语序、合并重复指代、把口语改为书面语;**不得**用虚构细节「让文章更好看」。"""
|
||||
4. 允许:去除口语赘词与寒暄、调整语序、合并重复指代、把口语改为书面语;**不得**用虚构细节「让文章更好看」。
|
||||
|
||||
## 以下操作是鼓励的(不算编造)
|
||||
- 口语转书面语:删语气词、用成语/四字词替换口语表达、调整语序
|
||||
- 过渡句与衔接句:如「那段日子」「回想起来」等,只要不引入新的实体
|
||||
- 基于口述已有情感的书面化渲染(如口述说「难受」,可改为「心里不好受」)——前提是不新增具体场景、数字、动作
|
||||
- 合并同义重复表述,让叙述更紧凑
|
||||
- 纠正明显的语音识别错字"""
|
||||
|
||||
|
||||
def _memoir_fidelity_user_profile_rules() -> str:
|
||||
@@ -185,7 +193,13 @@ def _memoir_editor_narrative_style_block() -> str:
|
||||
- 保留生动的细节,将口语表达改写为有画面感的书面叙述
|
||||
- 去除口语中的填充词和无意义重复
|
||||
- 保持时间顺序和逻辑清晰
|
||||
- **文采服务于真实**:可以有文学性的表达与恰当的情感渲染,但不得虚构新的事实来增色
|
||||
- **在事实边界内,鼓励使用有温度的传记笔法**,让读者感受到讲述者当时的心情;可有文学性的表达与恰当的情感渲染,但不得虚构新的事实来增色
|
||||
|
||||
### 示例(仅供参考允许的改写程度;只改语气、不加新事实)
|
||||
- 原文:「那时候穷啊,一家人挤一间房。」
|
||||
→ 改写:「那时家里拮据,一家人挤在一间屋里过日子。」
|
||||
- 原文:「后来他走了,我挺难受的。」
|
||||
→ 改写:「他走后的那段日子,心里一直不是滋味。」
|
||||
|
||||
### 输出格式约束
|
||||
- 使用第一人称
|
||||
@@ -193,12 +207,17 @@ def _memoir_editor_narrative_style_block() -> str:
|
||||
- 如有「衔接上下文」,仅保持语气与时间线连贯,不重复已有段落全文"""
|
||||
|
||||
|
||||
def get_narrative_editor_system_prompt(background_voice: str = "default") -> str:
|
||||
def get_narrative_editor_system_prompt(
|
||||
background_voice: str = "default", occupation: str = ""
|
||||
) -> str:
|
||||
"""故事/章节叙事:传记作家式书面语 + 事实边界(chapter 直接展示 story 时使用)。"""
|
||||
occ_hint = get_occupation_narrative_hint(occupation, background_voice)
|
||||
tail = get_background_voice_narrative_block(background_voice)
|
||||
base = f"""{get_memoir_fidelity_facts_only_prompt()}
|
||||
|
||||
{_memoir_editor_narrative_style_block()}"""
|
||||
if occ_hint:
|
||||
base = f"{base}\n\n{occ_hint}"
|
||||
if not tail:
|
||||
return base
|
||||
return f"{base}\n\n{tail}"
|
||||
@@ -396,6 +415,7 @@ def get_narrative_prompt(
|
||||
birth_year: Optional[int] = None,
|
||||
archived_summaries: str = "",
|
||||
background_voice: str = "default",
|
||||
occupation: str = "",
|
||||
) -> str:
|
||||
"""将新对话改写为叙述(只输出新内容的改写,不重复已有内容)"""
|
||||
context_tail = ""
|
||||
@@ -418,7 +438,7 @@ def get_narrative_prompt(
|
||||
age_hint = _build_age_hint(stage, birth_year)
|
||||
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
|
||||
|
||||
return f"""{get_narrative_editor_system_prompt(background_voice=background_voice)}
|
||||
return f"""{get_narrative_editor_system_prompt(background_voice=background_voice, occupation=occupation)}
|
||||
|
||||
阶段:{stage}
|
||||
可用信息(slots,仅可复述其中已出现事实):{slots}{profile_section}{time_section}
|
||||
@@ -449,6 +469,7 @@ def get_narrative_json_prompt(
|
||||
user_profile: str = "",
|
||||
birth_year: Optional[int] = None,
|
||||
background_voice: str = "default",
|
||||
occupation: str = "",
|
||||
) -> str:
|
||||
"""将新对话改写为叙述,输出 JSON 格式(paragraphs: [{content, image_description}])"""
|
||||
context_tail = ""
|
||||
@@ -465,7 +486,7 @@ def get_narrative_json_prompt(
|
||||
age_hint = _build_age_hint(stage, birth_year)
|
||||
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
|
||||
|
||||
return f"""{get_narrative_editor_system_prompt(background_voice=background_voice)}
|
||||
return f"""{get_narrative_editor_system_prompt(background_voice=background_voice, occupation=occupation)}
|
||||
|
||||
请将「本段用户口述」改写为第一人称书面叙述,并输出 **纯 JSON**,不要包含任何其他文字或 markdown 代码块。
|
||||
**JSON 输出**:接口已启用 `response_format=json_object`(与 DeepSeek JSON 模式一致),只输出一个合法 JSON 对象。
|
||||
@@ -481,7 +502,7 @@ def get_narrative_json_prompt(
|
||||
1. **只展开「本段用户口述」**;若有参考摘录区,不得把摘录中的具体事实写成本轮亲历经历(见系统说明)。
|
||||
2. 过滤语气词、寒暄、与 AI 的交互;不重复已有故事全文;本批只写同一主题/事件链。
|
||||
3. 段落数量与每段长度**随材料而定**,禁止为凑字数编造。
|
||||
4. 使用第一人称、**优雅书面语**(可适当过渡与铺陈,须基于口述事实);不要直接引用原话;不要用 `#`、`##`、表格。
|
||||
4. 使用第一人称、**优雅书面语**,改写须符合系统说明中的「传记作家文体」与「改写原则」(含示例):在事实边界内可做书面化、过渡与情感渲染,**须基于口述事实**;不要直接引用原话;不要用 `#`、`##`、表格。
|
||||
5. **不推断结局**:若用户未明确说结果(是否录取、是否被选中等),不要凭常识补全为确定结论;只复述已说清楚的内容。
|
||||
|
||||
## 输出格式(严格 JSON)
|
||||
@@ -527,6 +548,7 @@ def get_narrative_merge_json_prompt(
|
||||
user_profile: str = "",
|
||||
birth_year: Optional[int] = None,
|
||||
background_voice: str = "default",
|
||||
occupation: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
已有故事追加:将「已有全文(或节选)」与「本段口述」合并为**一篇**第一人称叙述,
|
||||
@@ -542,7 +564,7 @@ def get_narrative_merge_json_prompt(
|
||||
age_hint = _build_age_hint(stage, birth_year)
|
||||
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
|
||||
|
||||
return f"""{get_narrative_editor_system_prompt(background_voice=background_voice)}
|
||||
return f"""{get_narrative_editor_system_prompt(background_voice=background_voice, occupation=occupation)}
|
||||
|
||||
你正在**扩写并重组**一则已有回忆录故事:必须把「已有故事」中的事实全部保留在输出中(可合并重复表述、调整语序),并融入「本段用户口述」中的新事实;按**事件发生的时间顺序**排列段落(早→晚);禁止丢弃未矛盾的旧内容。
|
||||
|
||||
@@ -559,7 +581,7 @@ def get_narrative_merge_json_prompt(
|
||||
1. 输出为**完整故事正文**(不是仅写本段):`paragraphs` 须包含重组后的**全文**。
|
||||
2. **禁止编造**:不得新增用户未在「已有」或「本段」中出现的人名、地点、时间、对话、数字。
|
||||
3. 若本段与旧文完全重复或无新信息,可仅输出与旧文等价重组后的正文(不得无故缩短到明显少于旧文)。
|
||||
4. 使用第一人称、**优雅书面语**(与系统说明中的传记作家文体一致);不要用 `#`、`##`、表格。
|
||||
4. 使用第一人称、**优雅书面语**,改写须符合系统说明中的「传记作家文体」与「改写原则」(含示例):在事实边界内可做书面化、过渡与情感渲染,**须基于口述与旧文已有事实**;不要用 `#`、`##`、表格。
|
||||
5. **不推断结局**:本段口述未明确结果时,不要用常识补全落选/未通过等确定说法,除非旧文中已有同一事实。
|
||||
|
||||
## 输出格式(严格 JSON)
|
||||
|
||||
Reference in New Issue
Block a user