用 Outlines 实现 SimToM 两阶段视角代入 Agent:从 Prompt 模板到结构化生成的完整实践

📅 发布时间:2026/9/14 7:26:53
用 Outlines 实现 SimToM 两阶段视角代入 Agent:从 Prompt 模板到结构化生成的完整实践
用 Outlines 实现 SimToM 两阶段视角代入 Agent从 Prompt 模板到结构化生成的完整实践【免费下载链接】outlinesStructured Outputs项目地址: https://gitcode.com/GitHub_Trending/ou/outlines本篇技术指南以 Outlines 的官方示例文档 docs/examples/simtom.md 为主体讲解如何在当前仓库中用 Outlines 的 Prompt 模板Jinja2与结构化生成能力以十几行代码复现 SimToMSimulation Theory of Mind两阶段提示框架先让 LLM 站在指定角色视角过滤故事信息再基于该视角回答需要心智理论Theory of Mind推理的问题。读完本文你将掌握Template.from_file加载 Jinja2 提示词模板、用 Pydantic 模型约束 JSON 结构化输出、通过outlines.from_transformers接入 Hugging Face transformers 模型并执行两轮受约束生成从而构建一个可应用于 Agentic 工作流中的视角代入型推理链路。SimToM面向不一致世界状态的两阶段提示框架在需要跟踪多个角色各自掌握的不同信息即不一致的世界状态的任务中常见的 Chain-of-ThoughtCoT等提示策略往往力不从心——模型默认以全知视角作答容易把角色不该知道的信息混入推理。SimToM 受 Simulation Theory模拟理论启发提出一个简洁的两阶段提示框架其核心思路是显式地把推理与信息获取解耦视角代入Perspective-Taking第一轮提示接收一段story事件序列和指定的character目标是让模型以该角色的视角理解情境只保留这个角色知道的事件过滤掉其余内容问答Question-Answering第二轮提示接收上一步得到的角色视角事件列表要求模型仅基于该上下文回答给定question。根据论文报告SimToM 原文arXiv:2311.10227该框架在 ToMI 与 BigToM 这两个包含 Theory of Mind 问题的基准上优于 zero-shot 提示与 CoT。它的价值不仅在于评测场景还在于 Agent 系统Agent 应当基于自己所知道的信息行动而非利用全局可见的全部信息——SimToM 恰好把这种知识边界显式建模出来。视角代入的三条规则仓库中保留了两份与本文示例配套的真实提示词模板其中视角代入模板 docs/examples/prompt_templates/simtom_prospective_taking.txt 明确列出了判断角色知道什么的三条规则这是 SimToM 在第一阶段过滤事件的核心依据角色知道自己参与的所有事件角色身处某地点时知道该地点发生的所有其他事件包括他人进出、物体位置、物体被移动等角色离开某地点后不再知道该地点内发生的事件但可以再次进入。Outlines 实现总览三步完成 SimToM在 Outlines 中实现 SimToM 只需三个步骤全部围绕仓库核心 API 展开用Prompt 模板outlines.Template定义两轮提示词用Pydantic 模型声明每一轮要返回的 JSON 结构用transformers 集成outlines.from_transformers加载本地 LLM 并生成受约束的结构化响应。第一步使用 Prompt 模板装载提示词作者公开了 SimToM 的代码、提示词与数据。当前仓库已把 ToMI 数据集使用的两份提示词落盘为模板文件我们可以直接通过Template.from_file加载from outlines import Template perspective_taking Template.from_file(prompt_templates/simtom_prospective_taking.txt) simulation Template.from_file(prompt_templates/simtom_simulation.txt)注意上述路径为示例文档中的写法。在当前仓库中这两份模板的实际位置是 docs/examples/prompt_templates/simtom_prospective_taking.txt 与 docs/examples/prompt_templates/simtom_simulation.txt请按实际仓库路径加载。从源码看Template是 src/outlines/templates.py 中定义的一个 dataclass其内部封装了一个jinja2.Template实例并且可调用template(**kwargs)会调用self.template.render(**kwargs)返回渲染后的字符串。from_file通过jinja2.FileSystemLoader以模板文件所在目录为根来构建 Jinja2 环境因此支持 include/继承等 Jinja2 特性但不允许引用模板文件目录之外的相对文件。from_string则从字符串构建模板并会调用inspect.cleandoc做去缩进处理。Jinja2 环境在 create_jinja_env 中预置了若干实用 filtername函数名、description函数 docstring 首行、source函数源码、signature函数签名、schemaPydantic 模型 JSON Schema、args函数参数也允许用户传入自定义 filter 覆盖内置项。环境启用了trim_blocks、lstrip_blocks与keep_trailing_newline并将未定义变量设为StrictUndefined——任何模板变量的拼写错误都会在渲染阶段立刻报错而非静默输出空串。第二轮问答模板 docs/examples/prompt_templates/simtom_simulation.txt 充分利用了 Jinja2 的循环语法把事件列表逐条渲染进提示词s[INST] {% for event in events %} {{event}} {% endfor %} You are {{name}}. Based on the above information, answer the following question: {{question}} You must choose one of the above choices, do not say there is not enough information. Answer with a single word, do not output anything else. [/INST]第二步用 Pydantic 定义 JSON 结构化输出Outlines 会保证 LLM 返回合法的 JSON 对象且其结构由你指定的 Pydantic 模型决定在Generator中Pydantic 模型会先被转换为术语表示再编译为对应后端的 logits 处理器详见 src/outlines/generator.py。SimToM 需要两个 Pydantic 模型分别对应两轮提示from pydantic import BaseModel, Field from typing import List class PerspectiveTaking(BaseModel): This is for the first prompt. character: str Field(descriptionThe character we extract the events for.) events: List[str] Field(descriptionAll events that the character knows about.) class Simulation(BaseModel): This is for the second prompt. answer: strPerspectiveTaking承载第一轮输出character字段记录被考察的角色events字段以字符串列表保存该角色所知的事件Simulation承载第二轮输出answer字段记录最终单字答案。Field(description...)中的描述信息会进入 Pydantic 生成的 JSON Schema模板环境中内置的schemafilter 在渲染 Pydantic 模型的 schema 时也会优先取用这些描述见 src/outlines/templates.py让模型在生成时明确每个字段的语义。第三步调用 LLM 完成两轮受约束生成以 ToMI 数据集中的一则小故事为例。故事由 6 条按编号排列的事件组成Aria 与 Aiden 先后进入院子、西柚被放在绿桶中、Aria 把西柚移到蓝色容器、Aiden 离开院子、Noah 进入游戏室。问题问的是西柚最初在哪里角色指定为 Aria——而 Aria 在事件 4 之后才操作西柚且事件 3 发生时她在场所以她知道西柚原本在绿桶里却不知道事件 2Aiden 进入院子这类与她无关的信息story 1 Aria entered the front_yard. 2 Aiden entered the front_yard. 3 The grapefruit is in the green_bucket. 4 Aria moved the grapefruit to the blue_container. 5 Aiden exited the front_yard. 6 Noah entered the playroom. question 7 Where was the grapefruit at the beginning? character Aria接着用outlines.from_transformers加载 Hugging Face 上的Mistral-7B-Instruct-v0.3。从 src/outlines/models/transformers.py 的源码可以看到from_transformers接受一个PreTrainedModel与一个 tokenizer或ProcessorMixin此时返回多模态模型返回Transformers实例Transformers内部会通过TransformersTypeAdapter在检测到模型带 chat template 时自动应用apply_chat_template见 src/outlines/models/transformers.py并统一设置 tokenizer 的padding_side left。第一轮渲染视角代入提示词生成PerspectiveTaking结构import transformers import outlines # Load an LLM from Hugging Face MODEL_NAME mistral-community/Mistral-7B-Instruct-v0.3 model outlines.from_transformers( transformers.AutoModelForCausalLM.from_pretrained(MODEL_NAME), transformers.AutoTokenizer.from_pretrained(MODEL_NAME), ) perspective_prompt perspective_taking(storystory, charactercharacter) # Call Mistral 7B with the first prompt generator outlines.Generator(model, PerspectiveTaking) perspective generator(perspective_prompt, max_new_tokens1024) print(perspective) # {character: Aria, events: [1 Aria entered the front_yard., 3 The grapefruit is in the green_bucket., 4 Aria moved the grapefruit to the blue_container.]}outlines.Generator见 src/outlines/generator.py把模型 输出类型封装为可复用对象构造时根据输出类型编译并缓存 logits 处理器构造开销较大缓存后多次调用不再重复编译每次__call__前会调用logits_processor.reset()再传入模型的generate。输出中事件 1、3、4 被正确保留而事件 2、5、6Aria 不知道的其他角色行为被过滤——这正是第一轮的目标。第二轮把第一轮得到的events注入模拟模板基于 Aria 的视角回答问题import json sim_prompt simulation(eventsjson.loads(perspective)[events], namecharacter, questionquestion) # Call Mistral 7B with the second prompt generator outlines.Generator(model, Simulation) result generator(sim_prompt, max_new_tokens1024) print(result) # {answer: green_bucket}注意outlines.Generator(model, PerspectiveTaking)返回的是序列化后的 JSON 字符串因此在注入第二份模板前需要先用json.loads还原成 Python 对象再取events字段。最终模型基于 Aria 视角得出green_bucket——因为事件 4 的移动西柚发生在 Aria 进入院子之后她清楚地记得西柚一开始在绿桶里。两轮调用合计约十行代码SimToM 即完整落地。在 Agentic 工作流中的意义与已知局限SimToM 最自然的应用场景是 Agentic 工作流Agent 必须基于自己已知的信息采取行动而不是利用全局可见的全部信息。视角代入阶段相当于给 Agent 显式划分知识边界使其在部分可观测环境中做出符合自身信息状态的决策。需要说明的是SimToM 并非没有代价信息丢失风险视角代入阶段可能过滤掉对后续问答至关重要的信息导致最终答案错误定位正如论文作者所强调的SimToM 更适合作为一个简单而有效的基线用于评估 LLM 在 Theory of Mind 推理任务上的能力而非声称是终极解决方案。从示例到自定义进一步探索如果你想基于本示例做更多扩展仓库提供了可继续深入研读的素材模板与渲染的完整实现src/outlines/templates.py重点关注Template.from_file与create_jinja_envGenerator 与 logits 处理器编译逻辑src/outlines/generator.py理解输出类型到受约束解码的完整链路transformers 集成的Transformers类与from_transformers工厂src/outlines/models/transformers.py两份可直接复用的 SimToM 提示词模板docs/examples/prompt_templates/simtom_prospective_taking.txt 与 docs/examples/prompt_templates/simtom_simulation.txt更多结构化生成实战示例docs/examples/index.md如chain_of_density.md、knowledge_graph_extraction.md等以及源码级样例目录 examples如react.py、self_consistency.py。若要在本地运行本示例请先按 docs/guide/installation.md 安装 Outlines并确保环境中包含transformers、torch与pydantic依赖之后将模板加载路径替换为仓库内实际路径即可端到端复现。【免费下载链接】outlinesStructured Outputs项目地址: https://gitcode.com/GitHub_Trending/ou/outlines创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考