Skip to content

历史、函数选择、检索与编排 ​

对话历史是显式状态 ​

Kernel 不是自动记忆库。ChatHistory 记录消息,调用模型后必须保存 assistant 回复及工具往返,后续轮次才拥有所需上下文。业务长期资料存在数据库/检索系统,聊天摘要不是事务数据库。

沿用第一章环境与 Inventory 定义。下例保存 auto.py,把第一章 Inventory 类原样放在 imports 后,再运行;这部分调用模型并可能调用只读库存函数。

python
import asyncio,os
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion,OpenAIChatPromptExecutionSettings
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.contents import ChatHistory
# 此处放入上一节已定义的 Inventory 类及 kernel_function 导入。
async def main():
    kernel=Kernel()
    kernel.add_plugin(Inventory(),plugin_name='inventory')
    service=OpenAIChatCompletion(service_id='chat',ai_model_id=os.environ['OPENAI_MODEL'],
                                  api_key=os.environ['OPENAI_API_KEY'])
    kernel.add_service(service)
    settings=OpenAIChatPromptExecutionSettings()
    settings.function_choice_behavior=FunctionChoiceBehavior.Auto()
    history=ChatHistory()
    history.add_system_message('用库存工具核对,不猜测库存。')
    history.add_user_message('B 有库存吗?')
    response=await service.get_chat_message_content(chat_history=history,settings=settings,kernel=kernel)
    history.add_message(response)
    print(response)
asyncio.run(main())

这是有明确前置的局部组合;Inventory 与 decorator 导入必须来自上一章,不是隐藏依赖。Auto 允许模型选择函数并由框架执行,不能把写数据库函数未经授权直接注册。只读函数是此例的边界;生产设置调用上限并监控函数实际次数。

检索是插件,Embedding 不是答案 ​

检索链先将资料切成可定位段,计算 embedding 并建立索引,查询时计算问题向量并取候选,再将文本与来源交给模型。embedding 相似度不保证事实成立,检索得到的片段也可能过期或越权。Kernel 可以调用检索插件,但不会自动完成权限隔离。

先把检索接口从模型中剥离,使用原生确定性插件检查引用合同。以下独立 retrieve.py 不调用向量服务,明确是词匹配基线,不冒充向量检索:

python
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function,KernelArguments
class Search:
    @kernel_function(description='检索本地小型资料,返回带来源的文本')
    def find(self,query:str)->str:
        docs={'doc-1':'MCP 提供工具和资源','doc-2':'A2A 提供远程任务协议'}
        return '\n'.join(f'[{key}] {text}' for key,text in docs.items() if query in text)
async def main():
    k=Kernel();p=k.add_plugin(Search(),plugin_name='search')
    r=await k.invoke(p['find'],arguments=KernelArguments(query='MCP'))
    assert '[doc-1]' in r.value and '[doc-2]' not in r.value
    print(r.value)
asyncio.run(main())

替换为真实向量后端时保持相同来源合同,并记录 embedding 模型、维度、索引版本与租户过滤;更换 embedding 模型通常需要重建索引,不能只换查询模型。完整向量机制继续阅读 RAG 路线。

Planner、Process 和 Agent 的不同责任 ​

老 Planner 将目标变为计划,现代函数选择通过模型请求函数;二者都不能代替业务授权。Process 更适合明确事件与步骤的确定流程,Agent 管模型驱动的交互选择。长任务把状态持久化在应用层,不能以一个 Python 对象存活来保证恢复。

例如审批后才扣库存的流程,先检索→生成建议→等待授权→写操作,每步拥有输入与输出,失败时只恢复未完成步骤。不要让模型重规划把已经完成的扣库存再执行一次。

练习 ​

把 retrieve.py 查询改为不存在的词,要求返回空字符串;上层应解释“没有找到依据”,不能捏造 doc-3。再查询 A2A,应只返回 doc-2。

先记录自己的输出与解释,再核对本章答案。

用心记录每一步成长