☰
Julep mem-mcp Phase 2 兼容:让 .ctx 提示词包在 dotctx 加载器中忠实落地
2026/10/9 5:27:25 网站建设 项目流程
  • AI Agent
  • Agent 框架
  • 后端

【免费下载链接】julep

Julep — durable, composable AI agents. Flows that crash and resume, retry safely, and explain every step.

项目地址:https://gitcode.com/gh_mirrors/ju/julep
点击查看免费下载

导读

本文以 Julep 仓库中的实现计划 docs/plans/2026-07-01-mem-mcp-adoption-phase2-dotctx-compat.md 为主线,系统讲解 Julep 如何让 composable-agents 的提示词加载层完整兼容 mem-mcp 的真实.ctx提示词包:<<< role:... >>>角色标记模板、单文件.ctx(YAML frontmatter + Jinja 正文)、require_tool_call:/response_format:声明式设置键、Yglu 数值字符串强转,以及eval.py/eval.yaml数据面的无执行加载。读完本文,你将掌握这一兼容层的全部行为契约、源码实现位置与验收标准,能够直接在 Julep 中加载、校验和部署 mem-mcp 风格提示词包。

一、Phase 2 的目标与背景

mem-mcp 是一个以.ctx目录(或单文件)为单位的提示词包格式:一个包描述一次模型调用,包含模型、温度、轮次上限、系统提示词与可选回复 schema。Phase 1(见 docs/plans/2026-07-01-mem-mcp-adoption-phase1-llm-prompt-core.md)已经打通了 LLM 与提示词核心层:推理 effort 透传、mem-mcp 模型 slug 兼容、response_format回退的有据记录、有界结构化输出重试,以及带显式 env 绑定的 Yglu 求值settings.yaml。

Phase 2 的目标是让 Julep 的提示词加载层能够忠实加载 mem-mcp 全部真实.ctx提示词包,具体覆盖五类能力:

  • <<< role:... >>>角色标记模板(role-marker templates);
  • 单文件.ctx(YAML frontmatter + Jinja 正文);
  • require_tool_call:与response_format:两个设置键(声明式记录,循环内强制留待 Phase 3/4);
  • Yglu 数值字符串强转(numeric-string coercion);
  • eval.py/eval.yaml数据面(sample()/score()契约)——但加载提示词时绝不执行 eval 代码。

1.1 参考实现与关键源文件

计划以 mem-mcp 为语义基准(参考仓库为/home/diwank/github.com/julep-ai/mem-mcp,属于开发环境本地路径,仅作行为镜像参考),其关键语义来源包括:

  • 加载器语义:dotctx/loader.py中的FRONTMATTER_PATTERN、ROLE_MARKER_PATTERN = <<<\s*role:(\w+)\s*>>>、_parse_role_markers、_load_single_file、_load_directory;
  • Eval 数据面:dotctx/eval_types.py(Sample、Expected、ExpectedToolCall、MockToolConfig、Turn、StopFn、stop_after_turns、stop_when_terminal_tool、stop_when_non_tool、any_stop、all_stop)、eval_loader.py(EvalModule:必须提供sample(limit)+score(input, output, expected))、llm_utils.py(extract_llm_content、parse_llm_json、strip_markdown_codeblock)、eval_config.py(eval.yaml 结构:models/datasets/threshold/concurrency/scoring/agent/profiles);
  • 真实提示词:apps/memory-api/prompts/**/*.ctx共 33 个目录。

计划还给出了 mem-mcp 真实提示词包的设置键普查结论:model, temperature, output_retries, reasoning_effort, max_rounds, require_tool_call(5 处)、response_format(4 处,恒为 {type: json_object})、max_tokens;角色只用system(26 处)和user(25 处);没有messages/目录;6 个tools.pyi。

1.2 CA 侧涉及文件

按计划,Julep 侧(原文中为 composable_agents 包,仓库中即julep/包)触及的文件为:julep/dotctx.py、julep/dotctx_rich.py、新增julep/dotctx_evals.py、julep/execution/llm.py、julep/execution/llm_result.py(仅在需要新 meta 键时,优先复用现有response_format_fallback)、julep/deploy.py的_reasoner_identity(约第 159 行起)。

二、全局约束:兼容层的质量底线

Phase 2 在开工前先立下五条全局约束,它们决定了实现的形态:

  1. 质量门禁:测试必须用python -m pytest(禁止裸pytest),类型检查用uv run mypy --strict composable_agents(仅包内,不含测试),静态检查用ruff check composable_agents tests;
  2. 无 Temporal 依赖:新增测试不得依赖 Temporal,保证 temporal=off 的 CI 任务保持绿色;
  3. 部署 golden 字节级不变:新增 identity 字段必须按既有约定以 "omit-when-unset"(未设置即省略)方式进入_reasoner_identity,使既有制品哈希完全一致;
  4. G-8:禁止静默回退——不支持的值必须是响亮的教学式错误(teaching error);provider 回退必须记录在LlmCallMeta上;
  5. 依赖隔离:jinja2 留在[dotctx]extra 之后,yglu 留在[yglu]之后;julep/dotctx_evals.py必须在两者都未安装时也能干净导入(必要时使用惰性导入)。

此外有一条安全红线:加载.ctx时绝不执行eval.py——eval 加载是独立、显式的入口。

这些约束在源码中均有对应落地:例如julep/dotctx_rich.py顶部在缺少 jinja2 时抛出带安装提示的硬错误("jinja2 is required for the rich dotctx layout (prompt.j2 / messages/); install julep[dotctx]");julep/dotctx_evals.py模块 docstring 明确写明 "Loading a.ctxprompt NEVER executes eval code;load_ctx_evalsis the one explicit entry point"。

三、Task 1:角色标记模板拆分(role-marker splitting)

3.1 行为契约

mem-mcp 的单文件多消息格式用<<< role:name >>>标记把模板切成按角色分区的片段。Phase 2 在 rich 加载器中实现了等价的拆分,行为契约如下:

  • prompt.j2源码中若匹配<<<\s*role:(\w+)\s*>>>,则被拆分为按角色的模板源码;标记行的空格布局是灵活的——<<<role:system>>>同样匹配(镜像 mem-mcp 的正则);
  • v1 只接受与messages/包完全一致的形状:一个system段,可选地后跟一个user段;重复角色、其他角色(如assistant)或 user 在 system 之前 → 抛出ValueError,错误信息中带文件名与违规角色(教学式错误);
  • 第一个标记之前的内容必须为空白或 Jinja 注释/纯空白;该前置内容会被前拼到 system 段源码(保留{# AI-ANCHOR #}之类的头部,渲染时输出为空);非注释的普通文本 →ValueError;
  • 没有标记 → 维持既有行为(整个文件即 system 模板);
  • 拆分出的片段作为独立的渲染器注册(system/user角色),与今天messages/包完全一致——复用下游system_render/user_render接线,Reasoner 无需改动。

3.2 源码级实现

julep/dotctx_rich.py中的_split_role_markers(source, origin)完整实现了上述契约:

  • 标记正则_ROLE_MARKER_RE = re.compile(r"<<<\s*role:(\w+)\s*>>>")(julep/dotctx_rich.py),与 mem-mcp 的ROLE_MARKER_PATTERN一一对应;注释里特别指出裸<<</>>>heredoc 定界符不会误匹配(因为没有role:);
  • 用_JINJA_COMMENT_RE先把{# ... #}注释遮罩掉(保留换行、抹除其他字符),再逐行检查首个标记前的可见内容,只允许空白、Jinja 注释和#注释行——#头部行(mem-mcp 若干真实.j2文件中的 AI-ANCHOR 约定)会被丢弃,因为 mem-mcp 会丢弃所有标记前内容,而保留#行会渲染出可见文本;
  • 段校验用expected = ("system", "user")逐段比对,段数超过 2 时报错并点名多余角色(julep/dotctx_rich.py);
  • 拆分后返回(system, user | None),随后由_register_role_templates注册为dotctx/<package>/<role>@v<content-hash-prefix>形式的渲染器,内容哈希前缀 12 位,与任何注册的纯函数一致,从而提示词编辑可被 §6.4 漂移检测覆盖。

3.3 测试要点

tests/test_dotctx_rich.py扩展覆盖:真实形状 fixture 字符串(Jinja 注释头 + system + user,精神上取自episode_summary.ctx/prompt.j2)、紧凑标记间距、未知角色报错、重复 system 报错、user 在前报错、无标记文件行为不变、渲染器在 StrictUndefined 上下文下可渲染。

四、Task 2:单文件.ctx支持

4.1 行为契约

load_dotctx(path)当path是以.ctx结尾的文件时,走单文件解析:

  • 用 mem-mcp 的 frontmatter 正则^---\n(.*?)\n---\n(.*)$(DOTALL)解析:frontmatter YAML 走与settings.yaml相同的 Yglu-aware 设置路径(把_read_settings的文本解析核心重构为共享 helper,两边共用,保留has_yglu_tags门控);正文即模板,按 Task 1 的角色标记逻辑拆分;
  • 单文件.ctx总是路由到dotctx_rich(正文是模板 ⇒ 必然需要 jinja2;缺 extra 时同样硬抛 ImportError);
  • 无 frontmatter 匹配 → 设置为空{}(镜像 mem-mcp;Julep 既有默认值生效);Reasoner 名默认为去掉.ctx的文件名主干;
  • is_rich_dotctx与目录处理不变;名为foo.ctx的目录继续正常工作;
  • rich 加载器的未知设置键校验对 frontmatter 同样生效。

4.2 源码级实现

julep/dotctx_rich.py的load_single_file_dotctx(path, *, registry, env)是核心实现:

  • frontmatter 正则_FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n(.*)$", re.DOTALL)(julep/dotctx_rich.py);
  • 文件必须以.ctx结尾,否则ValueError;读取后尝试匹配 frontmatter:匹配则解析设置 + 取正文,否则settings, body = {}, content(全模板、空设置、默认值生效);
  • _validate_settings_keys(settings, path)对 frontmatter 做与目录版一致的未知键校验;
  • 单文件格式无法携带schema.pyi/tools.pyi,因此reply_schema恒为None,tools 只来自 frontmatter;skills设置在此被显式拒绝(skills 需要目录布局<package>.ctx/skills/<name>/SKILL.md);
  • 正文经由_split_role_markers拆分后注册角色渲染器,最后构造 Reasoner 并注册(julep/dotctx_rich.py)。

在julep/dotctx.py的load_dotctx中,文件路径统一分发到单文件加载器,并在加载后给出缺失输出 schema 的警告(MissingOutputSchemaWarning,因为单文件格式没有schema.pyi)。

4.3 测试要点

tests/test_dotctx_single_file.py覆盖:frontmatter + 角色标记正文加载(断言 model/temperature/name);带显式env=映射的 yglu!?标记 frontmatter;无 frontmatter 文件 → 纯模板 + 默认值;仅 frontmatter(无标记)→ system 模板;非.ctx文件 → 明确错误;文件缺失 → 明确错误。

五、Task 3:require_tool_call/response_format键 + Yglu 数值强转

5.1 Reasoner 新字段

julep/dotctx.py的Reasoner(frozen dataclass)新增两个关键字专用字段,默认值保持既有行为:

require_tool_call: bool = False # declarative; loop enforcement is Phase 3/4 response_format: Optional[str] = None # "json_object"; reply_schema wins at call time

从源码可见(julep/dotctx.py),Reasoner现已具备的完整字段还包括reasoning_effort、output_retries、prompt_cache、skills等。Reasoner的 docstring 明确记录:require_tool_call在 Phase 2 是声明式的——记录在案并哈希进部署 identity;agent 循环内的强制落地随原生工具调用在 Phase 3/4 到来。response_format把 mem-mcp 的response_format: {type: json_object}记录为字符串"json_object";同时声明 reply schema 与response_format是允许的,调用时 schema 胜出(provider 调用永远不同时携带两者)。

5.2 设置解析(两种加载器、snake 与 camel 变体)

  • require_tool_call:必须是布尔值。_require_tool_call_setting(julep/dotctx.py)同时接受requireToolCall变体;非布尔 →ValueError: require_tool_call must be true or false;
  • response_format:必须是映射{type: json_object},存为字符串"json_object";任何其他形状/类型 → 教学式ValueError,列出唯一支持形式(julep/dotctx.py):
    unsupported response_format {value!r}; the only supported form is the mapping 'response_format: {type: json_object}'

5.3 Yglu 数值字符串强转

Yglu/env 值以字符串到达(真实案例:record/execute.ctx的max_rounds: !? $env.get("RECORD_EXECUTE_MAX_ROUNDS", 12))。两种加载器均需对以下键做强转:

  • max_rounds、max_tokens、output_retries→int;
  • temperature→float;
  • 非数值字符串 → 教学式ValueError。

源码中的_as_int/_as_float(julep/dotctx.py)实现该契约:拒绝布尔值,接受原生 int/float,接受可解析的数字字符串,否则抛出带 key 名的ValueError,并提示 "env-sourced values must be numeric strings"。_model_and_effort(julep/dotctx.py)在此基础上完成模型 slug 规范化与reasoning_effort校验(显式键优先于@suffix,非法 effort 报错并列出合法集合),output_retries < 0也会报错。

5.4 部署 identity:omit-when-unset 保持 golden 不变

julep/deploy.py的_reasoner_identity以 "未设置即省略" 的方式把新字段写入制品哈希(julep/deploy.py):

if reasoner.require_tool_call: ident["requireToolCall"] = True if reasoner.response_format is not None: ident["responseFormat"] = reasoner.response_format

由于未设置时字段不进入 identity 字典,既有部署 golden(tests/ 中 freeze/golden/deploy 相关测试)保持字节级不变——这是计划中明确要求验证的("Existing deploy goldens must be byte-for-byte unchanged")。

5.5 Provider 接线:json_object 的调用路径与回退契约

julep/execution/llm.py中的complete_reasoner实现(julep/execution/llm.py 区域):

  • 当reply_schema is None且reasoner.response_format == "json_object"时,按 schema 派生的response_format同一路径发送response_format={"type": "json_object"}:同样遵守_PROMPT_FALLBACK_PROVIDERS、native_ok闩锁(latched:一次 response_format 失败后不再重试 native)与有据回退契约;
  • json_object 的回退行为是:重发请求但不带该 kwarg,并在LlmCallMeta.response_format_fallback上记录回退原因(无提示词注入——mem-mcp 提示词自训 JSON 输出);回退只记录一次(由闩锁保证,reason 不会被覆盖),并发出logging.warning;
  • CONFIG 类错误仍会重抛(Phase 1 规则):认证/配置失败绝不能被回退掩盖;
  • schema 与 json_object 同时存在时,schema 路径胜出(不会出现双重 kwarg)。

对应测试位于tests/test_dotctx_reply.py/tests/test_dotctx_rich.py扩展及tests/execution/下既有 response_format-fallback 测试附近:加载器解析(true/false/absent、json_object、坏形状报错);含 yglu 加载设置的强转矩阵;identity omit-when-unset(golden 相等性 + 设置后新字段出现);llm fake-provider 测试(发送 json_object kwarg、不支持 provider 的回退只记录一次并闩锁、schema 优先)。

六、Task 4:eval 兼容 ——dotctx_evals.py数据面

6.1 设计边界

julep/dotctx_evals.py是 mem-mcp eval数据面的忠实移植(不含 runner):类型与函数从 mem-mcp 的eval_types.py/llm_utils.py1:1 移植;加载器使真实 eval 文件原样加载。它只是数据——没有 runner、没有评分循环、没有 mock-tool 执行(留待 Phase 3/4)。

关键安全属性:加载.ctx提示词永不执行 eval 代码;load_ctx_evals是唯一显式入口(load_rich_dotctx不会调用它)。模块 docstring 还承诺:不安装[dotctx](jinja2)extra 也能干净导入;yaml 在配置加载器内部惰性加载;标记式 env 表达式用内置求值器。

6.2 移植的数据面

  • 类型/函数(来自eval_types.py):Input/Output/Score别名、Turn、StopFn、stop_after_turns、stop_when_terminal_tool、stop_when_non_tool、any_stop、all_stop、ExpectedToolCall、Expected、Sample、MockToolConfig;
  • 辅助函数(来自llm_utils.py):strip_markdown_codeblock、extract_llm_content、parse_llm_json(镜像 mem-mcp 行为,含边界情形)。

从源码看(julep/dotctx_evals.py),Turn携带output、tool_calls、tool_results、content、refusal五个字段;stop_after_turns(max_turns)生成的 StopFn 还带一个_julep_max_turns标记属性,供 Julep eval runner 无需内省不透明闭包即可读取 "stop_after_turns(n) -> max_rounds 覆盖"。

6.3EvalModule与 sys.modules shim

load_eval_module(path) -> EvalModule通过 importlib spec 以唯一模块名 execeval.py,要求sample和score为可调用对象,错误消息形状与 mem-mcp 完全一致(教学式)。exec 之前,会为sys.modules安装兼容别名:dotctx、dotctx.eval_types、dotctx.llm_utils指向本模块的命名空间——仅当这些名字尚不可导入时(绝不覆盖真实安装的dotctx),并在finally中恢复sys.modules。文档明确标注:仅限 CLI/测试时使用,非线程安全(与 Phase 1 的 yglu env 交换同样的注意事项)。

真实eval.py会写from dotctx.eval_types import Sample/from dotctx import extract_llm_content,shim 正是为此设计。

6.4EvalConfig与唯一入口

  • load_eval_config(path) -> EvalConfig:解析eval.yaml/eval.yml(通过dotctx_yglu实现 Yglu-aware,显式env=参数)为小型类型化 dataclass:models({id, tags} 列表)、datasets({file, format, tags} 列表)、threshold: float、concurrency: int、scoring: dict、agent: dict、profiles: dict[str, EvalConfig 形状的覆盖项];未知顶层键 → 教学式错误;只取数据,不执行求值;
  • load_ctx_evals(ctx_dir, *, env=None) -> CtxEvals(eval_module + eval_config,两者均可 Optional):唯一显式入口;load_rich_dotctx不得调用它。

6.5 测试要点

tests/test_dotctx_evals.py覆盖:使用from dotctx.eval_types import Sample+from dotctx import extract_llm_content的 fixture eval.py 经 shim 加载成功;缺sample/score→ 教学式错误;加载后sys.modules干净(未触碰的键被保留);带 yglu!?+ 显式 env 的 eval.yaml 解析成功;profiles 以原始数据解析;extract_llm_content/parse_llm_json/strip_markdown_codeblock行为矩阵镜像 mem-mcp(fenced JSON、dict 响应、嵌套输出)。

七、Task 5:真实提示词 fixtures、兄弟仓库扫描与文档

7.1 Fixtures 仓库

tests/fixtures/memmcp/收录 mem-mcp 真实提示词内容的 vendor 化 fixtures(长正文可裁剪,结构保持原样):

  • episode_summary.ctx/:jinja 注释头 + 角色标记 + Input-onlyschema.pyi+ yglu model + eval.py;
  • 一个以record/execute.ctx为模型的require_tool_call设置目录(yglumax_rounds);
  • cluster_label.ctx风格目录(response_format: {type: json_object});
  • 一个 mem-mcp frontmatter 格式的单文件.ctx;
  • 以briefs/draft.ctx为模型的eval.yaml。

从仓库现状看,tests/fixtures/memmcp/已包含brief_draft.ctx/(含 skills/natural-writing/SKILL.md)、cluster_label.ctx/、episode_summary.ctx/(含 eval.py、prompt.j2、schema.pyi、settings.yaml)、execute.ctx/(含 eval.yaml、prompt.j2、settings.yaml、tools.pyi)、execute_eval.ctx/、record_execute.ctx/(含 eval.py、eval.yaml、prompt.j2、settings.yaml、tools.pyi)、anchor_choose.ctx等。

7.2 兼容扫描测试

tests/test_memmcp_compat.py加载每个 fixture(显式env={}),断言:规范 model、effort、require_tool_call/response_format 字段、渲染器拆分、eval 模块/配置加载(通过load_ctx_evals加载 eval,证明提示词加载未 exec 它)。

另有一个兄弟仓库全量扫描测试,用pytest.mark.skipif(not os.path.isdir("/home/diwank/github.com/julep-ai/mem-mcp"), ...)门控:在兄弟仓库中 globapps/memory-api/prompts/**/*.ctx并逐一load_dotctx(..., env={})(每次加载使用全新 Registry 或唯一名字,需检查register_reasoner的重注册语义并妥善处理)。全部 33 个必须能加载。扫描不加载 eval 模块(任意代码风险;fixture 测试已覆盖 eval 加载)。

7.3 文档

docs/中的 dotctx 页面如有则补记(否则模块 docstring 已足够);julep/dotctx_rich.py的 docstring 已更新(原文曾写 eval 文件 "are ignored here",现指向julep.dotctx_evals.load_ctx_evals为显式入口)。

八、验收标准

Phase 2 的完整验收条件为:

  • mem-mcp 全部33 个.ctx提示词都能通过load_dotctx以env={}加载;
  • 角色标记提示词产出 system + user 渲染器;
  • require_tool_call/response_format记录在 Reasoner 与部署 identity 上(goldens 不变);
  • eval sample/score 模块与 eval.yaml 配置通过显式兼容入口加载;
  • 全部质量门禁(python -m pytest、mypy --strict、ruff)绿色。

九、显式非目标(Phase 3/4)

为保证 Phase 2 范围收敛,以下内容被显式排除:

  • 在 agent 循环中强制require_tool_call;原生工具调用(native tool-calling);
  • 运行evals(runner、评分循环、mock-tool 执行、成本核算)——Phase 2 只加载它们;
  • MCP 客户端、resilience-policy 移植、sub_deployments、CAS/wasm 分发。

这些内容属于后续阶段,本文档所述的加载层兼容为它们提供了稳定的数据底座:提示词包可以被忠实加载、校验与哈希,而无需在加载期承担任何执行风险。

十、关键文件速查

关注点仓库路径
Phase 2 实现计划docs/plans/2026-07-01-mem-mcp-adoption-phase2-dotctx-compat.md
Phase 1(LLM + 提示词核心、yglu 显式 env)docs/plans/2026-07-01-mem-mcp-adoption-phase1-llm-prompt-core.md
Reasoner新字段、设置解析、数值强转、load_dotctxjulep/dotctx.py
角色标记拆分、单文件.ctx、rich 加载器、_ALLOWED_SETTINGSjulep/dotctx_rich.py
eval 数据面(load_eval_module/load_eval_config/load_ctx_evals)julep/dotctx_evals.py
Yglu 求值设置与显式 env 绑定julep/dotctx_yglu.py
模型 slug / effort 规范化julep/model_slugs.py
部署 identity(omit-when-unset)julep/deploy.py
json_object 接线与有据回退julep/execution/llm.py
兼容 fixtures 与扫描测试tests/fixtures/memmcp/、tests/test_memmcp_compat.py
单文件.ctx/ eval / yglu / rich 测试tests/test_dotctx_single_file.py、tests/test_dotctx_evals.py、tests/test_dotctx_yglu.py、tests/test_dotctx_rich.py
  • AI Agent
  • Agent 框架
  • 后端

【免费下载链接】julep

Julep — durable, composable AI agents. Flows that crash and resume, retry safely, and explain every step.

项目地址:https://gitcode.com/gh_mirrors/ju/julep
点击查看免费下载
上一篇:AutoClip 产品路线图全解析:从本地桌面工具到 credits 商业化平台的演进规划
下一篇:Redwood 教程第 4 章:将全栈应用部署到 Netlify 的完整实战指南(Postgres 数据库迁移 + Serverless 部署)

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询