ell 自动版本化与追踪:基于词法闭包的提示词序列化、存储与调用溯源机制

发布时间:2026/10/12 5:19:21
ell 自动版本化与追踪:基于词法闭包的提示词序列化、存储与调用溯源机制
人工智能大模型提示工程AI 应用【免费下载链接】ellA language model programming library.项目地址https://gitcode.com/gh_mirrors/ell/ell点击查看免费下载本指南围绕 ellLanguage Model Programming Library的「自动版本化 调用追踪」核心能力展开。我们会先回答一个根本问题为什么提示词prompt工程需要一套类似机器学习训练时 checkpoint 的版本控制系统随后深入 ell 如何通过词法闭包lexical closure把语言模型程序LMP及其全部依赖序列化为自包含的源码快照并在此基础上构建版本哈希、依赖图、SQLite 存储、自动 commit message 与基于_lstr追踪对象的计算图。读完本文你将掌握ell.init(store..., autocommit...)的完整用法、底层存储表结构SerializedLMP/Invocation/InvocationContents、闭包哈希如何产生版本以及如何用 origin trace 还原一次多级 LMP 调用之间的数据流。为什么提示词工程需要版本化提示词工程本质上是对发送给语言模型的 system、user 与预组装 assistant 消息集合做快速迭代以最大化某个显式或隐式的目标函数。理想情况下我们拥有 reward model 或自动指标来评估提示词质量只需要修改文本与格式即可。现实要混乱得多。工程师往往只针对几个样例反复微调提示词一边改一边祈祷输出看起来更好。这个过程存在三个典型问题一次提示词改动是否会对 LMP 的质量带来一致性提升往往并不清楚由于代码库其他位置的依赖发生变化可能**悄悄引入回归regression**而无人察觉验证假设、回退测试依赖编辑器里的 undo/redo 快捷键无法有效追踪变更历史。把这个问题类比机器学习训练循环就能找到解提示词工程本质是参数搜索——我们随时间用局部更新修改一个模型目标是最大化/最小化某个全局目标函数。机器学习中每个参数实例叫 checkpoint定期保存并评估质量一旦超参数改动导致训练失败就快速回退。提示词版本化就是训练循环中 checkpoint 的类比。然而用裸的 API 调用或传统框架做这件事极其笨重。常见替代方案包括为每一次小改动把提示词代码提交进 git、把 commit hash 与输出一起存储、把提示词和输出保存到文本文件——这些做法都违背了软件工程的版本控制工作流。而一些提供版本化的框架又强制要求专用 IDE 或特定命名约定与调用散落在整个代码库各处的真实 LLM 应用脱节。ell 的关键特性正是这种后台自动运行的版本控制系统无需改变提示词工程师的任何工作流即可在开发与生产环境中对提示词进行比较、可视化与存储。通过词法闭包序列化提示词自动版本化之所以可能是因为 ell 把提示词视为离散的函数单元——语言模型程序LMP。将提示词封装在函数内之后可以使用静态与动态分析工具在任意时刻提取提示词程序的源码及其全部词法依赖捕获复现该提示词所需的精确源码集合。考虑下面这段深嵌在大代码库中的函数from myother_module import CONSTANT def other_code(): print(hello) def some_other_function(): return to bob ell.simple(modelgpt-4o) def hi(): You are a helpful assistant return fsay hi {some_other_function()} {CONSTANT} times. def some_other_code(): return some other code序列化并版本化 LMPhi意味着什么最朴素的做法是只捕获函数体与其签名ell.simple(modelgpt-4o) def hi(): You are a helpful assistant return fsay hi {some_other_function()} {CONSTANT} times.但这远远不够。如果依赖some_other_function变了LMPhi也从根本上变了所有预期输出都会随之改变。解法是计算词法闭包一个函数的闭包等于其源码加上它所依赖的每一个全局变量与自由变量的源码。例如 lexical_closure(hi) CONSTANT 6 def some_other_function(): return to bob ell.simple(modelgpt-4o) def hi(): You are a helpful assistant return fsay hi {some_other_function()} {CONSTANT} times. 从实现上看完整闭包由 src/ell/util/closure.py 中的lexical_closure()负责它解析函数及其全部绑定全局变量的抽象语法树AST递归枚举依赖计算出能够复现该函数的最小源码集合。为了保持闭包精炼系统会自动忽略由包管理器安装的第三方库与标准库模块——它们被视为执行环境的一部分而非函数专属闭包。判定逻辑位于 src/ell/util/should_import.py 的should_import()模块位于site-packages或标准库路径时返回 True以 import 语句形式保留位于项目根默认取ELL_PROJECT_ROOT环境变量或当前工作目录等本地路径时返回 False展开为具体源码。细节上闭包生成还会遍历函数的默认参数、参数注解与返回注解递归捕获其中的可调用对象_process_default_kwargs/_process_signature_dependency对多行字符串以...形式内联对不可变变量以# BV标记内联repr对可变对象以# BmV占位_process_other_variable通过get_referenced_names解析模块中被引用的属性如math.sin只提取这些属性对应的源码并把module.attr解引用为attr用 Black 统一格式化源码再结合去重、import 排序等清洗逻辑_clean_src。这些行为都有测试用例印证参见 tests/test_closure.py例如test_lexical_closure_with_global断言global_var 10出现在闭包中test_lexical_closure_with_nested_function断言嵌套的inner函数同样被包含test_get_referenced_names验证math.sin这类属性引用的提取。构建依赖图当 LMP 依赖另一个提示词即一个 LMP 调用另一个 LMP时被调用的提示词会自动出现在调用方的词法闭包内。这让我们可以构建一张计算图直观展示复杂 LMP 中不同提示词之间如何相互依赖、如何复用 test-time compute。下面的例子演示了四个 LMP 通过互相调用组装成一个完整的故事生成管线import ell from typing import List ell.simple(modelgpt-4o-mini, temperature1.0) def generate_story_ideas(about : str): You are an expert story ideator. Only answer in a single sentence. return fGenerate a story idea about {about}. ell.simple(modelgpt-4o-mini, temperature1.0) def write_a_draft_of_a_story(idea : str): You are an adept story writer. The story should only be 3 paragraphs. return fWrite a story about {idea}. ell.simple(modelgpt-4o, temperature0.1) def choose_the_best_draft(drafts : List[str]): You are an expert fiction editor. return fChoose the best draft from the following list: {\n.join(drafts)}. ell.simple(modelgpt-4-turbo, temperature0.2) def write_a_really_good_story(about : str): You are an expert novelist that writes in the style of Hemmingway. You write in lowercase. # Note: You can pass in api_params to control the language model call # in the case n 4 tells OpenAI to generate a batch of 4 outputs. ideas generate_story_ideas(about, api_params(dict(n4))) drafts [write_a_draft_of_a_story(idea) for idea in ideas] best_draft choose_the_best_draft(drafts) return fMake a final revision of this story in your voice: {best_draft}. story write_a_really_good_story(a dog)注意write_a_really_good_story的闭包中会递归包含generate_story_ideas、write_a_draft_of_a_story、choose_the_best_draft三个 LMP 的源码。在存储层这种谁使用了谁的关系通过SerializedLMPUses多对多关联表落地见 src/ell/stores/models/core.py 中的SerializedLMP.uses/used_by而SQLStore.write_lmp()会依据传入的uses字典为这些 LMP 建立关联见 src/ell/stores/sql.py。启用版本化ell.init 与 Store有了 checkpoint 与序列化能力就可以兑现提示词工程库的关键承诺自动版本化。它分为两种形态开发过程中的自动版本化工程师反复调优提示词时可以随时回退到旧版本或跨版本比较生产部署中的归档版本化为线上排障、回归检查以及构建大规模微调/对比数据集提供基础。ell 把两者都考虑在内。关键在于版本化完全在后台发生不规定任何工作流。只需在初始化时传入一个存储参数ell.init(store./logdir)store参数可以是本地路径也可以是一个ell.storage.Store对象。Store 是存储提示词及其调用记录LMP 的输入输出、调用的语言模型、生成结果及元数据的统一接口。默认情况下传入路径时 ell 使用本地 SQLite 数据库 可扩展的基于文件的 blob 存储后者用于存放无法塞进数据库行的超大 LMP 或调用数据。从 src/ell/configurator.py 的init()签名可以看到完整的初始化参数参数类型说明storeStore \| strStore 实例或本地路径字符串会构造SQLiteStoreverbosebool是否输出详细日志autocommitbool是否在版本间自动生成人类可读的 commit messagelazy_versioningbool惰性版本化首次调用 LMP 时才计算闭包默认开启default_api_paramsdict为语言模型调用注入默认参数default_clientAny默认 OpenAI 客户端autocommit_modelstr自动 commit 使用的模型默认gpt-4o-mini关于生产部署文档注明ell 的 Store 可以对接任意数据库并预告了类似 Weights Biaseswandb的集中式提示词版本控制服务支持团队协作与高级版本能力。当前仓库已内置SQLiteStore与PostgresStore两种 SQL 实现src/ell/stores/sql.py。SQLiteStore的构造要求传入目录而非.db文件源码中有assert not db_dir.endswith(.db)它会在目录下创建ell.db数据库文件并挂载一个 gzip 压缩、按两层目录分片存放的SQLBlobStore。当 ell 以任意 Store 初始化后LMP首次被调用时惰性版本化模式会计算其词法闭包源码并哈希成该 LMP 的版本哈希同时计算前述依赖图并把 LMP 写入 Store。调用完成后与该版本相关的所有输入输出数据也被存入数据库供后续分析。随着提示词工程持续进行新版本只会在至少被调用一次后进入 Store——未被调用过的代码不会污染版本历史。完整的运行示例import ell from ell.stores.sql import SQLiteStore ell.init(store./logdir, autocommitTrue) ell.simple(modelgpt-4o-mini) def greet(name: str): You are a friendly greeter. return fGenerate a greeting for {name}. result greet(Alice) print(result) # Output: Hello, Alice! Its wonderful to meet you.落库后发生了什么三条核心表记录执行结束后SerializedLMP表可能新增一行lmp_id: 1a2b3c4d5e6f7g8h name: greet source: ell.simple(model\gpt-4o-mini\)\ndef greet(name: str):\n \\\You are a friendly greeter.\\\\n return f\Generate a greeting for {name}.\ dependencies: created_at: 2023-07-15T10:30:00Z lmp_type: LM api_params: {model: gpt-4o-mini} initial_free_vars: {} initial_global_vars: {} num_invocations: 1 commit_message: Initial version of greet function version_number: 1对应的Invocation表行id: 9i8u7y6t5r4e3w2q lmp_id: 1a2b3c4d5e6f7g8h latency_ms: 250.5 prompt_tokens: 15 completion_tokens: 10 created_at: 2023-07-15T10:30:01Z以及关联的InvocationContentsinvocation_id: 9i8u7y6t5r4e3w2q params: {name: Alice} results: [Hello, Alice! Its wonderful to meet you.] invocation_api_params: {temperature: 1.0, max_tokens: 50}这三张表的真实字段定义在 src/ell/stores/models/core.py 中均可找到SerializedLMP的lmp_id主键、name带索引即全限定名、source、dependencies、lmp_type取值见 src/ell/types/lmp.py 的LMPType枚举LM/TOOL/LABELER/FUNCTION/OTHER、api_params、initial_free_vars、initial_global_vars、num_invocations、commit_message、version_numberInvocation额外记录了latency_ms、prompt_tokens、completion_tokens、state_cache_key与used_by_idInvocationContents则承载params、results、invocation_api_params、global_vars、free_vars五个 JSON 字段。值得补充的底层细节版本哈希_generate_function_hash用 MD5 对「格式化后的函数源码 依赖闭包源码 全限定名」取哈希前缀lmp-即lmp- hashlib.md5(...).hexdigest()。任何依赖变化都会产生全新的lmp_id。版本号递增在 src/ell/lmp/_track.py 的serialize_lmp()中若同名 LMP按全限定名__qualname__归类已存在新版本号为latest_lmp.version_number 1Invocation的lmp_id引用func.__ell_hash__。调用计数SQLStore.write_invocation()每次写入调用都会把对应SerializedLMP.num_invocations自增。超大内容外部化InvocationContents.should_externalize会序列化五个 JSON 字段并估算大小超过102400 字节约 100KB且 Store 具备 blob 能力时is_externalTrue内容被 gzip 压缩后写入 blob store参见 src/ell/stores/sql.py 的SQLBlobStore。自动提交Autocommit为版本差异生成 commit message由于提示词就是源码本身版本间的差异diff天然可在后台自动计算因此 ell 还能自动生成人类可读的 commit messageell.init(store./logdir, autocommitTrue)传入autocommitTrue后每当产生一个取代先前版本按全限定名归类的新版本时ell 会调用语言模型自动生成 commit message之后可随时回看版本间的有效变更。这既服务于本地开发时快速定位理想提示词也服务于生产环境追溯回归与性能变化。实现细节极具启发性自动提交本质上是另一个 LMP。src/ell/util/differ.py 中write_commit_message_for_diff被simple(config.autocommit_model, temperature0.2, ...)装饰默认模型gpt-4o-mini可在ell.init(autocommit_model...)覆盖。它会用difflib.unified_diff计算新旧两个版本源码含依赖闭包的 unified diff连同新旧源码一起拼进 prompt要求模型给出「一句话高度具体的摘要 逐条 bullet」规则包括不能超过 10 个词、必须区分 system prompt严禁称其为 docstring、具体说明改了什么而不是为什么改、考虑 globals 与 free variables 的变化等。测试 tests/test_autocommit_model.py 中留有多组真实 diff 与claude-3-haiku/gpt-4o-mini的生成示例例如把温度从 0.5 改为 0.7、把 world class 改为 world-renowned class 等都会逐条列出。追踪Tracing理解提示词如何被使用版本化回答的是提示词如何变化而追踪回答的是提示词如何被使用。没有专用框架时开发者只能手动拦截 API 调用、为生产应用自建数据库 schema 来存储输入输出——难以跨项目扩展、频繁重复造轮子。现有替代方案各有权衡函数级追踪如 Weave捕获任意 Python 函数的输入输出适合生产监控但无法跟踪本地开发迭代中同一版本内部的变更框架内版本化如 LangChain/LangSmith提示词被压缩为模板字符串或模板 版本化 Python 代码结构清晰但约束太强未必契合所有开发工作流。ell 同时吸收了两种思路它序列化的是任意 Python 代码因此既能通过输入输出追踪 LMP 的使用方式又能按版本组织这些使用记录以供比较——而用户全程只需要写普通 Python 代码来产出提示词字符串。追踪对象与 _lstr字符串上的起源标签为追踪 LMP 在运行时如何相互作用、构建类似 PyTorch/TensorFlow 的数据流计算图ell 为所有 LMP 的输出包裹了一个追踪对象tracing object。追踪对象是 Python 不可变基础类型的包装记录起源 LMP 与相关元数据并在任意后续操作中保留这条起源轨迹。其中最重要的就是_lstr实现见 src/ell/types/_lstr.py。import ell ell.simple(modelgpt-4o) # version: ae8f32s664200e1 def hi(): return say hi x hi() # invocation id: 4hdfjhe8ehf (version: ae8f32s664200e1)这里的x功能上就是一个字符串、行为也与字符串完全一致但它的实际类型是_lstr type(x) class ell.types._lstr.lstr x hi x.__origin_trace__ {4hdfjhe8ehf}对字符串的持续操作会保留其起源轨迹因为所有原生字符串操作都被覆写产生的新不可变实例会包含或合并各自的 origin trace x[0] h x[0].__origin_trace__ {4hdfjhe8ehf} x there hi there (x there).__origin_trace__ {4hdfjhe8ehf}当两个携带起源的对象被组合时结果的轨迹是两者轨迹的并集 x hi() # invocation id: 4hdfjhe8ehf y hi() # invocation id: 345hef345h z x y z.__origin_trace__ {4hdfjhe8ehf, 345hef345h}从源码看_lstr直接继承str以__origin_trace__FrozenSet[str]保存起源并系统性覆写了__add__、__mod__、__mul__、__rmul__、__getitem__、join、split、rsplit、splitlines、partition、rpartition等方法__getattribute__会拦截未显式覆写的字符串方法只要返回结果是字符串就包装回_lstr并合并参数中的轨迹。这意味着format、upper等几乎所有字符串操作都能自动延续追踪。tests/test_lstr.py 对拼接、取切片、join、split、partition、格式化等场景逐一验证了轨迹的保持与合并。构建计算图与落到存储层的溯源通过同时记录 LMP 的输入与输出ell 可以用这些 origin trace 构建计算图展示 LMP 在运行时的交互关系。基于此你可以轻松追踪模型输出的流向、定位提示词链中的薄弱环节、识别提示词输入输出在传递中的非预期变化并为未来对 LMP 的符号化与离散优化铺路。例如一个包含子调用、参数中传递了其他 LMP 输出的聊天程序ell.simple(modelgpt-4o-2024-08-06, temperature1.0) def create_personality() - str: You are backstoryGPT. You come up with a backstory for a character incljuding name. Choose a completely random name from the list. Format as follows. Name: name Backstory: 3 sentence backstory # System prompt return Come up with a backstory about random.choice(names_list) # User prompt def format_message_history(message_history : List[Tuple[str, str]]) - str: return \n.join([f{name}: {message} for name, message in message_history]) ell.simple(modelgpt-4o-2024-08-06, temperature0.3, max_tokens20) def chat(message_history : List[Tuple[str, str]], *, personality : str): return [ ell.system(fHere is your description. {personality}. Your goal is to come up with a response to a chat. Only respond in one sentence (should be like a text message in informality.) Never use Emojis.), ell.user(format_message_history(message_history)), ]追踪数据如何在存储层落地关键链路位于 src/ell/util/serialization.py 的prepare_invocation_params()它对调用参数做 cattrs 序列化后用正则提取 JSON 中所有__origin_trace__字段得到本调用消费consume了哪些 invocation随后 src/ell/lmp/_track.py 的_write_invocation()把消费列表写入Invocation其used_by_id记录父调用而SQLStore.write_invocation()会为每对「消费方 / 被消费方」写入一条InvocationTrace关联记录src/ell/stores/models/core.py。配合线程局部的调用栈push_invocation/pop_invocation同样在_track.py中整个数据流图在调用发生的那一刻就被完整序列化了。注意当前 origin tracing 只作用于字符串原语。文档明确说明对任意对象追踪的支持正在开发中将在未来版本发布以实现对 LMP 中更多数据类型的全面溯源。总结与下一步本文梳理了 ell 自动版本化与追踪的完整机制词法闭包把提示词连同其全部依赖序列化为可复现源码 →MD5 版本哈希 按全限定名的版本号递增实现 checkpoint →SQLite blob 双栈 Store持久化 LMP 与调用数据 →autocommit用 LLM 自动为版本差异生成 commit message →_lstr追踪对象把每次调用的起源轨迹贯穿任意字符串操作 →InvocationTrace关联表在存储层还原数据流计算图。这整套能力都以「不改动任何开发工作流」为前提提示词工程师依旧用普通 Python 编写、调试、提交代码。这些版本化与追踪数据的可视化、比较与排障将由下一章介绍的工具完成ell Studio本地开源提示词版本控制与监控界面启动方式见 docs/src/index.rst命令为ell-studio --storage ./logdir它能帮助你以经验化方式推进提示词优化并尽早发现回归。赞分享人工智能大模型提示工程AI 应用【免费下载链接】ellA language model programming library.项目地址https://gitcode.com/gh_mirrors/ell/ell点击查看免费下载相关推荐ell 语言模型程序的序列化与版本化存储从词法闭包到调用记录ell 语言模型程序的序列化与版本化存储从词法闭包到调用记录 导读 在 ell 中prompt 并不是一段被直接送入语言模型的纯文本而是一个生成这段文人工智能大模型提示工程AI 应用ell 提示词序列化与版本存储深度解析从 Optimizer/Serializer 设计草案到 Store 落地实现ell 提示词序列化与版本存储深度解析从 Optimizer/Serializer 设计草案到 Store 落地实现 本文围绕仓库内的设计草案文档 docs/人工智能大模型提示工程AI 应用VoltAgent VoltOps 提示词分析用量总览、版本指标与 Trace 溯源机制VoltAgent VoltOps 提示词分析用量总览、版本指标与 Trace 溯源机制 在 VoltAgent 的 VoltOps 提示词管理平台中 An人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音上一篇如何永久保存微信聊天记录WeChatMsg完整免费解决方案终极指南下一篇5分钟搞定数据库读写分离ShardingSphere性能优化实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考