虽然我个人对Coding Agent一直持保留的态度但不可否认的是这已经成为Agent应用最大的一个分支所以如何有效地评估Agent生成的代码就显得尤为重要。针对生成的代码的评估可以验证代码文本的合法性也可以验证其可执行性可以利用没有隔离的代码揭示和执行环境也可以借助Sandbox提供更加安全的隔离保障评估代码可以在本地执行也可以上传到云平台执行。本章介绍几种在本地进行的针对Python的代码评估器。1. 基于Pyright的Python代码评估器Pyright是由微软开发的一款静态类型检查工具专为Python语言设计。它也是目前非常流行的VS Code插件Pylance的底层核心引擎。核心优势与特点包括极致的速度这是Pyright最显著的标签。它完全使用TypeScript编写并在Node.js环境下运行。由于其创新的架构设计它的检查速度通常比用Python编写的mypy快出3到5倍在大型项目数百万行代码中优势尤为明显零配置开箱即用它不需要繁琐的配置。只要你的代码中包含了Python 3的类型注解Pyright就能直接进行精准的推导和检查高精度的类型推导即使你没有给变量显式写出类型Pyright也能根据上下文、函数的返回值以及第三方库的定义非常聪明地推断出变量的真实类型对最新Python特性的顶级支持作为微软官方维护的项目它对Python最新版本以及高级类型特性的支持速度极快。1.1 代码评估器的创建我们可以调用如下这两个工厂函数来创建基于基于Pyright的Python代码评估器。具体来说create_pyright_evaluator函数用来创建作为同步评估器的SimpleEvaluator对象而create_async_pyright_evaluator用来创建作为异步评估器的SimpleAsyncEvaluator对象。评估器并不要求提供存储的Python代码文本实施评估因为它具有从给出文本提取代码的能力。defcreate_pyright_evaluator(*,pyright_cli_args:list[str][],code_extraction_strategy:Literal[none,llm,markdown_code_blocks]none,code_extractor:Optional[Callable[[Any],str]]None,client:Optional[BaseChatModel]None,model:Optional[str]None,)-SimpleEvaluatordefcreate_async_pyright_evaluator(*,pyright_cli_args:list[str][],code_extraction_strategy:Literal[none,llm,markdown_code_blocks]none,code_extractor:Optional[Callable[[Any],Union[str,Awaitable[str]]]]None,client:Optional[BaseChatModel]None,model:Optional[str]None,)-SimpleAsyncEvaluator两个工厂函数的参数说明如下pyright_cli_args传递给底层pyright命令行工具的额外参数。默认情况下脚本已经固定写死了--outputjson和--level error。你可以通过这个参数传递其他Pyright支持的参数例如[“–pythonversion”, “3.11”]指定大模型生成的代码应该以Python 3.11的语法标准进行类型检查。[“–skipunannotated”]跳过对未编写类型注解的函数检查。code_extraction_strategy指定从LLM的原始文本回复中提取纯Python代码的策略具有如下三个选项none不做任何处理。直接将LLM返回的所有文本全部塞进.py文件。适用于你的大模型被严格限制了只能输出纯代码、不能带有任何自然语言解释的场景llm使用大模型提取。如果被评估的模型输出非常混乱例如把代码和大量文字混杂在一起且不用Markdown标签评估器会调用另一个独立的大模型裁判模型来帮你把纯代码捞出来markdown_code_blocks: 提取Markdown代码块。评估器会自动扫描文本只把被python ...包裹的代码提取出来进行测试。这是最常用的推荐设置。code_extractor自定义代码提取函数。如果上述三种自带的code_extraction_strategy策略都不满足你的需求你可以自己写一个Python函数传给它;client如果code_extraction_strategy选择llm则可以使用这个参数提供一个BaseChatModel组件调用LLMmodel: 如果code_extraction_strategy选择llm则可以使用这个参数提供一个标准的模型名称。1.2 评估的流程该评估器的核心逻辑可以概括为以下四个步骤代码提取从LLM的原始输出中提取出纯Python代码支持直接读取、Markdown块提取或通过LLM再次提取临时落盘将提取出的代码写入系统的临时文件.py静态分析在后台调用系统命令pyright对该临时文件进行扫描并输出JSON 格式的错误报告解析打分解析报告忽略缺失第三方库导入reportMissingImports的错误。若无其他错误则判定为通过score True否则返回具体的错误列表score False。如果将code_extraction_strategy参数设置为llmclient和model必须有一个被设置。如果设置的是model则使用init_model函数来创建对应的BaseChatModel对象。代码的提取是通过注册如下所示的两个工具来完成的。具体来说具体注册的是不具有实现的工具声明。如果成功提取到评估代码会在AIMessage中返回针对ExtractCode工具的调用并将代码片段作为code参数的值评估器只需要提取此参数即可。与之类似如果没有发现代码则会调用另一个NoCode工具。classExtractCode(TypedDict):Tool to call if there is code to extract. Omit installation instructions and shell commands.code:strclassNoCode(TypedDict):Tool to call to indicate no code was found.no_code:bool代码提取会使用如下的系统提示词和用户提示词You are an expert software auditor. Instructions Your job is to extract code from a given text. - If there is code - extract it into a single script by calling the provided ExtractCode tool. - If there is no code to extract - call NoCode. If you extract code, your response will be passed DIRECTLY into a code execution sandbox for further testing, so make sure to extract all code **without modifications**, even if it contains errors, since any modifications will ruin the integrity of the testing process. Omit installation instructions and shell commands from any code you extract. /InstructionsExtract code from the following: text {outputs} /text1.3 实例演示在如下的这段演示程序中我们利用create_async_pyright_evaluator函数创建了一个SimpleAsyncEvaluator对象我们将code_extraction_strategy参数设置成markdown_code_blocks。然后利用它了评估段包含Python代码的Markdown文本并将执行结果以JSON形式输出。由于评估器会将检测到的错误以JSON字符串的形式存储在comment字段中所以我们定义了辅助函数pretty_print对comment字段进行了格式化。fromopenevals.code.pyrightimportcreate_async_pyright_evaluatorfromopenevals.typesimportEvaluatorResultfromtypingimportcastimportjson,asyncio code1 这是一个计算斐波那契数列的Python函数 python def fibonacci(n: int) - list[int]: if n 0: return [] if n 1: return [0] sequence: list[int] [0, 1] while len(sequence) n: sequence.append(sequence[-1] sequence[-2]) return sequence code2 这里是一个处理用户数据的函数 python def process_user_age(age_str: str) - int: if not age_str.isdigit(): return Invalid Age age int(age_str) return age def greet_user(name: str, age: int) - str: return fHello {user_name}, you are {age} years old. defpretty_print(result:EvaluatorResult):ifresult[comment]!None:formatted:dict{key:result[key],score:result[score],comment:json.loads(result[comment]),metadata:result[metadata],source_run_id:result.get(source_run_id)}print(json.dumps(formatted,indent2,ensure_asciiFalse))returnprint(json.dumps(result,indent2,ensure_asciiFalse))asyncdefmain():evaluatorcreate_async_pyright_evaluator(code_extraction_strategymarkdown_code_blocks)resultawaitevaluator(outputscode1)pretty_print(cast(EvaluatorResult,result))resultawaitevaluator(outputscode2)pretty_print(cast(EvaluatorResult,result))asyncio.run(main())输出{key:pyright_succeeded,score:true,comment:[],metadata:null,source_run_id:null}{key:pyright_succeeded,score:false,comment:[{severity:error,message:Type \Literal[Invalid Age]\ is not assignable to return type \int\\n \Literal[Invalid Age]\ is not assignable to \int\,range:{start:{line:2,character:15},end:{line:2,character:28}},rule:reportReturnType},{severity:error,message:\user_name\ is not defined,range:{start:{line:8,character:20},end:{line:8,character:29}},rule:reportUndefinedVariable}],metadata:null,source_run_id:null}从输出可以看出第一段代码并没有问题第二段代码的如下两个问题被成功检测到process_user_age函数返回值与注解类型不一致greet_user函数使用了未定义的变量user_name。2. 基于Mypy的Python代码评估器MyPy是Python社区中最官方、最老牌且使用最广泛的静态类型检查工具。它由Python之父Guido van Rossum深度参与开发并极力推崇直接促成了Python 3.5引入官方的typing模块。基于Mypy的代码评估器与上面介绍的基于Pyright的评估器除了底层使用的静态代码分析引擎不一致在设计和API的使用上基本是一样的。如下是用来创建同步和异步评估器的两个工厂函数create_mypy_evaluator和create_async_mypy_evaluator它们具有与create_pyright_evaluator/create_async_pyright_evaluator函数完全一致的参数定义。defcreate_mypy_evaluator(*,mypy_cli_args:list[str][--no-incremental,--disallow-untyped-calls,--disallow-incomplete-defs,--ignore-missing-imports,],code_extraction_strategy:Literal[none,llm,markdown_code_blocks]none,code_extractor:Optional[Callable[[Any],str]]None,client:Optional[BaseChatModel]None,model:Optional[str]None,)-SimpleEvaluatordefcreate_async_mypy_evaluator(*,mypy_cli_args:list[str][--no-incremental,--disallow-untyped-calls,--disallow-incomplete-defs,--ignore-missing-imports,],code_extraction_strategy:Literal[none,llm,markdown_code_blocks]none,code_extractor:Optional[Callable[[Any],Union[str,Awaitable[str]]]]None,client:Optional[BaseChatModel]None,model:Optional[str]None,)-SimpleAsyncEvaluator3. 如何选择其实两种评估差不了太多如果一定有所选择的化可以根据如下的策略来选如果你追求极速与稳定性建议使用前面的Pyright评估器它的Node.js底层和原生JSON 输出非常适合高并发的AI自动化评测流水线如果你追求Python官方的严苛标准使用当前的Mypy评估器并保留其默认的严格参数可以逼迫大模型生成极其规范、完全符合PEP标准的现代 Python 代码更高级的玩法双剑合璧在LangSmith评测集里同时注册这两个评估器。如果一个LLM生成的代码能同时通过Pyright和Mypy的双重评估说明这段代码在任何工程环境下的类型安全度都已经达到了顶峰。