Day8 学习日志:CO-STAR 与「Embedding 课前知识储备」——Prompt、工具链与可观测性
📅 日期:2026-03-24
📌 定位:向量与检索正式开课前,把 输出可控、工具调用闭环、本地知识 mock、长文 Map-Reduce 和 运行日志 打牢。
一、CO-STAR:结构化 Prompt 的工程意义
在进入 Embedding / RAG 之前,要先承认一件事:检索再准,Prompt 写散了,模型照样会胡编或跑题。CO-STAR 把「长上下文里容易丢的约束」拆成固定维度,适合做成团队模板、评审清单。
| 维度 | 含义 | 和前端工作的类比 |
|---|---|---|
| Context | 任务背景、业务边界 | 需求背景、项目说明 |
| Objective | 要完成的单一目标 | 函数返回值 / 验收标准 |
| Style | 文体与表述方式 | 设计系统里的文案层级 |
| Tone | 情绪与态度 | 对用户说话的语气规范 |
| Audience | 读者是谁 | 用户画像、读者技术水平 |
| Response | 输出形态(列表、Markdown、字段名) | API 的 Response Schema |
和 RAG 的关系:检索片段只是「动态 Context」的一部分;Objective + Response 决定模型是「严格引用材料」还是「自由发挥」。课前把 CO-STAR 写熟,后面接向量库时少一半扯皮。
工程落地:练习里把 CO-STAR 与「工具分流」写进 唯一一条 system 消息;百炼 Generation.call 若同时传非空的 prompt=,SDK 会再追加一条 user,容易打乱角色边界,故长约束应放在 messages 的 role: system,而不是 prompt=。
二、工具调用:不是「注册了就智能」
这次练习里同时存在几类能力:列目录、读文本、拉监控快照。真实痛点是:模型不会自动知道该先读文档还是先查数,会受——用户措辞(是不是像「告警」)、工具在列表里的顺序、描述里有没有写清触发条件——共同影响。
几条可复用的工程经验:
- 把分流规则写进 System,用「必须 / 禁止 / 先后顺序」说话,比笼统「你可以调用工具」有效。
- 收窄「监控类」工具的描述:强调仅当用户明确要「当前 / 此刻」数值;流程、制度、联系人一律走读文档。
- 文档类工具放前、描述里写典型场景(SOP、报销、备份策略等),减轻「凡是告警就打监控」的路径依赖。
- 把「何时用哪个工具」也写进一份可被读取的说明(如
运维助手工具说明.md),让模型在犹豫时能读到「协议层」说明,而不是只靠参数猜。
三、多轮工具:一轮不够就必须循环
常见实现 bug 是:只执行第一轮 tool,把结果塞回上下文后又调一次模型,看到第二轮还是 tool_calls(例如要继续 read_local_file)却不再执行,直接回到读下一行用户输入——用户会感觉「没跑完」。
正确形态是 while / for 上限 内循环:每次请求 → 若有 tool_calls 就全部执行并 append → 再请求,直到某次响应没有 tool_calls、给出最终自然语言(或触达轮次上限并打日志)。这和后面做 Agent、ReAct 的状态机是同一类东西。

四、本地知识 mock:边界即安全
练习里知识被限制在固定根目录内:列举与读取分离——先枚举相对路径,再按路径读文本。实现上要注意:
- 路径规范化 + 禁止跳出根目录(
..、绝对路径),避免被提示词注入带偏。 - 列表默认浅层、加条数上限,避免一次返回过大 JSON。
- 大文件进日志时做截断,防止 JSONL 单条爆炸。
五、JSONL 运行日志:排障与复盘
按自然日一个文件、每轮模型响应写一行 JSON,字段里带上:用户原话、第几轮、是否调工具、assistant 的 content、请求的 tool 列表、本地执行结果(可截断)。终端只打印有内容时的最终回答,减少刷屏;真要查「为什么没读某文件」,打开当天 JSONL 一行行看即可。
实现要点(utils.append_practice_jsonl_line):写入后 flush + fsync,避免脚本未结束时在编辑器里看不到追加内容;对嵌套结构做 coerce + allow_nan=False,避免非标准键、NaN 等导致整行写失败;二级摘要 / Map-Reduce 的独立调用可记入同目录下的 YYYY-MM-DD_secondary.jsonl,与主对话日志分离,便于只看预处理链路。
六、长文本与 Map-Reduce(知识点 2)
问题:read_local_file 若一次性返回极长正文,主对话上下文与费用都会吃紧;在还没接向量库前,需要 Map-Reduce 思想做预处理。
| 思路 | 做法 | 代价与风险 |
|---|---|---|
| 单次摘要 | 长文交给「摘要专用」一次对话,再把摘要给主模型 | 实现简单;摘要易丢条款号、联系人等细节 |
| 分块 Map + 合并 Reduce | 按固定字符窗口切块(可设 overlap 保留边界上下文),每块单独摘要,再开一轮把多块摘要合成总摘要,最后交给主模型 | 调用次数多(N+1)、延迟高;切块若切断句子,靠 overlap 与合并提示缓解 |
练习代码中的对应关系:
process_long_file(content, chunk_size, overlap):切块 → 逐块_generation_plain_text(Map)→ 再合并(Reduce)。- 默认
chunk_size/overlap可由模块常量或环境变量MAP_REDUCE_CHUNK_SIZE/MAP_REDUCE_OVERLAP调整;模型名可用MAP_REDUCE_MODEL。 - 当
read_local_file结果超过TOOL_OUTPUT_SUMMARY_THRESHOLD_CHARS(可用环境变量覆盖)时,主链路会走 Map-Reduce 预处理,再作为tool内容回到多轮对话。
需要持续考虑的工程点:
- 健壮性:某块摘要或 Reduce 失败时,是否降级为「截断原文 / 拼接块摘要」,保证主流程不中断。
- 延迟与成本:对话轮次与 token 随块数上升,正式环境要权衡阈值(多长才触发 Map-Reduce)。
- 切块策略:当前按字符滑动窗口是学习用;生产可再换按段落、标题、token 等切分。
七、System 模板与「静态 RAG 占位」的区分(知识点 3)
把 CO-STAR 抽成 constants.PROFESSIONAL_SYSTEM_TEMPLATE 时,若沿用「KNOWLEDGE BASE + {retrieved_context}」这类 经典 RAG 注入 写法,在本练习里会失真:事实并不在 system 里预填,而是 tool 消息动态返回。
本次修正方向:
- KNOWLEDGE BASE 段改为明确写清:可引用的事实仅来自对话中的 tool 返回(并点名
list_doc_files/read_local_file/get_current_status)。 - 补回 Audience(受众) 占位,与 Role、Style、Tone 并列。
- EXECUTION STEPS 改为「判类型 → 按工具分流调工具 → 以 tool 内容作答」,不再引导模型去交叉引用一块空的静态检索区。
CO-STAR_Prompt.py里删除与模板重复的冗长常量,单一数据源在constants.py,避免两处改不全。
八、Prompt 变量占位符:Python 与模板习惯
| 场景 | 规则 | 注意 |
|---|---|---|
str.format() | {name} 由关键字参数替换 | 模板里若要输出字面量花括号,需写成 {{ 与 }} |
| RAG 注入 | 有的流水线用 {retrieved_context} 在服务端替换后再发给模型 | 与 tool 流 并存时,要在文案里说清「上下文从哪来」,避免模型盯着空的占位符 |
| 环境变量调参 | 如 TOOL_OUTPUT_SUMMARY_THRESHOLD_CHARS、CO_STAR_LOG_DIR | 适合测试阶段频繁改阈值而不改代码 |
九、今日小结与下一课
| 主题 | 收获 |
|---|---|
| CO-STAR | 长任务 Prompt 脚手架;与 RAG 的 Context 注入强相关;宜进 system 而非 SDK 的 prompt= |
| Tools | 描述 + 顺序 + System 分流,决定「像不像 Agent」 |
| 多轮 | 必须闭环到 stop,不能只做单次 tool |
| 知识边界 | 根目录、列表、防穿越、日志截断 |
| 长文 | Map-Reduce 预处理 + 失败降级 + 阈值/环境变量可配 |
| 模板 | 静态 RAG 占位 vs 工具返回事实,文案要与真实数据流一致 |
下一课预告:Embedding、向量空间直觉、与检索怎么接到 CO-STAR 的 Context 位上(届时可能是「向量检索结果注入」与「tool 读全文」的组合,而不是混用空占位)。