- AI 安全治理
- 模型安全
- AI 应用
【免费下载链接】guardrails
Adding guardrails to large language models.
本文是一份面向 Guardrails 开发者的 History & Logs 深度指南,系统讲解一次 Guard 执行(Call)如何被记录为可回溯的调用历史对象、每次校验循环(Iteration)如何留存原始输出、解析输出与校验日志,以及 Inputs / Outputs / CallInputs 等数据结构在其中的角色。读完本文,你将能熟练通过guard.history读取每次调用的完整生命周期数据——包括 Token 消耗、ReAsk 消息、失败校验日志与最终守卫输出,并理解pass/fail/error/not run状态机的判定逻辑。
一、核心概念:Call 与 Iteration 的关系
在 Guardrails 中,Call(一次调用)与Iteration(一次迭代)构成典型的"一 对多"层级关系。根据 history_and_logs.md 的定义:
一个 Call 代表一次 Guard 的执行。每当用户调用
Guard.__call__、Guard.parse或Guard.validate方法时,就会创建一个 Call。
而一个 Iteration 则代表校验循环中的单次迭代,包含一次(如适用)对 LLM 的调用。在一次 Call 中,首个 Iteration 对应初始校验轮次,此后每一次 ReAsk(重新询问 LLM 以修正输出)都会追加一个新的 Iteration。因此:
- Call= 用户发起的一次完整 Guard 执行(可包含多次 LLM 调用);
- Iteration= 该 Call 内的一次 LLM 调用 + 一轮解析/校验;
- Stack= 二者在历史中都以栈(Stack)形式组织,支持
first/last/at(i)等访问方式。
从源码看,Call 继承自ArbitraryModel,其核心字段为:
| 字段 | 类型 | 说明 |
|---|---|---|
iterations | Stack[Iteration] | 初始校验轮次 + 每次 ReAsk 产生的迭代的栈 |
inputs | CallInputs | 用户传入Guard.__call__/Guard.parse/Guard.validate的输入 |
exception | Optional[Exception] | 中断 Guard 执行的异常 |
id | str(computed) | 该 Call 的唯一标识,可用于标识某次具体执行 |
在 guard.py 中可以看到 Call 的创建与入栈过程:每次执行时先构造call_log = Call(inputs=call_inputs),随即self.history.push(call_log),把这次执行挂到 Guard 的历史栈上。同时Iteration还维护了index(该迭代在 Call 内的零基下标)与call_id(所属 Call 的唯一标识),见 iteration.py。
Guard.history:调用历史的入口
每一次 Guard 执行产生的 Call 都会按顺序压入guard.history,它是一个Stack[Call],默认最多保留最近 10 条历史(history_max_length参数可调,见 guard.py)。集成测试 test_guard.py 中大量使用guard.history.first来断言首次调用的迭代数量,例如assert call.iterations.length == 2(一次初始校验 + 一次 ReAsk)。
二、Call:一次 Guard 执行的完整快照
Call 是"追溯一次执行"的主入口,围绕它可拿到这次执行的所有侧面。以下属性均来自 call.py,与文档一一对应。
2.1 输入侧属性
prompt_params(Optional[Dict]):用户初始化或调用 Guard 时提供的提示词参数,直接转发自self.inputs.prompt_params;messages(Optional[Union[Messages, list[dict[str, str]]]]):用户初始化或调用 Guard 时提供的消息,转发自self.inputs.messages;compiled_messages(Optional[list[dict[str, str]]]):首次调用时真正传给 LLM 的"已编译消息"。源码实现中,它会取首个 Iteration 的inputs.messages,并逐条用prompt_params对content执行str.format(**prompt_params),将Prompt/Instructions对象还原为其源文本,最终返回role+content结构。若没有迭代则返回None;reask_messages(Stack[Messages]):ReAsk 期间使用的已编译消息栈,不含初始消息——实现上先复制迭代栈、移除首项,再逐条格式化 content。
2.2 输出侧属性
logs(Stack[str]):汇总所有迭代的日志,实现为遍历iterations并extend各迭代的logs;tokens_consumed(Optional[int]):所有迭代消耗的总 Token 数;若没有任何迭代上报 Token,则返回None。实现上只累加tokens_consumed不为None的迭代;prompt_tokens_consumed/completion_tokens_consumed:分别汇总所有迭代的 prompt/输入 Token 与 completion/输出 Token;raw_outputs(Stack[str]):所有 LLM 调用的原始输出。注意实现细节:它取自每个迭代outputs.llm_response_info.output(即LLMResponse.output),若llm_response_info为None则放入None;parsed_outputs(Stack[Union[str, List, Dict]]):LLM 输出经过解析后、进入校验前的形态集合;validation_response(Optional[Union[str, List, Dict, ReAsk]]):跨迭代聚合的校验响应,可能包含 ReAsk。源码中的合并逻辑很关键:若计划全 schema ReAsk、迭代数 < 2、最后一次校验响应本身就是顶层ReAsk或字符串,则直接返回最后一个迭代的响应;否则通过merge_reask_output把各迭代的校验响应逐步合并;fixed_output(Optional[Union[str, List, Dict]]):应用了自动修复的累计输出,由sub_reasks_with_fixed_values(self.validation_response)实现。若某项没有可用的修复值,其中仍可能残留 ReAsk;guarded_output(Optional[Union[str, List, Dict]]):全部校验阶段完成后的最终完整输出。从实现看,只有当 Call 处于pass状态,或最后一次迭代的失败校验全部为 no-op(值在前后校验中未发生改变)时,才会返回非空值——这与文档中"仅在 Guard 通过或 action 为 no-op 时有值"的描述完全一致;reasks(Stack[ReAsk]):校验中产生且无法自动修复的 ReAsk 集合,若还有剩余的 ReAsk 配额,它们会被拼入下一次 LLM 调用的提示词;validator_logs(Stack[ValidatorLogs]):所有迭代中每一次独立校验的结果;error(Optional[str]):中断运行异常的字符串消息;实现优先级为:self.exception→ 无迭代时None→ 最后一个迭代的error;failed_validations(Stack[ValidatorLogs]):全程校验失败的日志,过滤条件是validation_result存在且outcome == Outcome.FAIL;status(str):基于最终合并输出有效性得出的累计状态,取值见下文状态机一节;tree(Tree):返回 RichTree对象,为每个迭代渲染一个Panel(标题Step {i}),其中包含 Messages 表、Raw LLM Output、Validated Output 面板;若应用了修复且最终通过,还会用修正后的Validated Output面板替换最后一个面板。可直接打印在终端里查看日志树。
三、Iteration:单轮校验循环的明细
Iteration代表校验循环的单次迭代(包含一次 LLM 调用),其定义与实现见 iteration.py。它拥有四个字段:id(唯一标识)、call_id(所属 Call 标识)、index(Call 内零基下标)、inputs(本轮输入)与outputs(本轮输出)。
其核心属性如下:
| 属性 | 返回类型 | 语义 |
|---|---|---|
logs | Stack[str] | 本迭代的日志;实现上通过作用域日志处理器(get_scope_handler().get_logs(str(id(self))))按迭代实例 ID 取回 |
tokens_consumed | Optional[int] | 本迭代总 Token(prompt + completion,任一项非空即求和) |
prompt_tokens_consumed | Optional[int] | 来自outputs.llm_response_info.prompt_token_count |
completion_tokens_consumed | Optional[int] | 来自outputs.llm_response_info.response_token_count |
raw_output | Optional[str] | LLM 的精确原始输出(优先取LLMResponse.output) |
parsed_output | Optional[Union[str, List, Dict]] | 解析后、校验前的输出 |
validation_response | Optional[Union[ReAsk, str, List, Dict]] | 单阶段校验的响应,可能是合法输出与 ReAsk 的组合。文档特别提示:Guard 可能因 ReAsk 运行多次校验,要取最终输出请查看Call.guarded_output |
guarded_output | Optional[Union[str, List, Dict]] | 校验通过的有效值;部分值可能是校验中被"修复"的值;字段级 ReAsk 发生时可能是不完整结构 |
reasks | Sequence[ReAsk] | 校验产生的 ReAsk,将拼入下一次 LLM 调用 |
validator_logs | List[ValidatorLogs] | 本迭代每次校验的结果。注意流式场景(inputs.stream为 True)会过滤掉没有validated_chunk的日志 |
error/exception | Optional[str]/Optional[Exception] | 中断本迭代的异常消息与异常对象 |
failed_validations | List[ValidatorLogs] | 本迭代失败的校验日志 |
error_spans_in_output | List[ErrorSpan] | LLM 响应中的错误片段(索引相对完整 LLM 输出) |
status | str | 本迭代终态,OneOf:pass/fail/error/not run |
Iteration还暴露了rich_group属性,用于在 Rich 终端中渲染 Messages 表、Raw LLM Output 与 Validated Output 三块面板——这正是Call.tree中每个Step {i}面板的内容来源。
集成测试 test_guard.py 对 Iteration 与 Call 的联合断言非常直观:
call = guard.history.first assert call.iterations.length == 2 # 初始轮 + 一次 reask first = call.iterations.first assert first.prompt_tokens_consumed == 123 assert first.completion_tokens_consumed == 1234 assert first.raw_output == entity_extraction.LLM_OUTPUT assert first.validation_response == entity_extraction.VALIDATED_OUTPUT_REASK_1 assert call.reask_messages.first[1]["content"] == entity_extraction.COMPILED_PROMPT_REASK assert call.raw_outputs.at(1) == json.dumps(entity_extraction.VALIDATED_OUTPUT_REASK_2) assert call.guarded_output == entity_extraction.VALIDATED_OUTPUT_REASK_2四、Inputs / Outputs:校验循环的输入与输出
4.1 Inputs(inputs.py)
Inputs代表传入校验循环的输入数据,字段与文档一致:
llm_api(Optional[PromptCallableBase]):用于调用 LLM 的构造类;序列化时转为字符串,反序列化时若无法还原为PromptCallableBase则返回None;llm_output(Optional[str]):用户通过Guard.parse提供的外部 LLM 调用输出字符串;messages(Optional[List[Dict]]):聊天模型调用时提供的消息历史;序列化时会把Prompt对象展开为其source文本;prompt_params(Optional[Dict]):将被格式化进最终 LLM 提示词的参数;num_reasks(Optional[int]):允许的 ReAsk 总次数(用户提供或使用默认值);metadata(Optional[Dict[str, Any]]):用户提供、供校验阶段使用的元数据;full_schema_reask(Optional[bool]):ReAsk 是作用于整个 schema 还是字段级;stream(Optional[bool]):是否使用流式,默认False。
4.2 Outputs(outputs.py)
Outputs代表校验循环产出的数据:
llm_response_info(Optional[LLMResponse]):LLM 响应信息。LLMResponse定义在 llm_response.py,包含prompt_token_count(别名promptTokenCount)、response_token_count(别名responseTokenCount)与output三个字段,是 Token 统计的直接来源;raw_output(Optional[str]):LLM 的精确输出;parsed_output(Optional[Union[str, List, Dict]]):解析后的输出,即进入校验时的形态;validation_response(Optional[Union[str, ReAsk, List, Dict]]):校验过程返回的响应;guarded_output(Optional[Union[str, List, Dict]]):校验通过后的有效值(可能含修复值;字段级 ReAsk 时可能为部分结构);reasks(List[ReAsk]):校验失败时用于构造 LLM ReAsk 的信息,默认[];反序列化时通过to_reask将字典还原为ReAsk;validator_logs(List[ValidatorLogs]):每次独立校验的结果,默认[];error/exception:中断过程的错误消息与异常对象。
Outputs还提供三个派生属性:
failed_validations:validator_logs中validation_result.outcome == Outcome.FAIL的日志;error_spans_in_output:按验证器累计已校验文本长度,把FailResult.error_spans的相对索引偏移为相对完整 LLM 输出的绝对索引;status:校验运行的终态。其判定顺序(见 outputs.py)为:全部字段为空 →not run;存在error→error;存在没有fix_value的 ReAsk 失败结果 →fail;guarded_output为None且validation_response是ReAsk→fail;否则 →pass。
4.3 CallInputs(call_inputs.py)
CallInputs继承Inputs,代表用户传入 Guard 的输入,在父类基础上覆盖/新增:
llm_api(Optional[Callable[[Any], Awaitable[Any]]]):用户在Guard.__call__/Guard.parse时提供的 LLM 函数;messages(Optional[list[dict[str, str]]]):用户提供的消息;args(List[Any],默认[]):传给 LLM 的额外位置参数;kwargs(Dict[str, Any],默认{}):传给 LLM 的额外关键字参数。注意序列化时会对键名含key或token的字符串值做脱敏(只保留后 4 位,其余以*代替),避免 API Key 等敏感信息落盘。
五、状态机:pass / fail / error / not run
历史对象的status统一使用四种字面量状态,定义在 constants/init.py:error、fail、pass、not run。
Iteration.status:直接透传outputs.status(见 iteration.py),判定逻辑见上文Outputs.status;Call.status(call.py)则更宏观:迭代栈为空 →not run;存在error→error;存在未解决的失败(_has_unresolved_failures()判定)→fail;否则 →pass。_has_unresolved_failures会检查是否还有未修复的 ReAsk,以及fixed_output中对应property_path的值是否仍等于校验前值、或是Refrain/Filter等没有产生修复的占位对象。
因此,Call.status是"最终合并输出有效性"的累计表达,而Iteration.status只代表单轮迭代的终态。测试 test_guard.py 给出了失败场景的断言:call.status == "fail"且call.guarded_output is None——此时即便raw_outputs.last有原始输出,最终守卫输出也不会产生。
六、ValidatorLogs 与失败追溯
历史体系中另一个关键对象是ValidatorLogs(validator_logs.py),它记录单个验证器单次执行的完整信息:
| 字段 | 别名 | 说明 |
|---|---|---|
validator_name | validatorName | 验证器的类名 |
registered_name | registeredName | 验证器在注册表中的 ID |
value_before_validation | valueBeforeValidation | 校验前的值 |
validation_result | validationResult | PassResult/FailResult/ValidationResult之一(反序列化时按outcome区分) |
value_after_validation | valueAfterValidation | 校验后的值 |
start_time/end_time | startTime/endTime | 校验起止时间,序列化为 ISO 格式 |
instance_id | instanceId | 验证器实例 ID |
property_path | propertyPath | 被校验属性在 JSON 中的路径 |
Call.failed_validations与Outputs.failed_validations正是在validator_logs基础上按Outcome.FAIL过滤得到的。这也解释了Call.guarded_output实现中对 no-op 的兼容:当所有失败校验的value_after_validation与value_before_validation相同(即 on-fail 动作为 no-op)时,即便整体不是pass,也会返回最后一次迭代的guarded_output。
七、序列化、反序列化与终端日志树
所有历史对象均继承ArbitraryModel(Pydantic 模型),因此:
- 序列化:推荐
model_dump(exclude_none=True, by_alias=True);源码同时保留了to_interface()/to_dict()/from_interface()/from_dict()等兼容方法,但它们已被标记为deprecated,文档与代码均建议改用model_dump/model_validate; - 字段别名:如
callId、llmApi、numReasks、fullSchemaReask、validatorName、propertyPath等,序列化时输出驼峰别名; - 日志树:
Call.tree返回 RichTree,配合Iteration.rich_group可在终端中按Step 0、Step 1分层展示每条迭代的消息、原始 LLM 输出与校验输出。你可以在自己的代码里直接print(call.tree)查看某次 Guard 执行的完整日志回放。
八、实战小结:如何读取一次调用的历史
综合以上内容,一次典型的读取流程如下:
- 执行
guard(prompt_params=...)或guard.parse(llm_output=...)后,通过call = guard.history.first或guard.history.last拿到本次执行; - 用
call.status判断整体成败,用call.guarded_output取最终输出,用call.failed_validations定位失败校验; - 需要明细时遍历
call.iterations:iteration.raw_output(原始输出)、iteration.parsed_output(解析后输出)、iteration.validation_response(单轮校验响应)、iteration.prompt_tokens_consumed/completion_tokens_consumed(Token 消耗); - 需要排查 ReAsk 时使用
call.reask_messages与call.reasks; - 需要可视化时直接打印
call.tree。
这套"Call 为骨架、Iteration 为细节、Inputs/Outputs 为数据载体、status 为结论"的历史体系,既服务于开发调试(集成测试大量依赖它做断言),也为后续基于历史做观测、审计与成本统计提供了统一的数据模型。
- AI 安全治理
- 模型安全
- AI 应用
【免费下载链接】guardrails
Adding guardrails to large language models.
相关推荐
Guardrails历史追踪与日志系统:全面监控AI应用运行状态的终极指南
Guardrails历史追踪与日志系统:全面监控AI应用运行状态的终极指南 Guardrails是一款为大型语言模型 LLM 添加安全防护的开源工具,其历史追踪
AI 安全治理模型安全AI 应用NocoBase 日志体系全景:服务端日志、审计日志与历史记录
NocoBase 日志体系全景:服务端日志、审计日志与历史记录 NocoBase 的日志体系由三类记录组成:服务端日志(系统运行日志 + 请求日志)、审计日志(
低代码后端前端人工智能AI 应用工作流自动化TransformerLab 作业生命周期与状态机全解析:从 QUEUED 到终态的状态流转、日志体系与交互式任务
TransformerLab 作业生命周期与状态机全解析:从 QUEUED 到终态的状态流转、日志体系与交互式任务 本篇技术指南围绕 docs/task exe
人工智能大模型微调模型评测模型推理服务LLMOps本地部署后端CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考