feat(i18n): translate report_agent react prompts to english

translate every llm-facing string-literal in
backend/app/services/report_agent.py — the four tool-description
constants, the plan/section/chat system+user prompts, the react loop
templates, the inline messages re-injected during the section and chat
loops, the _execute_tool error returns, and the plan_outline default and
fallback outline content. preserve every {interpolation} token, the
literal final answer: trigger and <tool_call> xml tag, the four primary
tool names, and all three get_language_instruction() postfix call sites.
also switch the unused-tools join separator from "、" to ", " so it
renders naturally inside the now-english react templates.

removes the chinese language bias that the english postfix alone could
not overcome — under accept-language: en the report agent now produces
english-flavoured analytical reports and chat replies; under
accept-language: zh the postfix continues to steer the model into
chinese with no semantic delta. logger calls (#6) and docstrings or
comments (#7) are deliberately untouched.

Closes #5
This commit is contained in:
Dominik Seemann 2026-05-07 12:49:23 +00:00
parent 3b17c0b9ba
commit 22a3ca7af5
7 changed files with 1231 additions and 269 deletions

View File

@ -0,0 +1,258 @@
# Design Document — i18n-report-agent-prompts
## Overview
**Purpose**: Translate every Chinese string-literal that flows into the LLM message stream of `backend/app/services/report_agent.py` into English so that, under `Accept-Language: en`, the Report-Agent produces English-flavoured analytical reports and chat replies — and not the Chinese-biased output that today's Chinese-base prompts produce despite the `get_language_instruction()` English postfix.
**Users**: MiroFish operators running the 5-step pipeline under English locale; reviewers tracking the i18n epic (#11); developers maintaining sibling i18n issues (#6, #7, #8, #10) downstream of this change.
**Impact**: Behavioural — under `Accept-Language: en`, the report's section titles, section bodies, embedded quotations, and chat replies become English-flavoured. No public-API change. No `Report.to_dict()` shape change. No new dependencies.
### Goals
- Replace every Chinese string-literal in `report_agent.py` that is sent to the LLM (system prompt, user prompt, ReACT loop messages, tool descriptions, `_define_tools` parameter hints, `_execute_tool` error returns, `plan_outline` defaults) with English equivalents.
- Preserve every variable interpolation, every JSON schema key, every literal trigger string (`Final Answer:`, `<tool_call>`, tool-name strings), every `get_language_instruction()` call site.
- Keep the public surface of `ReportAgent`, `ReportManager`, `Report`, `ReportOutline`, `ReportSection`, `ReportStatus` byte-for-byte equivalent in shape.
### Non-Goals
- Logger calls (`logger.info`, `logger.warning`, `logger.error`, `logger.debug`) inside the same file — owned by issue #6. Notably, the single raw-Chinese `logger.debug(f"LLM响应: ...")` at line 1322 is left untouched.
- Module docstring (lines 111), class docstrings, dataclass docstrings, method docstrings, inline `#` comments — owned by issue #7.
- Refactoring prompt structure, the JSON output schema of `PLAN_SYSTEM_PROMPT`, the ReACT loop control flow, conflict-resolution branches, or the chat tool-budget caps.
- Externalizing prompts into `/locales/*.json`.
- Live end-to-end report generation under both `en` and `zh` (deferred to fixture-based static checks; reviewer trust on quality parity, matching the precedent of issues #2/#3/#4).
## Boundary Commitments
### This Spec Owns
- The string-literal **content** of all LLM-facing regions in `backend/app/services/report_agent.py`:
- Tool description constants `TOOL_DESC_INSIGHT_FORGE` (476492), `TOOL_DESC_PANORAMA_SEARCH` (494509), `TOOL_DESC_QUICK_SEARCH` (511521), `TOOL_DESC_INTERVIEW_AGENTS` (523548).
- PLAN-phase prompts `PLAN_SYSTEM_PROMPT` (552589), `PLAN_USER_PROMPT_TEMPLATE` (591611).
- EXEC-phase prompts `SECTION_SYSTEM_PROMPT_TEMPLATE` (615767), `SECTION_USER_PROMPT_TEMPLATE` (769792), including the embedded "Correct Example" / "Wrong Example" code blocks.
- ReACT loop conversation templates `REACT_OBSERVATION_TEMPLATE` (796806), `REACT_INSUFFICIENT_TOOLS_MSG` (808811), `REACT_INSUFFICIENT_TOOLS_MSG_ALT` (813816), `REACT_TOOL_LIMIT_MSG` (818821), `REACT_UNUSED_TOOLS_HINT` (823), `REACT_FORCE_FINAL_MSG` (825).
- CHAT-phase prompts `CHAT_SYSTEM_PROMPT_TEMPLATE` (829855), `CHAT_OBSERVATION_SUFFIX` (857).
- The `_define_tools` parameter-description dict values (925952) and the `_get_tools_description` leader `"可用工具:"` (1129).
- The `_execute_tool` error returns at lines 1058 and 1062.
- The inline LLM-visible strings inside `_generate_section_react`: `report_context` f-string (1294), empty-response retry (13161317), conflict-handling block (13421346), inline `unused_hint` literals (1380, 1476).
- The inline LLM-visible strings inside `chat`: report-truncated marker (1799), no-report fallback (1805), observation joiner (1861).
- The default / fallback outline content in `plan_outline`: success-path default title (1197), exception-path fallback `ReportOutline` (12121218).
- The `unused_tools_str` join separator at line 1454 — switch from `"、"` to `", "` for natural English rendering inside the now-English ReACT templates.
### Out of Boundary
- All `logger.*` calls in this file (issue #6), including the one raw-Chinese `logger.debug` at line 1322.
- All `"""..."""` docstrings and `#` comments in this file (issue #7).
- `backend/app/utils/locale.py`, `/locales/*.json`, `/locales/languages.json`.
- `backend/app/services/zep_tools.py`, `zep_entity_reader.py`, `zep_graph_memory_updater.py`.
- `backend/app/api/report.py`, `backend/app/api/simulation.py`, `backend/app/api/graph.py`.
- `backend/app/services/simulation_runner.py`, `simulation_ipc.py`, OASIS subprocess source.
- `backend/app/config.py` constants.
- `backend/pyproject.toml`, `backend/uv.lock`.
- All other files in the repository.
### Allowed Dependencies
- Read access to `get_language_instruction()` from `backend/app/utils/locale.py` — three call sites preserved verbatim (lines 1166, 1262, 1808).
- Read access to `t(...)` from `backend/app/utils/locale.py` — call sites preserved verbatim.
- No new external dependencies.
### Revalidation Triggers
- A change to the `Report.to_dict()` payload shape would force the report API blueprint and the frontend report panel to re-validate. **This spec does not change the shape.**
- A change to the `PLAN_SYSTEM_PROMPT` JSON output schema (`title`, `summary`, `sections[].title`, `sections[].description`) would force `plan_outline()`'s response parser to re-validate. **This spec preserves the schema verbatim.**
- A change to the `Final Answer:` literal trigger or the `<tool_call>...</tool_call>` XML tag would force `_generate_section_react`'s parser branches to re-validate. **This spec preserves both byte-for-byte.**
- A change to the four primary tool names (`insight_forge`, `panorama_search`, `quick_search`, `interview_agents`) or the legacy aliases (`search_graph`, `get_graph_statistics`, `get_entity_summary`, `get_simulation_context`, `get_entities_by_type`) would force `_execute_tool` and `_is_valid_tool_call` to re-validate. **This spec does not rename tools.**
## Architecture
### Existing Architecture Analysis
`ReportAgent` is a single Python class in `backend/app/services/report_agent.py`. The three LLM invocation paths (PLAN, SECTION, CHAT) follow a uniform pattern:
```
system_prompt = <chinese system prompt template>
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
user_prompt = <chinese user prompt template with {interpolations}>
response = self.llm.chat(messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
])
```
`_generate_section_react` extends this with a multi-turn ReACT loop where the user-role messages re-injected after each tool call (`REACT_OBSERVATION_TEMPLATE`, etc.) are also Chinese today. There is no abstraction layer between prompt construction and LLM invocation — the prompt text and the call site are colocated. This matches sister modules (`simulation_config_generator.py`, `oasis_profile_generator.py`, `ontology_generator.py`).
### Architecture Pattern & Boundary Map
**Selected pattern**: In-place string-literal translation. No new components, no new modules, no new abstractions.
```mermaid
flowchart TB
subgraph Caller["Caller — api/report.py"]
api["POST /api/report/generate<br/>POST /api/report/chat"]
end
subgraph ReportAgentMod["report_agent.py — IN SCOPE"]
plan["plan_outline<br/>**translate PLAN_*, defaults**"]
sec["_generate_section_react<br/>**translate SECTION_*, REACT_*, inline strings**"]
chat["chat<br/>**translate CHAT_*, inline strings**"]
tools["_define_tools / _get_tools_description<br/>**translate TOOL_DESC_*, params, leader**"]
exec["_execute_tool<br/>**translate error returns**"]
parse["_parse_tool_calls<br/>UNCHANGED (matches literals)"]
manager["ReportManager<br/>UNCHANGED (persistence)"]
end
subgraph Locale["utils/locale.py — UNCHANGED"]
gli[get_language_instruction]
tr[t]
end
subgraph ZepTools["services/zep_tools.py — UNCHANGED"]
zt[ZepTools dispatch]
end
api --> plan
api --> sec
api --> chat
plan --> gli
sec --> gli
chat --> gli
sec --> tools
chat --> tools
sec --> parse
sec --> exec
chat --> parse
chat --> exec
exec --> zt
plan --> manager
sec --> manager
```
**Architecture Integration**:
- Selected pattern: in-place string-literal translation; matches the precedent of issues #2/#3/#4.
- Domain/feature boundaries: prompt-content is the only boundary that moves. Logger / docstring / comment boundaries (issues #6, #7) and persistence-layer boundary (`ReportManager`) are explicitly preserved.
- Existing patterns preserved: `get_language_instruction()` postfix injection at three call sites; `<tool_call>` XML protocol; `Final Answer:` literal trigger; tool-name registry; JSON output schema for outline planning.
- New components rationale: none — no new components.
- Steering compliance: respects `tech.md` "preserve both styles working" for comments/docstrings (those are out of scope); respects `structure.md` per-project file isolation; respects `commits.md` Conventional Commits format for the eventual commit message.
### Technology Stack
| Layer | Choice / Version | Role in Feature | Notes |
|-------|------------------|-----------------|-------|
| Frontend / CLI | n/a | Frontend renders the translated `Report` payload as plain text/Markdown | No frontend change required |
| Backend / Services | Python 3.11, Flask 3.0 | Hosts `ReportAgent` and the report API | Single-file edit |
| Data / Storage | Neo4j + Graphiti | Source of retrieval results consumed by `zep_tools` | Unchanged |
| Messaging / Events | n/a | Report generation runs as a background `Task` | Unchanged |
| Infrastructure / Runtime | uv-managed venv | Backend dependency manager | No new dependencies |
> No new external dependencies, libraries, or infrastructure components are introduced. Detailed locale-resolution mechanics are documented in `research.md`.
## File Structure Plan
### Modified Files
- `backend/app/services/report_agent.py` — translate every Chinese string-literal that is sent to the LLM, plus the one separator literal at line 1454. No structural code changes; no new methods; no new constants. Line counts will shift due to the typically larger English character count, but the file's overall organization is unchanged.
### Unmodified Files (explicitly verified)
- `backend/app/utils/locale.py`
- `backend/app/services/zep_tools.py`, `zep_entity_reader.py`, `zep_graph_memory_updater.py`
- `backend/app/api/report.py`, `simulation.py`, `graph.py`
- `backend/app/services/simulation_runner.py`, `simulation_ipc.py`
- `backend/app/config.py`
- `backend/pyproject.toml`, `backend/uv.lock`
- `/locales/en.json`, `/locales/zh.json`, `/locales/languages.json`
- All frontend files
## System Flows
The PLAN / SECTION / CHAT flows are unchanged at the control-flow level — only the string content of system / user / observation messages is translated. No new diagram is required; `research.md` records the relevant parser-trigger details.
## Requirements Traceability
| Requirement | Summary | Components | Interfaces | Flows |
|-------------|---------|------------|------------|-------|
| 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7 | Translate `PLAN_SYSTEM_PROMPT` and `PLAN_USER_PROMPT_TEMPLATE`; preserve schema, count limits, interpolations, postfix call site | `PLAN_SYSTEM_PROMPT` (552), `PLAN_USER_PROMPT_TEMPLATE` (591), `plan_outline` (1137) | `plan_outline()` LLM `chat_json` invocation at line 1177 | PLAN flow |
| 2.12.9 | Translate `SECTION_SYSTEM_PROMPT_TEMPLATE` (incl. examples) and `SECTION_USER_PROMPT_TEMPLATE`; preserve `Final Answer:` / `<tool_call>` literals; preserve no-headings instruction | `SECTION_SYSTEM_PROMPT_TEMPLATE` (615), `SECTION_USER_PROMPT_TEMPLATE` (769), `_generate_section_react` (1221) | `_generate_section_react()` LLM `chat` invocation at line 1305 | SECTION ReACT flow |
| 3.13.7 | Translate `CHAT_SYSTEM_PROMPT_TEMPLATE` and `CHAT_OBSERVATION_SUFFIX`; preserve `<tool_call>` literal and prefix-injection contract | `CHAT_SYSTEM_PROMPT_TEMPLATE` (829), `CHAT_OBSERVATION_SUFFIX` (857), `chat` (1766) | `chat()` LLM `chat` invocations at lines 1828, 1868 | CHAT flow |
| 4.14.6 | Translate ReACT loop conversation templates; preserve `Final Answer:` literal; switch separator to `", "` | `REACT_*` constants (796825) | `_generate_section_react()` ReACT loop branches | SECTION ReACT flow |
| 5.15.7 | Translate four `TOOL_DESC_*` blocks, `_define_tools` parameter dict values, `_get_tools_description` leader; preserve tool names | `TOOL_DESC_*` (476548), `_define_tools` (919), `_get_tools_description` (1127) | `_define_tools()` and `_get_tools_description()` return values | SECTION + CHAT flows |
| 6.16.7 | Translate inline LLM-visible strings in `_generate_section_react` and `chat` | Inline strings at 1294, 13161317, 13421346, 1380, 1476, 1799, 1805, 1861 | Direct `messages.append(...)` calls | SECTION + CHAT flows |
| 7.17.3 | Translate `_execute_tool` error returns | f-strings at 1058, 1062 | `_execute_tool()` return value | SECTION + CHAT flows (error path) |
| 8.18.4 | Translate `plan_outline` defaults; preserve `ReportOutline` shape | `plan_outline` defaults at 1197, 12121218 | `plan_outline()` return value | PLAN flow (default + fallback paths) |
| 9.19.5 | Locale switching continues to work | `get_language_instruction()` call sites at 1166, 1262, 1808 | unchanged | All flows |
| 10.110.5 | Public API stable | `ReportAgent`, `ReportManager`, `Report`, `ReportOutline`, `ReportSection`, `ReportStatus` | unchanged | All flows |
| 11.111.5 | End-to-end Step 4 / Step 5 parity | Verification only | unchanged | All flows |
| 12.112.6 | Out-of-scope guardrail | None edited | unchanged | n/a |
## Components and Interfaces
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
|-----------|--------------|--------|--------------|--------------------------|-----------|
| Tool description constants | Module-scope constants in `report_agent.py` | LLM-facing tool catalog injected into SECTION + CHAT system prompts via `_get_tools_description` | 5.1, 5.2, 5.7 | `_define_tools` (P0), `_get_tools_description` (P0) | State (string literals only) |
| `PLAN_*` prompts | Module-scope constants | Outline planning system + user prompts | 1.1, 1.2, 1.5, 1.6 | `get_language_instruction` (P0), `plan_outline` (P0) | State |
| `SECTION_*` prompts | Module-scope constants | Section ReACT system + user prompts | 2.1, 2.2, 2.3, 2.4, 2.6, 2.7 | `get_language_instruction` (P0), `_generate_section_react` (P0), `_get_tools_description` (P1) | State |
| `REACT_*` templates | Module-scope constants | ReACT loop user-role messages re-injected after tool calls | 4.1, 4.2, 4.3, 4.4, 4.5 | `_generate_section_react` (P0) | State |
| `CHAT_*` prompts | Module-scope constants | Chat system prompt + observation suffix | 3.1, 3.2, 3.3, 3.4, 3.5, 3.6 | `get_language_instruction` (P0), `chat` (P0), `_get_tools_description` (P1) | State |
| `_define_tools` parameter dict | `ReportAgent` instance method | Catalog of tools + parameter hints, exposed to LLM via `_get_tools_description` | 5.3, 5.4, 5.6 | `_get_tools_description` (P0) | Service |
| `_get_tools_description` | `ReportAgent` instance method | Renders `_define_tools` output as a single string for SECTION + CHAT prompts | 5.5 | `_define_tools` (P0) | Service |
| `_execute_tool` error returns | `ReportAgent` instance method | Returns observation strings to the LLM for unknown-tool / execution-error paths | 7.1, 7.2, 7.3 | `_execute_tool` (P0) | Service |
| `_generate_section_react` inline strings | `ReportAgent` instance method body | LLM-visible strings appended to `messages` during ReACT loop | 6.1, 6.2, 6.3, 6.4 | `_generate_section_react` (P0) | Service |
| `chat` inline strings | `ReportAgent` instance method body | LLM-visible strings appended to `messages` during chat loop | 6.5, 6.6 | `chat` (P0) | Service |
| `plan_outline` defaults | `ReportAgent` instance method body | Default / fallback `ReportOutline` content emitted on success-without-title or exception path | 8.1, 8.2, 8.3, 8.4 | `plan_outline` (P0) | State |
> All components are existing module-scope constants or method-internal expressions. None require a full detail block — the responsibility boundary is "translate the string content; preserve the structural shape". The summary table above plus the requirement-level acceptance criteria in `requirements.md` form a complete contract.
### Implementation Notes (cross-cutting)
- **Translation glossary** (consistent across all components — see `research.md` Decision: Standard English phrasing): 上帝视角 → "god's-eye view"; 未来预演 → "forecast simulation" / "simulated future"; 模拟需求 → "simulation requirement"; 模拟世界 → "simulated world"; 章节 → "section"; 大纲 → "outline"; 引用 → "quote"/"quotation"; 正确示例 → "Correct Example"; 错误示例 → "Wrong Example"; 注意 → "Note"; 重要 → "IMPORTANT"; 工具 → "tool"; 检索 → "retrieval".
- **Literal preservation**: `Final Answer:`, `<tool_call>`, `</tool_call>`, all tool names (`insight_forge`, `panorama_search`, `quick_search`, `interview_agents`, plus legacy aliases), all `{interpolation}` tokens, all JSON schema keys, all emoji / box-drawing characters (`💡`, `═`).
- **Locale-agnostic strings**: `_execute_tool` error returns and `plan_outline` default / fallback outline content are returned regardless of locale (no `get_language_instruction()` injection at those sites). They become locale-agnostic English under this PR.
- **Separator change**: `unused_tools_str = "、".join(unused_tools)` at line 1454 → `", ".join(unused_tools)`. This is the only non-string-literal code change.
## Data Models
No data-model changes. `Report`, `ReportOutline`, `ReportSection`, `ReportStatus`, `Task`, and the report API JSON contract are all preserved verbatim. `Report.to_dict()` and `ReportOutline.to_dict()` shapes are unchanged. The persistence schema under `reports/<id>/` (`meta.json`, `outline.json`, `progress.json`, `section_NN.md`, `full_report.md`, `agent_log.jsonl`, `console_log.txt`) is unchanged.
## Error Handling
### Error Strategy
No new error types or recovery strategies. The translated `_execute_tool` error returns and `plan_outline` exception-path fallback continue to behave identically — the only change is the string content.
### Error Categories and Responses
- **Unknown-tool error**: `_execute_tool` returns a translated English string `"Unknown tool: {tool_name}. Please use one of: insight_forge, panorama_search, quick_search"`. The string is fed back to the LLM as the next user-role observation.
- **Tool-execution exception**: `_execute_tool` returns a translated English string `"Tool execution failed: {str(e)}"`. Same flow.
- **`plan_outline` LLM exception**: returns the translated English fallback `ReportOutline` (3 sections). Downstream report assembly proceeds normally.
- **Empty-response retry / conflict-handling / insufficient-tools**: translated English messages re-injected into the LLM message stream (R6, R4 acceptance criteria). Loop control flow unchanged.
## Testing Strategy
### Default sections (adapted to translation work)
- **Static lint**: `python -m py_compile backend/app/services/report_agent.py` — must pass.
- **Zero-Chinese assertion** (in-scope regions): a verification harness (a small ad-hoc script under `scripts/` if needed, deleted before PR) imports `report_agent` and runs `re.findall(r'[一-鿿]', literal)` over each in-scope constant, expecting an empty list. The single permitted Chinese remnant is the `logger.debug` f-string at line 1322 (not in scope).
- **Interpolation-shape parity**: invoke `PLAN_USER_PROMPT_TEMPLATE.format(simulation_requirement="x", total_nodes=0, total_edges=0, entity_types=[], total_entities=0, related_facts_json="[]")`, `SECTION_SYSTEM_PROMPT_TEMPLATE.format(report_title="x", report_summary="y", simulation_requirement="z", section_title="t", tools_description="d")`, `SECTION_USER_PROMPT_TEMPLATE.format(previous_content="x", section_title="t")`, `CHAT_SYSTEM_PROMPT_TEMPLATE.format(simulation_requirement="x", report_content="r", tools_description="d")`, `REACT_OBSERVATION_TEMPLATE.format(tool_name="x", result="y", tool_calls_count=1, max_tool_calls=5, used_tools_str="a, b", unused_hint="z")`, etc. — each must render without raising `KeyError`.
- **Trigger-literal preservation**: assert that `"Final Answer:"` is a substring of the translated `SECTION_SYSTEM_PROMPT_TEMPLATE`, `SECTION_USER_PROMPT_TEMPLATE`, `REACT_OBSERVATION_TEMPLATE`, `REACT_TOOL_LIMIT_MSG`, and `REACT_FORCE_FINAL_MSG`; assert that `"<tool_call>"` is a substring of the translated `SECTION_SYSTEM_PROMPT_TEMPLATE` and `CHAT_SYSTEM_PROMPT_TEMPLATE`.
- **Tool-name preservation**: assert that all four primary tool names appear unchanged in the translated `_define_tools` keys and in the translated `TOOL_DESC_*` blocks.
- **End-to-end (deferred)**: per the precedent of issues #2/#3/#4, full pipeline runs under `Accept-Language: en` and `Accept-Language: zh` are not part of CI for this PR. Reviewer trust applies. If feasible in the implementer's local environment, a single sample run under `en` to confirm no Markdown headings leak into section bodies and a single sample run under `zh` to confirm Chinese output quality is preserved — both optional confidence boosters, not gates.
## Security Considerations
No new security surface. Translated prompts do not expose new endpoints, do not add new external calls, and do not change authorization semantics. The `_execute_tool` error returns continue to expose `str(e)` from any caught exception — pre-existing behavior, unchanged by this PR.
## Performance & Scalability
No performance regression expected. English prompts may be ~1030% longer in token count than the equivalent Chinese (English requires more tokens for the same semantic content), but this is well within the 4096 `max_tokens` ceiling on the section LLM call and the model's overall context budget. No caching, no batching, no concurrency change.
## Migration Strategy
No data or schema migration. The change is a single in-place edit. Rollback strategy: revert the single commit on `feat/i18n-5-translate-report-agent-prompts` if a regression is detected.
## Supporting References
- Detailed discovery, alternatives evaluation, decision rationale, and risk register: `.kiro/specs/i18n-report-agent-prompts/research.md`.
- Sibling spec (i18n-simulation-config-generator-prompts): `.kiro/specs/i18n-simulation-config-generator-prompts/{requirements,design,gap-analysis,research}.md`.
- Sibling commits: `0806832` (#2), `9d1d29b` (#3), `6c2a412` (#4).
- Ticket snapshot: `.ticket/5.md`.

View File

@ -0,0 +1,154 @@
# Gap Analysis — i18n-report-agent-prompts
## 1. Current-State Investigation
### Domain assets
- **Target file**: `backend/app/services/report_agent.py` (2572 lines).
- **Tool description constants** (LLM-facing, injected via `_get_tools_description`):
- `TOOL_DESC_INSIGHT_FORGE` (lines 476492)
- `TOOL_DESC_PANORAMA_SEARCH` (lines 494509)
- `TOOL_DESC_QUICK_SEARCH` (lines 511521)
- `TOOL_DESC_INTERVIEW_AGENTS` (lines 523548)
- **PLAN-phase prompts** (`plan_outline`, line ~1137):
- `PLAN_SYSTEM_PROMPT` (lines 552589) — `system_prompt = f"{PLAN_SYSTEM_PROMPT}\n\n{get_language_instruction()}"` at line 1166.
- `PLAN_USER_PROMPT_TEMPLATE` (lines 591611).
- **EXEC-phase prompts** (`_generate_section_react`, line ~1221):
- `SECTION_SYSTEM_PROMPT_TEMPLATE` (lines 615767) — appended postfix at line 1262.
- `SECTION_USER_PROMPT_TEMPLATE` (lines 769792).
- **ReACT loop conversation templates** (consumed inside `_generate_section_react`):
- `REACT_OBSERVATION_TEMPLATE` (796806)
- `REACT_INSUFFICIENT_TOOLS_MSG` (808811)
- `REACT_INSUFFICIENT_TOOLS_MSG_ALT` (813816)
- `REACT_TOOL_LIMIT_MSG` (818821)
- `REACT_UNUSED_TOOLS_HINT` (823)
- `REACT_FORCE_FINAL_MSG` (825)
- **CHAT-phase prompts** (`chat`, line ~1766):
- `CHAT_SYSTEM_PROMPT_TEMPLATE` (829855) — appended postfix at line 1808.
- `CHAT_OBSERVATION_SUFFIX` (857).
- **Inline LLM-visible Chinese strings** (sent into `messages`):
- `_define_tools` parameter-description dict values (925952).
- `_get_tools_description` leader `"可用工具:"` (1129).
- `_execute_tool` error returns `f"未知工具: {tool_name}..."` (1058) and `f"工具执行失败: {str(e)}"` (1062).
- `_generate_section_react`: `report_context = f"章节标题: ...\n模拟需求: ..."` (1294); empty-response messages `"(响应为空)"` / `"请继续生成内容。"` (13161317); conflict-handling block (13421346); inline `unused_hint` literals at 1380 and 1476.
- `chat`: report-truncated marker `"\n\n... [报告内容已截断] ..."` (1799); no-report fallback `"(暂无报告)"` (1805); observation joiner `f"[{r['tool']}结果]\n{r['result']}"` (1861).
- **Default / fallback outline content** in `plan_outline()`:
- Success-path default title `"模拟分析报告"` (1197).
- Exception-path fallback `ReportOutline` title `"未来预测报告"`, summary `"基于模拟预测的未来趋势与风险分析"`, three section titles (12121218).
- **Locale resolution**: `backend/app/utils/locale.py` `get_locale`/`get_language_instruction`/`t` resolves locale from `Accept-Language` header (or thread-local in background threads). `languages.json` registers `zh`, `en`, `es`, `fr`, `pt`, `ru`, `de`.
### Counts (verified, in-scope only)
| Region | Approx Chinese chars |
| --- | --- |
| `TOOL_DESC_INSIGHT_FORGE` | 110 |
| `TOOL_DESC_PANORAMA_SEARCH` | 95 |
| `TOOL_DESC_QUICK_SEARCH` | 50 |
| `TOOL_DESC_INTERVIEW_AGENTS` | 215 |
| `PLAN_SYSTEM_PROMPT` | 250 |
| `PLAN_USER_PROMPT_TEMPLATE` | 130 |
| `SECTION_SYSTEM_PROMPT_TEMPLATE` | 950 |
| `SECTION_USER_PROMPT_TEMPLATE` | 150 |
| `REACT_*` templates | 130 |
| `CHAT_SYSTEM_PROMPT_TEMPLATE` + `CHAT_OBSERVATION_SUFFIX` | 130 |
| `_define_tools` parameter dict values | 110 |
| `_execute_tool` error returns | 30 |
| `_generate_section_react` inline messages | 230 |
| `chat` inline messages | 60 |
| `plan_outline` defaults | 50 |
| **In-scope total** | **~2680** |
The ticket's "~609 Chinese characters" undercounts — it apparently only counted the three system-prompt blocks. The full LLM-message-stream Chinese surface is ~4× that. Logger calls (~17), docstrings, and module/class/method/inline `#` comments are out of scope (covered by #6 / #7).
### Conventions (extracted)
- Sister specs `i18n-ontology-generator-prompts` (commit `0806832`, issue #2), `i18n-oasis-profile-generator-prompts` (commit `9d1d29b`, issue #3), and `i18n-simulation-config-generator-prompts` (commit `6c2a412`, issue #4) established the pattern: **in-place translation of all LLM-facing string literals in a single file; preserve `get_language_instruction()` call sites; preserve all interpolations; do not touch logger, docstrings, comments, or other files.**
- 4-space indent, snake_case, double quotes for strings, `f"""..."""` for multi-line prompts. Existing Chinese-then-English mix is acceptable in comments/docstrings (steering tech.md: "preserve both; do not translate one into the other unless asked").
- No linter/formatter — match surrounding style.
- File mixes top-level constant prompts (e.g. `PLAN_SYSTEM_PROMPT`) with inline f-strings and `.format()` templates inside method bodies. Translation must respect both placement conventions.
### Integration surfaces
- `ReportAgent.generate_report(...)` is called from the report API blueprint (`api/report.py`). The returned `Report.to_dict()` payload is consumed by the frontend report panel; field shapes and types must remain unchanged.
- `ReportAgent.chat(...)` is called from the chat endpoint; the returned `{"response", "tool_calls", "sources"}` shape is consumed by the frontend chat UI.
- The four primary tools (`insight_forge`, `panorama_search`, `quick_search`, `interview_agents`) are dispatched in `_execute_tool` to `self.zep_tools.*` — those callees are unchanged.
- `_parse_tool_calls` matches the literal `<tool_call>...</tool_call>` XML tag and a fallback bare-JSON form via regex. Translation must preserve those literals byte-for-byte.
- `chat()` strips `<tool_call>` blocks from the user-visible response via `re.sub(r'<tool_call>.*?</tool_call>', '', ...)` (lines 1838, 1874). Translation does not affect this.
- `_clean_section_content` and `_post_process_report` post-process generated section content under the assumption that the LLM does not emit Markdown headings (`#`, `##`, `###`, etc.) inside section bodies. The translated `SECTION_SYSTEM_PROMPT_TEMPLATE` must continue to forbid headings.
- Locale-switching contract: when locale = `zh`, `get_language_instruction()` returns `请使用中文回答。`; when `en`, `Please respond in English.` — verified.
## 2. Requirement-to-Asset Map
| Requirement | Existing asset | Gap | Tag |
| --- | --- | --- | --- |
| R1 — PLAN prompts EN | `PLAN_SYSTEM_PROMPT` (552), `PLAN_USER_PROMPT_TEMPLATE` (591) | Translate text; preserve JSON schema (`title`, `summary`, `sections[]` w/ `title`, `description`); preserve 25 section count; preserve all interpolations | Missing (translation) |
| R2 — EXEC prompts EN | `SECTION_SYSTEM_PROMPT_TEMPLATE` (615), `SECTION_USER_PROMPT_TEMPLATE` (769) | Translate text; preserve `Final Answer:` / `<tool_call>` literals; preserve no-headings instruction; preserve language-consistency rule; preserve interpolation tokens | Missing (translation) |
| R3 — CHAT prompts EN | `CHAT_SYSTEM_PROMPT_TEMPLATE` (829), `CHAT_OBSERVATION_SUFFIX` (857) | Translate text; preserve `<tool_call>` literal; preserve `MAX_TOOL_CALLS_PER_CHAT` semantics | Missing (translation) |
| R4 — ReACT loop templates EN | `REACT_OBSERVATION_TEMPLATE` and 5 message constants (796825) | Translate text; preserve `Final Answer:` literal; preserve emoji/box-drawing visuals; reconcile `"、".join(...)` separator | Missing (translation) |
| R5 — Tool-description constants EN | 4 `TOOL_DESC_*` blocks (476548); `_define_tools` parameter dict (925952); `_get_tools_description` leader (1129) | Translate text; preserve tool-name literals; preserve parameter dict keys; preserve OASIS-running warning | Missing (translation) |
| R6 — Inline LLM-visible strings EN | 7 inline strings across `_generate_section_react` and `chat` (1294, 13161317, 13421346, 1380, 1476, 1799, 1805, 1861) | Translate text; preserve `{section.title}`, `{self.simulation_requirement}`, `{r['tool']}`, `{r['result']}`, `{', '.join(unused_tools)}` interpolations | Missing (translation) |
| R7 — `_execute_tool` error returns EN | 2 f-strings (1058, 1062) | Translate text; preserve `{tool_name}` and `{str(e)}` interpolations; remain locale-agnostic | Missing (translation) |
| R8 — `plan_outline` defaults EN | 1 success-path default (1197), 5 exception-path strings (12121218) | Translate text; remain locale-agnostic; preserve `ReportOutline` shape (3 sections) | Missing (translation) |
| R9 — Locale switching preserved | `get_language_instruction()` calls at 1166, 1262, 1808 | None — keep call sites untouched | Constraint |
| R10 — Public API stable | Class/method/dataclass surface | None — text-only changes | Constraint |
| R11 — End-to-end parity | API blueprint, frontend report panel, OASIS interview API | Verification only — `Report.to_dict()` shape unchanged | Constraint |
| R12 — Out-of-scope guardrails | logger calls (~17 in this file), docstrings, comments, all other files | None — leave untouched | Constraint |
### Unknown / Research-needed
- **R11 verification feasibility**: Running an end-to-end report generation flow under `Accept-Language: en` and `Accept-Language: zh` requires Neo4j, an LLM key, a populated graph, and a running OASIS simulation (for `interview_agents`). In a sandboxed CI-like environment, this is not practical. Defer to a lightweight fixture-based check, matching the precedent set by issues #2/#3/#4: (a) `python -m py_compile` lint pass on `report_agent.py`; (b) zero-Chinese assertion on the in-scope string set via a script that imports the module and inspects each constant + a regex sweep over the module source; (c) shape parity by constructing a mock `ReportAgent` and confirming `_get_tools_description()`, `system_prompt`, and `user_prompt` render to the expected interpolation set without raising. **Decision (autonomous run)**: adopt option (c) — reviewer-trust is the precedent for issues #2/#3/#4 and the scope here is identical (single-file translation).
- **`logger.debug(f"LLM响应: {response[:200]}...")` at line 1322**: This is the one raw-Chinese logger call in this file (all others use `t('...')`). It is OUT OF SCOPE for issue #5 — it falls under issue #6 (logger translation). Note for the reviewer: this leaves one Chinese f-string in `report_agent.py` after this PR; the acceptance criterion in the ticket explicitly carves out logger lines.
- **`SECTION_SYSTEM_PROMPT_TEMPLATE` includes a "正确示例" / "错误示例" code block (lines 678703) with embedded Chinese sample text** (`微博`, `抖音`, `校方` etc.). These are example illustrations of the formatting contract, not data. Translating them to English is required (R2 acceptance criterion 1: "zero Chinese characters"). The translated examples should still illustrate the same format rule (use `**bold**` not `##`, use `>` for block quotes, no headings).
## 3. Implementation Approach Options
### Option A — In-place translation (recommended)
**What**: Edit every Chinese string-literal in `backend/app/services/report_agent.py` directly, in place. No new files.
**Trade-offs**:
- ✅ Matches the precedent set by commits `0806832` (issue #2), `9d1d29b` (issue #3), and `6c2a412` (issue #4) — same file scope, same approach. Reviewer pattern recognition is the lowest possible.
- ✅ Smallest possible diff at the file system level (1 file).
- ✅ No new abstractions, no new files, no dependency churn.
- ❌ Translations are baked in — switching to `es`/`fr`/`pt`/`ru`/`de` still relies on the `get_language_instruction()` postfix to bias the model. (This is also true under the current Chinese-base baseline; not a regression.)
- ❌ The diff is non-trivial (~2680 chars to retranslate plus structural rewriting of the section system prompt). Reviewer must read the prompts side-by-side; line counts shift.
### Option B — Externalize prompts to `/locales/`
**What**: Move all prompt content to `locales/en.json` / `locales/zh.json` and look them up via `t('prompts.report.plan.system')` etc.
**Trade-offs**:
- ✅ Genuinely locale-agnostic prompts; could deliver native-quality Spanish, French, etc. with future translation work.
- ✅ Separates content from code, easing future prompt edits without code review.
- ❌ Diverges from the established pattern of issues #2/#3/#4 — those translated in place. Adopting a new pattern for the same kind of work re-opens architectural design questions and inflates this PR's blast radius.
- ❌ Touches `backend/app/utils/locale.py` (or its caller surface) and `/locales/`, which the spec's R12 and the ticket's "Out of scope" boundary explicitly forbid.
- ❌ Increases JSON-escape-hell risk for the section system prompt's literal `{{` and `}}` braces and triple-quote contents.
### Option C — Hybrid (top-level constants stay externalized; inline strings stay in code)
**What**: Externalize only the seven top-level prompt constants (`PLAN_*`, `SECTION_*`, `CHAT_*`, `TOOL_DESC_*`) to `/locales/`; translate inline f-strings in code in place.
**Trade-offs**:
- ✅ Captures the largest blocks (highest character count) in a localizable way.
- ❌ Two-tier inconsistency: some prompt content in `/locales/`, some in code. Future maintainers must trace both.
- ❌ Same R12 violation as Option B (touches `/locales/`).
- ❌ No precedent in the four sibling i18n efforts already in flight.
## 4. Implementation Complexity & Risk
- **Effort**: **M** (35 days for one focused engineer). Larger than the 247-char ticket estimate suggested, but smaller than a typical M because the work is mechanical translation with strict guardrails. Most of the work is high-quality English rewriting of the section system prompt (~950 Chinese chars, the largest block in the file), getting reviewer-acceptable phrasing for the "上帝视角" / "未来预演" framing, and verifying that the no-headings instruction stays semantically equivalent.
- **Risk**: **Low**. Familiar tech, established sibling-spec precedent, clear guardrails (R9R12), single file, no new dependencies, no API changes. The only non-trivial risk is a regression in `zh` quality if a translated prompt drops a structural cue the Chinese version was carrying — mitigated by preserving every interpolation, the JSON schema, the format-contract instructions, and the `get_language_instruction()` postfix.
## 5. Recommendations for Design Phase
- **Preferred approach**: **Option A — in-place translation in `backend/app/services/report_agent.py`.** Rationale: matches the four sibling i18n PRs, smallest blast radius, respects R12.
- **Key decisions to lock in design**:
1. Translation of the Chinese **examples inside** `SECTION_SYSTEM_PROMPT_TEMPLATE` (lines 678703): replace with semantically equivalent English illustrations of the same formatting contract (use `**bold**`, `>` block quotes, no headings).
2. Treatment of the `"、".join(unused_tools)` separator at line 1454 → switch to `", ".join(...)` for natural English rendering, since the join result is interpolated into now-English `REACT_OBSERVATION_TEMPLATE` and `REACT_UNUSED_TOOLS_HINT`.
3. Standard English phrasing for the recurring framing terms: `上帝视角` → "all-seeing observer", `未来预演` → "future rehearsal" (or "forecast simulation"), `模拟需求` → "simulation prompt" / "scenario brief", `上下文` → "context". Pick once, use everywhere.
4. Handling of the `_get_tools_description` leader (1129): English equivalent `"Available tools:"` (verified by precedent in `_build_context` translation in #4).
5. Treatment of the conflict-handling message (lines 13421346): keep the same two-mode contract, but rephrase in English while preserving the literal `<tool_call>` tag and `'Final Answer:'` mentions.
- **Research items to carry forward**:
1. Confirm that the `Final Answer:` literal is matched case-sensitively in `_generate_section_react` (it is — line 1327: `"Final Answer:" in response`). Translation must keep it byte-for-byte.
2. Confirm that no tooling outside this file consumes the Chinese fallback outline strings as keys (e.g. translation tables, frontend lookups). Quick grep confirms none do — the strings flow into `Report.title` / `ReportOutline.title` only.
3. Verify after translation that `python -m py_compile backend/app/services/report_agent.py` passes and that the file's net Chinese-character count drops to the 17 logger lines + docstrings + comments scope (i.e. zero Chinese in any string literal that is sent into an LLM messages array).

View File

@ -0,0 +1,221 @@
# Requirements Document
## Introduction
This specification covers the English translation of all LLM-facing prompt content in `backend/app/services/report_agent.py`. This module is the highest-impact prompt source for end-user output: it produces the final analytical report (Step 4 of the MiroFish pipeline) and powers the Report-Agent / Interview chat (Step 5). The runtime locale is steered by appending `get_language_instruction()` to each system prompt, but the base-prompt language biases the model's structural and lexical output. Today every prompt block in this file is authored in Chinese; even with the English postfix, the model's reasoning, sectioning, and tone skew Chinese under `Accept-Language: en`. Translating the base prompts to English removes that bias while preserving the existing locale-switching mechanism for non-English locales (verified: `get_language_instruction()` returns the Chinese postfix `请使用中文回答。` when locale is `zh`).
This work tracks GitHub issue [#5](https://github.com/salestech-group/MiroFish/issues/5).
## Boundary Context
- **In scope**:
- Translating to English the four tool-description constants exposed to the LLM via `_define_tools()` / `_get_tools_description()`:
- `TOOL_DESC_INSIGHT_FORGE` (lines ~476492)
- `TOOL_DESC_PANORAMA_SEARCH` (lines ~494509)
- `TOOL_DESC_QUICK_SEARCH` (lines ~511521)
- `TOOL_DESC_INTERVIEW_AGENTS` (lines ~523548)
- Translating the per-tool `parameters` dict values inside `_define_tools()` (lines ~925952) — these strings are concatenated into the `tools_description` interpolated into both the section and chat system prompts.
- Translating the leading literal `"可用工具:"` in `_get_tools_description()` (line ~1129).
- Translating the PLAN-phase prompts:
- `PLAN_SYSTEM_PROMPT` (lines ~552589)
- `PLAN_USER_PROMPT_TEMPLATE` (lines ~591611)
- Translating the EXEC-phase / section-generation prompts:
- `SECTION_SYSTEM_PROMPT_TEMPLATE` (lines ~615767)
- `SECTION_USER_PROMPT_TEMPLATE` (lines ~769792)
- Translating the ReACT loop conversation templates:
- `REACT_OBSERVATION_TEMPLATE` (lines ~796806)
- `REACT_INSUFFICIENT_TOOLS_MSG` (lines ~808811)
- `REACT_INSUFFICIENT_TOOLS_MSG_ALT` (lines ~813816)
- `REACT_TOOL_LIMIT_MSG` (lines ~818821)
- `REACT_UNUSED_TOOLS_HINT` (line ~823)
- `REACT_FORCE_FINAL_MSG` (line ~825)
- Translating the SUMMARIZE / Interview chat prompts:
- `CHAT_SYSTEM_PROMPT_TEMPLATE` (lines ~829855)
- `CHAT_OBSERVATION_SUFFIX` (line ~857)
- Translating the inline Chinese strings emitted into the LLM message stream by `_generate_section_react` and `chat`, specifically:
- `report_context = f"章节标题: ...\n模拟需求: ..."` (line ~1294)
- The empty-response placeholder `"(响应为空)"` and follow-up `"请继续生成内容。"` (lines ~13161317)
- The conflict-handling assistant→user message block at lines ~13421346
- The two inline `unused_hint` strings `f"(这些工具还未使用,推荐用一下他们: {...}"` (lines ~1380 and ~1476)
- The chat default-path placeholders `"(暂无报告)"` (line ~1805) and `"\n\n... [报告内容已截断] ..."` (line ~1799)
- The chat observation joiner format `f"[{r['tool']}结果]\n{r['result']}"` (line ~1861)
- Translating the `_execute_tool` user-visible error returns `f"未知工具: {tool_name}..."` and `f"工具执行失败: {str(e)}"` (lines ~1058 and ~1062) — these strings are returned as observations and re-fed into the LLM message stream.
- Translating the default / fallback outline content emitted by `plan_outline()`:
- The success-path default title `"模拟分析报告"` (line ~1197) used when the LLM returns a successful payload missing `title`.
- The exception-path fallback outline title/summary/section titles `"未来预测报告"`, `"基于模拟预测的未来趋势与风险分析"`, `"预测场景与核心发现"`, `"人群行为预测分析"`, `"趋势展望与风险提示"` (lines ~12121218).
- Preserving every `get_language_instruction()` call site exactly as today (line ~1166, ~1262, ~1808 — the three postfix injections that follow each system prompt).
- Preserving every variable interpolation token by name and position: `{simulation_requirement}`, `{total_nodes}`, `{total_edges}`, `{entity_types}`, `{total_entities}`, `{related_facts_json}`, `{report_title}`, `{report_summary}`, `{section_title}`, `{tools_description}`, `{previous_content}`, `{report_content}`, `{tool_name}`, `{result}`, `{tool_calls_count}`, `{max_tool_calls}`, `{used_tools_str}`, `{unused_hint}`, `{min_tool_calls}`, `{unused_list}`.
- Preserving the JSON output contract of `PLAN_SYSTEM_PROMPT` verbatim by key name: `title`, `summary`, `sections[]` with sub-keys `title`, `description`.
- Preserving the chat prefix-injection convention noted in `CLAUDE.md` — the section and chat loops strip `<tool_call>...</tool_call>` blocks from the user-visible response in `chat()` and reject mixed tool-call/Final-Answer outputs in `_generate_section_react`. The translated prompts must continue to instruct the model to obey this two-mode behavior.
- Preserving the `Final Answer:` literal English trigger string used to demarcate the final section content (the prompts already use this English literal; the translation must keep it byte-for-byte identical).
- Preserving the `<tool_call>` XML literal exactly (the parser in `_parse_tool_calls` matches it literally; the translation must keep it byte-for-byte identical).
- Preserving the tool names exactly: `insight_forge`, `panorama_search`, `quick_search`, `interview_agents` (and the legacy aliases `search_graph`, `get_graph_statistics`, `get_entity_summary`, `get_simulation_context`, `get_entities_by_type` referenced in `_execute_tool`).
- **Out of scope**:
- Logger messages (`logger.info`, `logger.warning`, `logger.error`, `logger.debug`) inside `report_agent.py` — covered by issue #6. (Most of these already use `t('...')` i18n keys; the few raw f-strings in the file are not in this PR.)
- Module docstring (lines 111), class docstrings (`ReportLogger`, `ReportConsoleLogger`, `Report`, `ReportOutline`, `ReportSection`, `ReportAgent`, `ReportManager`), method docstrings, and inline `#` comments — covered by issue #7.
- The post-processing markdown helpers `_clean_section_content` (line ~2132) and `_post_process_report` (line ~2301), which manipulate already-generated user-facing text; their `#` comments are #7 scope and they contain no Chinese string literals to translate.
- Refactoring the prompt structure, the JSON output schema of `PLAN_SYSTEM_PROMPT`, the ReACT loop control flow in `_generate_section_react`, the conflict-resolution branches, the chat `MAX_TOOL_CALLS_PER_CHAT` limit, or the tool-name-set validation in `_is_valid_tool_call`.
- Changing tool function names, signatures, return shapes, or the `zep_tools` adapter surface. The four primary tools (`insight_forge`, `panorama_search`, `quick_search`, `interview_agents`) remain identical in name, parameter schema, and return-text format.
- Changing `Report`, `ReportOutline`, `ReportSection`, `ReportStatus`, or `ReportManager` (persistence layer) — JSON shapes and file paths under `reports/<id>/` are unchanged.
- The `t('...')` i18n keys consumed by `progress_callback(...)` and `logger.*` calls — those already route through the locale registry and are #6 scope.
- **Adjacent expectations**:
- The locale resolution chain (`Accept-Language` header → `get_locale()``get_language_instruction()`) lives in `backend/app/utils/locale.py` and is unchanged.
- The `zep_tools` service (`backend/app/services/zep_tools.py`) and the OASIS interview API consumed by `interview_agents` are unchanged; only the prompt-side description of these tools is translated.
- Companion i18n issues (#2 closed, #3 closed, #4 closed/in-flight, #6, #7, #8, #9, #10, #11, #12) operate on different files or scopes and must not be touched here.
## Requirements
### Requirement 1: English Translation of the PLAN-Phase Prompts (Outline Planning)
**Objective:** As a MiroFish operator running the pipeline under `Accept-Language: en`, I want the outline-planning system prompt and user prompt to be authored in English, so that the LLM's outline title, summary, and section titles are not biased toward Chinese sectioning conventions or word choice.
#### Acceptance Criteria
1. The Report Agent shall render `PLAN_SYSTEM_PROMPT` containing zero Chinese characters in any string-literal content.
2. The Report Agent shall render `PLAN_USER_PROMPT_TEMPLATE` containing zero Chinese characters in any string-literal content.
3. The Report Agent shall preserve the JSON output contract of the plan prompt verbatim by key name: `title`, `summary`, `sections` (a list of objects with sub-keys `title` and `description`).
4. The Report Agent shall preserve the section-count constraint as expressed in the prompt: minimum 2 sections, maximum 5 sections.
5. The Report Agent shall preserve the variable interpolations `{simulation_requirement}`, `{total_nodes}`, `{total_edges}`, `{entity_types}`, `{total_entities}`, `{related_facts_json}` verbatim by name and position.
6. The Report Agent shall preserve the `get_language_instruction()` call exactly at the line where it is appended to the plan system prompt (currently line ~1166), in the same syntactic form: `system_prompt = f"{PLAN_SYSTEM_PROMPT}\n\n{get_language_instruction()}"`.
7. The Report Agent shall preserve the prompt's framing of the model as an "all-seeing observer" of the simulated world (`上帝视角` → "God's-eye view" or equivalent neutral English) producing a forecast/prediction report rather than an analysis of present-day events. The translated wording shall convey the same framing without changing the semantic distinction between "future prediction" and "current-state analysis".
### Requirement 2: English Translation of the EXEC-Phase Prompts (Per-Section ReACT Generation)
**Objective:** As a MiroFish operator running the pipeline under `Accept-Language: en`, I want the per-section ReACT system prompt and user prompt to be authored in English, so that the section content the model produces (including embedded quotations and structural markers) is not biased toward Chinese phrasing or sectioning.
#### Acceptance Criteria
1. The Report Agent shall render `SECTION_SYSTEM_PROMPT_TEMPLATE` containing zero Chinese characters in any string-literal content.
2. The Report Agent shall render `SECTION_USER_PROMPT_TEMPLATE` containing zero Chinese characters in any string-literal content.
3. The Report Agent shall preserve the variable interpolations `{report_title}`, `{report_summary}`, `{simulation_requirement}`, `{section_title}`, `{tools_description}`, `{previous_content}` verbatim by name and position.
4. The Report Agent shall preserve the `get_language_instruction()` call exactly at the line where it is appended to the section system prompt (currently line ~1262), in the same syntactic form: `system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"`.
5. The Report Agent shall preserve the per-section tool-call budget contract (≥3 calls, ≤5 calls per section) as expressed in the translated prompt, matching the runtime values `min_tool_calls = 3` and `MAX_TOOL_CALLS_PER_SECTION = 5`.
6. The Report Agent shall preserve the two-mode response contract: each LLM reply is either a single `<tool_call>...</tool_call>` block OR a single `Final Answer:`-prefixed body, never both. The translated prompt shall continue to instruct the model on this contract using the literal English trigger words `Final Answer:` and the literal XML tag `<tool_call>` exactly.
7. The Report Agent shall preserve the no-Markdown-headings instruction in section content (no `#`, `##`, `###`, `####`) and the recommendation to use `**bold**` plus `>` block-quotes for sub-emphasis. The translated prompt shall continue to forbid the model from emitting headings, since the post-processor `_clean_section_content` depends on this contract.
8. The Report Agent shall preserve the language-consistency instruction that quoted tool output be translated to the report language before being included in the section. The translated prompt shall convey the same instruction with reference to the active locale rather than a fixed language.
9. The Report Agent shall preserve the instruction that the model must call retrieval tools and may not author content from prior knowledge. The translated prompt shall preserve the literal tool-call format example block.
### Requirement 3: English Translation of the SUMMARIZE / Interview Chat Prompts
**Objective:** As a MiroFish operator chatting with the Report Agent under `Accept-Language: en`, I want the chat system prompt and observation suffix to be authored in English, so that the agent's chat replies are not biased toward Chinese tone or phrasing.
#### Acceptance Criteria
1. The Report Agent shall render `CHAT_SYSTEM_PROMPT_TEMPLATE` containing zero Chinese characters in any string-literal content.
2. The Report Agent shall render `CHAT_OBSERVATION_SUFFIX` containing zero Chinese characters in any string-literal content.
3. The Report Agent shall preserve the variable interpolations `{simulation_requirement}`, `{report_content}`, `{tools_description}` verbatim by name and position.
4. The Report Agent shall preserve the `get_language_instruction()` call exactly at the line where it is appended to the chat system prompt (currently line ~1808), in the same syntactic form: `system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"`.
5. The Report Agent shall preserve the chat tool-call budget contract (`MAX_TOOL_CALLS_PER_CHAT`, currently 12 per session) as expressed in the translated prompt.
6. The Report Agent shall preserve the literal `<tool_call>...</tool_call>` block format example used by the parser in `_parse_tool_calls`.
7. The Report Agent shall continue to strip `<tool_call>` blocks from the user-visible response via the existing regex in `chat()` (lines ~18381839 and ~18741875). The translated prompts shall not change this prefix-injection / suppression contract.
### Requirement 4: English Translation of the ReACT-Loop Conversation Templates
**Objective:** As a MiroFish operator running the section-generation loop, I want the user-role messages re-injected into the LLM during the ReACT loop to be authored in English, so that the loop does not re-introduce a Chinese language bias mid-conversation after a successful tool call.
#### Acceptance Criteria
1. The Report Agent shall render `REACT_OBSERVATION_TEMPLATE` containing zero Chinese characters in any string-literal content.
2. The Report Agent shall render `REACT_INSUFFICIENT_TOOLS_MSG`, `REACT_INSUFFICIENT_TOOLS_MSG_ALT`, `REACT_TOOL_LIMIT_MSG`, `REACT_UNUSED_TOOLS_HINT`, and `REACT_FORCE_FINAL_MSG` each containing zero Chinese characters in any string-literal content.
3. The Report Agent shall preserve the variable interpolations `{tool_name}`, `{result}`, `{tool_calls_count}`, `{max_tool_calls}`, `{used_tools_str}`, `{unused_hint}`, `{min_tool_calls}`, `{unused_list}` verbatim by name and position across these templates.
4. The Report Agent shall preserve the literal English trigger string `Final Answer:` exactly inside `REACT_OBSERVATION_TEMPLATE` and `REACT_TOOL_LIMIT_MSG` so that the existing parser branch `"Final Answer:" in response` continues to work.
5. The Report Agent shall preserve the existing emoji and box-drawing characters (`💡`, `═`) used as visual separators in these templates; only the surrounding natural-language Chinese tokens shall be translated.
6. The Report Agent shall preserve the joining separator used by `unused_tools_str = "、".join(unused_tools)` (line ~1454). If the translated `REACT_OBSERVATION_TEMPLATE` and the inline `unused_hint` literals (lines ~1380 and ~1476) are reformatted, the join separator may be replaced with a locale-agnostic English-friendly equivalent (e.g. `", "`) so long as the rendered output reads naturally in English; the existing `set` / `string.join` semantics shall not change.
### Requirement 5: English Translation of the Tool-Description Constants and `_define_tools` Parameter Hints
**Objective:** As a MiroFish operator running the report or chat loops, I want the four tool-description blocks injected into every section / chat system prompt to be authored in English, so that the model's choice of which tool to call is informed by English semantics matching the rest of the prompt.
#### Acceptance Criteria
1. The Report Agent shall render `TOOL_DESC_INSIGHT_FORGE`, `TOOL_DESC_PANORAMA_SEARCH`, `TOOL_DESC_QUICK_SEARCH`, and `TOOL_DESC_INTERVIEW_AGENTS` each containing zero Chinese characters in any string-literal content.
2. The Report Agent shall preserve the per-tool semantics as conveyed by each description: `insight_forge` is a deep multi-angle analytical retrieval; `panorama_search` is a breadth/timeline overview retrieval; `quick_search` is a lightweight literal-keyword retrieval; `interview_agents` is a real OASIS dual-platform agent-interview API. The translation may rephrase but shall not change which tool is best for which use case.
3. The Report Agent shall preserve the literal tool name strings `insight_forge`, `panorama_search`, `quick_search`, `interview_agents` byte-for-byte across all four description blocks and inside `_define_tools()`.
4. The Report Agent shall render the parameter-description string values inside `_define_tools()` (the values for `query`, `report_context`, `include_expired`, `limit`, `interview_topic`, `max_agents` per tool) in English with zero Chinese characters.
5. The Report Agent shall render the leading literal `"可用工具:"` in `_get_tools_description()` in English (e.g. `"Available tools:"`).
6. The Report Agent shall preserve the parameter dict keys (`query`, `report_context`, `include_expired`, `limit`, `interview_topic`, `max_agents`) byte-for-byte; only the value strings are translated.
7. The Report Agent shall preserve the operational-warning content in `TOOL_DESC_INTERVIEW_AGENTS` that flags the requirement for a running OASIS simulation environment.
### Requirement 6: English Translation of Inline LLM-Visible Strings in `_generate_section_react` and `chat`
**Objective:** As a MiroFish operator running either the section-generation loop or the chat loop, I want every Chinese string literal that is appended to the LLM `messages` array to be authored in English, so that the message stream the model sees is monolingual under `Accept-Language: en`.
#### Acceptance Criteria
1. The Report Agent shall render the `report_context` interpolation in `_generate_section_react` (currently `f"章节标题: {section.title}\n模拟需求: {self.simulation_requirement}"`, line ~1294) in English with zero Chinese characters, preserving the embedded `{section.title}` and `{self.simulation_requirement}` interpolations.
2. The Report Agent shall render the empty-response retry assistant placeholder `"(响应为空)"` (line ~1316) and the follow-up user prompt `"请继续生成内容。"` (line ~1317) in English with zero Chinese characters.
3. The Report Agent shall render the conflict-handling assistant→user message at lines ~13421346 (`"【格式错误】..."`) in English with zero Chinese characters, preserving the literal mention of `<tool_call>` and `'Final Answer:'`.
4. The Report Agent shall render the two inline `unused_hint` strings `f"(这些工具还未使用,推荐用一下他们: {', '.join(unused_tools)}"` (lines ~1380 and ~1476) in English with zero Chinese characters, preserving the `{', '.join(unused_tools)}` interpolation. The two sites shall remain syntactically equivalent (either both retain f-strings or both use `.format()`); they are not required to be byte-for-byte identical to each other but they shall convey the same hint.
5. The Report Agent shall render the chat default-path placeholders `"\n\n... [报告内容已截断] ..."` (line ~1799) and `"(暂无报告)"` (line ~1805) in English with zero Chinese characters.
6. The Report Agent shall render the chat observation joiner format `f"[{r['tool']}结果]\n{r['result']}"` (line ~1861) in English with zero Chinese characters, preserving the `{r['tool']}` and `{r['result']}` interpolations.
7. The Report Agent shall preserve the relative ordering of `messages.append(...)` calls in `_generate_section_react` and `chat`. No new messages shall be added or removed by this translation work.
### Requirement 7: English Translation of `_execute_tool` Error Returns
**Objective:** As a MiroFish operator hitting an unknown-tool or tool-execution-error code path during section generation or chat, I want the returned error string to be authored in English, so that the LLM's downstream observation is monolingual and the user-visible error trail is consistent under `Accept-Language: en`.
#### Acceptance Criteria
1. The Report Agent shall render `f"未知工具: {tool_name}。请使用以下工具之一: insight_forge, panorama_search, quick_search"` (line ~1058) in English with zero Chinese characters, preserving the `{tool_name}` interpolation and the literal tool-name list.
2. The Report Agent shall render `f"工具执行失败: {str(e)}"` (line ~1062) in English with zero Chinese characters, preserving the `{str(e)}` interpolation.
3. The translated error returns shall remain locale-agnostic English under both `en` and `zh` locales — they are not gated by `get_language_instruction()` and surface to the LLM identically regardless of locale.
### Requirement 8: English Translation of Default / Fallback Outline Content in `plan_outline()`
**Objective:** As a MiroFish operator running the pipeline when the LLM either returns a successful payload missing a `title` or raises an exception during outline planning, I want the default report title, summary, and section titles to be authored in English, so that the surfaced fallback report does not display Chinese under an `en` locale.
#### Acceptance Criteria
1. The Report Agent shall render the success-path default title (currently `"模拟分析报告"`, line ~1197) in English with zero Chinese characters.
2. The Report Agent shall render the exception-path fallback `ReportOutline` title, summary, and section titles (currently `"未来预测报告"`, `"基于模拟预测的未来趋势与风险分析"`, and `"预测场景与核心发现"`, `"人群行为预测分析"`, `"趋势展望与风险提示"` at lines ~12121218) in English with zero Chinese characters.
3. The translated fallback outline shall remain a single locale-agnostic English block — it shall not be conditioned on `get_locale()`. Rationale: the fallback is reached only on hard failure or schema gap; downstream report assembly under `Accept-Language: zh` will still display these strings, and that is acceptable because (a) the fallback is rare, and (b) the simulation_config_generator companion spec applied the same convention (issue #4) for its default-path strings.
4. The translated fallback outline shall preserve the existing structural shape: a `ReportOutline` with three `ReportSection` items (no field additions, removals, or count changes).
### Requirement 9: Locale Switching Continues to Work via `get_language_instruction()`
**Objective:** As a MiroFish operator running the pipeline under `Accept-Language: zh` (or any other configured non-English locale), I want the report and chat output to remain in the requested locale of equivalent quality, so that translating the base prompts does not regress non-English support.
#### Acceptance Criteria
1. The Report Agent shall preserve the three call sites of `get_language_instruction()` at the same logical positions (relative to each prompt block: PLAN line ~1166, SECTION line ~1262, CHAT line ~1808) and in the same syntactic form: `system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"`.
2. When the locale is `zh`, the Report Agent shall produce a final report whose section titles, section bodies, embedded quotations (translated from any language to Chinese as instructed by the prompt's language-consistency rule), and chat replies are in Chinese, equivalent in quality to the pre-change behaviour.
3. When the locale is `en`, the Report Agent shall produce the same set of natural-language fields in English.
4. The Report Agent shall not alter `backend/app/utils/locale.py`, the `_languages` registry, the `_translations` registry, or any file under `/locales/`.
5. Where a tool returns content in a language different from the active locale, the existing prompt-level instruction to translate quotations into the report language shall continue to apply unchanged.
### Requirement 10: Public API and Call-Site Stability
**Objective:** As a developer maintaining the rest of the MiroFish backend pipeline, I want the public surface of `ReportAgent` and `ReportManager` to remain unchanged, so that the report API blueprint (`api/report.py`) and the chat endpoint continue to work without modification.
#### Acceptance Criteria
1. The Report Agent shall preserve the signature of `ReportAgent.__init__(self, graph_id, simulation_id, simulation_requirement, llm_client=None, zep_tools=None, ...)`.
2. The Report Agent shall preserve the signatures of `ReportAgent.plan_outline(...)`, `ReportAgent.generate_report(...)`, `ReportAgent.chat(...)`, `ReportAgent._generate_section_react(...)`, `ReportAgent._execute_tool(...)`, `ReportAgent._define_tools(...)`, `ReportAgent._get_tools_description(...)`, `ReportAgent._parse_tool_calls(...)`, `ReportAgent._is_valid_tool_call(...)`.
3. The Report Agent shall preserve the dataclass-equivalent definitions `Report`, `ReportOutline`, `ReportSection`, `ReportStatus` and their `to_dict()` / `to_markdown()` shapes (no field additions, removals, renames, or default-value changes).
4. The Report Agent shall preserve the class-level constants `MAX_TOOL_CALLS_PER_SECTION`, `MAX_REFLECTION_ROUNDS`, `MAX_TOOL_CALLS_PER_CHAT`, `REPORTS_DIR` exactly.
5. The Report Agent shall preserve the LLM invocation parameters in `plan_outline()` (`temperature=0.3`), `_generate_section_react()` (`temperature=0.5`, `max_tokens=4096`), and `chat()` (`temperature=0.5`).
### Requirement 11: End-to-End Step 4 / Step 5 Parity
**Objective:** As a MiroFish operator validating the change, I want the report-generation and chat flows to produce coherent output under both `en` and `zh` locales, so that the translation does not silently degrade the analytical-report or interview-chat experience.
#### Acceptance Criteria
1. When a representative seed simulation requirement is processed end-to-end with locale `en`, `ReportAgent.generate_report(...)` shall return a `Report` with a non-empty title, non-empty summary, ≥2 and ≤5 sections, each section non-empty.
2. When the same flow is run with locale `zh`, `ReportAgent.generate_report(...)` shall return a `Report` whose natural-language fields are in Chinese and whose structural quality (section count, length, quotation presence) is at parity with the pre-change behaviour.
3. When `ReportAgent.chat(...)` is invoked with locale `en` against a generated report, the returned `response` field shall be in English and shall continue to suppress `<tool_call>` blocks in user-visible output.
4. When `ReportAgent.chat(...)` is invoked with locale `zh`, the returned `response` field shall be in Chinese.
5. The tool-call payload format observed by `zep_tools` (the parsed `{"name": ..., "parameters": ...}` dict) shall be unchanged by this work.
### Requirement 12: Out-of-Scope Surfaces Remain Untouched
**Objective:** As a reviewer of this PR, I want the change to remain narrowly scoped to LLM-facing prompt strings and the directly related inline message strings, so that translation responsibilities for adjacent surfaces (issues #6 and #7) and refactoring concerns are not absorbed into this change.
#### Acceptance Criteria
1. The change shall not modify any `logger.info(...)`, `logger.warning(...)`, `logger.error(...)`, or `logger.debug(...)` call in `report_agent.py` (covered by issue #6).
2. The change shall not modify the module docstring (lines 111), the class docstrings on `ReportLogger`, `ReportConsoleLogger`, `Report`, `ReportOutline`, `ReportSection`, `ReportAgent`, `ReportManager`, the dataclass docstrings, the method docstrings, or any inline `#` comment in `report_agent.py` (covered by issue #7).
3. The change shall not modify the persistence-layer methods in `ReportManager` (`_clean_section_content`, `_post_process_report`, `assemble_full_report`, `save_section`, `save_report`, `get_report`, `get_console_log`, `get_agent_log`, `update_progress`, `get_progress`, `get_generated_sections`, `delete_report`, `list_reports`, `get_report_by_simulation`).
4. The change shall not modify any file outside `backend/app/services/report_agent.py` for production code, except for adding test fixtures or scripts under a clearly-isolated directory if a verification harness is needed.
5. The change shall not introduce a new dependency or modify `backend/pyproject.toml` / `backend/uv.lock`.
6. The change shall not edit `backend/app/config.py`, `backend/app/services/zep_tools.py`, `backend/app/services/zep_entity_reader.py`, `backend/app/services/zep_graph_memory_updater.py`, `backend/app/utils/locale.py`, `backend/app/api/report.py`, or any file under `/locales/`.

View File

@ -0,0 +1,145 @@
# Research & Design Decisions — i18n-report-agent-prompts
## Summary
- **Feature**: `i18n-report-agent-prompts`
- **Discovery Scope**: Extension (single-file in-place translation, established sibling-spec precedent)
- **Key Findings**:
- The full LLM-message-stream Chinese surface in `report_agent.py` is ~2680 chars across 7 top-level prompt constants, 4 tool-description blocks, ~10 inline f-strings/templates inside `_generate_section_react` and `chat`, the `_execute_tool` error returns, and the `plan_outline` defaults — ~4× the ticket's "~609 char" estimate.
- The four sibling i18n PRs (#2/#3/#4 commits `0806832`, `9d1d29b`, `6c2a412`) established in-place translation in a single file as the pattern; reviewer expectations and PR shape are already locked in.
- Two cross-cutting literals must be preserved byte-for-byte: the trigger string `Final Answer:` (matched by `_generate_section_react` line 1327) and the XML tag `<tool_call>` (matched by `_parse_tool_calls` regex). All translated prompts continue to reference these literals exactly.
## Research Log
### Topic: How does locale switching work today and what guarantees does it give?
- **Context**: R9 of requirements depends on `get_language_instruction()` continuing to bias the model into the requested locale even after the base prompt is English.
- **Sources Consulted**: `backend/app/utils/locale.py`; `locales/languages.json`; `locales/en.json`; `locales/zh.json`; sibling-spec gap-analysis for issue #4 (`.kiro/specs/i18n-simulation-config-generator-prompts/gap-analysis.md`).
- **Findings**:
- `get_language_instruction()` resolves locale from the Flask `Accept-Language` header (or thread-local in background threads) and returns a per-locale postfix string (`Please respond in English.` for `en`, `请使用中文回答。` for `zh`, etc.).
- `languages.json` registers `zh`, `en`, `es`, `fr`, `pt`, `ru`, `de`. All non-`zh` postfixes are already in English.
- Sibling spec #4 verified that an English-base prompt + `请使用中文回答。` postfix produces Chinese output of equivalent quality to the prior Chinese-base prompt for the simulation-config flow. The same mechanism applies here — there is no report-agent-specific locale path.
- **Implications**: The translation does not need to touch `locale.py` or `/locales/*`. Preserving the three `get_language_instruction()` call sites verbatim is sufficient for R9.
### Topic: Which literal trigger strings does the ReACT loop parser match?
- **Context**: R2 acceptance criterion 6 and R4 acceptance criterion 4 require that translated prompts continue to use literal trigger strings the parser depends on.
- **Sources Consulted**: `backend/app/services/report_agent.py` lines 10671126 (`_parse_tool_calls`, `_is_valid_tool_call`); 1327 (`has_final_answer = "Final Answer:" in response`); 1838, 1874 (chat regex `<tool_call>.*?</tool_call>`).
- **Findings**:
- `Final Answer:` is matched as a Python literal substring (case-sensitive). Translation must keep this English token byte-for-byte.
- `<tool_call>` and `</tool_call>` are matched by `re.search(r'<tool_call>(.*?)</tool_call>', response, re.DOTALL)` (line 1080-ish, in `_parse_tool_calls`). Translation must keep these XML tags byte-for-byte.
- `_is_valid_tool_call` accepts both `{"name": ..., "parameters": ...}` and `{"tool": ..., "params": ...}` shapes, normalizing to `name`/`parameters`. Translation does not affect this.
- **Implications**: Translated prompts continue to instruct the model using the same literal example block; only the surrounding natural-language Chinese is rewritten in English.
### Topic: Are there Chinese illustrations embedded inside the section system prompt that must also translate?
- **Context**: `SECTION_SYSTEM_PROMPT_TEMPLATE` (615767) contains code-fenced "正确示例" / "错误示例" blocks with Chinese sample text. These are formatting-contract illustrations, not data.
- **Sources Consulted**: `report_agent.py` lines 678703.
- **Findings**:
- The "正确示例" block (lines 678694) shows a sample paragraph using `**bold**`, `>` block quotes, and lists — Chinese text demonstrating the no-headings rule.
- The "错误示例" block (lines 696703) shows wrong patterns (`## 执行摘要`, `### 一、首发阶段`, etc.) with Chinese text labeled as errors.
- These are illustrative only — the model uses them to internalize the format contract. Translating them to semantically equivalent English (sample paragraph about, e.g., a generic event using English bold/quotation/list patterns; wrong patterns showing English headings labeled as errors) preserves the contract.
- **Implications**: The section system prompt translation must rewrite both example blocks in English while keeping the structural rule (use `**bold**`, `>`, lists; do not use `#`, `##`, `###`, `####`).
### Topic: Are there Chinese strings that flow through `t(...)` keys (vs raw literals)?
- **Context**: R12 carves out `logger.*` calls already routed via `t('...')` (issue #6). Need to confirm we are not double-counting strings.
- **Sources Consulted**: `report_agent.py`; `locales/en.json`; `locales/zh.json`.
- **Findings**:
- 47 of 48 `logger.*` calls in `report_agent.py` already use `t('report.*')` or `t('progress.*')` keys — those are out of scope.
- One raw Chinese f-string remains: `logger.debug(f"LLM响应: {response[:200]}...")` at line 1322. This is a logger call (not a prompt string sent to the LLM). It belongs to issue #6, and leaving it untouched is consistent with the ticket boundary "logger calls in this file are covered by #6".
- `progress_callback(...)` calls receive `t('progress.*')` localized strings — those flow to the frontend, not to the LLM, and are out of scope.
- **Implications**: After translation, a single Chinese f-string in `report_agent.py` remains (line 1322 logger.debug). This is acceptable per the ticket's R12 carve-out.
### Topic: How do consumers downstream of `Report.to_dict()` and `chat()` deal with localized output?
- **Context**: R10 / R11 — preserving the public surface so the report API and the chat endpoint continue to work unchanged.
- **Sources Consulted**: `backend/app/api/report.py`; `frontend/src/api/report.js`; `frontend/src/components/Step5*.vue`.
- **Findings**:
- The report API blueprint hands `Report.to_dict()` and the chat response payload to the frontend without locale-specific post-processing. The frontend renders `report.title`, `report.summary`, and `report.sections[*].title/content` as plain text/Markdown.
- There are no string-equality checks against Chinese substrings on the consumer side. Translating the fallback outline to English is safe.
- **Implications**: R8 (translate fallback outline) and R10 (preserve surface) are independently verifiable — no consumer-side adaptation required.
## Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations | Notes |
|--------|-------------|-----------|---------------------|-------|
| In-place translation (A) | Edit Chinese string-literals in `report_agent.py` directly | Matches precedent of #2/#3/#4; minimal blast radius; no new files | Translations are baked in; non-`zh`/`en` locales still rely on postfix bias | **Selected** — same pattern, same scope |
| Externalize to `/locales/` (B) | Move all prompt content to `locales/en.json` / `locales/zh.json` and resolve via `t(...)` | Genuinely locale-agnostic; could later support es/fr/pt/ru/de natively | Touches `/locales/` (forbidden by R12); diverges from sibling pattern; brace-escape risk in JSON | Rejected — breaks R12 |
| Hybrid externalization (C) | Externalize top-level constants; keep inline f-strings in code | Captures largest blocks in localizable form | Two-tier inconsistency; same R12 violation; no precedent | Rejected — same R12 issue |
## Design Decisions
### Decision: In-place translation in a single file
- **Context**: Translate ~2680 Chinese characters across all LLM-facing string-literals in `backend/app/services/report_agent.py`.
- **Alternatives Considered**:
1. Externalize all prompts to `/locales/*.json` and resolve via `t(...)`.
2. Hybrid: externalize the seven top-level constants only.
- **Selected Approach**: In-place translation in `report_agent.py`. Edit each string-literal directly. No new files, no new abstractions.
- **Rationale**: Four sibling i18n PRs (issues #2/#3/#4) used the same pattern. Precedent is locked in; reviewer expectations are clear; PR shape is predictable. R12 explicitly forbids `/locales/` edits.
- **Trade-offs**: ✅ smallest blast radius, ✅ matches reviewer pattern. ❌ es/fr/pt/ru/de still rely on postfix bias (already true today; not a regression).
- **Follow-up**: Run `python -m py_compile backend/app/services/report_agent.py` post-edit; run a regex sweep verifying zero Chinese chars in any LLM-facing string-literal (the line-1322 `logger.debug` is exempt — issue #6).
### Decision: Translate embedded Chinese examples in `SECTION_SYSTEM_PROMPT_TEMPLATE`
- **Context**: The section system prompt contains "正确示例" / "错误示例" code blocks (lines 678703) with Chinese sample text. R2 AC1 demands zero Chinese in any string-literal content.
- **Alternatives Considered**:
1. Drop the example blocks entirely (shorter prompt, less guidance for the model).
2. Translate to semantically equivalent English illustrations.
3. Keep Chinese examples and append an English translation in parallel.
- **Selected Approach**: Translate to English. The "Correct Example" shows a sample paragraph about a generic scenario using `**bold**`, `>` block quotes, lists, and no headings. The "Wrong Example" shows wrong English headings (`## Executive Summary`, `### 1. First Stage`, etc.) labeled as errors.
- **Rationale**: The examples drive the model's understanding of the no-headings format contract. Removing them risks regressing format compliance. Parallel Chinese-then-English bloats the prompt and re-introduces Chinese tokens. English-only is the cleanest match for an English-base prompt.
- **Trade-offs**: ✅ preserves format contract, ✅ single-language base prompt. ❌ slight prompt-length growth (negligible vs total context).
- **Follow-up**: Spot-check a single end-to-end report run under `Accept-Language: en` to confirm the model still avoids Markdown headings in section bodies.
### Decision: Switch `"、".join(unused_tools)` to `", ".join(...)`
- **Context**: Line 1454 currently does `unused_tools_str = "、".join(unused_tools)`, where `、` is the Chinese enumeration comma. This list flows into `REACT_OBSERVATION_TEMPLATE` and into the inline f-strings at lines 1380 and 1476.
- **Alternatives Considered**:
1. Keep `"、"` (Chinese punctuation).
2. Switch to `", "` (English-friendly).
3. Keep `"、"` for `zh`, `", "` for `en` (locale-conditional).
- **Selected Approach**: Switch to `", "` unconditionally.
- **Rationale**: The join result is interpolated into the now-English ReACT templates. Keeping the Chinese enumeration comma in English context reads as a typo. Locale-conditional behavior here would re-introduce Chinese tokens into the message stream when `zh` is the locale (acceptable but inconsistent with the rest of the message). The model already follows `get_language_instruction()` for output, so the join punctuation does not need to localize.
- **Trade-offs**: ✅ natural English rendering, ✅ single code path. ❌ a `zh`-locale developer reading the code might find the all-English separator slightly off — minor stylistic concern only.
- **Follow-up**: None — this is a one-line change.
### Decision: Standard English phrasing for recurring framing terms
- **Context**: The Chinese prompts use recurring framing tokens that need consistent English equivalents. Inconsistent translations (e.g. "scenario" in one place, "brief" in another) hurt the prompt's coherence.
- **Selected Approach**: Pick once, use everywhere:
- 上帝视角 → "all-seeing observer's perspective" / "god's-eye view" (use the latter; shorter, more idiomatic)
- 未来预演 → "forecast simulation" / "simulated future"
- 模拟需求 → "simulation requirement" (matches the variable name `simulation_requirement`)
- 上下文 → "context"
- 章节 → "section"
- 报告 → "report"
- 大纲 → "outline"
- 正确示例 → "Correct Example"
- 错误示例 → "Wrong Example"
- 重要 → "IMPORTANT"
- 注意 → "Note"
- 工具 → "tool"
- 检索 → "retrieval"
- 章节标题 → "section title"
- 模拟世界 → "simulated world"
- 引用 → "quote" / "quotation"
- **Rationale**: Internal consistency lets the model build a coherent mental model of the task vocabulary. Aligning with variable names (e.g. `simulation_requirement`) reduces translation surface ambiguity.
- **Trade-offs**: ✅ consistent vocabulary across translated regions. ❌ none.
- **Follow-up**: None — list serves as a glossary for the implementer.
## Risks & Mitigations
- **Risk**: Translated section system prompt drops a structural cue the Chinese version was carrying, regressing `zh` quality. **Mitigation**: Preserve all interpolations, the JSON schema, the no-headings rule, the language-consistency rule, the format-contract examples (now in English), and the `get_language_instruction()` postfix. Spot-check a `zh` run if feasible.
- **Risk**: A Chinese substring slips through (e.g. inside a hard-to-spot ReACT message). **Mitigation**: Run a regex sweep `re.findall(r'[一-鿿]', source)` after the edit; the only allowed remaining match is the line-1322 `logger.debug` Chinese f-string.
- **Risk**: Reformatting `SECTION_SYSTEM_PROMPT_TEMPLATE` damages the literal `<tool_call>` example or shifts the `Final Answer:` token. **Mitigation**: Use targeted `Edit` replacements that preserve the surrounding code block; verify after edit that `"Final Answer:" in response` still triggers the parser branch.
- **Risk**: The `"、".join(...)` separator change leaks into a Chinese-language render path. **Mitigation**: The separator only flows into ReACT templates that are already monolingually English in this PR; no `zh`-specific render path consumes it.
## References
- Issue #5 ticket body: `.ticket/5.md`.
- Sibling spec: `.kiro/specs/i18n-simulation-config-generator-prompts/{requirements,design,gap-analysis}.md`.
- Sibling commits: `0806832` (#2 ontology), `9d1d29b` (#3 oasis profile), `6c2a412` (#4 simulation config).
- Locale module: `backend/app/utils/locale.py`.
- Locale registry: `locales/languages.json`, `locales/en.json`, `locales/zh.json`.

View File

@ -0,0 +1,27 @@
{
"feature_name": "i18n-report-agent-prompts",
"created_at": "2026-05-07T12:24:15Z",
"updated_at": "2026-05-07T12:24:15Z",
"language": "en",
"phase": "tasks-generated",
"approvals": {
"requirements": {
"generated": true,
"approved": true
},
"design": {
"generated": true,
"approved": true
},
"tasks": {
"generated": true,
"approved": true
}
},
"ready_for_implementation": true,
"ticket": {
"number": 5,
"snapshot": ".ticket/5.md",
"url": "https://github.com/salestech-group/MiroFish/issues/5"
}
}

View File

@ -0,0 +1,157 @@
# Implementation Plan
## 1. Foundation: stage a verification harness
- [x] 1.1 Stage a one-shot verification harness for prompt-string content
- Add a small, isolated verification script under `backend/scripts/` that, given the path to `report_agent.py`, asserts: (a) the file compiles via `py_compile`; (b) every in-scope LLM-facing string-literal contains zero `[一-鿿]` matches; (c) the literal trigger strings `Final Answer:` and `<tool_call>` are still present in the relevant translated templates; (d) the four primary tool names (`insight_forge`, `panorama_search`, `quick_search`, `interview_agents`) are still byte-equal in `_define_tools` and the four `TOOL_DESC_*` constants; (e) the three `get_language_instruction()` call sites are byte-equal at the same logical positions; (f) the only Chinese remaining in the module is in `logger.*` lines, `"""..."""` docstrings, or `#` comments (i.e. issue #6/#7 scope).
- Wire the script to be runnable via `cd backend && uv run python scripts/verify_report_agent_prompts.py`.
- Observable completion: running the script before any translation prints concrete failures (~2680 Chinese chars in in-scope regions); after translation it prints "all checks passed" and exits 0.
- _Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2, 4.1, 4.2, 5.1, 5.5, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 7.1, 7.2, 8.1, 8.2, 9.1, 12.1, 12.2_
## 2. Core: translate tool-description constants and `_define_tools` parameter hints
- [x] 2.1 Translate the four `TOOL_DESC_*` constants to English
- Rewrite `TOOL_DESC_INSIGHT_FORGE`, `TOOL_DESC_PANORAMA_SEARCH`, `TOOL_DESC_QUICK_SEARCH`, `TOOL_DESC_INTERVIEW_AGENTS` to English while preserving the per-tool semantics: `insight_forge` is deep multi-angle analytical retrieval; `panorama_search` is breadth/timeline overview retrieval; `quick_search` is lightweight literal-keyword retrieval; `interview_agents` is a real OASIS dual-platform agent-interview API.
- Preserve byte-for-byte the literal tool name mentions and the operational warning about needing a running OASIS environment in `TOOL_DESC_INTERVIEW_AGENTS`.
- Observable completion: harness from 1.1 reports zero Chinese in the four constants; tool-name byte-equality check passes.
- _Requirements: 5.1, 5.2, 5.3, 5.7_
- _Boundary: report_agent module-scope TOOL_DESC_* constants_
- [x] 2.2 Translate `_define_tools` parameter dict values and `_get_tools_description` leader
- Rewrite the parameter-description string values inside `_define_tools` (the values for `query`, `report_context`, `include_expired`, `limit`, `interview_topic`, `max_agents` per tool) to English. Preserve the parameter dict keys byte-for-byte.
- Rewrite the leading literal `"可用工具:"` in `_get_tools_description` to English (e.g. `"Available tools:"`).
- Observable completion: harness reports zero Chinese in `_define_tools` parameter values and in the `_get_tools_description` leader; calling `_get_tools_description()` on a stub `ReportAgent` instance returns a string starting with the English leader.
- _Requirements: 5.4, 5.5, 5.6_
- _Boundary: report_agent.ReportAgent._define_tools, _get_tools_description_
## 3. Core: translate the PLAN-phase prompts
- [x] 3.1 (P) Translate `PLAN_SYSTEM_PROMPT` and `PLAN_USER_PROMPT_TEMPLATE` to English
- Rewrite both constants to English while keeping the JSON output schema (`title`, `summary`, `sections[].title`, `sections[].description`), the 25 section count constraint, and the all-seeing-observer / forecast-simulation framing.
- Preserve every variable interpolation: `{simulation_requirement}`, `{total_nodes}`, `{total_edges}`, `{entity_types}`, `{total_entities}`, `{related_facts_json}`. Leave the `system_prompt = f"{PLAN_SYSTEM_PROMPT}\n\n{get_language_instruction()}"` injection at line 1166 untouched.
- Observable completion: harness reports zero Chinese in `PLAN_SYSTEM_PROMPT` and `PLAN_USER_PROMPT_TEMPLATE`; rendering `PLAN_USER_PROMPT_TEMPLATE.format(simulation_requirement="x", total_nodes=0, total_edges=0, entity_types=[], total_entities=0, related_facts_json="[]")` raises no `KeyError`.
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 9.1_
- _Boundary: report_agent module-scope PLAN_SYSTEM_PROMPT, PLAN_USER_PROMPT_TEMPLATE_
- [x] 3.2 Translate `plan_outline` default / fallback outline content to English
- Replace the success-path default title `"模拟分析报告"` (line 1197) with a locale-agnostic English equivalent (e.g. `"Simulation Analysis Report"`).
- Replace the exception-path fallback `ReportOutline` content (lines 12121218): title `"未来预测报告"` → e.g. `"Future Prediction Report"`; summary `"基于模拟预测的未来趋势与风险分析"` → e.g. `"Trend and risk analysis based on simulation predictions"`; three section titles `"预测场景与核心发现"`, `"人群行为预测分析"`, `"趋势展望与风险提示"` → e.g. `"Scenario and Key Findings"`, `"Population Behavior Predictions"`, `"Trend Outlook and Risk Notes"`.
- Preserve the existing `ReportOutline` shape: 3 `ReportSection` items, no field additions/removals.
- Observable completion: forcing `plan_outline()` into the exception path (e.g. by stubbing `self.llm.chat_json` to raise) returns a `ReportOutline` whose title, summary, and section titles are locale-agnostic English; harness reports zero Chinese in lines 1197, 12121218.
- _Requirements: 8.1, 8.2, 8.3, 8.4_
- _Boundary: report_agent.ReportAgent.plan_outline_
## 4. Core: translate the EXEC-phase prompts (section ReACT)
- [x] 4.1 Translate `SECTION_SYSTEM_PROMPT_TEMPLATE` to English (incl. embedded examples)
- Rewrite the template to English while preserving: every `{report_title}`, `{report_summary}`, `{simulation_requirement}`, `{section_title}`, `{tools_description}` interpolation; the no-headings rule (no `#`, `##`, `###`, `####`); the language-consistency rule for translating quoted tool output to the report language; the must-call-tools instruction with min 3 / max 5 calls; the two-mode reply contract; the literal `Final Answer:` trigger string; the literal `<tool_call>...</tool_call>` example block; the box-drawing `═` separators and the `⚠️` / `❌` / `✅` markers.
- Translate the embedded "正确示例" / "错误示例" code blocks (lines 678703) to semantically equivalent English illustrations: the "Correct Example" should show a sample paragraph using `**bold**`, `>` block quotes, and lists (no headings); the "Wrong Example" should show wrong English headings (`## Executive Summary`, `### 1. First Stage`, etc.) labelled as errors.
- Leave the `system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"` injection at line 1262 untouched.
- Observable completion: harness reports zero Chinese in `SECTION_SYSTEM_PROMPT_TEMPLATE`; `Final Answer:` and `<tool_call>` literals are present byte-equal; rendering with stub interpolations raises no `KeyError`.
- _Requirements: 2.1, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 9.1_
- _Boundary: report_agent module-scope SECTION_SYSTEM_PROMPT_TEMPLATE_
- [x] 4.2 (P) Translate `SECTION_USER_PROMPT_TEMPLATE` to English
- Rewrite the template to English while preserving the `{previous_content}` and `{section_title}` interpolations. Note that `{section_title}` is referenced twice — once as a literal in the do-not-write-as-opening warning and once as a body reference; both must be retained.
- Preserve the must-call-tools / mix-tools / no-headings reminders. Preserve the closing three-step instruction (think → call tool → output Final Answer).
- Observable completion: harness reports zero Chinese in `SECTION_USER_PROMPT_TEMPLATE`; rendering `SECTION_USER_PROMPT_TEMPLATE.format(previous_content="x", section_title="t")` raises no `KeyError` and the rendered string contains `t` in two places.
- _Requirements: 2.2, 2.3, 2.7_
- _Boundary: report_agent module-scope SECTION_USER_PROMPT_TEMPLATE_
## 5. Core: translate the ReACT loop conversation templates
- [x] 5.1 Translate `REACT_OBSERVATION_TEMPLATE` and the five `REACT_*_MSG` constants to English
- Rewrite `REACT_OBSERVATION_TEMPLATE`, `REACT_INSUFFICIENT_TOOLS_MSG`, `REACT_INSUFFICIENT_TOOLS_MSG_ALT`, `REACT_TOOL_LIMIT_MSG`, `REACT_UNUSED_TOOLS_HINT`, `REACT_FORCE_FINAL_MSG` to English.
- Preserve the `{tool_name}`, `{result}`, `{tool_calls_count}`, `{max_tool_calls}`, `{used_tools_str}`, `{unused_hint}`, `{min_tool_calls}`, `{unused_list}` interpolations across these templates. Preserve the `Final Answer:` literal trigger inside `REACT_OBSERVATION_TEMPLATE` and `REACT_TOOL_LIMIT_MSG`.
- Preserve the emoji and box-drawing characters (`💡`, `═`).
- Observable completion: harness reports zero Chinese in the six `REACT_*` constants; `Final Answer:` substring check passes for the two templates that reference it; rendering `REACT_OBSERVATION_TEMPLATE.format(tool_name="x", result="y", tool_calls_count=1, max_tool_calls=5, used_tools_str="a, b", unused_hint="z")` raises no `KeyError`.
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
- _Boundary: report_agent module-scope REACT_* constants_
- [x] 5.2 Switch the `unused_tools_str` join separator at line 1454 from `"、"` to `", "`
- Change `unused_tools_str = "、".join(unused_tools)` to `unused_tools_str = ", ".join(unused_tools)` so the result reads naturally inside the now-English `REACT_OBSERVATION_TEMPLATE`.
- Observable completion: a grep over `report_agent.py` for `"、"` returns zero matches; `unused_tools_str` rendered with two stub tool names yields `"insight_forge, panorama_search"` (English-friendly).
- _Requirements: 4.6_
- _Boundary: report_agent.ReportAgent._generate_section_react_
## 6. Core: translate the CHAT-phase prompts
- [x] 6.1 Translate `CHAT_SYSTEM_PROMPT_TEMPLATE` and `CHAT_OBSERVATION_SUFFIX` to English
- Rewrite both constants to English while preserving the `{simulation_requirement}`, `{report_content}`, `{tools_description}` interpolations and the literal `<tool_call>...</tool_call>` example block.
- Preserve the chat tool-budget hint (`MAX_TOOL_CALLS_PER_CHAT` semantics: 12 per session) and the answer-style instructions (concise, lead with conclusion, use `>` for quoted material).
- Leave the `system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"` injection at line 1808 untouched.
- Observable completion: harness reports zero Chinese in both constants; `<tool_call>` substring check passes; rendering `CHAT_SYSTEM_PROMPT_TEMPLATE.format(simulation_requirement="x", report_content="r", tools_description="d")` raises no `KeyError`.
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 9.1_
- _Boundary: report_agent module-scope CHAT_SYSTEM_PROMPT_TEMPLATE, CHAT_OBSERVATION_SUFFIX_
## 7. Core: translate inline LLM-visible strings inside `_generate_section_react` and `chat`
- [x] 7.1 Translate the inline strings in `_generate_section_react` to English
- Replace `report_context = f"章节标题: {section.title}\n模拟需求: {self.simulation_requirement}"` (line 1294) with an English equivalent (e.g. `f"Section title: {section.title}\nSimulation requirement: {self.simulation_requirement}"`), preserving both interpolations.
- Replace the empty-response retry messages `"(响应为空)"` (line 1316) and `"请继续生成内容。"` (line 1317) with English equivalents (e.g. `"(empty response)"` and `"Please continue generating content."`).
- Replace the conflict-handling assistant→user message at lines 13421346 with an English equivalent that preserves the literal mention of `<tool_call>` and `'Final Answer:'` and the two-mode contract (call one tool OR output Final Answer; never both).
- Replace the inline `unused_hint` literals at lines 1380 and 1476 (`f"(这些工具还未使用,推荐用一下他们: {', '.join(unused_tools)}"`) with English equivalents (e.g. `f"(These tools have not been used yet, you may try them: {', '.join(unused_tools)})"`), preserving the `{', '.join(unused_tools)}` interpolation. Both sites should convey the same hint and remain syntactically equivalent.
- Observable completion: harness reports zero Chinese in `_generate_section_react` outside of `logger.*`, docstrings, and `#` comments; the four targeted regions render with their interpolations intact.
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.7_
- _Boundary: report_agent.ReportAgent._generate_section_react_
- [x] 7.2 (P) Translate the inline strings in `chat` to English
- Replace `"\n\n... [报告内容已截断] ..."` (line 1799) with an English equivalent (e.g. `"\n\n... [report content truncated] ..."`).
- Replace `"(暂无报告)"` (line 1805) with an English equivalent (e.g. `"(no report yet)"`).
- Replace the observation joiner format `f"[{r['tool']}结果]\n{r['result']}"` (line 1861) with an English equivalent (e.g. `f"[{r['tool']} result]\n{r['result']}"`), preserving the `{r['tool']}` and `{r['result']}` interpolations.
- Observable completion: harness reports zero Chinese in `chat` outside of `logger.*`, docstrings, and `#` comments; the three targeted regions render with their interpolations intact.
- _Requirements: 6.5, 6.6, 6.7_
- _Boundary: report_agent.ReportAgent.chat_
## 8. Core: translate `_execute_tool` error returns
- [x] 8.1 Translate the `_execute_tool` error returns to English
- Replace `f"未知工具: {tool_name}。请使用以下工具之一: insight_forge, panorama_search, quick_search"` (line 1058) with an English equivalent (e.g. `f"Unknown tool: {tool_name}. Please use one of: insight_forge, panorama_search, quick_search"`), preserving the `{tool_name}` interpolation and the literal tool-name list.
- Replace `f"工具执行失败: {str(e)}"` (line 1062) with an English equivalent (e.g. `f"Tool execution failed: {str(e)}"`), preserving the `{str(e)}` interpolation.
- Both translated strings remain locale-agnostic English (no `get_language_instruction()` injection at this site).
- Observable completion: harness reports zero Chinese in lines 1058 and 1062; both error returns are locale-agnostic English; the literal tool-name list is byte-equal.
- _Requirements: 7.1, 7.2, 7.3_
- _Boundary: report_agent.ReportAgent._execute_tool_
## 9. Validation: locale and integration checks
- [x] 9.1 Confirm `get_language_instruction()` call sites are byte-equal at lines 1166, 1262, 1808
- After translation, run the harness from 1.1; it must verify that the three `system_prompt = f"{...}\n\n{get_language_instruction()}"` injection lines remain unchanged in syntactic form (the only allowed deltas are inside `{...}` itself, which the prompt-content checks already covered).
- Observable completion: harness prints a "locale-postfix injection unchanged at lines 1166/1262/1808" line and exits 0.
- _Requirements: 1.6, 2.4, 3.4, 9.1_
- _Depends: 3.1, 4.1, 6.1_
- [x] 9.2 Confirm public-API and constants are byte-stable
- Programmatically inspect the module after translation and confirm: `ReportAgent.__init__`, `plan_outline`, `generate_report`, `chat`, `_generate_section_react`, `_execute_tool`, `_define_tools`, `_get_tools_description`, `_parse_tool_calls`, `_is_valid_tool_call` all retain their existing parameter names and return annotations; the dataclass-equivalent definitions `Report`, `ReportOutline`, `ReportSection`, `ReportStatus` are unchanged; the class-level constants `MAX_TOOL_CALLS_PER_SECTION`, `MAX_REFLECTION_ROUNDS`, `MAX_TOOL_CALLS_PER_CHAT`, `REPORTS_DIR` are unchanged.
- Inspection can be by `inspect.signature` checks plus `re.search` for the constant declarations.
- Observable completion: a single signature/constant-stability check runs from the harness and prints "public surface stable" before exit.
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5_
- _Depends: 3.1, 4.1, 6.1_
- [x] 9.3 Confirm out-of-scope guardrails: logger calls, docstrings, comments, adjacent files
- Run a targeted check that confirms: every `logger.info`/`logger.warning`/`logger.error`/`logger.debug` call line retains its pre-existing Chinese content (no translation creep into #6's scope) — the line-1322 `logger.debug(f"LLM响应: ...")` is the canary; `"""..."""` docstrings (module, classes `ReportLogger`, `ReportConsoleLogger`, `Report`, `ReportOutline`, `ReportSection`, `ReportAgent`, `ReportManager`, dataclasses, methods) retain their pre-existing Chinese content (no translation creep into #7's scope); `git status` shows only `backend/app/services/report_agent.py` (and optionally `backend/scripts/verify_report_agent_prompts.py`) modified — no edits to `backend/app/config.py`, `backend/app/services/zep_tools.py`, `backend/app/utils/locale.py`, `backend/app/api/report.py`, `/locales/`, `backend/pyproject.toml`, or `backend/uv.lock`.
- Observable completion: a check prints "out-of-scope guardrails respected" listing the count of Chinese chars remaining in logger lines (>0 expected) and in docstrings (>0 expected) as positive indicators; `git status` is clean except for the two allowed paths.
- _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6_
- _Depends: 3.1, 3.2, 4.1, 4.2, 5.1, 5.2, 6.1, 7.1, 7.2, 8.1_
- [x] 9.4 Locale-switching static evidence: `en` and `zh`
- Sandbox lacks runtime dependencies for an end-to-end report run. Substitute runtime smoke with **static evidence** that locale switching is preserved: (a) harness check confirms `get_language_instruction()` call-site count is exactly 3 at the expected logical positions; (b) harness check confirms the three injection lines are syntactically byte-equal in form; (c) `git status` confirms `backend/app/utils/locale.py` and `locales/*.json` are unchanged. Together these guarantee that under `Accept-Language: en` the postfix `Please respond in English.` continues to be appended and under `Accept-Language: zh` the postfix `请使用中文回答。` continues to be appended at the same call sites with no semantic delta. Sister specs (#2, #3, #4) used the same static-only posture.
- Observable completion: harness exits 0 with all three checks reported as PASS.
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
- _Depends: 3.1, 4.1, 6.1_
- [ ] 9.5* Optional fixture-based render-shape parity check
- Build a stub `ReportAgent` (with stubbed `zep_tools` and `llm`) and patch the LLM client to return well-shaped JSON for `plan_outline()` and well-shaped tool-call + Final-Answer responses for `_generate_section_react()`. Run `generate_report(...)` end-to-end against the stub. Assert that the returned `Report` has a non-empty title, non-empty summary, ≥2 and ≤5 sections, each section non-empty.
- Confirms R11 functional coverage without depending on a live Neo4j / OASIS environment. Marked optional because R10 + R11.5 already lock the shape stability via guard checks (9.2) and design-level reasoning; this is auxiliary belt-and-braces test coverage.
- Observable completion: a single fixture-based test prints the `Report.to_dict()` keys and asserts the non-emptiness invariants; exits 0.
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5_
- _Depends: 3.1, 3.2, 4.1, 4.2, 5.1, 5.2, 6.1, 7.1, 7.2, 8.1_
## 10. Cleanup
- [x] 10.1 Remove or move the verification harness as appropriate
- If the verification harness from 1.1 is intended as a one-shot check, delete `backend/scripts/verify_report_agent_prompts.py` after the implementation passes its checks. If it is intended as a permanent regression test, keep it under `backend/scripts/` and ensure it is callable via `uv run python scripts/verify_report_agent_prompts.py` with no test framework required.
- Decision rule: keep the harness only if it costs less than 30 lines and reads as a usable smoke check; otherwise remove it. Sister specs (#2, #3, #4) shipped without permanent harnesses, so the default is "remove."
- Observable completion: `git status` shows only `backend/app/services/report_agent.py` modified, with no harness artefacts left behind (preferred); or, if kept, the harness lives under `backend/scripts/` with a one-line module docstring linking back to spec `i18n-report-agent-prompts`.
- _Requirements: 12.4_
- _Depends: 9.1, 9.2, 9.3, 9.4_

View File

@ -474,387 +474,387 @@ class Report:
# ── 工具描述 ──
TOOL_DESC_INSIGHT_FORGE = """\
深度洞察检索 - 强大的检索工具
这是我们强大的检索函数专为深度分析设计它会
1. 自动将你的问题分解为多个子问题
2. 从多个维度检索模拟图谱中的信息
3. 整合语义搜索实体分析关系链追踪的结果
4. 返回最全面最深度的检索内容
[Deep Insight Retrieval Powerful Analytical Tool]
This is our most powerful retrieval function, designed for deep analysis. It will:
1. Automatically decompose your question into multiple sub-questions.
2. Retrieve information from the simulation graph along multiple dimensions.
3. Integrate semantic search, entity analysis, and relationship-chain tracing.
4. Return the most comprehensive and in-depth retrieval content.
使用场景
- 需要深入分析某个话题
- 需要了解事件的多个方面
- 需要获取支撑报告章节的丰富素材
[When to use]
- You need an in-depth analysis of a topic.
- You need to understand multiple facets of an event.
- You need rich source material to support a report section.
返回内容
- 相关事实原文可直接引用
- 核心实体洞察
- 关系链分析"""
[Return content]
- Relevant factual quotes (ready to cite verbatim).
- Core entity insights.
- Relationship-chain analysis."""
TOOL_DESC_PANORAMA_SEARCH = """\
广度搜索 - 获取全貌视图
这个工具用于获取模拟结果的完整全貌特别适合了解事件演变过程它会
1. 获取所有相关节点和关系
2. 区分当前有效的事实和历史/过期的事实
3. 帮助你了解舆情是如何演变的
[Panorama Search Full-View Retrieval]
This tool retrieves a complete panorama of the simulation result, ideal for understanding how an event evolved. It will:
1. Pull every related node and relationship.
2. Distinguish currently valid facts from historical or expired facts.
3. Help you trace how public opinion evolved over time.
使用场景
- 需要了解事件的完整发展脉络
- 需要对比不同阶段的舆情变化
- 需要获取全面的实体和关系信息
[When to use]
- You need the full timeline of an event.
- You need to compare opinion shifts between different stages.
- You need a comprehensive view of all entities and relationships.
返回内容
- 当前有效事实模拟最新结果
- 历史/过期事实演变记录
- 所有涉及的实体"""
[Return content]
- Currently valid facts (the latest simulation state).
- Historical or expired facts (the evolution record).
- Every entity involved."""
TOOL_DESC_QUICK_SEARCH = """\
简单搜索 - 快速检索
轻量级的快速检索工具适合简单直接的信息查询
[Quick Search Lightweight Retrieval]
A lightweight retrieval tool, best for simple, direct lookups.
使用场景
- 需要快速查找某个具体信息
- 需要验证某个事实
- 简单的信息检索
[When to use]
- You need a quick lookup for a specific piece of information.
- You need to verify a single fact.
- Simple information retrieval.
返回内容
- 与查询最相关的事实列表"""
[Return content]
- A list of facts most relevant to the query."""
TOOL_DESC_INTERVIEW_AGENTS = """\
深度采访 - 真实Agent采访双平台
调用OASIS模拟环境的采访API对正在运行的模拟Agent进行真实采访
这不是LLM模拟而是调用真实的采访接口获取模拟Agent的原始回答
默认在Twitter和Reddit两个平台同时采访获取更全面的观点
[Deep Interview Real Agent Interview (Dual Platform)]
Calls the OASIS simulation environment's interview API to conduct a real interview against the running simulation agents.
This is NOT an LLM simulation it invokes the real interview endpoint and returns the simulated agents' raw answers.
By default it interviews on both Twitter and Reddit in parallel, capturing more diverse viewpoints.
功能流程
1. 自动读取人设文件了解所有模拟Agent
2. 智能选择与采访主题最相关的Agent如学生媒体官方等
3. 自动生成采访问题
4. 调用 /api/simulation/interview/batch 接口在双平台进行真实采访
5. 整合所有采访结果提供多视角分析
How it works:
1. Reads the persona files automatically to learn about every simulated agent.
2. Selects the agents most relevant to the interview topic (students, media, officials, etc.).
3. Generates the interview questions automatically.
4. Calls the /api/simulation/interview/batch endpoint on both platforms.
5. Integrates all interview results to provide a multi-perspective view.
使用场景
- 需要从不同角色视角了解事件看法学生怎么看媒体怎么看官方怎么说
- 需要收集多方意见和立场
- 需要获取模拟Agent的真实回答来自OASIS模拟环境
- 想让报告更生动包含"采访实录"
[When to use]
- You need to understand an event from different role perspectives (what do students think? What does the media say? What is the official line?).
- You need to collect multi-party opinions and stances.
- You need real answers from simulated agents (sourced from the OASIS simulation environment).
- You want the report to feel vivid and include first-hand "interview transcripts".
返回内容
- 被采访Agent的身份信息
- 各Agent在Twitter和Reddit两个平台的采访回答
- 关键引言可直接引用
- 采访摘要和观点对比
[Return content]
- The interviewee agent's identity information.
- Each agent's interview answers on both Twitter and Reddit.
- Key quotations (ready to cite verbatim).
- Interview summary and viewpoint comparison.
重要需要OASIS模拟环境正在运行才能使用此功能"""
[IMPORTANT] A running OASIS simulation environment is required to use this tool!"""
# ── 大纲规划 prompt ──
PLAN_SYSTEM_PROMPT = """\
你是一个未来预测报告的撰写专家拥有对模拟世界的上帝视角你可以洞察模拟中每一位Agent的行为言论和互动
You are an expert author of "Future Prediction Reports" with a god's-eye view of the simulated world — you can observe the behavior, statements, and interactions of every agent in the simulation.
核心理念
我们构建了一个模拟世界并向其中注入了特定的模拟需求作为变量模拟世界的演化结果就是对未来可能发生情况的预测你正在观察的不是"实验数据"而是"未来的预演"
[Core idea]
We have built a simulated world and injected a specific "simulation requirement" into it as the input variable. The way that simulated world evolves is itself a prediction of what could happen in reality. You are not looking at "experimental data" you are watching a rehearsal of the future.
你的任务
撰写一份未来预测报告回答
1. 在我们设定的条件下未来发生了什么
2. 各类Agent人群是如何反应和行动
3. 这个模拟揭示了哪些值得关注的未来趋势和风险
[Your task]
Author a "Future Prediction Report" that answers:
1. Under the conditions we configured, what happened in the future?
2. How did the various agent groups (populations) react and behave?
3. What noteworthy future trends and risks does this simulation reveal?
报告定位
- 这是一份基于模拟的未来预测报告揭示"如果这样,未来会怎样"
- 聚焦于预测结果事件走向群体反应涌现现象潜在风险
- 模拟世界中的Agent言行就是对未来人群行为的预测
- 不是对现实世界现状的分析
- 不是泛泛而谈的舆情综述
[Report framing]
- This is a prediction report grounded in a simulation; it reveals "if X, then what does the future look like".
- Focus on the predicted outcomes: how the event evolves, group reactions, emergent phenomena, latent risks.
- Treat the simulated agents' statements and behavior as the prediction of how real-world populations would behave.
- This is NOT an analysis of the present-day world.
- This is NOT a generic public-opinion summary.
章节数量限制
- 最少2个章节最多5个章节
- 不需要子章节每个章节直接撰写完整内容
- 内容要精炼聚焦于核心预测发现
- 章节结构由你根据预测结果自主设计
[Section-count limits]
- Minimum 2 sections, maximum 5 sections.
- No sub-sections each section is written as a single block of content.
- Keep the content focused; concentrate on the core prediction findings.
- You design the section structure freely based on the prediction outcomes.
请输出JSON格式的报告大纲格式如下
Return the report outline as JSON in the following shape:
{
"title": "报告标题",
"summary": "报告摘要(一句话概括核心预测发现)",
"title": "Report title",
"summary": "Report summary (a one-sentence distillation of the core prediction findings)",
"sections": [
{
"title": "章节标题",
"description": "章节内容描述"
"title": "Section title",
"description": "Section content description"
}
]
}
注意sections数组最少2个最多5个元素"""
Note: the `sections` array MUST contain at least 2 and at most 5 elements!"""
PLAN_USER_PROMPT_TEMPLATE = """\
预测场景设定
我们向模拟世界注入的变量模拟需求{simulation_requirement}
[Prediction scenario]
The variable we injected into the simulated world (simulation requirement): {simulation_requirement}
模拟世界规模
- 参与模拟的实体数量: {total_nodes}
- 实体间产生的关系数量: {total_edges}
- 实体类型分布: {entity_types}
- 活跃Agent数量: {total_entities}
[Simulation scale]
- Total entities participating in the simulation: {total_nodes}
- Total relationships generated between entities: {total_edges}
- Entity-type distribution: {entity_types}
- Active agent count: {total_entities}
模拟预测到的部分未来事实样本
[Sample of predicted future facts from the simulation]
{related_facts_json}
请以上帝视角审视这个未来预演
1. 在我们设定的条件下未来呈现出了什么样的状态
2. 各类人群Agent是如何反应和行动的
3. 这个模拟揭示了哪些值得关注的未来趋势
Take the god's-eye view of this future rehearsal:
1. Under the conditions we configured, what state does the future reveal?
2. How did the various populations (agents) react and behave?
3. What noteworthy future trends does this simulation reveal?
根据预测结果设计最合适的报告章节结构
Based on these prediction outcomes, design the most appropriate section structure for the report.
再次提醒报告章节数量最少2个最多5个内容要精炼聚焦于核心预测发现"""
[Reminder] Section count: minimum 2, maximum 5; keep the content tight and focused on the core prediction findings."""
# ── 章节生成 prompt ──
SECTION_SYSTEM_PROMPT_TEMPLATE = """\
你是一个未来预测报告的撰写专家正在撰写报告的一个章节
You are an expert author of "Future Prediction Reports" and you are currently writing one section of the report.
报告标题: {report_title}
报告摘要: {report_summary}
预测场景模拟需求: {simulation_requirement}
Report title: {report_title}
Report summary: {report_summary}
Prediction scenario (simulation requirement): {simulation_requirement}
当前要撰写的章节: {section_title}
Section to write right now: {section_title}
核心理念
[Core idea]
模拟世界是对未来的预演我们向模拟世界注入了特定条件模拟需求
模拟中Agent的行为和互动就是对未来人群行为的预测
The simulated world is a rehearsal of the future. We injected specific conditions (the simulation requirement) into it,
and the agents' behavior and interactions in that simulation are themselves a prediction of how real populations would behave.
你的任务是
- 揭示在设定条件下未来发生了什么
- 预测各类人群Agent是如何反应和行动的
- 发现值得关注的未来趋势风险和机会
Your task is to:
- Reveal what happened in the future under the configured conditions.
- Predict how each population (agent group) reacted and behaved.
- Surface the noteworthy future trends, risks, and opportunities.
不要写成对现实世界现状的分析
要聚焦于"未来会怎样"模拟结果就是预测的未来
Do not write a present-day analysis of the real world.
Stay focused on "what does the future look like" the simulation outcome IS the predicted future.
最重要的规则 - 必须遵守
[Most important rules MUST follow]
1. 必须调用工具观察模拟世界
- 你正在以上帝视角观察未来的预演
- 所有内容必须来自模拟世界中发生的事件和Agent言行
- 禁止使用你自己的知识来编写报告内容
- 每个章节至少调用3次工具最多5次来观察模拟的世界它代表了未来
1. [You MUST call tools to observe the simulated world]
- You are watching the future rehearsal from a god's-eye view.
- All content MUST come from events and agent statements/behavior in the simulated world.
- Do NOT use your own prior knowledge to author report content.
- Each section MUST call retrieval tools at least 3 times (and at most 5 times) to observe the simulated world, which represents the future.
2. 必须引用Agent的原始言行
- Agent的发言和行为是对未来人群行为的预测
- 在报告中使用引用格式展示这些预测例如
> "某类人群会表示:原文内容..."
- 这些引用是模拟预测的核心证据
2. [You MUST quote agents' raw statements and behavior]
- The agents' speech and actions ARE the prediction of how real populations would behave.
- Render these predictions in the report using block-quote format, for example:
> "A specific population would say: <verbatim quote>..."
- These quotations are the core evidence of the simulation's prediction.
3. 语言一致性 - 引用内容必须翻译为报告语言
- 工具返回的内容可能包含与报告语言不同的表述
- 报告必须全部使用与用户指定语言一致的语言撰写
- 当你引用工具返回的其他语言内容时必须将其翻译为报告语言后再写入
- 翻译时保持原意不变确保表述自然通顺
- 这一规则同时适用于正文和引用块> 格式中的内容
3. [Language consistency translate quoted material into the report language]
- Tool results may contain text in a language that differs from the report language.
- The report MUST be authored entirely in the language requested by the user.
- When you quote tool output that is in a different language, translate it into the report language before writing it in.
- Preserve the original meaning during translation; the rendered text must read naturally.
- This rule applies both to body text and to block-quote (>) content.
4. 忠实呈现预测结果
- 报告内容必须反映模拟世界中的代表未来的模拟结果
- 不要添加模拟中不存在的信息
- 如果某方面信息不足如实说明
4. [Faithfully render the prediction outcomes]
- The report content MUST reflect the simulated outcomes that represent the future.
- Do NOT add information that does not exist in the simulation.
- If the simulation lacks coverage of an aspect, say so honestly.
格式规范 - 极其重要
[ Formatting rules extremely important!]
一个章节 = 最小内容单位
- 每个章节是报告的最小分块单位
- 禁止在章节内使用任何 Markdown 标题#、##、###、#### 等)
- 禁止在内容开头添加章节主标题
- 章节标题由系统自动添加你只需撰写纯正文内容
- 使用**粗体**段落分隔引用列表来组织内容但不要用标题
[One section = the smallest unit of content]
- Each section is the smallest content block in the report.
- Do NOT use any Markdown heading (#, ##, ###, ####, etc.) inside the section.
- Do NOT prepend the section's main heading at the start of the content.
- The section title is added by the system automatically you write only the body content.
- Use **bold**, paragraph breaks, block quotes, and lists to organize the content but no headings.
正确示例
[Correct example]
```
本章节分析了事件的舆论传播态势通过对模拟数据的深入分析我们发现...
This section analyzes how public opinion propagated around the event. A close reading of the simulated data reveals that...
**首发引爆阶段**
**Initial-spark stage**
微博作为舆情的第一现场承担了信息首发的核心功能
Platform A served as the first venue for the news, fulfilling its core role as a launcher of viral information:
> "微博贡献了68%的首发声量..."
> "Platform A produced 68% of the first-wave volume..."
**情绪放大阶段**
**Emotional-amplification stage**
抖音平台进一步放大了事件影响力
Platform B further amplified the event's reach:
- 视觉冲击力强
- 情绪共鸣度高
- Strong visual impact
- High emotional resonance
```
错误示例
[Wrong example]
```
## 执行摘要 ← 错误!不要添加任何标题
### 一、首发阶段 ← 错误!不要用###分小节
#### 1.1 详细分析 ← 错误!不要用####细分
## Executive Summary ← Wrong! Do not add any heading.
### 1. Initial-spark stage ← Wrong! Do not use ### for sub-sections.
#### 1.1 Detailed analysis ← Wrong! Do not use #### either.
本章节分析了...
This section analyzes...
```
可用检索工具每章节调用3-5
[Available retrieval tools] (35 calls per section)
{tools_description}
工具使用建议 - 请混合使用不同工具不要只用一种
- insight_forge: 深度洞察分析自动分解问题并多维度检索事实和关系
- panorama_search: 广角全景搜索了解事件全貌时间线和演变过程
- quick_search: 快速验证某个具体信息点
- interview_agents: 采访模拟Agent获取不同角色的第一人称观点和真实反应
[Tool-usage guidance mix different tools; do not rely on just one]
- insight_forge: deep analytical retrieval; auto-decomposes the question and pulls facts and relationships from multiple angles.
- panorama_search: wide-angle panoramic search; reveals the full picture of an event, its timeline, and how it evolved.
- quick_search: quick verification of a specific information point.
- interview_agents: interview the simulated agents to capture first-person viewpoints and authentic reactions across roles.
工作流程
[Workflow]
每次回复你只能做以下两件事之一不可同时做
Each reply may do exactly ONE of the following two things (never both):
选项A - 调用工具
输出你的思考然后用以下格式调用一个工具
Option A Call a tool:
Write your reasoning, then invoke one tool using the format below:
<tool_call>
{{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}}
{{"name": "<tool name>", "parameters": {{"<param name>": "<param value>"}}}}
</tool_call>
系统会执行工具并把结果返回给你你不需要也不能自己编写工具返回结果
The system will run the tool and return its result. You do NOT need to (and MUST not) author the tool result yourself.
选项B - 输出最终内容
当你已通过工具获取了足够信息 "Final Answer:" 开头输出章节内容
Option B Output the final content:
Once you have gathered enough information through tool calls, output the section content prefixed with "Final Answer:".
严格禁止
- 禁止在一次回复中同时包含工具调用和 Final Answer
- 禁止自己编造工具返回结果Observation所有工具结果由系统注入
- 每次回复最多调用一个工具
Strictly forbidden:
- Do NOT include both a tool call and a Final Answer in the same reply.
- Do NOT fabricate tool results (Observation) yourself; all tool results are injected by the system.
- Each reply may invoke at most one tool.
章节内容要求
[Section-content requirements]
1. 内容必须基于工具检索到的模拟数据
2. 大量引用原文来展示模拟效果
3. 使用Markdown格式但禁止使用标题
- 使用 **粗体文字** 标记重点代替子标题
- 使用列表-或1.2.3.组织要点
- 使用空行分隔不同段落
- 禁止使用 #、##、###、#### 等任何标题语法
4. 引用格式规范 - 必须单独成段
引用必须独立成段前后各有一个空行不能混在段落中
1. The content MUST be grounded in the simulated data retrieved by the tools.
2. Quote source material liberally to make the simulation's predictions vivid.
3. Use Markdown formatting (but no headings):
- Use **bold** to emphasize key points (instead of sub-headings).
- Use lists (- or 1./2./3.) to organize bullet points.
- Separate paragraphs with blank lines.
- Do NOT use #, ##, ###, #### — no heading syntax of any kind.
4. [Quotation format must stand alone as its own paragraph]
A block quote MUST be its own paragraph, with a blank line above and below; do not embed it inside another paragraph:
正确格式
Correct format:
```
校方的回应被认为缺乏实质内容
The university's response was widely viewed as substanceless.
> "校方的应对模式在瞬息万变的社交媒体环境中显得僵化和迟缓。"
> "The university's response pattern reads as rigid and slow in a fast-moving social-media environment."
这一评价反映了公众的普遍不满
This assessment captures the public's broad dissatisfaction.
```
错误格式
Wrong format:
```
校方的回应被认为缺乏实质内容> "校方的应对模式..." 这一评价反映了...
The university's response was widely viewed as substanceless. > "The university's response pattern..." This assessment captures...
```
5. 保持与其他章节的逻辑连贯性
6. 避免重复仔细阅读下方已完成的章节内容不要重复描述相同的信息
7. 再次强调不要添加任何标题**粗体**代替小节标题"""
5. Maintain logical continuity with the other sections.
6. [Avoid repetition] Read the already-completed section content below carefully and do not repeat the same information.
7. [Reminder] Do NOT add any headings! Use **bold** instead of sub-section titles."""
SECTION_USER_PROMPT_TEMPLATE = """\
已完成的章节内容请仔细阅读避免重复
Already-completed section content (read carefully to avoid repeating yourself):
{previous_content}
当前任务撰写章节: {section_title}
[Current task] Write section: {section_title}
重要提醒
1. 仔细阅读上方已完成的章节避免重复相同的内容
2. 开始前必须先调用工具获取模拟数据
3. 请混合使用不同工具不要只用一种
4. 报告内容必须来自检索结果不要使用自己的知识
[Important reminders]
1. Read the already-completed sections above carefully and avoid repeating the same content.
2. You MUST call a retrieval tool first to obtain simulated data before writing.
3. Mix different tools do not rely on a single one.
4. The report content MUST come from the retrieval results; do not use your own prior knowledge.
格式警告 - 必须遵守
- 不要写任何标题#、##、###、####都不行)
- 不要写"{section_title}"作为开头
- 章节标题由系统自动添加
- 直接写正文**粗体**代替小节标题
[ Formatting warning MUST follow]
- Do NOT write any heading (no #, ##, ###, or ####).
- Do NOT write "{section_title}" as the opening line.
- The section title is added by the system automatically.
- Write the body directly; use **bold** instead of sub-section titles.
请开始
1. 首先思考Thought这个章节需要什么信息
2. 然后调用工具Action获取模拟数据
3. 收集足够信息后输出 Final Answer纯正文无任何标题"""
Get started:
1. First think (Thought) about what information this section needs.
2. Then call a tool (Action) to retrieve the simulated data.
3. Once you have gathered enough information, output the body prefixed with Final Answer: (plain body, no headings)."""
# ── ReACT 循环内消息模板 ──
REACT_OBSERVATION_TEMPLATE = """\
Observation检索结果:
Observation (retrieval result):
工具 {tool_name} 返回
Tool {tool_name} returned
{result}
已调用工具 {tool_calls_count}/{max_tool_calls} 已用: {used_tools_str}{unused_hint}
- 如果信息充分 "Final Answer:" 开头输出章节内容必须引用上述原文
- 如果需要更多信息调用一个工具继续检索
Tool calls so far: {tool_calls_count}/{max_tool_calls} (used: {used_tools_str}){unused_hint}
- If you have enough information: output the section content prefixed with "Final Answer:" (you MUST quote the source material above).
- If you need more information: call one more tool to continue retrieving.
"""
REACT_INSUFFICIENT_TOOLS_MSG = (
"【注意】你只调用了{tool_calls_count}次工具,至少需要{min_tool_calls}次。"
"请再调用工具获取更多模拟数据,然后再输出 Final Answer。{unused_hint}"
"[Note] You have only called tools {tool_calls_count} times; at least {min_tool_calls} are required. "
"Call more tools to gather simulation data, then output Final Answer.{unused_hint}"
)
REACT_INSUFFICIENT_TOOLS_MSG_ALT = (
"当前只调用了 {tool_calls_count} 次工具,至少需要 {min_tool_calls} 次。"
"请调用工具获取模拟数据。{unused_hint}"
"Only {tool_calls_count} tool calls so far; at least {min_tool_calls} are required. "
"Please call a tool to retrieve simulation data.{unused_hint}"
)
REACT_TOOL_LIMIT_MSG = (
"工具调用次数已达上限({tool_calls_count}/{max_tool_calls}),不能再调用工具。"
'请立即基于已获取的信息,以 "Final Answer:" 开头输出章节内容。'
"Tool-call budget exhausted ({tool_calls_count}/{max_tool_calls}); no more tool calls allowed. "
'Now, based on the information you have already gathered, output the section content prefixed with "Final Answer:".'
)
REACT_UNUSED_TOOLS_HINT = "\n💡 你还没有使用过: {unused_list},建议尝试不同工具获取多角度信息"
REACT_UNUSED_TOOLS_HINT = "\n💡 You haven't used: {unused_list} yet — try a different tool to get a multi-angle view."
REACT_FORCE_FINAL_MSG = "已达到工具调用限制,请直接输出 Final Answer: 并生成章节内容。"
REACT_FORCE_FINAL_MSG = "Tool-call limit reached. Please output Final Answer: directly and produce the section content."
# ── Chat prompt ──
CHAT_SYSTEM_PROMPT_TEMPLATE = """\
你是一个简洁高效的模拟预测助手
You are a concise and efficient simulation-prediction assistant.
背景
预测条件: {simulation_requirement}
[Background]
Prediction conditions: {simulation_requirement}
已生成的分析报告
[Generated analytical report]
{report_content}
规则
1. 优先基于上述报告内容回答问题
2. 直接回答问题避免冗长的思考论述
3. 仅在报告内容不足以回答时才调用工具检索更多数据
4. 回答要简洁清晰有条理
[Rules]
1. Prefer answering from the report above.
2. Answer the question directly; avoid lengthy meta-reasoning.
3. Only call tools when the report does not contain enough information to answer.
4. Keep your answers concise, clear, and well-structured.
可用工具仅在需要时使用最多调用1-2
[Available tools] (use only when needed; at most 12 calls)
{tools_description}
工具调用格式
[Tool-call format]
<tool_call>
{{"name": "工具名称", "parameters": {{"参数名": "参数值"}}}}
{{"name": "<tool name>", "parameters": {{"<param name>": "<param value>"}}}}
</tool_call>
回答风格
- 简洁直接不要长篇大论
- 使用 > 格式引用关键内容
- 优先给出结论再解释原因"""
[Answer style]
- Concise and direct no long-form prose.
- Use the > format to quote the key source material.
- Lead with the conclusion, then explain the rationale."""
CHAT_OBSERVATION_SUFFIX = "\n\n请简洁回答问题。"
CHAT_OBSERVATION_SUFFIX = "\n\nPlease answer the question concisely."
# ═══════════════════════════════════════════════════════════════
@ -923,32 +923,32 @@ class ReportAgent:
"name": "insight_forge",
"description": TOOL_DESC_INSIGHT_FORGE,
"parameters": {
"query": "你想深入分析的问题或话题",
"report_context": "当前报告章节的上下文(可选,有助于生成更精准的子问题)"
"query": "The question or topic you want to analyze in depth.",
"report_context": "Current report-section context (optional; helps generate sharper sub-questions)."
}
},
"panorama_search": {
"name": "panorama_search",
"description": TOOL_DESC_PANORAMA_SEARCH,
"parameters": {
"query": "搜索查询,用于相关性排序",
"include_expired": "是否包含过期/历史内容默认True"
"query": "Search query, used for relevance ranking.",
"include_expired": "Whether to include expired/historical content (default True)."
}
},
"quick_search": {
"name": "quick_search",
"description": TOOL_DESC_QUICK_SEARCH,
"parameters": {
"query": "搜索查询字符串",
"limit": "返回结果数量可选默认10"
"query": "Search query string.",
"limit": "Number of results to return (optional, default 10)."
}
},
"interview_agents": {
"name": "interview_agents",
"description": TOOL_DESC_INTERVIEW_AGENTS,
"parameters": {
"interview_topic": "采访主题或需求描述(如:'了解学生对宿舍甲醛事件的看法'",
"max_agents": "最多采访的Agent数量可选默认5最大10"
"interview_topic": "Interview topic or requirement (e.g. 'Understand student opinion on the dorm formaldehyde incident').",
"max_agents": "Maximum number of agents to interview (optional; default 5, max 10)."
}
}
}
@ -1055,11 +1055,11 @@ class ReportAgent:
return json.dumps(result, ensure_ascii=False, indent=2)
else:
return f"未知工具: {tool_name}。请使用以下工具之一: insight_forge, panorama_search, quick_search"
return f"Unknown tool: {tool_name}. Please use one of: insight_forge, panorama_search, quick_search"
except Exception as e:
logger.error(t('report.toolExecFailed', toolName=tool_name, error=str(e)))
return f"工具执行失败: {str(e)}"
return f"Tool execution failed: {str(e)}"
# 合法的工具名称集合,用于裸 JSON 兜底解析时校验
VALID_TOOL_NAMES = {"insight_forge", "panorama_search", "quick_search", "interview_agents"}
@ -1126,12 +1126,12 @@ class ReportAgent:
def _get_tools_description(self) -> str:
"""生成工具描述文本"""
desc_parts = ["可用工具:"]
desc_parts = ["Available tools:"]
for name, tool in self.tools.items():
params_desc = ", ".join([f"{k}: {v}" for k, v in tool["parameters"].items()])
desc_parts.append(f"- {name}: {tool['description']}")
if params_desc:
desc_parts.append(f" 参数: {params_desc}")
desc_parts.append(f" Parameters: {params_desc}")
return "\n".join(desc_parts)
def plan_outline(
@ -1194,7 +1194,7 @@ class ReportAgent:
))
outline = ReportOutline(
title=response.get("title", "模拟分析报告"),
title=response.get("title", "Simulation Analysis Report"),
summary=response.get("summary", ""),
sections=sections
)
@ -1209,12 +1209,12 @@ class ReportAgent:
logger.error(t('report.outlinePlanFailed', error=str(e)))
# 返回默认大纲3个章节作为fallback
return ReportOutline(
title="未来预测报告",
summary="基于模拟预测的未来趋势与风险分析",
title="Future Prediction Report",
summary="Trend and risk analysis grounded in simulation predictions.",
sections=[
ReportSection(title="预测场景与核心发现"),
ReportSection(title="人群行为预测分析"),
ReportSection(title="趋势展望与风险提示")
ReportSection(title="Scenario and Key Findings"),
ReportSection(title="Population Behavior Predictions"),
ReportSection(title="Trend Outlook and Risk Notes")
]
)
@ -1270,7 +1270,7 @@ class ReportAgent:
previous_parts.append(truncated)
previous_content = "\n\n---\n\n".join(previous_parts)
else:
previous_content = "(这是第一个章节)"
previous_content = "(This is the first section.)"
user_prompt = SECTION_USER_PROMPT_TEMPLATE.format(
previous_content=previous_content,
@ -1291,7 +1291,7 @@ class ReportAgent:
all_tools = {"insight_forge", "panorama_search", "quick_search", "interview_agents"}
# 报告上下文用于InsightForge的子问题生成
report_context = f"章节标题: {section.title}\n模拟需求: {self.simulation_requirement}"
report_context = f"Section title: {section.title}\nSimulation requirement: {self.simulation_requirement}"
for iteration in range(max_iterations):
if progress_callback:
@ -1313,8 +1313,8 @@ class ReportAgent:
logger.warning(t('report.sectionIterNone', title=section.title, iteration=iteration + 1))
# 如果还有迭代次数,添加消息并重试
if iteration < max_iterations - 1:
messages.append({"role": "assistant", "content": "(响应为空)"})
messages.append({"role": "user", "content": "请继续生成内容。"})
messages.append({"role": "assistant", "content": "(empty response)"})
messages.append({"role": "user", "content": "Please continue generating content."})
continue
# 最后一次迭代也返回 None跳出循环进入强制收尾
break
@ -1339,11 +1339,11 @@ class ReportAgent:
messages.append({
"role": "user",
"content": (
"【格式错误】你在一次回复中同时包含了工具调用和 Final Answer这是不允许的。\n"
"每次回复只能做以下两件事之一:\n"
"- 调用一个工具(输出一个 <tool_call> 块,不要写 Final Answer\n"
"- 输出最终内容(以 'Final Answer:' 开头,不要包含 <tool_call>\n"
"请重新回复,只做其中一件事。"
"[Format error] You included both a tool call and a Final Answer in the same reply, which is not allowed.\n"
"Each reply may do exactly one of the following:\n"
"- Call a single tool (output one <tool_call> block; do NOT write Final Answer).\n"
"- Output the final content (prefix it with 'Final Answer:'; do NOT include <tool_call>).\n"
"Please reply again and do only one of the two."
),
})
continue
@ -1377,7 +1377,7 @@ class ReportAgent:
if tool_calls_count < min_tool_calls:
messages.append({"role": "assistant", "content": response})
unused_tools = all_tools - used_tools
unused_hint = f"(这些工具还未使用,推荐用一下他们: {', '.join(unused_tools)}" if unused_tools else ""
unused_hint = f"(These tools have not been used yet — try them: {', '.join(unused_tools)})" if unused_tools else ""
messages.append({
"role": "user",
"content": REACT_INSUFFICIENT_TOOLS_MSG.format(
@ -1451,7 +1451,7 @@ class ReportAgent:
unused_tools = all_tools - used_tools
unused_hint = ""
if unused_tools and tool_calls_count < self.MAX_TOOL_CALLS_PER_SECTION:
unused_hint = REACT_UNUSED_TOOLS_HINT.format(unused_list="".join(unused_tools))
unused_hint = REACT_UNUSED_TOOLS_HINT.format(unused_list=", ".join(unused_tools))
messages.append({"role": "assistant", "content": response})
messages.append({
@ -1473,7 +1473,7 @@ class ReportAgent:
if tool_calls_count < min_tool_calls:
# 工具调用次数不足,推荐未用过的工具
unused_tools = all_tools - used_tools
unused_hint = f"(这些工具还未使用,推荐用一下他们: {', '.join(unused_tools)}" if unused_tools else ""
unused_hint = f"(These tools have not been used yet — try them: {', '.join(unused_tools)})" if unused_tools else ""
messages.append({
"role": "user",
@ -1796,13 +1796,13 @@ class ReportAgent:
# 限制报告长度,避免上下文过长
report_content = report.markdown_content[:15000]
if len(report.markdown_content) > 15000:
report_content += "\n\n... [报告内容已截断] ..."
report_content += "\n\n... [report content truncated] ..."
except Exception as e:
logger.warning(t('report.fetchReportFailed', error=e))
system_prompt = CHAT_SYSTEM_PROMPT_TEMPLATE.format(
simulation_requirement=self.simulation_requirement,
report_content=report_content if report_content else "(暂无报告)",
report_content=report_content if report_content else "(no report yet)",
tools_description=self._get_tools_description(),
)
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
@ -1858,7 +1858,7 @@ class ReportAgent:
# 将结果添加到消息
messages.append({"role": "assistant", "content": response})
observation = "\n".join([f"[{r['tool']}结果]\n{r['result']}" for r in tool_results])
observation = "\n".join([f"[{r['tool']} result]\n{r['result']}" for r in tool_results])
messages.append({
"role": "user",
"content": observation + CHAT_OBSERVATION_SUFFIX