Skip to content

过滤器、维护与生产验证 ​

过滤器在执行边界观察和阻断 ​

提示规则只影响模型,函数调用过滤器包围实际函数执行,适合记录耗时、检查可信上下文或拒绝动作。过滤器应 await next 才会继续调用;漏掉它可能让函数根本不执行,重复调用它可能重复副作用。

以下 filter.py 是完整本地实验,沿用第一章依赖,不需要模型:

python
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function,KernelArguments
from semantic_kernel.filters import FilterTypes
class Echo:
    @kernel_function(description='返回输入')
    def read(self,text:str)->str:return text
async def main():
    k=Kernel();p=k.add_plugin(Echo(),plugin_name='echo')
    seen=[]
    @k.filter(FilterTypes.FUNCTION_INVOCATION)
    async def audit(context,next):
        seen.append('before')
        if context.arguments.get('text')=='blocked':raise PermissionError('blocked')
        await next(context)
        seen.append('after')
    r=await k.invoke(p['read'],arguments=KernelArguments(text='ok'))
    assert r.value=='ok' and seen==['before','after']
    print('filter order',seen)
asyncio.run(main())

记录 after 说明下游正常返回,不代表外部业务事务已经持久化。异常路径若需记录结束,用 try/finally 记录独立的 exit 事件,同时保留原异常,不能吞掉失败返回成功。

多模型与权限不应耦合 ​

模型服务切换只影响生成能力,不能改变注册工具的授权范围。根据任务类型路由较小或较强模型前,固定相同函数合同与测试集。高风险写函数必须在服务端验证身份、资源与审批状态;模型生成的 tenant_id 不能作为授权来源。

日志保留插件名、函数名、调用时长和结果状态,参数中可能含隐私或密钥,应按字段脱敏。过滤器不等于操作系统沙箱,运行代码或访问文件仍需实际进程与网络限制。

维护旧调用方式 ​

旧 Semantic Kernel 教程可能使用 skill、semantic function、旧 planner 或旧 memory API。先记录锁文件与实际导入,再按对应版本修复,不能把旧 kernel.run 示例直接与现代 KernelArguments 混合。Python、.NET、Java 的支持程度和 API 名称不同,迁移记录要写明语言。

上游当前将 Microsoft Agent Framework 1.0 定位为 Semantic Kernel 的后继框架。本课程仍以已安装验证的 Semantic Kernel 1.36.0 维护分支讲清 Kernel;新项目选型时另读其 迁移指南,不能误认为 SK 1.x API 就是后继框架 API。

将旧能力映射为 plugin/function 后,逐项比较输入变量、默认设置、函数选择、自动调用次数和错误类型。新 Process/Agent 或 Microsoft Agent Framework 是新的编排选择,不是修改包名就完成迁移;保留旧业务验收样本,再决定是否切换。

维护旧输入合同,而不是只替换方法名 ​

旧业务保存的变量常用input作为默认输入;现代原生函数可能明确需要text。先保持旧调用方的JSON合同,把转换集中到适配器并拒绝未知字段。以下legacy_adapter.py沿用1.36.0,不运行旧kernel.run;它是实证的旧输入兼容路径,适用于已经保存的调用记录。

python
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.functions import KernelArguments,kernel_function
class Plugin:
    @kernel_function
    def upper(self,text:str)->str:return text.upper()
def adapt(old):
    if set(old)!={'input'} or not isinstance(old['input'],str):
        raise ValueError('invalid legacy input')
    return KernelArguments(text=old['input'])
async def main():
    k=Kernel();p=k.add_plugin(Plugin(),plugin_name='text')
    for old,expected in [({'input':'abc'},'ABC'),({'input':''},'')]:
        r=await k.invoke(p['upper'],arguments=adapt(old))
        assert r.value==expected
    for old in ({'text':'abc'},{'input':3},{'input':'a','tenant':'admin'}):
        try:adapt(old)
        except ValueError:pass
        else:raise AssertionError('legacy boundary widened')
    print('legacy contract retained')
asyncio.run(main())

空字符串属于旧合同的合法输入,不能用truthiness误判缺失;字段input与text的变化由适配器承担,业务调用者不用同时升级。这个测试不证明旧Planner计划与现代自动函数选择等价:旧计划若含重复写操作必须重新验证授权和幂等,不能仅把步骤名重命名后执行。

生产验收的两层证据 ​

确定性层运行原生函数、过滤器、检索空结果、异常和幂等测试;模型层使用固定问题测函数选择、参数正确性、引用依据和拒绝行为。只有第一层通过,不能声称 Agent 质量已通过。SDK、模型服务、提示、插件与索引版本组成一个可回滚组合。

本章原生实验可本地运行;自动函数选择、多模型路由与真实向量索引未在线实测。参阅 过滤器 与 迁移指南。

练习 ​

改造过滤器:无论下游成功或失败都追加 exit;blocked 输入不得进入函数。为允许与拒绝路径分别预测 seen。

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

下一篇: 读取、修改与执行旧 SequentialPlanner 计划 。

用心记录每一步成长