feat: 实现AI拒答机制和语言控制优化

- 添加 RunStatus.REFUSED 状态,AI可明确表达拒答意图
- 优化 System Prompt 使用视觉强调提高AI对拒答规则的遵守
- User Prompt 添加 SCOPE CHECK 和语言约束
- Worker Rules 添加多语言版本角色扮演规则
- Runner 传递 language 参数到 worker stage
- json_finalize 添加语言约束参数
- 更新单元测试匹配新的 prompt 结构
This commit is contained in:
ZL-Q
2026-04-29 01:38:59 +08:00
parent adb2b3bcc3
commit f497afbff2
15 changed files with 1293 additions and 117 deletions
@@ -0,0 +1,329 @@
# IMPLEMENTATION PLAN:修复 AI 英文输出失效
## 前置条件
| 条件 | 状态 |
|------|------|
| `language` 参数已从 runner 正确读取 | ✅ |
| `_build_output_rules` 正确注入语言要求 | ✅ |
| `get_worker_output_rules` 按 language 分发 | ✅ |
| 已知 role playing 忽略 language | ✅ 确认为根因 |
## 实现步骤
### Step 1: 新增英文版角色扮演提示词
**文件**: `backend/src/core/agentscope/prompts/worker_rules.py`
```python
_WORKER_ROLE_PLAYING_EN = """\
You are a Liu Yao (Six Lines) divination master who strictly follows the logic of Five Elements (Wu Xing) generation-restriction and hexagram imagery. Your sole task is to produce rule-based professional interpretations based on the structured hexagram data provided.
[Boundaries & Prohibitions]
- Only deduce from the six-line information in the input data. Never fabricate data.
- Never introduce external systems such as astrology, Tarot, Ba Zi (Eight Characters), or Zi Wei.
- Never quote long passages from the original I Ching text. Liu Yao centers on Five Elements generation and restriction.
[Deduction Axioms] (in descending priority)
1. Hexagram Primacy Rule: First determine the hexagram type (Six-Clash hexagram → matters dissolve quickly and scatter; Six-Union hexagram → matters progress slowly and converge). This is the irreversible background tone. Then examine line changes.
2. Movement-Stillness Rule: Still lines cannot form special patterns (Three Union, Six Union) among themselves unless there is a moving line or Day-Month induction. A changing line whose transformed line encounters Void, Break, Tomb, or Severed is treated as movement without result — the matter falls through.
3. Generation-Restriction Priority: All generation and restriction is ultimately adjudicated by Month Branch prosperity/decline and Day Stem generation/restriction. The Five Element statuses in the input data are established facts and must not be altered.
[Six Relatives (Liu Qin) Category Mapping]
Based on the question type, the Six Relatives map as follows:
Career/Work questions:
- Officer (Guan Gui): supervisor, work pressure, position, authority
- Parent (Fu Mu): documents, contracts, projects, organization, credentials
- Wealth (Qi Cai): salary, income, resources
- Children (Zi Sun): subordinates, skills, relief from trouble
- Sibling (Xiong Di): colleagues, competitors
Wealth/Investment questions:
- Wealth (Qi Cai): financial resources, earnings, capital (primary Yong Shen)
- Sibling (Xiong Di): wealth-draining, competition, risk
- Children (Zi Sun): source of wealth, blessings
- Parent (Fu Mu): documents, licenses, platforms
- Officer (Guan Gui): wealth depletion, pressure
Relationships/Marriage questions:
- Male querent: Wealth line represents the partner; Officer line represents romantic rival
- Female querent: Officer line represents the partner; Wealth line represents romantic rival
- Parent (Fu Mu): marriage contract, documents, family
- Children (Zi Sun): children, relief
Health/Illness questions:
- Officer (Guan Gui): illness, pathology (Ji Shen / feared spirit)
- Children (Zi Sun): medicine, doctor, relief spirit (Yong Shen / useful spirit)
- Parent (Fu Mu): hospital, elders
- Sibling (Xiong Di): peers, support
[Thinking Chain Requirement]
You must explicitly output your reasoning in the following order:
1. Hexagram Classification: Determine the hexagram type (Six-Clash / Six-Union / Returning Spirit / Wandering Spirit) and establish the macro backdrop.
2. Yong Shen Identification: Based on the question, identify the Yong Shen (useful spirit) and Ji Shen (feared spirit). Check whether they appear in the hexagram and whether they are changing lines.
3. Prosperity & Void: Month Branch determines prosperity/decline (Prosperous / Strong / Resting / Imprisoned / Dead). Day Stem determines generation/restriction (Twelve Growth Stages and Clash/Union). Movement and change determine substance vs. void (advancing / retreating / turning void / turning break).
4. Generation-Restriction Chains: List the specific generation-restriction chains between Self Line, Response Line, moving lines, changing lines, Day, and Month. Explain each one's effect on the Yong Shen.
5. Special Combinations: Only when permitted by the Movement-Stillness Rule, assess hidden movement, Three Union patterns, reverse generation/restriction, etc.
6. Comprehensive Verdict: Combine the hexagram backdrop with the line dynamics to produce the trend conclusion, core risk points, and turning-point conditions.
[Strength Hierarchy]
- When a changing line generates or restricts in reverse, the changing line's strength exceeds the original line
- Self-Response > Moving Lines > Changing Lines > Day-Month > Still Lines
[Expression Style]
Professional, precise, restrained. Speak like someone who truly reads hexagrams.
Do not write literary prose, do not pile on vague words, do not feign profundity.
You may explain, but all explanation must be anchored to the hexagram image itself.
Your goal is not to 'sound like' a divination reading, but to actually interpret according to Liu Yao rules.
[Sign-Level Reference Anchoring]
Sign-level assessment should integrate hexagram backdrop and movement/change auspiciousness, referencing the following principles:
- Top-Top (Shang Shang): Six-Union hexagram or non-Six-Clash hexagram + Yong Shen prosperous + moving line generates Self/Yong Shen with strength + no reverse restriction, void, or break.
- Upper-Middle (Zhong Shang): Non-Six-Clash hexagram + Yong Shen has vitality + minor obstructions exist (e.g. Yong Shen still and unmoving, or Ji Shen secretly moves but can be restrained).
- Lower-Middle (Zhong Xia): Six-Clash hexagram with inauspicious backdrop / Yong Shen weak / Yong Shen receives restriction but still has rescue / moving-then-reverse-restriction but Self Line unharmed.
- Bottom-Bottom (Xia Xia): Six-Clash hexagram + Yong Shen monthly break and void + moving line reverse-restricts Self/restricts Yong Shen + Day and Month offer no help.
When the hexagram shows mixed auspicious and inauspicious signs, the 'hexagram backdrop' takes first weight and the 'Self Line's safety' takes second weight.
"""
```
更新 `get_worker_role_playing`:
```python
def get_worker_role_playing(language: str) -> str:
if language.startswith("en"):
return _WORKER_ROLE_PLAYING_EN
if language.startswith("zh-Hant") or language.startswith("zh_Hant"):
return _WORKER_ROLE_PLAYING # 目前暂用简体版,后续可补充繁体版
return _WORKER_ROLE_PLAYING
```
---
### Step 2: 安全规则支持中英文
**文件**: `backend/src/core/agentscope/prompts/system_prompt.py`
`_build_safety_section` 中增加英文版:
```python
_SAFETY_RULES_ZH = "\n".join([
"[Safety Rules]",
"- 你是六爻解卦助手,只回答与六爻占卜、卦象分析、易理探讨相关的问题。遇到无关提问时,明确告知超出服务范围,不做任何妥协或绕行。",
"- 拒绝回答任何与六爻无关的问题,包括但不限于:政治、军事、违法活动、个人隐私窃取、有害信息等。",
"- Never expose secrets, tokens, credentials, or private identifiers.",
"- Do not invent tool outputs, user data, or system state.",
"- Never bypass schema constraints (enum/type/required/extra fields).",
"- If required data is missing, ask minimal clarification or return constrained safe output.",
])
_SAFETY_RULES_EN = "\n".join([
"[Safety Rules]",
"- You are a Liu Yao (Six Lines) divination assistant. Only answer questions related to Liu Yao divination, hexagram analysis, and I Ching philosophy. When encountering unrelated questions, clearly state the scope limitation without compromise or circumvention.",
"- Refuse to answer any questions unrelated to Liu Yao, including but not limited to: politics, military, illegal activities, personal privacy theft, harmful information, etc.",
"- Never expose secrets, tokens, credentials, or private identifiers.",
"- Do not invent tool outputs, user data, or system state.",
"- Never bypass schema constraints (enum/type/required/extra fields).",
"- If required data is missing, ask minimal clarification or return constrained safe output.",
])
def _build_safety_section(*, language: str) -> str:
if language.startswith("en"):
return wrap_section("safety", _SAFETY_RULES_EN)
return wrap_section("safety", _SAFETY_RULES_ZH)
```
`build_system_prompt` 调用改为 `_build_safety_section(language=language)`
---
### Step 3: User Prompt 国际化
**文件**: `backend/src/core/agentscope/prompts/user_prompt.py`
将硬编码的中文字段标签抽取为映射表,根据 `language` 选择:
```python
_ZH_FIELDS = {
"user_question": "用户问题",
"question_type": "问题类型",
"divination_method": "起卦方式",
"divination_time": "起卦时间",
"ben_gua": "【本卦】",
"gua_name_tpl": "卦名:{name}(上{upper}{lower}",
"gua_xiang": "卦象",
"bian_gua": "【变卦】",
"ganzhi": "【干支】",
"year_pillar": "年柱", "month_pillar": "月柱", "day_pillar": "日柱", "time_pillar": "时柱",
"yue_jian": "月建", "ri_chen": "日辰", "yue_po": "月破", "ri_chong": "日冲",
"year_kong": "年空亡", "month_kong": "月空亡", "day_kong": "日空亡", "time_kong": "时空亡",
"wu_xing": "【五行旺衰】",
"ben_yao": "【本卦爻象】",
"yao_position": "{pos}",
"yang_yao": "", "yin_yao": "",
"dong_mark": "(动)", "shi_mark": "", "ying_mark": "",
"bian_yao": "【变卦爻象】",
"fushen": "【伏神】",
"special_status": "【特殊状态标注】",
"interactions": "【全局冲合提示】",
"time_effect": "【时令关键点】",
"ri_chen_zhang_sheng": "【日辰十二长生】",
"closing": "——以上为起卦所得完整数据,请据此进行六爻解读。",
}
_EN_FIELDS = {
"user_question": "User Question",
"question_type": "Question Type",
"divination_method": "Divination Method",
"divination_time": "Divination Time",
"ben_gua": "[Original Hexagram]",
"gua_name_tpl": "Name: {name} (Upper: {upper}, Lower: {lower})",
"gua_xiang": "Trigram Code",
"bian_gua": "[Changed Hexagram]",
"ganzhi": "[Stems & Branches]",
"year_pillar": "Year Pillar", "month_pillar": "Month Pillar", "day_pillar": "Day Pillar", "time_pillar": "Time Pillar",
"yue_jian": "Month Branch", "ri_chen": "Day Stem", "yue_po": "Month Break", "ri_chong": "Day Clash",
"year_kong": "Year Void", "month_kong": "Month Void", "day_kong": "Day Void", "time_kong": "Time Void",
"wu_xing": "[Five Element Status]",
"ben_yao": "[Original Hexagram Lines]",
"yao_position": "Line {pos}",
"yang_yao": "Yang", "yin_yao": "Yin",
"dong_mark": " (changing)", "shi_mark": " Self", "ying_mark": " Response",
"bian_yao": "[Changed Hexagram Lines]",
"fushen": "[Hidden Lines (Fu Shen)]",
"special_status": "[Special Status Annotations]",
"interactions": "[Global Clash/Union Notes]",
"time_effect": "[Seasonal Key Points]",
"ri_chen_zhang_sheng": "[Day Stem Twelve Growth Stages]",
"closing": "—— End of hexagram data. Please interpret according to Liu Yao principles.",
}
def _get_field_map(language: str) -> dict[str, str]:
if language.startswith("en"):
return _EN_FIELDS
return _ZH_FIELDS
```
`build_divination_user_prompt` 签名变为 `build_divination_user_prompt(*, derived: DerivedDivinationData, language: str = "zh-CN")`
---
### Step 4: Runner 调用链传递 language
**文件**: `backend/src/core/agentscope/runtime/runner.py`
1. `_build_worker_input_messages` 增加 `language` 参数并传递给 `build_divination_user_prompt`
```python
def _build_worker_input_messages(
self,
*,
context_messages: list[Msg],
run_input: RunAgentInput,
derived_divination: DerivedDivinationData | None,
language: str,
) -> list[Msg]:
if derived_divination is not None:
user_text = build_divination_user_prompt(derived=derived_divination, language=language)
else:
user_text, _ = extract_latest_user_payload(run_input)
...
```
2. `_execute_worker_step` 调用 `_build_worker_input_messages` 时传入 `language`
---
### Step 5: 新增/更新测试
**文件**: `backend/tests/unit/test_agentscope_prompts.py`
新增测试用例:
```python
def test_system_prompt_en_has_english_role_playing() -> None:
"""English system prompt should contain English role playing content."""
prompt = build_system_prompt(
agent_type=AgentType.WORKER,
language="en-US",
llm_config=SystemAgentLLMConfig(),
)
assert "Liu Yao" in prompt or "divination master" in prompt
assert "[Boundaries" in prompt or "Career/Work" in prompt
def test_system_prompt_en_safety_is_english() -> None:
"""English safety section should be in English."""
prompt = build_system_prompt(
agent_type=AgentType.WORKER,
language="en-US",
llm_config=SystemAgentLLMConfig(),
)
assert "scope limitation" in prompt
def test_system_prompt_zh_cn_role_playing_unchanged() -> None:
"""Chinese system prompt should retain original Chinese role playing."""
prompt = build_system_prompt(
agent_type=AgentType.WORKER,
language="zh-CN",
llm_config=SystemAgentLLMConfig(),
)
assert "六爻解卦师" in prompt
assert "推演公理" in prompt
def test_agent_prompt_en_has_english_output_rules() -> None:
"""English agent prompt should contain English output rules."""
prompt = build_agent_prompt(
agent_type=AgentType.WORKER,
language="en-US",
llm_config=SystemAgentLLMConfig(),
)
assert "focus_points" in prompt
assert "opening paragraph must state" in prompt
assert "段间用" not in prompt # No Chinese formatting rules
def test_agent_prompt_zh_cn_unchanged() -> None:
"""Chinese agent prompt should remain unchanged."""
prompt = build_agent_prompt(
agent_type=AgentType.WORKER,
language="zh-CN",
llm_config=SystemAgentLLMConfig(),
)
assert "段间用\\n\\n" in prompt
assert "六爻解卦师" in prompt
def test_user_prompt_en_has_english_labels() -> None:
"""English user prompt should have English field labels."""
from core.agentscope.prompts.user_prompt import build_divination_user_prompt
# Need a minimal DerivedDivinationData for testing
# ... assert "User Question" in result
def test_user_prompt_zh_cn_unchanged() -> None:
"""Chinese user prompt should remain unchanged."""
from core.agentscope.prompts.user_prompt import build_divination_user_prompt
# ... assert "用户问题" in result
```
---
## 验证
```bash
# 运行测试
cd backend
uv run pytest tests/unit/test_agentscope_prompts.py -v
# 类型检查
uv run basedpyright src/core/agentscope/
# Lint
uv run ruff check src/core/agentscope/
```
@@ -0,0 +1,2 @@
{"file": ".opencode/commands/trellis/finish-work.md", "reason": "Finish work checklist"}
{"file": ".opencode/commands/trellis/check-backend.md", "reason": "Backend check spec"}
@@ -0,0 +1 @@
{"file": ".opencode/commands/trellis/check-backend.md", "reason": "Backend check spec"}
@@ -0,0 +1,8 @@
{"file": ".trellis/workflow.md", "reason": "Project workflow and conventions"}
{"file": ".trellis/spec/backend/index.md", "reason": "Backend development guide"}
{"file": "backend/src/core/agentscope/prompts/worker_rules.py", "reason": "Core fix: role playing language dispatch"}
{"file": "backend/src/core/agentscope/prompts/system_prompt.py", "reason": "Safety section i18n"}
{"file": "backend/src/core/agentscope/prompts/user_prompt.py", "reason": "User prompt i18n"}
{"file": "backend/src/core/agentscope/prompts/agent_prompt.py", "reason": "Agent prompt composition"}
{"file": "backend/src/core/agentscope/runtime/runner.py", "reason": "Language parameter passing"}
{"file": "backend/tests/unit/test_agentscope_prompts.py", "reason": "Test cases"}
@@ -0,0 +1,76 @@
# PRD:修复 AI 英文输出失效
## 1. 背景
用户选择 English 作为语言设置后,AI 仍然输出中文。已在上一轮分析中确认根因:
- `system_prompt.py:_build_output_rules` 正确注入了 `"You MUST respond in English"` 指令
-**`worker_rules.py:get_worker_role_playing` 完全忽略 `language` 参数**,始终返回中文版 `_WORKER_ROLE_PLAYING`
- 角色扮演提示词是系统提示词中体量最大的部分,其全部中文内容形成了压倒性的中文 priming 效应
- 此外 `user_prompt.py` 的排盘数据也硬编码为中文,`_build_safety_section` 同样硬编码中文
## 2. 目标
1.`language` 为英文时,AI 的角色扮演提示词也应为英文
2. 英文模式的 safety section 应包含英文版规则
3. 英文模式的 user prompt 字段应包含英文标签
4. 最小化影响范围——不改变任何业务逻辑或数据流
## 3. 变更内容
### 3.1 Worker Rules(核心修复)
**`worker_rules.py`**:
- 新增 `_WORKER_ROLE_PLAYING_EN` 英文版角色扮演提示词
- `get_worker_role_playing(language)` 根据 language 前缀分发中/英文版本(新增繁体分支以备后用)
### 3.2 System Prompt
**`system_prompt.py`**:
- `_build_safety_section` 接受 `language` 参数,提供英文版安全规则
- 保持现有中文版不变,在 `language.startswith("en")` 时返回英文版
### 3.3 User Prompt
**`user_prompt.py`**:
- 新增 `_BUILD_FIELD_MAP_EN` 英文版字段标签
- `build_divination_user_prompt` 接受 `language` 参数
-`language` 为英文时使用英文标签
### 3.4 Runner 调用链
**`runner.py`**:
- `_build_worker_input_messages` 接受 `language` 参数,传递给 `build_divination_user_prompt`
- `_run_worker_stage``language` 传入 `_build_worker_input_messages`
### 3.5 测试
**`test_agentscope_prompts.py`**:
- 新增测试:`language="en-US"` 时 system prompt 不含大量中文
- 新增测试:`language="en-US"` 时 worker role playing 为英文
- 新增测试:`language="zh-CN"` 时行为不变
## 4. 文件变更清单
| 文件 | 变更 |
|------|------|
| `backend/src/core/agentscope/prompts/worker_rules.py` | 新增 `_WORKER_ROLE_PLAYING_EN``get_worker_role_playing` 分发 |
| `backend/src/core/agentscope/prompts/system_prompt.py` | `_build_safety_section(language)``build_system_prompt` 传参 |
| `backend/src/core/agentscope/prompts/user_prompt.py` | `build_divination_user_prompt(language=)`,新增英文字段标签 |
| `backend/src/core/agentscope/runtime/runner.py` | `_build_worker_input_messages(language=)`,传递 language |
| `backend/tests/unit/test_agentscope_prompts.py` | 新增语言切换相关测试 |
## 5. 不在此范围的变更
- **不修改** `get_worker_output_rules` — 已正确按 language 分发(EN/ZH-Hant/ZH-CN
- **不修改** `_build_output_rules` — 已正确工作
- **不涉及前端** — 前端之前的重构已完成 `language` 统一,无需改动
- **不涉及协议文档** — 协议已定义 language 字段
- **不涉及数据库迁移** — 数据结构已正确
## 6. 验收标准
1. `language="en-US"` 时,完整 system prompt 的语境以英文为主(role playing + safety 均为英文)
2. `language="en-US"` 时,user prompt 中排盘数据的字段标签为英文
3. `language="zh-CN"` 时,所有表现与修复前完全一致(回归)
4. 所有现有测试通过
@@ -0,0 +1,44 @@
{
"id": "fix-ai-english-output",
"name": "fix-ai-english-output",
"title": "Fix AI English Output - Worker Role Playing Ignores Language Parameter",
"description": "Worker role-playing prompt always returns Chinese regardless of language parameter, causing AI to output Chinese even when user selects English.",
"status": "planning",
"dev_type": null,
"scope": null,
"priority": "P0",
"creator": "zl-q",
"assignee": "zl-q",
"createdAt": "2026-04-28",
"completedAt": null,
"branch": null,
"base_branch": "dev",
"worktree_path": null,
"current_phase": 0,
"next_action": [
{
"phase": 1,
"action": "implement"
},
{
"phase": 2,
"action": "check"
},
{
"phase": 3,
"action": "finish"
},
{
"phase": 4,
"action": "create-pr"
}
],
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}