跳转至

以下内容基本上是 AI 生成的,我还没校对,可能质量不高

promplate-recipes - 最佳实践合集

GitHub   ·   PyPI   ·   Core Framework

Promplate 框架量身打造的实用工具集,收录我在生产实践中总结的最佳实践。由于这些实现较为 opinionated,我选择独立维护而非并入核心包。

其实一开始我也没想过要单独发包的,但用着用着发现很多模式反复出现,就干脆整理出来了。

核心架构:分层上下文管理

项目最核心的创新是 SafeChainMapContext 的三层架构设计:

# BuiltinsLayer -> ComponentsLayer -> defaultdict(SilentBox)
context = SafeChainMapContext()

第一层:BuiltinsLayer

注入内置函数和常量,提供模板渲染的基础环境。

第二层:ComponentsLayer

动态组件加载系统,通过文件系统 glob 模式自动发现组件,实现组件的惰性加载。

这个设计灵感来自于我写 HMR 时对模块加载的思考:为什么不让组件也像模块一样自动发现呢?

第三层:SilentBox 智能缺省

自定义 Box 子类,空对象访问返回空字符串而非异常,调试模式下打印访问信息。

调试模板时最烦的就是各种 KeyError,这个小改动让开发体验好太多。

组件系统

Recipes 提供了两种组件实现模式:

SimpleComponent - 参数自适应

@component
def my_component():
    # 无参调用:直接执行
    return "result"

@component  
def smart_component(context):
    # 有参调用:传入上下文
    return context.get("data", "default")

CallableWrapper - 元编程命名

class MyWrapper(CallableWrapper, AutoNaming):
    # 自动将函数名作为模板名
    pass

关键实现细节

惰性组件加载

ComponentsLayer 通过文件系统扫描实现组件的按需加载:

# 仅在首次访问时加载组件
components = ComponentsLayer("components/*.py")
result = components.my_component  # 触发加载

流式节点处理

SimpleNode 支持生成器和异步生成器,实现中间结果的实时回调:

@node
def streaming_node():
    for chunk in generate_chunks():
        yield chunk  # 实时输出

这个设计让我想起写 HMR 时处理异步更新的那些夜晚,同样的模式在不同场景下居然都适用。

DotTemplate 增强

继承自标准 Template,自动注入多层上下文:

template = DotTemplate("Hello {{name}} from {{component.helper}}")
result = template.render(context)

我干了些什么骚操作

  1. 分层隔离:三层上下文设计既保证了灵活性又维护了安全性
  2. 智能缺省SilentBox 让模板编写更加宽容,减少调试时间
  3. 动态发现:组件通过文件系统 glob 自动注册,无需手动配置
  4. 参数自适应:组件根据调用方式自动调整行为
  5. 元编程命名:利用 AutoNaming 自动生成有意义的标识符

其实很多想法都来自于我在实际项目中遇到的痛点,比如 Free Chat 的模板渲染和 Papers 的组件管理。

生产应用

Recipes 中的工具已经在多个项目中得到验证:

  • Promplate 生态:作为核心框架的官方配套工具集
  • Free Chat:LLM 聊天界面,使用 recipes 的上下文管理
  • Papers:PDF 处理工作流,采用 recipes 的组件架构

相关项目

  • promplate/core - 核心框架,支持 Jinja2 + 聊天标记 + FSM
  • free-chat - LLM 聊天 UI,基于 SolidJS + Astro
  • papers - PDF 嵌入工作流,RAG 实现

设计理念

Recipes 代表了我对框架设计的思考:实用主义优先。与其追求完美的抽象,不如从实际问题出发,提供可立即使用的解决方案。

说到底,编程就是解决问题嘛。