跳到主要内容

评测与护栏:从可观测到可评估的平台层

30 秒导读: 前四章讲的都是"看得见"——用 OTel 把每一次 LLM 调用记成 span。本章讲"管得住、评得了": 在被观测的函数输出上再跑一组检查(护栏),按策略拦/放;把检查抽象成后端 evaluator,配上数据集 就能跑离线实验;再用一个后台线程把托管 prompt 和配置同步下来。这些是 Traceloop 在"纯埋点"之外的 平台层能力,而它们全部复用前几章的 span/上下文机制。


1. 这是什么(零基础也能懂)

一句话定义: 这是 OpenLLMetry 的 SDK(traceloop-sdk 包)里超出"给 LLM 调用埋点"的那部分—— 它让你不仅能"记录"模型干了什么,还能在模型输出上跑检查、做评测、拉配置

前几章的能力可以概括成一句:观测。本章的能力可以概括成另外三句:

能力白话对应机制
护栏(guardrail)函数一返回,就在它的输出上跑一组检查,不合格就按策略拦截@guardrail 装饰器 + Guardrails 运行器
评测(evaluation)把"检查"抽象成后端托管的 evaluator,配上数据集跑一整批离线实验Evaluator / Experiment / Datasets
同步(sync)后台线程周期性把服务端的托管 prompt、PII 白名单等配置拉到本地Fetcher + PromptRegistry

给谁用、解决什么问题: 假设你已经用 第 4 章@workflow/@task 把一个 RAG 应用埋好了点,现在产品同学问你:"能不能保证回答里不带 PII?上线前能不能拿 200 条测试集自动评一遍 质量?" 这两个诉求,前四章的"埋点"答不了——它们只记录,不判断、不拦截。本章讲的就是补上"判断和拦截"的那层。

一句话直觉/类比: 如果说前几章是给代码装了行车记录仪(忠实记录),本章就是装了安全气囊 + 年检台—— 安全气囊(护栏)在出事的一瞬间拦一下;年检台(评测/实验)在上路前把整车指标跑一遍。

用起来什么样: 护栏最小用法就是给一个函数加一行装饰器——

# 示意,非源码。真实工厂函数见 guardrail/guards.py
from traceloop.sdk.decorators import guardrail
from traceloop.sdk.guardrail import pii_guard, toxicity_guard

@guardrail(pii_guard(), toxicity_guard(), on_failure="raise")
async def answer(prompt: str) -> str:
return await llm.complete(prompt)

# 直接调用,护栏自动在返回值上跑;命中 PII 或毒性就抛异常
reply = await answer("介绍一下你自己")

本节不谈底层。你只要记住:本章 = 在"被观测的函数"周围,加上"检查—拦截—评测—同步"这一圈平台能力。


2. 顶层全景(它大概怎么转)

这套平台层由两条独立的路组成,它们共享同一套 span/上下文基建,但触发时机完全不同:

  • 在线护栏路(请求内、逐次):函数返回 → 跑 guard → 按 on_failure 决定拦/放。
  • 离线评测路(请求外、批量):数据集 → 逐行跑 task → 每个 task 输出交给 evaluator 打分。

外加一条旁路后台线程——把托管 prompt 和配置同步进本地注册表。

怎么读下面这张图:左半是"在线护栏",右半是"离线评测",底部横条是"后台同步";竖直虚线是进程边界, 虚线右侧的 evaluator 计算发生在 Traceloop 后端(SDK 只见客户端侧 HTTP)。

┌──────────────── traceloop-sdk 进程内 ────────────────┐ : Traceloop 后端
在线护栏路 │ │ :
(逐次请求) │ @guardrail(...) │ :
你的函数 ──返回──▶ │ └─ Guardrails.run(func) │ :
│ ├─ 跑 func_to_guard() │ :
│ ├─ 把输出映射成 guard 输入 (default_mapper) │ :
│ ├─ 并行/串行跑每个 guard ─┐ │ :
│ │ 每个 guard 一个 span │ evaluator-backed │ :
│ │ (gen_ai.guard) └── HTTP POST ────────┼──▶: /v2/guardrails/{slug}/execute
│ └─ 不过 → on_failure(raise/log/ignore/fallback) │ : (真正的打分在这算)
│ │ :
离线评测路 │ Experiment.run(task, evaluators, dataset) │ :
(批量) │ ├─ Datasets.get_version_jsonl ────── HTTP GET ─────┼──▶: /datasets/{slug}/versions/..
│ ├─ 每行并发跑 task() (信号量=50) │ :
│ └─ 每个 task 输出 → Evaluator ─── HTTP POST+SSE ────┼──▶: /v2/evaluators/{slug}/executions
│ │ : (流式回结果)
后台同步旁路 │ Fetcher.thread_func (daemon 线程,每 5s) │ :
│ └─ refresh_data ─────────────── HTTP GET ──────────┼──▶: /v1/traceloop/prompts
│ └─ 写入 PromptRegistry / ContentAllowList │ :
└──────────────────────────────────────────────────────┘ :

各部件一句话职责:

部件干什么在哪个文件
guardrail 装饰器把普通函数包成"返回后自动跑护栏"的函数decorators/__init__.py:218
Guardrails护栏运行器:跑 guard、开 span、按 on_failure 处理失败guardrail/guardrail.py:48
OnFailure / resolve_on_failure"raise"/"log"/"ignore"/字符串/callable 归一成一个处理器guardrail/on_failure.py:15
内置 guard 工厂pii_guard()toxicity_guard() 等——本质是"调用某 evaluator + 判条件"guardrail/guards.py:35
Evaluator触发后端 evaluator 执行,SSE 等结果evaluator/evaluator.py:45
generated/evaluators 注册表slug → 请求/响应 Pydantic 模型的映射(代码生成)generated/evaluators/registry.py:91
Experiment用 dataset + evaluators 跑离线实验experiment/experiment.py:25
Datasets数据集 CRUD、按版本取 JSONLdatasets/datasets.py:30
Fetcher后台线程轮询,同步 prompt/配置fetcher.py:27
Client持有 HTTP/异步 HTTP 客户端,挂载 datasets/experimentclient/client.py:13

诚实标注: 图里竖虚线右侧的一切(evaluator 到底怎么算分、guardrail 后端如何判定)在本仓库源码里看不到—— SDK 只包含客户端侧的 HTTP 调用(路径、请求体、响应模型)。凡涉及后端契约,本章只描述 SDK 发出/接收的部分。


3. 护栏:@guardrail 装饰器 + Guardrails 运行器

3.1 它要解决的小问题

前几章的埋点是被动的:函数跑完,span 记下"输入是什么、输出是什么",仅此而已。 护栏要做的是主动:函数跑完后,立刻在它的返回值上跑一组检查,不合格就改变程序行为(抛异常/换降级值/记日志)。

所以护栏必然是"函数的一层包装"。它和第 4 章的 @task 有本质区别:

@task(第 4 章)@guardrail(本章)
目的记录这次调用检查这次调用的输出,并可拦截
对返回值原样返回可能替换/拦截
失败语义记录异常on_failure 策略决定

3.2 装饰器:同一个 @guardrail 同时吃 sync 和 async

guardrail(*guards, input_mapper, on_failure, name) 返回一个装饰器,在装饰时就用 asyncio.iscoroutinefunction(func) 分叉出 async 和 sync 两条包装(decorators/__init__.py:264-311)。

async 分支很直白:每次调用新建一个 Guardrails,把原函数交给它 run:

# 真实源码节选 decorators/__init__.py:268-283 (async_wrapper)
@wraps(func)
async def async_wrapper(*args, **kwargs):
from traceloop.sdk.guardrail import Guardrails
g = Guardrails(*guards_list, on_failure=failure_handler, name=name or func.__name__)
return await g.run(func, *args, input_mapper=input_mapper, **kwargs)

装饰器只负责"分叉 + 构造 Guardrails + 调 run";真正的机制在 Guardrails。至于装饰器本身如何 用 functools.wraps 保签名、如何做上下文传播,属于第 4 章已讲的装饰器底层,这里不重复。

3.3 Guardrails.run:一条五步流水线

Guardrails(guardrail/guardrail.py:48)是护栏的心脏。run(guardrail/guardrail.py:155)把"执行—映射—检查—处理"串成一条流水线, 并且整条流水线包在一个 span 里({name}.guardrail):

run(func):
① result = await func(*args) # 先把被保护函数跑出来
② guard_inputs = 映射(result) # 输出→每个 guard 的输入
③ all_passed = 跑所有 guard(带 span) # 每个 guard 各自一个子 span
④ if not all_passed: return on_failure(...) # 不过 → 按策略处理
⑤ else: return result # 全过 → 原样返回

对应源码 guardrail/guardrail.py:204-231。三个值得记住的细节:

  • 步骤②的映射:没给 input_mapper 时走 default_input_mapper (guardrail/default_mapper.py:9)——它把 str 输出摊成 {"text","prompt","completion"} 三个字段 的字典,把 dict 输出补上 query/completion 等别名,好让各种 evaluator 都能对上字段名。
  • 步骤③前有类型校验:_validate_inputs(guardrail/guardrail.py:333)用 Pydantic TypeAdapter 按每个 guard 第一个参数的类型注解校验输入,不匹配抛 GuardInputTypeError
  • 步骤④的失败容器:传给 on_failure 的是 GuardedResult(result, guard_inputs) (guardrail/model.py:19),既带原始输出、又带"当时检查了什么",方便自定义处理器决策。

3.4 on_failure:四类失败策略,一个归一函数

护栏"不过"之后干什么,由 on_failure 决定。它接受字符串或 callable,resolve_on_failure (guardrail/on_failure.py:109)把它归一成统一的处理器:

on_failure 传入行为归一到
"raise"(默认)GuardValidationErrorOnFailure.raise_exception()
"log"记一条 WARNING,然后照常返回原结果OnFailure.log()
"ignore"静默返回原结果(影子模式,只观测不拦)OnFailure.noop()
任意其它字符串把该字符串本身当降级值返回OnFailure.return_value(value)
callable原样当处理器用——

关键设计:只有 "raise"/"log"/"ignore" 是保留关键字(_ON_FAILURE_KEYWORDS,guardrail/on_failure.py:103); 任何其它字符串都被当成 fallback 文案。所以 on_failure="抱歉,我无法回答" 会在护栏失败时直接把这句话返回给调用者。

处理器还可以是 async 的——run 里用 asyncio.iscoroutine 判断并 await(guardrail/guardrail.py:226-227)。

失败还分两种,别混:

  • guard 返回 False → 是"没通过检查",走 on_failure
  • guard 自己抛异常 → 是"检查跑不起来",直接抛 GuardExecutionError(guardrail/guardrail.py:484,guardrail/model.py:78),不走 on_failure

3.5 每个 guard 都写成 gen_ai.guardrail.* span

护栏不是"黑箱拦截"——它把自己的每一步也变成可观测的 span,复用第 1 章那条导出管道。

_run_single_guard(guardrail/guardrail.py:386)给每个 guard 开一个 {guard_name}.guard 子 span, operation.name="guard",并在其上记录输入、状态、时长、错误。外层 run 的聚合 span 则记总状态、 guard 总数、失败数。用到的属性键集中在 guardrail/span_attributes.py:

常量span 属性键记什么
GEN_AI_GUARDRAIL_NAMEgen_ai.guardrail.nameguard / 护栏名
GEN_AI_GUARDRAIL_STATUSgen_ai.guardrail.statusPASSED / FAILED
GEN_AI_GUARDRAIL_DURATIONgen_ai.guardrail.duration毫秒耗时
GEN_AI_GUARDRAIL_GUARD_COUNTgen_ai.guardrail.guard_countguard 总数
GEN_AI_GUARDRAIL_FAILED_GUARD_COUNTgen_ai.guardrail.failed_guard_count失败 guard 数
GEN_AI_GUARDRAIL_INPUT / _OUTPUTgen_ai.guardrail.input / .outputguard 的输入 / evaluator 输出
GEN_AI_GUARDRAIL_ERROR_TYPE / _MESSAGEgen_ai.guardrail.error.*异常类型 / 消息

这些键沿用第 3 章gen_ai.* 命名风格,是对 GenAI 语义约定的一个护栏方向扩展 (注意:gen_ai.guardrail.* 是本仓库自定义的,不是官方 semconv 里的标准键)。

并发与短路也在这层:_run_guards(guardrail/guardrail.py:427)默认 parallel=Trueasyncio.gather 齐跑; 串行模式下,若某 guard 未过且 run_all=False,就 break 掉后续 guard(fail-fast)。

3.6 sync 函数怎么跑 async guard:借一条独立线程的事件循环

一个棘手问题:Guardrails.runasync 的(要 await guard、await HTTP),但用户可能把 @guardrail 贴在同步函数上,而调用处可能已经在一个事件循环里(Jupyter、Django 等)。同步上下文里不能直接 await,已在循环里又不能再 asyncio.run

SDK 的解法(decorators/__init__.py:301-309):

# 真实源码节选 decorators/__init__.py:301-309 (sync_wrapper)
try:
asyncio.get_running_loop()
# 已在某个事件循环里(Jupyter/Django):另起一个线程,给它独立的事件循环
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
return pool.submit(asyncio.run, to_run).result()
except RuntimeError:
# 没有正在运行的循环:直接 asyncio.run
return asyncio.run(to_run)

再配一个细节:被保护的同步函数本身也不能阻塞事件循环,所以 run 收到的是 asyncio.to_thread(func, ...)(decorators/__init__.py:295)——原同步函数被丢到线程池执行。

一句话记忆:"外层可能在跑循环 → 换条线程开新循环;内层是同步函数 → to_thread 丢线程池",两处都在用线程绕开"同步/异步"的阻抗失配。

3.7 内置 guard 从哪来:其实是"调 evaluator + 判条件"

pii_guard()toxicity_guard() 这些不是本地规则,而是对后端 evaluator 的封装。核心工厂 _create_guard(guardrail/guards.py:35)把一个 EvaluatorDetails 变成一个 async guard 函数:

guard_fn(input):
→ Guardrails().execute_evaluator(slug, input, ...) # POST /v2/guardrails/{slug}/execute
→ 把 evaluator 输出记到当前 span 的 gen_ai.guardrail.output
→ 取 condition_field 那个字段,交给 condition(...) 判 True/False

条件由 guardrail/conditions.py 提供:is_true()/is_false()(严格 bool)、gt(x)/between(a,b) 等。 不同 guard 用不同默认条件——比如 pii_guard 默认 is_false()(检测到 PII 才算失败), toxicity_guard 默认 is_true()(is_safe 为真才通过),见 guardrail/guards.py:163:216

execute_evaluator(guardrail/guardrail.py:280)最终打到 /v2/guardrails/{slug}/execute (guardrail/guardrail.py:513),返回 GuardrailResponse{result, pass}(evaluator/model.py:73, 注意 pass 是 Python 关键字,用 Field(alias="pass") 接)。


4. evaluator 层与 generated/evaluators 注册表

护栏里那个"调 evaluator"只是 evaluator 能力的一个入口。独立的 Evaluator 类 (evaluator/evaluator.py:45)是更完整的一层,主要服务离线实验。

4.1 两条 evaluator 执行路,SSE 等结果

Evaluator.execute(evaluator/evaluator.py:79)走 /v2/evaluators/{slug}/executions:先 POST 触发, 拿回 {execution_id, stream_url},再用 SSEClient.wait_for_result 流式等最终结果。 实验内评测则走 run_experiment_evaluator(evaluator/evaluator.py:144),打到实验专属的 /v2/experiments/{slug}/runs/{run_id}/tasks/{task_id};还有一个"只触发不等"的 trigger_experiment_evaluator

4.2 注册表:slug → Pydantic 模型,做输入校验和类型化输出

generated/evaluators/registry.py 是一张代码生成的映射表(文件头写明 DO NOT EDIT MANUALLY), 把 evaluator 的 slug 映到它的请求/响应模型:REQUEST_MODELS(registry.py:91)、RESPONSE_MODELS(:130), 外加两个查表函数 get_request_model/get_response_model(:169)。

它在两处发挥作用:

  • 发请求前校验:_validate_evaluator_input(evaluator/evaluator.py:18)用 get_request_model(slug) 拿到模型,校验 input 字段,不合法抛带 slug 的 ValueError——在本地就挡下拼错字段的请求
  • 收响应后类型化:ExecutionResponse.typed_result(Model)(evaluator/model.py:55)把后端返回的 松散 dict 解析成具体 Pydantic 模型,让下游有 IDE 补全。

和注册表配套的还有 EvaluatorMadeByTraceloop 工厂(generated/evaluators/definitions.py), 每个方法产出一个 EvaluatorDetails,预填 slug、condition_field、输入/输出 schema、必需字段。例如:

# 真实源码节选 generated/evaluators/definitions.py:674 (toxicity_detector)
def toxicity_detector(threshold: float = 0.7) -> EvaluatorDetails:
config = {"threshold": threshold}
return EvaluatorDetails(
slug="toxicity-detector",
condition_field="is_safe",
output_schema=ToxicityDetectorResponse,
input_schema=ToxicityDetectorRequest,
config=config,
required_input_fields=['text'],
)

这就把第 3.7 节的 toxicity_guard() 和这里的注册表串起来了:guard 工厂拿的正是这些 EvaluatorDetails


5. datasets 与 experiment:离线跑一整批评测

护栏是"逐次、在线";实验是"批量、离线"。同一批 evaluator,既能当在线护栏,也能在实验里对整个数据集打分。

5.1 Experiment.run:数据集 × task × evaluators

Experiment(experiment/experiment.py:25)持有 DatasetsEvaluatorrun(:41)按环境二选一: CI 里(GITHUB_ACTIONS)走 _run_in_github(:265,把结果交后端、由后端评测并回帖 PR),否则走 _run_locally(:94)。本地这条最能说明设计:

_run_locally(task, evaluators, dataset_slug, dataset_version):
① 初始化实验,拿 run_id # PUT /experiments/initialize
② 拉数据集 JSONL,parse 成 rows # Datasets.get_version_jsonl
③ 对每一行 row 并发:
task_result = await task(row) # 你的被测函数
(可选) 校验+规范化字段名 # validate_and_normalize_task_output
创建 task 记录,拿 task_id # POST .../tasks
对每个 evaluator: 打分并收结果 # run_experiment_evaluator (SSE)
④ 汇总 (results, errors) 返回

对应 experiment/experiment.py:146-263。三个要点:

  • 并发有上限:用 asyncio.Semaphore(50)(:237)限制同时在跑的行数,再 as_completed 收集。
  • 字段名容错:task 输出的字段名可能和 evaluator 要的对不上,validate_and_normalize_task_output (evaluator/evaluator.py:231)用同义词表规范化,缺字段时报错还带"你是不是想要 X?"的建议。
  • 错误不炸整批:默认 stop_on_error=False,单行/单 evaluator 失败只记进 errors,继续跑其余行。

5.2 Datasets:数据集从哪来,怎么按版本取

Datasets(datasets/datasets.py:30)是数据集这侧的客户端:from_csv/from_dataframe 建集、 create/override 带附件上传、get_version_jsonl(:248)按版本取 JSONL。实验用的正是 get_version_jsonl + _parse_jsonl_to_rows(experiment/experiment.py:448)——注意后者跳过第一行 (那是列定义),其余每行 JSON 解析成一个 row 字典。


6. 托管 prompt 与配置同步:一条后台轮询线程

最后一块平台能力和护栏/评测都不同——它是旁路的:一个 daemon 线程在后台周期性把服务端配置拉下来。

6.1 traceloop_sync_enabled 是总开关

Traceloop.init(..., traceloop_sync_enabled=False) 默认关闭。开启且指向 traceloop.com 时, 才会建 Fetcherrun()(__init__.py:236-238)。这条旁路和主埋点管道互不影响——不开同步,埋点照常工作。

6.2 Fetcher:两条线程,一拉一守

Fetcher(fetcher.py:27)构造时建两条线程,run()(:54)先同步拉一次再启动它们:

Fetcher.run():
refresh_data(...) # 先阻塞拉一次,保证启动即有数据
exit_monitor.start() # 守护线程:join 主线程,主线程一退就置停止事件
poller_thread.start() # 轮询线程:每 POLLING_INTERVAL(默认5s) 拉一次
  • 轮询线程 thread_func(fetcher.py:131):while not stop_event: refresh_data(); sleep(5); 重试耗尽(RetryError)就停止轮询。
  • 退出监控 monitor_exit(fetcher.py:162):main_thread().join() 阻塞到主线程结束,再 set() 停止事件——这样进程退出时轮询线程能干净收尾。
  • 拉取本身 fetch_url(fetcher.py:97):tenacity 指数退避重试,仅对 5xx 重试(RetryIfServerError,:74), 401/403 直接判为 API key 无效。

6.3 refresh_data:同步的到底是什么

refresh_data(fetcher.py:149)拉两样,分别写进两个本地注册表:

拉取端点写入
托管 prompt/v1/traceloop/promptsPromptRegistry.load(prompts/registry.py:13)
PII 追踪白名单/v1/traceloop/pii/tracing-allow-listContentAllowList.load

PromptRegistry(prompts/registry.py:6)就是个 key → Prompt 的字典。取用侧是 get_prompt(key, **args)(prompts/__init__.py:4)→ PromptRegistryClient.render_prompt (prompts/client.py:48):按 version/name/hash 选出生效版本,用 Jinja2 渲染成 {messages, ...} 参数。

6.4 渲染 prompt 时,顺手把版本信息写进上下文

render_prompt 收尾会调 set_managed_prompt_tracing_context(tracing/tracing.py:296), 把 prompt_keyprompt_versionversion_nameversion_hash、模板变量 attach 进 OTel 上下文。 于是用托管 prompt 发起的那次 LLM 调用,其 span 上会自动带上"用了哪个 prompt 的哪个版本"—— 这正是第 3 章语义约定与第 4 章上下文传播的复用,同步只是把"用哪个 prompt"这条信息喂了进去。

6.5 Client:HTTP 客户端的持有者

Client(client/client.py:13)在 sync 开启的同一段初始化里创建(__init__.py:244),持有同步 HTTPClient 和异步 httpx.AsyncClient(:56,预置 Bearer 头、120s 超时),并挂上 datasetsexperiment(:69)。前面所有走 HTTP 的能力——实验、数据集、evaluator——用的都是它这两个客户端。


7. 边界与局限(诚实标注)

  • 后端契约只见客户端侧。 evaluator 怎么打分、guardrail 后端怎么判定,源码里看不到。本章能确证的 只有 SDK 发出的请求路径与请求体、以及它反序列化的响应模型(如 GuardrailResponseExecutionResponse)。 任何关于"后端如何计算"的说法都超出本仓库范围。
  • 护栏是"后置检查",不是"生成时约束"。 它在函数返回后才跑;拦截手段是抛异常/换降级值, 而非在生成过程中改写 token。
  • 内置 guard 依赖网络。 pii_guard() 等都要打后端 /v2/guardrails/...,有 60s 默认超时 (guardrail/guards.py:163 等);离线/断网环境下这些 guard 无法工作(纯本地的 lambda guard 不受此限)。
  • 同步默认关闭。 traceloop_sync_enabled=False 时没有 Fetcher,托管 prompt 注册表为空, get_prompt 会因查不到而抛异常。
  • 实验的并发是固定的 50。 Semaphore(50) 是硬编码(experiment/experiment.py:237),大数据集时不可调。

8. 横向对比(与同组各章)

关切与本章的关系
01 SDK 启动与导出管道span 怎么被导出护栏 span、evaluator 都复用这条管道
02 自动埋点猴补丁拦 LLM 调用本章是"主动检查",与被动拦截互补
03 语义约定gen_ai.* 记什么gen_ai.guardrail.* 是它的护栏向扩展
04 手动装饰器workflow/task/tool 装饰器@guardrail 是第四个装饰器,复用其上下文传播

一句话:前四章把"一次调用"变成可观测的 span;本章在同一套基建上,再叠一层"检查—评测—同步"。


9. 代码地图(导航索引)

主题文件路径符号名
护栏装饰器(sync/async 分叉)decorators/__init__.py:218guardrail
sync 借独立线程跑 async guarddecorators/__init__.py:301sync_wrapper
护栏运行器guardrail/guardrail.py:48Guardrails
五步流水线guardrail/guardrail.py:155Guardrails.run
每 guard 一个 spanguardrail/guardrail.py:386_run_single_guard
并行/串行 + fail-fastguardrail/guardrail.py:427_run_guards
执行 guardrail evaluatorguardrail/guardrail.py:280 :505execute_evaluator / _execute_guardrail_request
输入类型校验guardrail/guardrail.py:333_validate_inputs
span 属性键guardrail/span_attributes.py:1GEN_AI_GUARDRAIL_*
失败策略归一guardrail/on_failure.py:109 :15resolve_on_failure / OnFailure
护栏异常族guardrail/model.py:47 :61 :78 :112GuardrailError / GuardValidationError / GuardExecutionError / GuardInputTypeError
默认输入映射guardrail/default_mapper.py:9default_input_mapper
内置 guard 工厂guardrail/guards.py:35 :103_create_guard / custom_evaluator_guard
条件判定guardrail/conditions.py:21is_true / is_false / gt / between
evaluator 执行 + SSEevaluator/evaluator.py:79 :144Evaluator.execute / run_experiment_evaluator
输入校验 / 字段规范化evaluator/evaluator.py:18 :231_validate_evaluator_input / validate_and_normalize_task_output
响应模型 / 类型化evaluator/model.py:49 :55 :73ExecutionResponse / typed_result / GuardrailResponse
evaluator 注册表generated/evaluators/registry.py:91 :169REQUEST_MODELS / get_request_model
evaluator 工厂generated/evaluators/definitions.py:674EvaluatorMadeByTraceloop.toxicity_detector
实验运行experiment/experiment.py:25 :41 :94Experiment / run / _run_locally
JSONL 解析(跳列头行)experiment/experiment.py:448_parse_jsonl_to_rows
数据集客户端datasets/datasets.py:30 :248Datasets / get_version_jsonl
后台同步 Fetcherfetcher.py:27 :54 :131 :149 :162Fetcher / run / thread_func / refresh_data / monitor_exit
prompt 注册表 / 渲染prompts/registry.py:6 · prompts/client.py:48PromptRegistry / render_prompt
渲染时写上下文tracing/tracing.py:296set_managed_prompt_tracing_context
同步开关traceloop/sdk/__init__.py:236traceloop_sync_enabled
HTTP 客户端持有者client/client.py:13Client