22 KiB
NS2:AI Prompt 细化 — ai-prompt-refinement
优先级:高(NS1 完成后立即执行,Prompt 细化是 AI 功能闭环的关键) 预估工作量:中等 前置条件:P5-A 已完成(AI 骨架就绪)、NS1 已完成(后端 API 数据结构确定) 参考基准:
docs/prd/specs/P5-miniapp-ai-integration.md(P5-B 阶段定义)
一、背景与目标
P5-A 阶段已交付 AI 集成管道:百炼封装、缓存 API、SSE 框架、应用 2/8 完整实现、应用 1/3/4/5/6/7 的触发机制和调用骨架。但应用 1/3/4/5/6/7 的 build_prompt() 函数仍为占位骨架,核心数据字段(consumption_records、service_history、objective_data 等)标记为 TODO。
本 SPEC 目标:将 6 个应用的 Prompt 拼接函数从骨架升级为完整实现,使 AI 应用能基于真实数据生成有价值的分析结果。
当前骨架状态
| 应用 | 文件 | 骨架状态 | 待细化字段 |
|---|---|---|---|
| 应用 1 | app1_chat.py |
页面上下文文本化工具留接口 | page_context、screen_content 各页面文本化 |
| 应用 3 | app3_clue.py |
build_prompt() 占位 |
consumption_records(DWD+DWS 订单明细全维度) |
| 应用 4 | app4_analysis.py |
build_prompt() 占位 |
service_history、assistant_info |
| 应用 5 | app5_tactics.py |
build_prompt() 占位 |
service_history、assistant_info(同应用 4) |
| 应用 6 | app6_note.py |
build_prompt() 占位 |
consumption_data(同应用 3 的 main_data) |
| 应用 7 | app7_customer.py |
build_prompt() 占位 |
objective_data(同应用 3 的 main_data) |
二、技术架构
2.1 数据获取层(新建共享模块)
Prompt 细化的核心是从数据库获取真实数据并格式化为 AI 可读的 JSON。多个应用共享相同的数据获取逻辑,应抽取为共享模块:
apps/backend/app/ai/
├── data_fetchers/ 🆕 新建
│ ├── __init__.py
│ ├── member_data.py # 客户数据获取(消费记录、会员卡、余额等)
│ ├── assistant_data.py # 助教数据获取(基本信息、服务历史)
│ ├── service_history.py # 服务记录获取(助教-客户维度)
│ └── page_context.py # 页面上下文文本化(应用 1 专用)
├── apps/
│ ├── app1_chat.py 🔧 补充页面文本化调用
│ ├── app3_clue.py 🔧 补充 consumption_records 拼接
│ ├── app4_analysis.py 🔧 补充 service_history + assistant_info
│ ├── app5_tactics.py 🔧 补充 service_history + assistant_info
│ ├── app6_note.py 🔧 补充 consumption_data
│ └── app7_customer.py 🔧 补充 objective_data
2.2 数据获取函数设计
member_data.py — 客户数据获取(应用 3/6/7 共用)
async def fetch_member_consumption_data(
site_id: int, member_id: int, months: int = 3
) -> dict:
"""获取客户近 N 个月消费数据(DWD+DWS 全维度)。
返回结构对应 P5 spec 中 main_data:
- consumption_records: 台桌结账 + 商城订单明细列表
- member_cards: 会员卡明细
- card_balance_total: 储值卡余额合计
- stored_value_balance_total: 储值余额合计
- expected_visit_date: 预计到店日期
- days_since_last_visit: 距上次到店天数
"""
数据源(全部通过 FDW):
fdw_etl.v_dwd_settlement_head+fdw_etl.v_dwd_table_fee_log(台桌结账)fdw_etl.v_dwd_store_goods_sale(商城订单)fdw_etl.v_dwd_recharge_order(充值记录)fdw_etl.v_dim_member_card_account(会员卡明细)fdw_etl.v_dws_member_consumption_summary(消费汇总 + 余额)fdw_etl.v_dws_member_visit_detail(到店明细 → 计算预计到店日期)
⚠️ 金额口径:使用
items_sum(= table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money),禁止consume_money⚠️ 会员字段断档(DQ-6/DQ-7):member_phone/member_name 通过 member_id JOIN dim_member
assistant_data.py — 助教数据获取(应用 4/5 共用)
async def fetch_assistant_info(
site_id: int, assistant_id: int
) -> dict:
"""获取助教基本信息(花名、级别、工龄等)。"""
async def fetch_service_history(
site_id: int, assistant_id: int, member_id: int, months: int = 3
) -> list[dict]:
"""获取助教服务该客户的历史记录。"""
数据源:
fdw_etl.v_dim_assistant(基本信息)fdw_etl.v_dwd_assistant_service_log(服务记录,按助教+客户筛选)fdw_etl.v_dws_member_assistant_relation_index(关系指数)fdw_etl.v_dws_member_assistant_intimacy(亲密度)
page_context.py — 页面上下文文本化(应用 1 专用)
async def build_page_text(
source_page: str, context_data: dict, site_id: int
) -> str:
"""将页面数据转换为 AI 可读的结构化文本。
根据 source_page 类型,从数据库获取对应数据并格式化。
"""
支持的页面类型及数据获取:
| source_page | 数据获取 | 文本化内容 |
|---|---|---|
task-detail |
coach_tasks + member + notes + ai_cache | 任务信息 + 客户信息 + 备注 + AI 分析 |
customer-detail |
member + consumption + clues | 客户全信息 + 消费记录 + 维客线索 |
coach-detail |
assistant + tasks + notes | 助教信息 + 任务统计 + 备注 |
board-finance |
finance DWS 汇总 | 财务数据摘要 |
board-customer |
当前筛选维度 top 列表 | 客户排名摘要 |
board-coach |
当前筛选维度排名 | 助教排名摘要 |
performance |
salary_calc + daily_detail | 绩效数据摘要 |
my-profile |
user info + assistant binding | 个人信息摘要 |
2.3 数据库连接模式
- 所有 FDW 查询通过
get_etl_readonly_connection(site_id)或fdw_etl.*视图 - 业务库查询通过
get_connection() - 查询前必须
SET LOCAL app.current_site_id(RLS 隔离)
三、各应用 Prompt 细化详细设计
3.1 应用 3:客户数据维客线索分析
build_prompt() 需要拼接的完整 JSON 结构(参考 P5 spec):
{
"current_time": "2026-03-08 14:30:25",
"member_nickname": "客户昵称",
"main_data": {
"consumption_records": [
{
"settle_date": "2026-03-05",
"settle_type": 1,
"items_sum": 280.00,
"table_charge_money": 180.00,
"assistant_pd_money": 80.00,
"assistant_cx_money": 0,
"goods_money": 20.00,
"room_name": "VIP-3",
"duration_minutes": 120,
"assistant_names": ["张助教"]
}
],
"member_cards": [
{"card_type": "金卡", "balance": 1500.00, "gift_balance": 200.00}
],
"card_balance_total": 1700.00,
"stored_value_balance_total": 1700.00,
"expected_visit_date": "2026-03-10",
"days_since_last_visit": 15
},
"reference": {
"app6_clues": [...],
"app8_history": [
{"generated_at": "2026-03-01", "clues": [...]},
{"generated_at": "2026-02-15", "clues": [...]}
]
}
}
实现要点:
consumption_records从v_dwd_settlement_head+v_dwd_table_fee_log获取,settle_type IN (1,3)- 金额字段逐项拆分(table_charge_money / assistant_pd_money / assistant_cx_money / goods_money),不使用 consume_money
expected_visit_date从v_dws_member_visit_detail的到店间隔推算- reference 从
ai_cache获取 app6 + app8 最近 2 套历史
3.2 应用 4:关系分析/任务建议
build_prompt() 需要拼接的完整 JSON 结构:
{
"current_time": "2026-03-08 14:30:25",
"assistant_info": {
"nickname": "张助教",
"level": "高级",
"hire_date": "2024-06-01",
"tenure_months": 21,
"monthly_customers": 45,
"performance_tier": "A档"
},
"service_history": [
{
"service_date": "2026-03-01",
"duration_minutes": 90,
"items_sum": 250.00,
"room_name": "VIP-3",
"is_pd": true
}
],
"task_assignment_basis": "优先召回",
"customer_data": {
"system_data": { /* 同应用 3 的 main_data */ },
"notes": [
{"recorded_by": "张助教", "content": "客户喜欢打斯诺克", "created_at": "2026-02-28"}
]
},
"reference": {
"app8_current": {...},
"app8_history": [...]
}
}
实现要点:
assistant_info从v_dim_assistant+v_dws_assistant_salary_calc获取service_history从v_dwd_assistant_service_log按助教+客户筛选customer_data.system_data复用member_data.fetch_member_consumption_data()customer_data.notes从biz.notes获取该客户的全部备注- reference 从
ai_cache获取 app8 最新 + 最近 2 套历史
3.3 应用 5:话术参考
结构与应用 4 基本一致,额外增加 task_suggestion 字段(应用 4 的返回结果)。
实现要点:
- 复用应用 4 的
assistant_info+service_history+customer_data获取逻辑 task_suggestion从应用 4 的run()返回值传入(调用链:应用 4 → 应用 5)
3.4 应用 6:备注分析
{
"current_time": "2026-03-08 14:30:25",
"current_note": {
"content": "客户说下周要带朋友来打球",
"recorded_by": "张助教",
"created_at": "2026-03-08 14:25:00"
},
"reference": {
"member_nickname": "王先生",
"consumption_data": { /* 同应用 3 的 main_data */ },
"all_notes": [...],
"app3_clues": [...],
"app8_history": [...]
}
}
实现要点:
current_note从触发上下文获取(备注提交事件传入)consumption_data复用member_data.fetch_member_consumption_data()all_notes从biz.notes获取所有助教对该客户的全部备注- reference 从
ai_cache获取 app3 + app8 历史
3.5 应用 7:客户分析
{
"current_time": "2026-03-08 14:30:25",
"member_id": 12345,
"member_nickname": "王先生",
"objective_data": { /* 同应用 3 的 main_data */ },
"subjective_data": {
"notes": [
{"recorded_by": "张助教", "content": "...", "created_at": "..."}
]
},
"reference": {
"app8_current": {...},
"app8_history": [...]
}
}
实现要点:
objective_data复用member_data.fetch_member_consumption_data()subjective_data.notes从biz.notes获取全部备注,标注创建者- 主观信息需在 Prompt 中标注【来源:XXX,请甄别信息真实性】
- reference 从
ai_cache获取 app8 最新 + 最近 2 套历史
3.6 应用 1:biz_params 权限隔离(已实现,需确认)
✅ RNS1.4 已在
app1_chat.py的_build_system_prompt()中实现 biz_params 拼接。
应用 1 通过 biz_params.user_prompt_params 向百炼 AI 传入用户身份信息,实现数据查询隔离:
system_content = {
"task": "你是台球门店的 AI 助手...",
"biz_params": {
"user_prompt_params": {
"User_ID": str(user_id),
"Role": role, # "助教" | "管理者"
"Nickname": nickname,
},
},
}
权限控制规则(百炼平台侧执行):
- 助教身份:仅允许查询与该助教相关的数据,禁止查询其他助教业绩/工资/客户关系等敏感数据,禁止查询门店级财务数据
- 管理者身份:可查询该门店下所有数据
- 所有查询必须包含
site_id过滤
NS2 需确认事项:
- 前端调用
POST /api/chat/conversations/:id/messages时,是否正确传入user_id/role/nickname - 后端从 JWT token 中提取用户信息并传入
_build_system_prompt(),确保不可伪造
3.7 应用 1:页面上下文传递与文本化
当前问题(P5 PRD 与 RNS1.4 实现的差异)
P5 PRD 原始设计:
前端跳转 chat.html 时传入 source_page + page_context + screen_content
→ 后端接收后文本化 → 拼接为首条 user message
RNS1.4 当前实现:
前端跳转 chat 页面时仅传入 contextType + contextId(如 task/12345)
→ 后端 _build_page_context() 为占位实现,page_context 和 screen_content 为空
→ AI 无法获得页面上下文
解决方案:后端根据 contextType 自动获取页面上下文
采用方案 B(简化前端,后端自动查询),与 RNS1.4 已有的 contextType/contextId 参数兼容:
前端传入 contextType="task-detail" + contextId="12345"
→ 后端 build_page_text("task-detail", 12345, site_id)
→ 后端从数据库获取任务详情、客户信息、备注、AI 分析等
→ 格式化为结构化中文文本
→ 拼接为首条 user message 的 page_context 字段
contextType 映射表
| 前端 contextType | 后端数据获取 | 文本化内容 |
|---|---|---|
task-detail |
coach_tasks + member + notes + ai_cache | 任务信息 + 客户信息 + 备注 + AI 分析 |
customer-detail |
member + consumption + clues | 客户全信息 + 消费记录 + 维客线索 |
coach-detail |
assistant + tasks + notes | 助教信息 + 任务统计 + 备注 |
board-finance |
finance DWS 汇总(按当前筛选条件) | 财务数据摘要 |
board-customer |
当前筛选维度 top 列表 | 客户排名摘要 |
board-coach |
当前筛选维度排名 | 助教排名摘要 |
performance |
salary_calc + daily_detail | 绩效数据摘要 |
my-profile |
user info + assistant binding | 个人信息摘要 |
task-list |
长按的任务摘要 + 客户-助教关系 | 任务概要 |
customer-service-records |
服务记录列表 | 服务记录摘要 |
前端需要补充的参数
当前前端仅传 contextType + contextId,对于看板类页面需要额外传入筛选条件:
| contextType | 必传参数 | 可选参数 |
|---|---|---|
task-detail |
contextId(taskId) | — |
customer-detail |
contextId(memberId) | — |
coach-detail |
contextId(assistantId) | — |
board-finance |
— | timeDimension(时间维度)、areaFilter(区域筛选) |
board-customer |
— | dimension(排序维度)、typeFilter(类型筛选) |
board-coach |
— | dimension、projectFilter、timeDimension |
performance |
— | timeDimension |
task-list |
contextId(taskId) | — |
my-profile |
— | — |
实现要点
- 每个页面类型对应一个文本化函数,在
data_fetchers/page_context.py中实现 - 文本化输出为结构化中文描述(非 JSON),便于 AI 理解上下文
- 数据量控制:每个页面上下文不超过 2000 字符,避免 token 浪费
- 敏感信息脱敏:不传入 member_phone 等断档字段
- 看板类页面:若前端未传筛选条件,使用默认值(如 board-finance 默认"本月")
- 错误降级:数据获取失败时,page_context 传入"页面上下文获取失败",不阻断对话
四、数据库审查
4.1 数据获取涉及的表
| 数据获取函数 | 涉及表 | 连接方式 |
|---|---|---|
fetch_member_consumption_data |
v_dwd_settlement_head, v_dwd_table_fee_log, v_dwd_store_goods_sale, v_dwd_recharge_order, v_dim_member_card_account, v_dws_member_consumption_summary, v_dws_member_visit_detail | FDW |
fetch_assistant_info |
v_dim_assistant, v_dws_assistant_salary_calc | FDW |
fetch_service_history |
v_dwd_assistant_service_log, v_dws_member_assistant_relation_index, v_dws_member_assistant_intimacy | FDW |
build_page_text |
以上全部 + biz.coach_tasks, biz.notes, biz.ai_cache, public.member_retention_clue | FDW + 业务库 |
4.2 无需新建表
本 SPEC 不需要新建数据库表。所有数据获取基于 NS1 已建立的 FDW 映射和 P4/P5-A 已建立的业务表。
4.3 性能考虑
fetch_member_consumption_data涉及 7 张 FDW 表查询,建议:- 消费记录限制最近 3 个月(WHERE settle_date >= NOW() - INTERVAL '3 months')
- 单次查询最多返回 100 条记录
- 考虑使用 NS1 中建议的
biz.task_detail_cache缓存聚合结果
- 页面文本化(应用 1)需要实时获取数据,建议设置 5 秒查询超时
五、调用链与数据流
5.1 消费事件链
新结算单到达
→ 应用 3.build_prompt(member_data) → 百炼 API → ai_cache
→ 应用 8.run() → member_retention_clue
→ 应用 7.build_prompt(member_data + notes) → 百炼 API → ai_cache
→ 应用 4.build_prompt(assistant_data + member_data) → 百炼 API → ai_cache
→ 应用 5.build_prompt(app4_result) → 百炼 API → ai_cache
5.2 备注提交事件链
备注提交
→ 应用 6.build_prompt(note + member_data) → 百炼 API → ai_cache
→ 应用 8.run() → member_retention_clue
5.3 应用 1 对话流
用户点击 AI 入口
→ 前端传入 contextType + contextId(+ 可选筛选参数)
→ 后端 build_page_text(contextType, contextId, site_id, filters?)
→ 从数据库获取对应页面数据,格式化为结构化中文文本
→ 拼接为首条 user message 的 page_context 字段
→ 注入 biz_params(User_ID/Role/Nickname)到 system prompt
→ SSE 流式调用百炼 API
⚠️ 与 P5 PRD 原始设计的差异:P5 设计为前端传入
source_page+page_context+screen_content, RNS1.4 实际实现为前端传入contextType+contextId,后端自动查询数据库获取上下文。 NS2 沿用 RNS1.4 方案(后端自动获取),不要求前端传入原始页面数据。
六、参考文档
| 文档 | 路径 | 用途 |
|---|---|---|
| P5 AI 集成 spec | docs/prd/specs/P5-miniapp-ai-integration.md |
Prompt JSON 结构定义、调用链时序 |
| AI 应用 Prompt | docs/prd/ai-app-prompts.md |
8 个应用的 System Prompt、biz_params 定义 |
| AI 需求文档 | docs/prd/AI需求2.md |
8 个应用的详细需求表、传参格式 |
| AI 应用→页面映射 | docs/reports/2026-03-20__ai_app_page_mapping.md |
各应用输出内容在哪个页面的什么位置展示 |
| API 契约 | docs/miniprogram-dev/API-contract.md |
接口响应格式(决定数据结构) |
| DWD-DOC 标杆 | docs/reports/DWD-DOC/ |
金额口径、字段语义权威参考 |
| AI 应用骨架代码 | apps/backend/app/ai/apps/app*.py |
当前骨架实现(含 biz_params 已实现) |
| 百炼客户端 | apps/backend/app/ai/bailian_client.py |
API 调用封装 |
| 缓存服务 | apps/backend/app/ai/cache_service.py |
ai_cache 读写 |
| NS1 接口设计 | docs/prd/Neo_Specs/NS1-xcx-backend-api.md |
后端数据结构参考 |
| RNS1.4 任务计划 | .kiro/specs/rns1-chat-integration/tasks.md |
当前 chat 模块实施进度 |
七、预审查清单(SPEC 启动前确认)
7.1 数据结构
- 消费记录字段范围:
consumption_records中每条记录需要包含哪些字段?是否需要包含折扣信息(discount_manual/discount_other)?是否需要包含支付方式明细(balance_pay/cash_pay/online_pay)? - 服务记录字段范围:
service_history中每条记录需要包含哪些字段?是否需要包含台桌类型(room_category)和客户评价? - 备注内容截断:
all_notes中每条备注是否需要全文传入?长备注是否截断?截断长度? - 会员卡明细粒度:
member_cards是否需要包含卡号、开卡日期、有效期等详细信息,还是只需要卡类型和余额?
7.2 Prompt 优化
- Token 预算:每个应用的首条 Prompt 的 token 上限是多少?百炼 API 的单次请求 token 限制?
- 数据时间窗口:消费记录默认取近 3 个月,是否需要可配置?不同应用是否需要不同时间窗口?
- 空数据处理:当客户无消费记录/无备注/无服务历史时,Prompt 如何处理?是否需要特殊提示词?
7.3 页面文本化(应用 1)
- 文本化格式:页面上下文是输出为结构化中文文本还是 JSON?AI 对哪种格式理解更好?
- 数据量控制:每个页面上下文的字符上限?是否需要根据页面类型动态调整?
- 实时性要求:应用 1 的页面上下文是否需要实时获取最新数据?还是可以使用缓存(如 task_detail_cache)?
7.4 性能与安全
- FDW 查询并发:多个数据获取函数是否可以并发执行(asyncio.gather)?FDW 连接池是否支持?
- 数据脱敏:传入百炼 API 的数据中,哪些字段需要脱敏?member_phone 已断档不传,还有其他敏感字段吗?
- 错误降级:某个数据获取函数失败时(如 FDW 超时),是否跳过该部分继续生成 Prompt?还是整体失败?
八、任务清单(草案,SPEC 细化后调整)
Batch A:共享数据获取层
- T1:创建
data_fetchers/member_data.py(客户消费数据获取,应用 3/6/7 共用) - T2:创建
data_fetchers/assistant_data.py(助教信息 + 服务历史获取,应用 4/5 共用) - T3:创建
data_fetchers/page_context.py(页面上下文文本化框架,应用 1 专用)
Batch B:Prompt 拼接实现
- T4:完善
app3_clue.py的build_prompt()(客户消费数据 → 维客线索分析) - T5:完善
app4_analysis.py的build_prompt()(助教+客户数据 → 关系分析) - T6:完善
app5_tactics.py的build_prompt()(复用应用 4 数据 + task_suggestion) - T7:完善
app6_note.py的build_prompt()(备注+客户数据 → 备注分析) - T8:完善
app7_customer.py的build_prompt()(客户全量数据 → 运营策略)
Batch C:应用 1 页面文本化 + 前端配合
- T9:实现各页面类型的文本化函数(task-detail/customer-detail/board-*/performance 等)
- T10:补充
app1_chat.py的_build_page_context()调用文本化函数,根据contextType路由到对应文本化函数 - T11:前端补充看板类页面的筛选参数传递(board-finance/board-customer/board-coach 跳转 chat 时传入当前筛选条件)
- T12:确认 biz_params 端到端正确性(前端 JWT → 后端提取 user_id/role/nickname → system prompt 注入 → 百炼权限隔离生效)
Batch D:联调与验证
- T13:端到端联调(触发事件 → Prompt 拼接 → 百炼调用 → 缓存写入 → 前端展示)
- T14:应用 1 页面上下文联调(各入口页面 → contextType/contextId → 后端文本化 → AI 对话验证上下文感知)