- 人工智能
- AI 应用
- AI 评测
- RAG
- 微调
- 提示工程
- 开发工具
- 桌面应用
【免费下载链接】Kiln
Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.
导读
input_transform是 Kiln(项目根目录)为运行配置(RunConfig)新增的一项通用基础设施能力:在任务调用时刻,用 Jinja2 模板把原始任务输入渲染成模型实际看到的首条用户消息。本文以 Kiln 仓库中specs/projects/templates规格项目的第一阶段(Phase 1: Engine + Datamodel Foundation)为骨架,完整讲解引擎模块、Pydantic 数据模型与字段落位,并对照当前仓库中已完成的真实实现(jinja_engine.py、input_transform.py、run_config.py)逐一印证设计与代码。读完本文,你将掌握:如何为 RunConfig 配置输入变换、Jinja2 沙箱环境的双实例设计为何必要、StrictUndefined与默认Undefined两种语义的分工、保存期与运行期的错误处理边界,以及 Phase 1 对应的全部单元测试清单。
1. Phase 1 目标与交付物
Phase 1 是整个 Input Transform 项目(规格见 project_overview.md 与 functional_spec.md)的第一阶段,其定位是引擎 + 数据模型地基,目标是三份可独立测试、无需改动任何适配器的交付物:
- 引擎模块
libs/core/kiln_ai/utils/jinja_engine.py:Jinja2 沙箱环境的两个实例与四个公开函数; - 数据模型
libs/core/kiln_ai/datamodel/input_transform.py:JinjaInputTransform模型与InputTransform判别联合(discriminated union); KilnAgentRunConfigProperties上的新字段input_transform: InputTransform | None(位于 run_config.py)。
项目整体仅分两个阶段:Phase 1 完成后,Phase 2(见 phase_2.md)再把变换挂入BaseAdapter的同步与流式执行管线。实施顺序与总览见 implementation_plan.md。
当前仓库中该功能已实现完毕(规格文件均标记status: complete),因此本文讲解的设计细节均可在真实源码中找到对应实现。
2. 新增依赖:jinja2
Phase 1 的第一步是把 Jinja2 加入核心库依赖。规格要求在libs/core/pyproject.toml中增加jinja2 >= 3.1.0,然后运行uv lock更新锁文件。
实际实现与规格一致,libs/core/pyproject.toml 中可以看到依赖声明:
"jinja2>=3.1.0",实操提示:在仓库根目录执行
uv lock即可完成依赖锁定;后续运行测试可用uv run pytest <路径>或项目自带的uv run ./checks.sh --agent-mode(见 implementation_plan.md)。
选择 Jinja2 3.1.x 的理由在规格中写得很明确:模板作者需要熟悉且完整的模板语法({{ }}替换、{% if %}/{% for %}块、{% set %}等),同时项目需要SandboxedEnvironment这类官方沙箱能力来承接用户编写的模板内容。
3. 引擎模块:双沙箱环境 + 四个公开函数
引擎是 Phase 1 的核心。规格要求模块级创建两个SandboxedEnvironment实例,并提供四个公开函数;真实实现(jinja_engine.py)完全遵循了这一设计。
3.1 为什么是两个 SandboxedEnvironment
undefined=这个设置是**环境级(environment-scoped)**的,无法在同一实例上按调用切换,因此必须用两个实例:
| 环境 | undefined配置 | 服务对象 | 缺失变量时的行为 |
|---|---|---|---|
_template_env | StrictUndefined | compile_template_or_raise、render_input_transform | 抛UndefinedError(硬失败) |
_expression_env | Undefined(默认) | compile_expression_or_raise、extract | 返回Undefined(falsy,且与None可区分) |
模板渲染必须硬失败:模板里打错变量名是作者必须立刻看到的 bug,StrictUndefined保证缺变量时报错而不是渲染成空串;而表达式求值(extract)需要软语义:调用方要能区分"键缺失"(返回Undefined)与"显式 null"(返回None),不依赖 try/except。
两个环境共享相同的安全边界与语法面(同一SandboxedEnvironment类),且都开启:
trim_blocks=True:去除块标签({% ... %})后的换行,输出更干净;lstrip_blocks=True:去除块标签前的行首空白;- 不使用
FileSystemLoader或任何 I/O loader,模板只从内联字符串渲染; - V1 不注册自定义过滤器(纯内置过滤器)。
真实实现如下:
_template_env = SandboxedEnvironment( undefined=StrictUndefined, trim_blocks=True, lstrip_blocks=True, ) _expression_env = SandboxedEnvironment( undefined=Undefined, trim_blocks=True, lstrip_blocks=True, )3.2 四个公开函数的行为契约
真实实现(jinja_engine.py)中四个函数的行为如下:
def compile_template_or_raise(template: str) -> None: """Validate that the template compiles. Raises ValueError on syntax error.""" try: _template_env.from_string(template) except TemplateSyntaxError as e: raise ValueError( f"Invalid Jinja2 template: {e.message} (line {e.lineno})" ) from e def compile_expression_or_raise(expression: str) -> None: """Validate that a Jinja2 expression compiles. Raises ValueError on syntax error.""" try: _expression_env.compile_expression(expression) except TemplateSyntaxError as e: raise ValueError( f"Invalid Jinja2 expression: {e.message} (line {e.lineno})" ) from e def render_input_transform(transform: InputTransform, task_input: Any) -> str: """Render the transform against the task input, returning the first-user-message string.""" from kiln_ai.datamodel.input_transform import JinjaInputTransform if isinstance(transform, JinjaInputTransform): namespace = _build_namespace(task_input) return _template_env.from_string(transform.template).render(**namespace) raise ValueError(f"Unknown InputTransform variant: {type(transform).__name__}") def extract(expression: str, data: dict) -> Any: """Evaluate a Jinja2 expression against a dict; returns the Python value.""" try: compiled = _expression_env.compile_expression(expression, undefined_to_none=False) except TemplateSyntaxError as e: raise ValueError( f"Invalid Jinja2 expression: {e.message} (line {e.lineno})" ) from e try: result = compiled(**data) if isinstance(result, types.GeneratorType): result = list(result) except UndefinedError as e: raise JinjaExtractionError(str(e)) from e return result各函数要点:
compile_template_or_raise/compile_expression_or_raise:只做编译校验、返回None,不返回编译后的对象。语法错误统一转成带行号的ValueError(e.message+e.lineno)。这是保存期校验的钩子:数据模型的field_validator直接调用它们。render_input_transform:根据变换类型分发(V1 只有JinjaInputTransform),先由私有函数_build_namespace构造命名空间,再用_template_env.from_string(template).render(**namespace)渲染。遇到未知变换类型抛ValueError。extract:用表达式环境求值。缺失键返回Undefined;显式None返回None;生成器结果自动物化为列表(map/selectattr/groupby等返回惰性生成器,只有list()之后逐项查询才会真正执行)。在真实实现中,extract还多了规格之外的一层:对缺失值做进一步操作(属性访问、索引、过滤器)时抛出的UndefinedError会被转换为自定义的JinjaExtractionError(ValueError子类),使调用方可以统一按"数据不符合表达式要求"处理,而非未预期的崩溃。
3.3_build_namespace:输入处理规则与为何保持私有
命名空间构造遵循规格 §5 的输入处理规则(真实实现在 jinja_engine.py):
- 模板始终只看到一个变量
input,不做顶层展开、不做别名,因此同一模板可以跨不同输入形状的任务移植; - dict(对象 schema)输入:原样暴露为
input,可用{{ input.field_name }}、{{ input.items() }}; - list(数组 schema)输入:原样暴露为
input,可用{{ input[0] }}、{{ input | length }}; - 字符串(纯文本)输入:先尝试
json.loads(...),成功则用解析后的值(dict/list/标量),失败则回退为原始字符串。这一"魔法"意味着"42"会解析成 int 42、"true"解析成 bool True,而 Jinja2 都能正确处理这些类型。
def _build_namespace(task_input: Any) -> dict: if isinstance(task_input, str): try: parsed = json.loads(task_input) except (ValueError, TypeError): parsed = task_input return {"input": parsed} return {"input": task_input}_build_namespace是私有的,原因有二(架构文档 §3.3):一是 eval v2 消费方会自行构造四字段合成 dict(final_message、trace、reference_data、task_input)直接传给render_input_transform,不需要纯文本自动解析;二是没有其他 V1 消费方,过早暴露属于投机性 API 面。
3.4 编译成本与缓存预留
render_input_transform与extract每次调用都会重新编译源码,而compile_*_or_raise返回None(不缓存编译产物)。规格明确这是有意的:Jinja2 对提示词规模的模板编译只需微秒级,V1 可接受。若未来性能剖析证明有必要,可在引擎模块内部加一个以源码字符串为键的模块级 LRU 缓存,而公开 API 无需任何改动——校验器返回None正是为这个未来敞开的。
4. 数据模型:JinjaInputTransform 与判别联合
4.1 类定义与保存期校验
真实实现(input_transform.py)与规格一致:
class JinjaInputTransform(BaseModel): type: Literal["jinja"] = "jinja" template: str = Field( description="Jinja2 template source. Validated at save time.", ) @field_validator("template") @classmethod def validate_template_compiles(cls, v: str) -> str: from kiln_ai.utils.jinja_engine import compile_template_or_raise compile_template_or_raise(v) return v def _get_input_transform_type(data: Any) -> str | None: if isinstance(data, dict): return data.get("type") return getattr(data, "type", None) InputTransform = Annotated[ Union[Annotated[JinjaInputTransform, Tag("jinja")],], Discriminator(_get_input_transform_type), ]要点:
type是判别标签(字面量"jinja");template字段的@field_validator调用compile_template_or_raise,因此格式错误的模板在保存期就被拒绝(Pydantic 抛ValidationError),运行时不会遇到语法错误;validate_template_compiles内的延迟导入(lazy import)是刻意为之:jinja_engine的类型注解需要InputTransform,而input_transform需要compile_template_or_raise,若不延迟导入会形成datamodel <-> utils的循环导入;_get_input_transform_type是判别器:从 dict 取type键、从模型实例取type属性;- 联合类型 V1 只有
jinja一个成员,后续增加新变换变体只需在Union中追加一行并新增一个BaseModel类。
4.2 字段落位:为什么在 KilnAgentRunConfigProperties
新字段input_transform落在KilnAgentRunConfigProperties,与tools_config平级(真实实现见 run_config.py):
input_transform: InputTransform | None = Field( default=None, description=( "Optional transform applied to the task input at run time, producing " "the first user message sent to the model. Default None preserves " "the identity path." ), )规格明确不放在其他三个位置,理由各有讲究:
- 不放
TaskRunConfig(持久化包装类,见 task.py):agent 专属配置应属于 agent 的 RunConfigProperties 子类; - 不放
Task:同一个任务可以有多个携带不同变换的 RunConfig("同一个评测、不同 judge 提示词"的工作流依赖这一点); - 不放
McpRunConfigProperties:MCP 工具接收原始输入,不需要变换。
input_transform默认None即"恒等路径"(identity path),保证所有既有 RunConfig 行为完全不变。规格还指出TaskRunConfig遵循"创建一次、按 ID 引用"的约定不可变惯例,input_transform自动参与该约定。
4.3 字段级向后兼容
磁盘上既有的 RunConfig JSON 没有input_transform键,加载时通过 Pydantic 默认值自动得到input_transform=None。无需任何迁移代码,也不需要upgrade_old_entries式的钩子。TaskRunConfig上已有的model_validator(mode="before") upgrade_old_entries(task.py)只回填structured_output_mode,完全不触碰input_transform,因此不受影响。
5. 输入处理与输出契约
5.1 输入规则汇总
| 任务输入形状 | 预处理 | 模板中input的值 |
|---|---|---|
| 对象 schema(dict) | 无 | dict,如{{ input.field_name }}、{{ input.items() }} |
| 数组 schema(list) | 无 | list,如{{ input[0] }}、{{ input \| length }} |
| 纯文本(str) | 先json.loads(...),成功用解析值、失败用原串 | 解析后的 dict/list/标量,或失败时的原始字符串 |
5.2 输出契约
render_input_transform(...)返回str,该字符串成为发送给模型的首条用户消息正文,替代原本会传给 prompt 构建器/格式化器的输入。注意:
- 输出内容不限——即使是恰好合法的 JSON 字符串,对模型而言仍是文本,V1 不区分"字符串 vs JSON"输出类型;
- 空字符串允许(模型收到空首条用户消息),无特殊处理;
- 变换不产生、不修改系统提示词(来自
prompt_id)、后续多轮用户消息、助手消息或对话其他部分。
6. 运行时执行与 TaskRun.input 的持久化语义
6.1 执行顺序
在同步路径_run_returning_run_output与流式路径_prepare_stream(base_adapter.py)中,变换的运行顺序为:
- 输入 schema 校验(既有行为,变换不能绕过它);
- 若
run_config.input_transform非空:构造命名空间(§5.1 规则)→ 调用render_input_transform得到字符串 → 以该字符串作为模型面对的首条用户消息; - provider 专属格式化器(既有行为,此时看到的是字符串,与无变换的纯文本任务路径完全一致);
- 推理(既有行为)。
input_transform is None时第 2 步整体跳过,恒等路径保持原样。
6.2 关键实现细节:原输入与模型输入分离
规格特意强调一个易错点:TaskRun.input永远保存原始未变换输入。因为渲染结果是衍生值(原输入 + 变换可随时重算),变换不可逆(若只存渲染串则无法还原原始输入),而且对对象/数组 schema 任务而言渲染串几乎不可能通过任务input_json_schema校验——把它写进TaskRun.input会破坏 schema 不变量。
因此 Phase 2 的接线方式(真实实现见 base_adapter.py)是:model_input = self._apply_input_transform(input),把model_input交给格式化器,而原始input继续流向generate_run用于持久化。_apply_input_transform辅助方法(base_adapter.py)把"渲染变换"这一逻辑收敛到一处,同步与流式两条路径共用,未来的变换变体只需更新这一处:
def _apply_input_transform(self, input: InputType) -> InputType: if not isinstance(self.run_config, KilnAgentRunConfigProperties): return input transform = self.run_config.input_transform if transform is None: return input try: return render_input_transform(transform, input) except Exception as e: raise ValueError(f"Input transform failed: {e}") from e注意isinstance(self.run_config, KilnAgentRunConfigProperties)守卫:MCP 运行配置没有input_transform字段,直接原样返回输入,MCP 执行路径零改动。
渲染后的字符串经格式化器进入既有的 trace 机制,落在TaskRun.trace[0](首条用户消息),因此无需为 TaskRun 新增任何字段即可恢复"模型实际看到了什么"。
7. 错误处理:保存期硬校验,运行期硬失败
所有变换错误都发生在推理之前。规格的错误处理表如下:
| 失败场景 | 发生时机 | 呈现方式 |
|---|---|---|
| 模板语法错误 | 保存期 | PydanticValidationError(保存被拒绝) |
| 模板语法错误(防御性,正常不会发生) | 运行期 | 原样重新抛出 |
UndefinedError(缺失变量) | 运行期 | 重新抛出;不发起推理 |
| 过滤器等渲染异常 | 运行期 | 重新抛出;不发起推理 |
沙箱SecurityError | 运行期 | 重新抛出;不发起推理 |
适配器不捕获这些异常:它们从_run_returning_run_output/_prepare_stream中冒出。真实实现中_apply_input_transform会把渲染异常统一包装为带说明的ValueError(f"Input transform failed: {e}")。规格特别指出:由于变换运行在try包装区之前,KilnRunError包装不适用于变换错误——它们以普通 Python 异常呈现给调用方,因为变换错误属于"配置/创作错误"而非"运行执行错误",不应与模型失败混为一谈(这与既有的 schema 校验错误同待遇)。
设计哲学一句话:fail loud, fail fast, fail attributable(响亮失败、快速失败、可归因失败)。需要更软语义的消费方(如 eval v2 的 skip-with-reason)应在调用前用自己的预检查包装该 API,通用任务路径保持硬失败。
8. 安全模型:模板是 Jinja2 语法,不是 Python 代码
威胁模型:模板是用户编写的内容,来源从本地用户(低风险)到共享/社区数据集(高风险)不等。规格 §13 的安全承诺(真实实现由SandboxedEnvironment承载)非常明确:
模板能做到:
- Jinja2 语法:
{{ ... }}替换、{% if %}/{% for %}块、{% set %}等; - 内置过滤器与测试:
| default、| length、| join、| upper、| tojson、is defined、is none等; - 对传入数据的属性访问与索引:
{{ input.field }}、{{ input[0] }}; - 作用域内值的算术与字符串拼接。
模板做不到:
- 无任意 Python 执行:没有
{% python %}块、没有eval(),模板语法除 Jinja2 自身暴露的过滤器/测试外无法定义或调用 Python 函数; - 无文件 I/O:无
open(...)/read()/Path(...),模板只从内联字符串渲染,不配置FileSystemLoader; - 无网络 I/O:无
requests/urllib/socket; - 无 shell 访问:无
subprocess/os.system/popen; - 无
import:模板无法导入 Python 模块; - 无法访问 dunder 属性(
__class__、__bases__、__subclasses__、__globals__等)——SandboxedEnvironment阻断已知的 Jinja2 SSTI 攻击链遍历; - 无副作用过滤器:V1 不注册任何自定义过滤器。
真实测试 test_jinja_engine.py 专门覆盖了沙箱违反场景:{{ input.__class__ }}、{{ input.__class__.__bases__ }}、{{ input.__class__.__mro__[1].__subclasses__() }}、{{ ''.__class__.__mro__[2].__subclasses__() }}全部断言抛出SecurityError。
明确记录的 V1 范围外风险:渲染无 CPU/内存上限——病态模板(如对超大生成器做{% for %})可能挂起渲染调用。V1 接受该风险,待真实出现问题再处理。
源码注记:真实实现还在两个环境中注册了两个内置过滤器
fromjson(把 JSON 字符串解析为 Python 对象,解析失败抛JinjaExtractionError)与format_trace(把对话 trace 渲染为带角色标签的规范转录文本,与EvalTraceFormatter保持一致)。这两个过滤器是规格之外、由消费方需求演化出的补充,分别服务于 eval v2 的表达式提取与 LLM 可读的 trace 渲染(相关测试见 test_jinja_engine.py 与 test_jinja_engine.py)。
9. 引擎层单元测试:test_jinja_engine.py
Phase 1 要求引擎测试覆盖规格 §7.1 的全部用例。真实实现 test_jinja_engine.py 分门别类组织如下(均可作为行为契约的活文档):
- 编译校验(
TestCompileTemplateOrRaise、TestCompileExpressionOrRaise):合法模板/表达式静默通过;{% if、未闭合块{% for x in items %}、foo ||| bar等语法错误抛带"line"/"Invalid Jinja2 template"/"Invalid Jinja2 expression"信息的ValueError; - 渲染(
TestRenderInputTransform):- dict 输入属性访问:
{{ input.foo }}+{"foo": 1}→"1";多字段{{ input.a }} and {{ input.b }}; - list 输入索引与过滤器:
{{ input[0] }}→"1"、{{ input | length }}→"3"; - 字符串输入:
{{ input }}+"hello"→"hello"; - 纯文本 JSON 自动解析:
{"foo": 1}、[10, 20]、"42"三种形状分别走{{ input.foo }}、{{ input[0] }}、{{ input }}; - 非 JSON 回退:
"not json"→"not json"; - 缺失属性:
{{ input.missing }}+{"foo": 1}抛UndefinedError; - 沙箱违反:dunder 访问抛
SecurityError(见 §8); - 空模板返回空串;未知变换变体抛
ValueError;
- dict 输入属性访问:
- 表达式提取(
TestExtract):返回普通值(int/str/list/dict);缺失键返回Undefined实例(而非None、而非抛异常);显式None返回None;生成器物化为列表(data | map(attribute='x') | list→[1, 2],无list过滤器时也物化);嵌套属性访问;畸形表达式抛ValueError; - 对缺失值操作的边界(
TestExtractOperationsOnMissingData):missing | int、missing + 1、(final_message | fromjson).user.status(解析出的 JSON 无该字段)、trace[-1].tool_calls[0].function.name(越界索引)等强制求值操作抛JinjaExtractionError且保留 Jinja 原始消息;而final_message ~ typo这类~拼接把Undefined字符串化为''的静默操作被专门测试钉住; - 空白控制(
TestTrimAndLstripBlocks):trim_blocks去掉块标签后换行、lstrip_blocks去掉块前缩进、两者组合的代表性模板输出; - 表达式变量提取(
TestExpressionVariables):expression_variables返回表达式读取的命名空间变量集合(final_message、trace、outpt.status等),用于消费方预检查; - 规范表达式(
TestCanonicalExpressions):eval UI 的规范示例表达式(从 JSON 取字段、截断长输出、取 trace 最后消息、大写、统计消息数、取工具调用名、JSON 数组长度)全部可运行。
10. 数据模型测试:test_input_transform.py 与 test_run_config.py 扩展
10.1 datamodel/test_input_transform.py
真实实现 test_input_transform.py 覆盖规格 §7.2:
JinjaInputTransform(template="{{ input }}")构造成功,type == "jinja";带块的模板也合法;- 畸形模板
{% if、未闭合块抛 PydanticValidationError(消息含"Invalid Jinja2 template"); .model_dump()后model_validate重建相等;model_dump_json()后model_validate_json相等(JSON 序列化往返);- 判别联合:
{"type": "jinja", "template": "..."}分派到JinjaInputTransform;缺type键、未知type(如"unknown")抛ValidationError;模型实例直接验证通过;经TypeAdapter往返一致。
10.2 datamodel/test_run_config.py 扩展
真实实现新增TestInputTransformField(test_run_config.py 附近),覆盖规格 §7.3:
- 不带
input_transform构造时默认None; - 传入
JinjaInputTransform(template="{{ input.name }}")被接受且类型正确; - 传入
{"type": "jinja", "template": "{{ input }}"}dict 时判别器正确分派; - 向后兼容回归:无
input_transform键的旧 JSON 加载后input_transform is None; - 负向测试:
McpRunConfigProperties没有input_transform属性(not hasattr(config, "input_transform")); - 带变换的配置往返(
model_dump→ 重建)保持相等。
11. 完整测试清单(Phase 1 验收标准)
按规格 phase_1.md 的 Tests 小节整理,全部用例在当前仓库中均有对应实现:
引擎测试(utils/test_jinja_engine.py)
test_compile_template_valid/test_compile_template_syntax_error(带行号信息)test_compile_expression_valid/test_compile_expression_syntax_errortest_render_dict_input:{{ input.foo }}test_render_list_input:{{ input[0] }}/{{ input | length }}test_render_string_input:{{ input }}test_render_plaintext_json_dict:JSON 字符串自动解析为 dicttest_render_plaintext_json_fallback:非 JSON 字符串回退原串test_render_undefined_error:缺失属性抛UndefinedErrortest_render_sandbox_violation:dunder 访问抛SecurityErrortest_extract_returns_value/test_extract_missing_key_returns_undefined/test_extract_explicit_nonetest_extract_materializes_generators:生成器结果变 listtest_trim_lstrip_blocks:trim_blocks/lstrip_blocks空白行为
数据模型测试(datamodel/test_input_transform.py)
test_jinja_input_transform_valid/test_jinja_input_transform_invalid(ValidationError)test_jinja_input_transform_roundtrip:model_dump+ 重建相等test_discriminated_union_dispatch:{"type": "jinja", ...}分派到JinjaInputTransform
RunConfig 扩展(datamodel/test_run_config.py)
test_run_config_input_transform_default_nonetest_run_config_input_transform_accepts_validtest_run_config_input_transform_dict_dispatchtest_run_config_backcompat_no_input_transform:旧 JSON 加载后为Nonetest_mcp_no_input_transform:McpRunConfigProperties无该字段
此外,规格架构文档还预留了适配器集成测试清单(Phase 2,见 test_base_adapter.py 的TestInputTransformIntegration):对象 schema 任务渲染串进入TaskRun.trace首条消息且TaskRun.input保留原始 dict、纯文本 JSON 与非 JSON 两条路径、数组 schema 的{{ input[0] }}、transform=None恒等路径字节级一致、流式路径与同步路径同构、UndefinedError在推理前抛出(mock provider 断言从未被调用)、MCP 配置行为不变。
12. 边界与未来钩子
规格明确列出 V1 范围外事项,避免误用:
- 渲染无 CPU/内存上限(V1 接受的风险);
- V1 无编译结果缓存(引擎 API 设计已保证未来可在模块内部加 LRU 而不破坏调用方);
- V1 无自定义 Jinja2 过滤器(仅内置过滤器;真实实现后来为消费方补充了
fromjson与format_trace两个无副作用过滤器,属实现演化而非违背 V1 约束); - 无 eval 侧代码(由未来的 eval v2 项目消费);
- 无变换编写 UI(未来项目);
- 不公开暴露
_build_namespace(未来按需提升)。
一句话总结 Phase 1 的验收姿态:引擎、数据模型、字段落位三者独立可测,全部测试无需触碰任何适配器即可运行;实现顺序上,先加依赖与锁文件、再写引擎与引擎测试、再写数据模型与数据模型测试、最后落字段并扩展test_run_config.py,最后跑uv run ./checks.sh --agent-mode验证全量检查。
- 人工智能
- AI 应用
- AI 评测
- RAG
- 微调
- 提示工程
- 开发工具
- 桌面应用
【免费下载链接】Kiln
Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.
相关推荐
Kiln Input Transform 实现指南:Jinja2 模板引擎、数据模型与适配器集成全解析
Kiln Input Transform 实现指南:Jinja2 模板引擎、数据模型与适配器集成全解析 本文以 Kiln 仓库中 specs/projects/
人工智能AI 应用AI 评测RAG微调提示工程开发工具桌面应用MLOpsKiln Input Transform 架构解析:用 Jinja2 模板引擎在运行期变换任务输入
Kiln Input Transform 架构解析:用 Jinja2 模板引擎在运行期变换任务输入 本文以 Kiln 仓库中 Input Transform 架
人工智能AI 应用AI 评测RAG微调提示工程开发工具桌面应用MLOpsKiln 输入转换器(Input Transform)只读 UI 落地指南:Phase 1 类型、格式化助手与属性行接线实践
Kiln 输入转换器(Input Transform)只读 UI 落地指南:Phase 1 类型、格式化助手与属性行接线实践 导读 本文基于 Kiln 仓库中
人工智能AI 应用AI 评测RAG微调提示工程开发工具桌面应用MLOps
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考