上下文选择、排序与预算
正在读取学习记录…
上下文是模型这一次回答时能读到的信息。用户的问题、程序的规则、刚查到的资料和部分聊天历史,都可以放进去。例如客户问“退款什么时候到账”,我们把退款规则放在问题旁边,模型才有这份资料可参考。
可以把上下文想成开卷考试时摊在桌上的几页纸。资料库是书架,上下文是桌面。桌面大小有限,先摆最有用的几页,查错了问题就会挤占位置。系统需要做三件事:选择放什么,决定怎样排列,控制总长度。
从四段资料认识选择与预算
我们准备四段已经写好的文字,客户正在咨询退款到账时间:
| 名称 | 原文 | 字符数 | 选择理由 |
|---|---|---|---|
| 规则 | 回答需附来源 | 6 | 每次回答都要遵循 |
| 退款 | 退款通常三至五个工作日到账 | 13 | 直接回答当前问题 |
| 历史 | 客户昨天咨询退款 | 8 | 补充对话背景 |
| 配送 | 配送通常两天 | 6 | 和当前到账问题关联较低 |
预算是允许占用的总长度。为了直接数清楚,演示以字符计量;实际模型接口按 Token 计量,Token 是分词器切出的文本单位,中文字符与 Token 的数量会因模型而变化。真正发送请求时,应使用对应模型的计数方法,把规则、历史、问题、工具描述等都算进去,并为生成回答留出空间。
四段文字合计33个字符。预算20时,我们能放规则和退款说明,共19字符;预算30时,还能加入8字符的历史,共27字符。配送资料相关性较低,最后再考虑。
保存一个完整程序,直接运行
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-context" -Force
cd "F:\学习\l5-context"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
在资源管理器新建学习文件夹,例如 F:\学习\l5-context。把下面完整程序保存为 context_demo.py。然后在该文件夹地址栏输入 powershell,回车打开终端。
macOS:先在 Python 篇的学习文件夹中打开终端,执行 source .venv/bin/activate。再输入 cd ,把当前例子的文件夹拖入终端并回车,运行下面的命令。
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-context" -Force
cd "F:\学习\l5-context"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
"""观察固定资料在不同上下文预算下的选择、排序和占用量。"""
from dataclasses import dataclass
@dataclass(frozen=True)
class Fragment:
name: str
text: str
relevance: int
required: bool = False
def assemble(fragments, budget):
# 演示按字符计量。实际接口应使用对应模型的分词器计数。
if type(budget) is not int or budget < 0:
raise ValueError("预算应为非负整数")
required = [item for item in fragments if item.required]
used = sum(len(item.text) for item in required)
if used > budget:
raise ValueError("预算放不下必要规则")
chosen = list(required)
skipped = []
# 同样相关的片段优先选择较短的;名称用于稳定处理平局。
optional = sorted((item for item in fragments if not item.required),
key=lambda item: (-item.relevance, len(item.text), item.name))
for item in optional:
if used + len(item.text) <= budget:
chosen.append(item)
used += len(item.text)
else:
skipped.append(item.name)
return chosen, skipped, used
FRAGMENTS = [
Fragment("规则", "回答需附来源", 0, True),
Fragment("退款", "退款通常三至五个工作日到账", 10),
Fragment("配送", "配送通常两天", 1),
Fragment("历史", "客户昨天咨询退款", 5),
]
def main():
for budget in (20, 30):
chosen, skipped, used = assemble(FRAGMENTS, budget)
print(f"预算={budget},已用={used}")
print("选入:" + "、".join(item.name for item in chosen))
print("暂未选入:" + ("、".join(skipped) or "无"))
for item in chosen:
print(f"[{item.name}] {item.text}")
try:
assemble(FRAGMENTS, 3)
except ValueError as error:
print("小预算:" + str(error))
if __name__ == "__main__":
main()在 PowerShell 中执行:
cd "F:\学习\l5-context"
ls
python -X utf8 context_demo.py如果使用其他学习目录,把第一行换成自己的目录路径;cd 进入目录,dir 列出文件。确认能看到 context_demo.py 后执行最后一行。程序已经内置两种预算和一次容量不足的情况,无需修改数据。
程序输出:
预算=20,已用=19
选入:规则、退款
暂未选入:历史、配送
[规则] 回答需附来源
[退款] 退款通常三至五个工作日到账
预算=30,已用=27
选入:规则、退款、历史
暂未选入:配送
[规则] 回答需附来源
[退款] 退款通常三至五个工作日到账
[历史] 客户昨天咨询退款
小预算:预算放不下必要规则看到这组输出,就能观察到选择的全过程:20字符预算先保住规则与答案依据,30字符预算多放一段历史。最后的3字符预算连规则都放不下,程序解释原因并结束这组尝试。
沿数据流读懂程序
Fragment 表示一段资料。它的 name 是显示名称,text 是文字,relevance 是与当前问题的相关等级,required 表示必须保留。这里的等级由演示直接给定:退款10、历史5、配送1。在完整检索系统中,排序信号可以来自关键词匹配、向量相似度或重排模型。
assemble 的输入是资料列表与预算,输出包含选中的片段、未选中的名称、已用长度。函数依次执行:
- 先检查预算。非负整数可以表示容量;字符串和开关值需要先修正。
- 取出必需资料,算出6字符的占用。必需内容已经超出容量时,直接返回错误原因。
- 将其他资料按相关等级降序排列。同等级时短片段靠前,名称用来固定平局顺序。
- 逐段检查“已用长度+本段长度”。放得下就加入,放不下就记录到未选入列表。
预算20时,已有规则6字符,加退款13字符得到19;加历史会变成27,加配送会变成25,都超出20。预算30时,历史可以加入;再加配送会达到33,仍超过30。
这种逐段选取叫贪心选择:先拿当前排序中最值得选的项。它实现简单,但可能跳过一段较长资料后选入后面的短资料,也可能不如另一种组合节省空间。重要条款跨两个片段时,应该先把它们组成完整证据单元再考虑容量,以免只选条件或只选结论。
选择顺序与展示顺序怎样配合
选择顺序解决谁先占容量,展示顺序解决资料怎样交给模型阅读。程序先放规则,随后放退款依据和历史;真实请求还会把当前问题放到明确位置,并用资料编号标记出处。
| 信息 | 选择时的处理 | 进入请求时的处理 |
|---|---|---|
| 程序规则 | 预留容量 | 放到相应指令位置 |
| 退款依据 | 优先选完整条款 | 标出文档、版本和适用条件 |
| 最近对话 | 按当前任务相关性选取 | 保留用户说法和必要上下文 |
| 低相关资料 | 预算充足再考虑 | 避免混入无关业务结论 |
例如订单属于海外渠道,而规则只适用国内订单,就应先筛选适用渠道,再做相关性排序。同样,客户无权访问的资料应在检索或选入阶段过滤。相关等级只回答“像不像当前问题”,适用条件和权限还有各自的判断步骤。
上下文长度接近上限时,可以减掉无关历史、选取完整的必要段落,或者压缩重复信息。压缩后的摘要仍要保住日期、对象和例外条件。这就是下一篇要处理的问题。
改一处输入,核对机制变化
把 main 中 (20, 30) 改为 (20, 33),运行 python -X utf8 context_demo.py。第二组已用33字符,规则、退款、历史、配送都选入,暂未选入为无。33 = 6 + 13 + 8 + 6,每项仍按既有顺序装入。恢复30后重跑,配送再次留在上下文外。
运行配套检查
下面的测试文件保存为 test_context_demo.py,与主程序放在同一个目录。四项测试分别检查20字符和30字符的结果、必要规则容量不足、非法预算。
查看测试代码:test_context_demo.py
"""检查必要规则、选择顺序、容量和非法预算。"""
import unittest
from context_demo import FRAGMENTS, assemble
class ContextTests(unittest.TestCase):
def test_small_budget_keeps_rule_and_refund(self):
chosen, skipped, used = assemble(FRAGMENTS, 20)
self.assertEqual([item.name for item in chosen], ["规则", "退款"])
self.assertEqual(skipped, ["历史", "配送"])
self.assertEqual(used, 19)
def test_larger_budget_adds_history(self):
chosen, skipped, used = assemble(FRAGMENTS, 30)
self.assertEqual([item.name for item in chosen], ["规则", "退款", "历史"])
self.assertEqual(skipped, ["配送"])
self.assertEqual(used, 27)
def test_required_content_must_fit(self):
with self.assertRaisesRegex(ValueError, "必要规则"):
assemble(FRAGMENTS, 3)
def test_budget_type_and_range(self):
for invalid in (True, -1, "20"):
with self.assertRaises(ValueError):
assemble(FRAGMENTS, invalid)
if __name__ == "__main__":
unittest.main()python -X utf8 -m unittest -v test_context_demo.py应看到四个测试名称后面的 ok,最后显示 Ran 4 tests 与 OK。测试里的19和27是根据当前四段文字计算出来的;文字变化时,需要重新核对字符数和结果。
| 运行时看到什么 | 原因 | 操作 |
|---|---|---|
| 找不到 context_demo.py | 终端目录不匹配 | 运行 dir,切换到保存文件的目录 |
| 预算放不下必要规则 | 容量小于必需内容长度 | 使用程序中20或30的演示预算 |
| 修改后结果没变 | 文件还未保存,或运行了另一个副本 | 保存后核对当前文件路径再运行 |
| 某条资料一直未选入 | 排序靠后且剩余空间不足 | 对照相关等级和逐次占用量 |
小结
上下文组装先保证必要规则,再在容量内选取与问题相关的资料。容量增加会改变选中的集合;排序、适用条件和资料完整性决定这些位置值不值得占用。上述数字展示的是资料选择过程,最终回答质量还要对照原文检查。
参考阅读:LlamaIndex 的 RAG 流程介绍提供了加载、索引、检索和生成之间的联系。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
objective · 上下文选择、排序与预算
围绕 L5-context 说明一个可验证的工程做法。
对应知识点:L5-context
short_answer · 上下文选择、排序与预算
围绕 L5-context 说明一个可验证的工程做法。
对应知识点:L5-context
coding · 上下文选择、排序与预算
围绕 L5-context 说明一个可验证的工程做法。
对应知识点:L5-context
scenario · 上下文选择、排序与预算
围绕 L5-context 说明一个可验证的工程做法。
对应知识点:L5-context
下一篇:压缩、摘要失真与缓存。我们继续看怎样缩短资料,同时保留影响答案的条件。
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。