# 需求文档 — NS2：AI Prompt 细化

## 简介

NS2 将 P5-A 阶段交付的 6 个 AI 应用（应用 1/3/4/5/6/7）的 `build_prompt()` 占位骨架升级为完整实现。核心工作包括：创建共享数据获取层（`data_fetchers/`）从 FDW 视图获取真实业务数据、完善 6 个应用的 Prompt 拼接函数使 AI 能基于真实数据生成分析、以及实现应用 1 的页面上下文文本化（根据 `contextType` 自动获取并格式化页面数据）。

### 依赖

- P5-A（AI 集成管道）— 百炼封装、缓存 API、SSE 框架、应用 2/8 完整实现、应用 1/3/4/5/6/7 触发机制和调用骨架
- NS1（小程序后端 API）— 后端数据结构确定、FDW 映射建立
- RNS1.4（CHAT 对齐与联调收尾）— chat 模块路径迁移、`contextType`/`contextId` 参数机制

### 来源文档

- `docs/prd/Neo_Specs/NS2-ai-prompt-refinement.md` — NS2 完整 PRD
- `docs/prd/specs/P5-miniapp-ai-integration.md` — P5 AI 集成 spec（Prompt JSON 结构定义）
- `docs/miniprogram-dev/API-contract.md` — 接口契约（数据结构参考）
- `docs/prd/Neo_Specs/NS1-xcx-backend-api.md` — NS1 后端 API spec

## 术语表

- **Data_Fetcher**：共享数据获取模块，位于 `apps/backend/app/ai/data_fetchers/`，负责从数据库查询并格式化 AI 所需数据
- **Build_Prompt**：各 AI 应用中的 Prompt 拼接函数，将数据获取层返回的数据组装为百炼 API 所需的消息列表
- **FDW_View**：PostgreSQL Foreign Data Wrapper 视图，前缀 `fdw_etl.v_*`，用于从业务库只读访问 ETL 库数据
- **Page_Context_Builder**：页面上下文文本化模块，根据 `contextType` 从数据库获取页面数据并格式化为 AI 可读的结构化中文文本
- **Bailian_Client**：百炼 API 统一封装层，位于 `apps/backend/app/ai/bailian_client.py`
- **AI_Cache**：AI 缓存表 `biz.ai_cache`，存储各应用的 AI 分析结果供前端读取和跨应用引用
- **items_sum**：DWD-DOC 强制使用的消费金额口径，= `table_charge_money` + `goods_money` + `assistant_pd_money` + `assistant_cx_money` + `electricity_money`
- **RLS**：Row Level Security，通过 `SET LOCAL app.current_site_id` 实现多门店数据隔离
- **contextType**：前端跳转 chat 页面时传入的页面类型标识（如 `task-detail`、`board-finance`），后端据此路由到对应的文本化函数
- **contextId**：前端跳转 chat 页面时传入的页面实体 ID（如 taskId、memberId）
- **biz_params**：应用 1 system prompt 中注入的用户身份参数，百炼平台侧据此执行数据查询隔离

## 需求

### 需求 1：客户消费数据获取模块

**用户故事：** 作为 AI 应用（应用 3/6/7），我需要获取客户近期消费数据的完整结构化信息，以便基于真实数据生成维客线索分析、备注分析和运营策略。

#### 验收标准

1. THE Data_Fetcher SHALL 提供 `fetch_member_consumption_data(site_id, member_id, months)` 异步函数，从 FDW_View 获取客户近 N 个月（默认 3 个月）的消费数据，返回包含以下字段的字典：`consumption_records`（消费记录列表）、`member_cards`（会员卡明细列表）、`card_balance_total`（储值卡余额合计）、`stored_value_balance_total`（储值余额合计）、`expected_visit_date`（预计到店日期）、`days_since_last_visit`（距上次到店天数）
2. THE Data_Fetcher SHALL 从 `fdw_etl.v_dwd_settlement_head` 和 `fdw_etl.v_dwd_table_fee_log` 获取台桌结账记录，从 `fdw_etl.v_dwd_store_goods_sale` 获取商城订单记录，仅包含正向交易（`settle_type IN (1, 3)`）
3. THE Data_Fetcher SHALL 为每条消费记录返回以下金额拆分字段：`settle_date`、`settle_type`、`items_sum`、`table_charge_money`、`assistant_pd_money`、`assistant_cx_money`、`goods_money`、`room_name`、`duration_minutes`、`assistant_names`（服务助教列表）
4. THE Data_Fetcher SHALL 使用 `items_sum` 作为消费金额口径，禁止使用 `consume_money`
5. THE Data_Fetcher SHALL 从 `fdw_etl.v_dim_member_card_account` 获取会员卡明细，每张卡包含 `card_type`（卡类型）、`balance`（余额）、`gift_balance`（赠送余额）
6. THE Data_Fetcher SHALL 从 `fdw_etl.v_dws_member_visit_detail` 的到店间隔数据推算 `expected_visit_date`，并计算 `days_since_last_visit`
7. THE Data_Fetcher SHALL 通过 `member_id` JOIN `fdw_etl.v_dim_member`（`scd2_is_current=1`）获取会员昵称和手机号，禁止使用结算单冗余的 `member_phone`/`member_name` 字段
8. THE Data_Fetcher SHALL 在执行 FDW 查询前通过 `get_etl_readonly_connection(site_id)` 获取连接，并执行 `SET LOCAL app.current_site_id` 设置 RLS 隔离
9. THE Data_Fetcher SHALL 将单次查询返回的消费记录数限制为最多 100 条，按 `settle_date` 倒序排列
10. IF FDW 查询超时（超过 5 秒）或连接失败，THEN THE Data_Fetcher SHALL 抛出明确的异常，包含失败的视图名称和错误类型，由调用方决定降级策略

### 需求 2：助教数据获取模块

**用户故事：** 作为 AI 应用（应用 4/5），我需要获取助教基本信息和助教-客户服务历史，以便基于真实数据生成关系分析和话术参考。

#### 验收标准

1. THE Data_Fetcher SHALL 提供 `fetch_assistant_info(site_id, assistant_id)` 异步函数，从 `fdw_etl.v_dim_assistant` 和 `fdw_etl.v_dws_assistant_salary_calc` 获取助教基本信息，返回包含以下字段的字典：`nickname`（花名）、`level`（级别）、`hire_date`（入职日期）、`tenure_months`（工龄月数）、`monthly_customers`（本月服务客户数）、`performance_tier`（绩效档位）
2. THE Data_Fetcher SHALL 提供 `fetch_service_history(site_id, assistant_id, member_id, months)` 异步函数，从 `fdw_etl.v_dwd_assistant_service_log` 获取助教服务该客户的历史记录（默认近 3 个月），每条记录包含：`service_date`、`duration_minutes`、`items_sum`、`room_name`、`is_pd`（是否陪打）
3. THE Data_Fetcher SHALL 使用 `dwd_assistant_service_log_ex.is_trash` 排除废单记录，禁止使用已废弃的 `dwd_assistant_trash_event` 表
4. THE Data_Fetcher SHALL 从 `fdw_etl.v_dws_member_assistant_relation_index` 获取助教-客户关系指数，从 `fdw_etl.v_dws_member_assistant_intimacy` 获取亲密度数据
5. THE Data_Fetcher SHALL 在执行 FDW 查询前通过 `get_etl_readonly_connection(site_id)` 获取连接，并执行 `SET LOCAL app.current_site_id` 设置 RLS 隔离
6. IF FDW 查询超时或连接失败，THEN THE Data_Fetcher SHALL 抛出明确的异常，包含失败的视图名称和错误类型

### 需求 3：应用 3 Prompt 拼接完善（客户数据维客线索分析）

**用户故事：** 作为系统，我需要在客户新增消费时，将真实的消费数据、会员卡信息和历史线索拼接为完整的 Prompt JSON，以便 AI 能基于真实数据提取维客线索。

#### 验收标准

1. WHEN 应用 3 的 `build_prompt()` 被调用时，THE Build_Prompt SHALL 调用 `fetch_member_consumption_data()` 获取客户近 3 个月消费数据，替换当前的 TODO 占位符
2. THE Build_Prompt SHALL 将获取到的数据组装为 P5 spec 定义的 Prompt JSON 结构，包含 `current_time`（精确到秒）、`member_nickname`、`main_data`（含 `consumption_records`、`member_cards`、`card_balance_total`、`stored_value_balance_total`、`expected_visit_date`、`days_since_last_visit`）、`reference`（含 `app6_clues` 和 `app8_history`）
3. THE Build_Prompt SHALL 在 `reference.app8_history` 中包含最近 2 套应用 8 历史结果，每套附带 `generated_at` 时间戳
4. IF 客户无消费记录，THEN THE Build_Prompt SHALL 将 `consumption_records` 设为空数组，其余字段使用默认值（余额为 0、`days_since_last_visit` 为 null），Prompt 中标注"该客户暂无消费记录"
5. THE Build_Prompt SHALL 将每条消费记录中的金额字段逐项拆分（`table_charge_money`、`assistant_pd_money`、`assistant_cx_money`、`goods_money`），禁止使用 `consume_money`

### 需求 4：应用 4 Prompt 拼接完善（关系分析/任务建议）

**用户故事：** 作为系统，我需要在助教参与新结算或被分配召回任务时，将助教信息、服务历史和客户数据拼接为完整的 Prompt JSON，以便 AI 能生成关系分析和任务建议。

#### 验收标准

1. WHEN 应用 4 的 `build_prompt()` 被调用时，THE Build_Prompt SHALL 调用 `fetch_assistant_info()` 获取助教基本信息，调用 `fetch_service_history()` 获取助教-客户服务历史，调用 `fetch_member_consumption_data()` 获取客户消费数据，替换当前的 TODO 占位符
2. THE Build_Prompt SHALL 将获取到的数据组装为 P5 spec 定义的 Prompt JSON 结构，包含 `current_time`、`assistant_info`、`service_history`、`task_assignment_basis`、`customer_data`（含 `system_data` 和 `notes`）、`reference`（含 `app8_current` 和 `app8_history`）
3. THE Build_Prompt SHALL 从 `biz.notes` 获取所有助教对该客户的全部备注，每条备注包含 `recorded_by`（创建者）、`content`（内容）、`created_at`（创建时间）
4. THE Build_Prompt SHALL 对备注内容进行截断处理，单条备注最大 500 字符，超出部分截断并附加"…（已截断）"标记
5. IF 助教无服务该客户的历史记录，THEN THE Build_Prompt SHALL 将 `service_history` 设为空数组，Prompt 中标注"该助教暂无服务该客户的记录"
6. THE Build_Prompt SHALL 在 `reference` 中包含应用 8 最新结果（`app8_current`）和最近 2 套历史（`app8_history`），每套附带 `generated_at` 时间戳

### 需求 5：应用 5 Prompt 拼接完善（话术参考）

**用户故事：** 作为系统，我需要在应用 4 完成后，将应用 4 的分析结果连同助教信息和服务历史拼接为完整的 Prompt JSON，以便 AI 能生成针对性的沟通话术。

#### 验收标准

1. WHEN 应用 5 的 `build_prompt()` 被调用时，THE Build_Prompt SHALL 复用应用 4 的数据获取逻辑（`fetch_assistant_info()`、`fetch_service_history()`、`fetch_member_consumption_data()`），替换当前的 TODO 占位符
2. THE Build_Prompt SHALL 将获取到的数据组装为 P5 spec 定义的 Prompt JSON 结构，包含 `current_time`、`assistant_info`、`service_history`、`task_assignment_basis`、`customer_data`（含 `system_data` 和 `notes`）、`task_suggestion`（应用 4 的完整返回结果）、`reference`（含 `app8_history`）
3. THE Build_Prompt SHALL 从 `context["app4_result"]` 获取应用 4 的返回结果，作为 `task_suggestion` 字段传入 Prompt
4. IF `context["app4_result"]` 为空或缺失，THEN THE Build_Prompt SHALL 将 `task_suggestion` 设为空对象，Prompt 中标注"暂无任务建议"

### 需求 6：应用 6 Prompt 拼接完善（备注分析）

**用户故事：** 作为系统，我需要在助教提交备注后，将备注内容、客户消费数据和历史备注拼接为完整的 Prompt JSON，以便 AI 能分析备注价值并提取维客线索。

#### 验收标准

1. WHEN 应用 6 的 `build_prompt()` 被调用时，THE Build_Prompt SHALL 调用 `fetch_member_consumption_data()` 获取客户消费数据，替换当前的 TODO 占位符
2. THE Build_Prompt SHALL 将获取到的数据组装为 P5 spec 定义的 Prompt JSON 结构，包含 `current_time`、`current_note`（含 `content`、`recorded_by`、`created_at`）、`reference`（含 `member_nickname`、`consumption_data`、`all_notes`、`app3_clues`、`app8_history`）
3. THE Build_Prompt SHALL 从 `biz.notes` 获取所有助教对该客户的全部备注作为 `all_notes`，每条备注包含 `recorded_by`、`content`、`created_at`
4. THE Build_Prompt SHALL 对 `all_notes` 中每条备注内容进行截断处理，单条备注最大 500 字符
5. THE Build_Prompt SHALL 在 `reference` 中包含应用 3 最新线索（`app3_clues`）和最近 2 套应用 8 历史（`app8_history`），每套附带 `generated_at` 时间戳
6. IF 客户无历史备注，THEN THE Build_Prompt SHALL 将 `all_notes` 设为空数组

### 需求 7：应用 7 Prompt 拼接完善（客户分析）

**用户故事：** 作为系统，我需要在消费事件链中应用 8 完成后，将客户全量客观数据和主观备注拼接为完整的 Prompt JSON，以便 AI 能生成全局运营策略。

#### 验收标准

1. WHEN 应用 7 的 `build_prompt()` 被调用时，THE Build_Prompt SHALL 调用 `fetch_member_consumption_data()` 获取客户消费数据，替换当前的 TODO 占位符
2. THE Build_Prompt SHALL 将获取到的数据组装为 P5 spec 定义的 Prompt JSON 结构，包含 `current_time`、`member_id`、`member_nickname`、`objective_data`（同应用 3 的 `main_data` 结构）、`subjective_data`（含 `notes`）、`reference`（含 `app8_current` 和 `app8_history`）
3. THE Build_Prompt SHALL 从 `biz.notes` 获取该客户的全部备注作为 `subjective_data.notes`，每条备注包含 `recorded_by`（创建者）、`content`、`created_at`
4. THE Build_Prompt SHALL 对来自备注的主观信息在 Prompt 中标注"【来源：{recorded_by}，请甄别信息真实性】"
5. THE Build_Prompt SHALL 在 `reference` 中包含应用 8 最新结果（`app8_current`）和最近 2 套历史（`app8_history`），每套附带 `generated_at` 时间戳
6. IF 客户无备注记录，THEN THE Build_Prompt SHALL 将 `subjective_data.notes` 设为空数组，Prompt 中标注"该客户暂无主观备注信息"

### 需求 8：页面上下文文本化框架

**用户故事：** 作为助教，我希望从任意页面进入 AI 对话时，AI 能自动了解当前页面的上下文信息（客户数据、任务信息、看板数据等），以便提供与当前场景相关的回答。

> **P5 对齐说明**：P5 spec 定义应用 1 首条 Prompt 包含 `page_context`（页面上下文摘要）和 `screen_content`（用户当前屏幕可见内容）两个独立字段。NS2 采用后端自动查询方案，通过 `build_page_text()` 生成合并的页面上下文文本，等效覆盖两个字段的信息需求。详见 NS2 PRD 3.7 设计决策。

#### 验收标准

1. THE Page_Context_Builder SHALL 提供 `build_page_text(source_page, context_id, site_id, filters)` 异步函数，根据 `source_page`（即 `contextType`）从数据库获取对应页面数据并格式化为结构化中文文本
2. THE Page_Context_Builder SHALL 支持以下 10 种页面类型的文本化：`task-detail`、`customer-detail`、`coach-detail`、`board-finance`、`board-customer`、`board-coach`、`performance`、`my-profile`、`task-list`、`customer-service-records`
3. WHEN `source_page` 为 `task-detail` 时，THE Page_Context_Builder SHALL 从 `biz.coach_tasks`、会员信息、备注和 `ai_cache` 获取数据，输出包含任务信息、客户信息、备注摘要和 AI 分析的结构化文本
4. WHEN `source_page` 为 `customer-detail` 时，THE Page_Context_Builder SHALL 从会员信息、消费记录和维客线索获取数据，输出包含客户全信息、消费记录摘要和维客线索的结构化文本
5. WHEN `source_page` 为 `coach-detail` 时，THE Page_Context_Builder SHALL 从助教信息、任务统计和备注获取数据，输出包含助教信息、任务统计和备注摘要的结构化文本
6. WHEN `source_page` 为看板类页面（`board-finance`、`board-customer`、`board-coach`）时，THE Page_Context_Builder SHALL 接受可选的筛选参数（`timeDimension`、`dimension`、`areaFilter` 等），从对应 DWS 汇总视图获取数据，输出数据摘要文本
7. WHEN `source_page` 为 `performance` 时，THE Page_Context_Builder SHALL 从 `fdw_etl.v_dws_assistant_salary_calc` 获取绩效数据，输出绩效数据摘要文本
8. THE Page_Context_Builder SHALL 将每个页面上下文的输出文本控制在 2000 字符以内，避免 token 浪费
9. THE Page_Context_Builder SHALL 输出结构化中文描述文本（非 JSON），使用分段标题和缩进格式便于 AI 理解
10. IF 看板类页面未传入筛选参数，THEN THE Page_Context_Builder SHALL 使用默认值（如 `board-finance` 默认"本月"、`board-customer` 默认按消费金额排序）
11. IF 数据获取失败（FDW 超时、连接失败等），THEN THE Page_Context_Builder SHALL 返回"页面上下文获取失败，请直接描述您的问题"文本，不阻断对话流程
12. THE Page_Context_Builder SHALL 不传入 `member_phone` 等已断档的敏感字段，会员信息通过 `member_id` JOIN `v_dim_member` 获取

### 需求 9：应用 1 页面上下文集成

**用户故事：** 作为助教，我希望从不同页面入口进入 AI 对话时，AI 的首条回复能体现对当前页面上下文的理解，而不是一个通用的空白对话。

#### 验收标准

1. WHEN 用户从任意页面进入 chat 页面时，THE Build_Prompt SHALL 在 `app1_chat.py` 的 `_build_page_context()` 中调用 `Page_Context_Builder.build_page_text()`，根据 `contextType` 和 `contextId` 获取页面上下文文本
2. THE Build_Prompt SHALL 将 `build_page_text()` 返回的文本作为 system prompt 中的 `page_context` 字段注入，替换当前的空透传实现
3. WHEN `contextType` 为详情类页面（`task-detail`、`customer-detail`、`coach-detail`）时，THE Build_Prompt SHALL 使用 `contextId` 作为实体 ID 传入 `build_page_text()`
4. WHEN `contextType` 为看板类页面（`board-finance`、`board-customer`、`board-coach`）时，THE Build_Prompt SHALL 将前端传入的筛选参数（`timeDimension`、`dimension` 等）作为 `filters` 传入 `build_page_text()`
5. IF `contextType` 为空或未识别的类型，THEN THE Build_Prompt SHALL 跳过页面上下文注入，AI 以通用对话模式响应
6. THE Build_Prompt SHALL 确保 `biz_params.user_prompt_params`（`User_ID`、`Role`、`Nickname`）在页面上下文注入后仍正确存在于 system prompt 中

### 需求 10：前端看板筛选参数传递

**用户故事：** 作为助教或管理者，我希望从看板页面进入 AI 对话时，AI 能了解当前看板的筛选条件（时间维度、排序维度等），以便基于当前视图提供分析。

#### 验收标准

1. WHEN 用户从 `board-finance` 页面跳转到 chat 页面时，THE Miniprogram SHALL 将当前的 `timeDimension`（时间维度）和 `areaFilter`（区域筛选）作为额外参数传入 chat 页面
2. WHEN 用户从 `board-customer` 页面跳转到 chat 页面时，THE Miniprogram SHALL 将当前的 `dimension`（排序维度）和 `typeFilter`（类型筛选）作为额外参数传入 chat 页面
3. WHEN 用户从 `board-coach` 页面跳转到 chat 页面时，THE Miniprogram SHALL 将当前的 `dimension`、`projectFilter`（技能筛选）和 `timeDimension` 作为额外参数传入 chat 页面
4. THE Miniprogram SHALL 将看板筛选参数编码为 chat 页面 URL 的查询参数，后端从请求中提取并传入 `build_page_text()` 的 `filters` 参数

### 需求 11：数据获取层全局约束

**用户故事：** 作为后端开发者，我需要确保所有数据获取函数遵循统一的数据库连接、金额口径和安全规范，以保证数据一致性和多门店隔离。

#### 验收标准

1. THE Data_Fetcher SHALL 对所有 FDW 查询使用 `get_etl_readonly_connection(site_id)` 获取只读连接，对业务库查询使用 `get_connection()` 获取连接
2. THE Data_Fetcher SHALL 在每次 FDW 查询前执行 `SET LOCAL app.current_site_id = {site_id}`，确保 RLS 多门店数据隔离生效
3. THE Data_Fetcher SHALL 在所有涉及金额的查询和计算中使用 `items_sum` 口径，禁止使用 `consume_money`
4. THE Data_Fetcher SHALL 在所有涉及助教费用的查询中将费用拆分为 `assistant_pd_money`（陪打）和 `assistant_cx_money`（超休），禁止使用合并的 `service_fee`
5. THE Data_Fetcher SHALL 在所有涉及会员信息的查询中通过 `member_id` JOIN `v_dim_member`（`scd2_is_current=1`）获取昵称和手机号，禁止使用结算单冗余字段
6. THE Data_Fetcher SHALL 支持多个数据获取函数并发执行（通过 `asyncio.gather`），以减少 Prompt 构建的总耗时
7. THE Data_Fetcher SHALL 为所有 FDW 查询设置 5 秒超时，超时后抛出 `TimeoutError` 并记录慢查询日志

### 需求 12：空数据与错误降级处理

**用户故事：** 作为系统，我需要在数据获取部分失败或数据为空时，仍能生成有效的 Prompt 并调用 AI，以保证 AI 功能的可用性。

#### 验收标准

1. IF 某个数据获取函数失败（FDW 超时、连接失败等），THEN THE Build_Prompt SHALL 将该部分数据设为默认空值（空数组或空对象），在 Prompt 中标注"该部分数据获取失败"，继续生成 Prompt 并调用百炼 API
2. IF 客户无消费记录、无备注、无服务历史，THEN THE Build_Prompt SHALL 在 Prompt 中使用明确的空状态提示词（如"该客户暂无消费记录，请基于已有信息分析"），而非传入空数据不做说明
3. IF AI_Cache 中无 reference 历史数据（如新客户首次结算），THEN THE Build_Prompt SHALL 将 `reference` 设为空对象，在 Prompt 中标注"暂无历史线索"
4. THE Build_Prompt SHALL 确保在任何数据获取失败的情况下，Prompt 的整体 JSON 结构仍然合法，不出现 null 值导致百炼 API 解析失败
5. IF 应用 1 的页面上下文获取失败，THEN THE Build_Prompt SHALL 使用"页面上下文获取失败，请直接描述您的问题"作为 `page_context`，不阻断对话

### 需求 13：biz_params 端到端正确性

**用户故事：** 作为系统管理员，我需要确保应用 1 的用户身份信息从前端到百炼平台的完整传递链路正确无误，以保证数据查询隔离生效。

#### 验收标准

1. THE Backend SHALL 从 JWT token 中提取 `user_id`、`role`、`nickname`，传入 `app1_chat.py` 的 `_build_system_prompt()`，确保用户身份信息不可由前端伪造
2. THE Build_Prompt SHALL 在 system prompt 的 `biz_params.user_prompt_params` 中注入 `User_ID`（字符串）、`Role`（"助教"或"管理者"）、`Nickname`（昵称），供百炼平台侧执行数据查询隔离
3. WHILE 用户身份为助教时，THE Bailian_Client SHALL 确保百炼平台仅允许查询与该助教相关的数据，禁止查询其他助教业绩、工资、客户关系等敏感数据，禁止查询门店级财务数据
4. WHILE 用户身份为管理者时，THE Bailian_Client SHALL 确保百炼平台允许查询该门店下所有数据
5. THE Backend SHALL 确保所有传入百炼平台的查询均包含 `site_id` 过滤，防止跨门店数据泄露

### 需求 14：Prompt Token 预算控制

**用户故事：** 作为系统，我需要控制每个应用首条 Prompt 的数据量，避免超出百炼 API 的 token 限制或产生不必要的 token 消耗。

#### 验收标准

1. THE Build_Prompt SHALL 将应用 3/4/5/6/7 的首条 Prompt JSON 总字符数控制在 8000 字符以内（约 4000 token）
2. THE Build_Prompt SHALL 将应用 1 的 system prompt（含页面上下文）总字符数控制在 4000 字符以内（约 2000 token）
3. WHEN 消费记录数量超过 100 条时，THE Data_Fetcher SHALL 仅返回最近 100 条记录，并在 Prompt 中标注"仅展示最近 100 条，共 {total} 条"
4. WHEN 备注总数超过 50 条时，THE Build_Prompt SHALL 仅传入最近 50 条备注（按 `created_at` 倒序），并在 Prompt 中标注"仅展示最近 50 条备注"
5. THE Build_Prompt SHALL 对所有传入 Prompt 的文本字段进行长度检查，单个文本字段不超过 1000 字符
