跳到主要内容

数据截至 (上游 commit 36c7a7f6eca6)

MLflow — 架构与原理

30 秒导读: MLflow 是开源的 AI 工程平台,本文只讲它现在最重的那一半——GenAI 的可观测与评估。它的全部设计可以压成一句话:把 OpenTelemetry 的 span 树当成 LLM 应用的通用数据结构。埋点产出它,存储保存它,评估读它,打完分再把结果作为「批注」写回同一条 trace。于是调试、离线回归、线上监控共用一份数据,不需要三套 schema。


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

1.1 一句话定义

MLflow 的 GenAI 部分是「AI 应用的行车记录仪 + 考官」:记录仪把每次请求的完整调用树录下来(trace),考官对着录像逐条打分(assessment),分数就贴在录像上。

1.2 它解决的真实痛点

普通程序出错会抛异常,看堆栈就能定位。LLM 应用不抛异常——它只是答得不好

这带来两个具体麻烦:

  • 看不见:一次请求里可能有改写 query、检索、三次工具调用、两层 agent 嵌套。哪一步开始跑偏?日志里只有一行「请求成功」。
  • 说不清:你改完 prompt,凭什么说这次是真变好了,而不是碰巧那几个例子答对了?

MLflow 把这两件事拆成两个可工程化的动作:先把过程结构化录下来,再拿录像去打分

1.3 给谁用

  • 在搭 RAG、agent、聊天机器人,需要调试「链路里哪一步坏了」的工程师。
  • 要给这些系统做质量守门(上线前跑回归、上线后持续监控)的团队。
  • 已经在用传统 MLflow 管 model / run / registry,现在想把 GenAI 也纳进来的团队。

1.4 它能做什么(功能速览)

能力说明入口
手工埋点装饰器 / 上下文管理器 / 无上下文 API 三种粒度mlflow/tracing/fluent.py:123 trace
自动埋点一句 mlflow.autolog() 覆盖 17 个 GenAI 集成模块(字典里 19 个 import 名,google.genai/google.generativeai 同指 gemini、autogen/autogen_agentchat 分指 ag2 与 autogen,去重后 17 个模块)mlflow/tracking/fluent.py:3643 GENAI_LIBRARY_TO_AUTOLOG_MODULE
落库与检索trace / span / assessment 三类表,支持结构化搜索mlflow/store/tracking/sqlalchemy_store.py:3977 search_traces
离线评估数据集 × predict_fn × scorers 跑成一次 runmlflow/genai/evaluation/base.py:56 evaluate
内置打分器检索相关性、工具调用效率、正确性、安全、PII 等 20+ 个mlflow/genai/scorers/builtin_scorers.py:314 BuiltInScorer
自定义 judge一句自然语言指令生成一个 LLM 评审员mlflow/genai/judges/make_judge.py:113 make_judge
judge 对齐用人类反馈反过来优化 judge 自己mlflow/genai/judges/base.py:107 Judge.align
在线监控给生产流量按采样率持续打分mlflow/genai/scorers/job.py:430 run_online_scoring_scheduler

注意区分两个数:上面这 17 个是 mlflow.autolog() 一键能开的;单独 import 后手动调 mlflow.<flavor>.autolog() 的 GenAI 集成有 19 个,再加上接收外部 OTel 数据的 mlflow.otel 就是 20 个(详见 03 章 的集成矩阵)。

1.5 用起来什么样

最小的一次埋点,就是给函数加一行装饰器:

# 示意,非源码:MLflow 最小追踪用法
import mlflow

mlflow.openai.autolog() # 第三方 SDK 的调用自动变成 span


@mlflow.trace # 你自己的函数:最外层这个会开一棵新 trace
def answer(question: str) -> str:
docs = retrieve(question) # 嵌套的 @mlflow.trace 自动变成子 span
return llm(f"{docs}\n\n{question}")


answer("MLflow 是什么?") # 一次调用 = 一棵 trace,函数返回时落库

重点看:父子关系你一个字都不用传。装饰器把 span 挂进 OpenTelemetry 的当前上下文,嵌套调用自然接上(mlflow/tracing/fluent.py:299 _wrap_function)。

录下来之后,同一份数据直接就是考卷:

# 示意,非源码:拿录下来的 trace 当评测集
import mlflow
from mlflow.genai.scorers import Correctness, Safety

trace_df = mlflow.search_traces(experiment_ids=["1"]) # 把线上痕迹捞出来
mlflow.genai.evaluate(data=trace_df, scorers=[Correctness(), Safety()])

1.6 一句话直觉

把 trace 想成一段行车记录仪录像,把 assessment 想成贴在录像某一帧上的批注

MLflow 的全部工程量,都花在让「录像」这一种数据结构同时满足三个互相拉扯的需求:录的时候要快(不能拖慢业务)、存的时候要能搜(不能只是一坨 JSON)、评的时候要好读(judge 要能按结构问「第三个工具调用返回了什么」)。


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

2.1 第一张图:一条 trace 的一生

怎么读这张图: 从左到右是时间顺序。①②③在你的进程里(微秒级),④之后才涉及网络与磁盘。

① 埋点 ② OTel 内核 ③ 聚树 ④ 落库
┌──────────────┐ ┌────────────────┐ ┌──────────────────┐ ┌────────────────┐
│ @mlflow.trace│──►│ Tracer 建 span │──►│ InMemoryTrace │──►│ Tracking Store │
│ autolog 补丁 │ │ processor 挂钩 │ │ Manager 攒成一棵 │ │ trace_info │
│ 手工 start_ │ │ on_start/on_end │ │ 树,root 结束才 │ │ spans │
│ span │ │ │ │ pop 出来 │ │ assessments │
└──────────────┘ └────────────────┘ └──────────────────┘ └────────────────┘

关键的一步是③。OpenTelemetry 原生是一条 span 结束就导出一条,但评估需要的是一整棵树。MLflow 因此插了一层内存态的 InMemoryTraceManager:所有 span 先在这里按 trace ID 归堆,只有根 span 结束时才把整棵树弹出去导出(mlflow/tracing/trace_manager.py:195 pop_trace)。

2.2 第二张图:录下来之后干什么

怎么读这张图: 中间那个 store 是共用的;三条支路都从它读 trace,判分结果又都写回它。

┌────────────────────┐
│ Tracking Store │◄────── 埋点写入
│ (trace + span) │
└────────┬───────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 人看 UI │ │ 离线评估 │ │ 在线监控 │
│ 调试单条 │ │ evaluate() │ │ 定时抽样 │
└────────────┘ └─────┬──────┘ └─────┬──────┘
│ │
└───► assessment 写回同一条 trace

2.3 部件一句话职责

部件干什么在哪
trace 装饰器包住函数,进出各建/收一个 spanmlflow/tracing/fluent.py:123
Span / LiveSpanMLflow 的 span 对象,本质是 OTel span 的薄包装mlflow/entities/span.py:99:643
SpanAttributeKey所有 MLflow 私货塞进 mlflow.* 前缀的 OTel attributemlflow/tracing/constant.py:98
InMemoryTraceManager进程内单例,按 trace ID 把 span 聚成树mlflow/tracing/trace_manager.py:57
BaseMlflowSpanProcessorOTel 钩子:on_start 建 trace、on_end 收尾并转交导出mlflow/tracing/processor/base_mlflow.py:176
MlflowV3SpanExporter真正往后端写:span 增量写、root 结束时写整条 tracemlflow/tracing/export/mlflow_v3.py:77
_get_span_processors决定这次导出去哪:MLflow / Unity Catalog / OTLP / 推理表mlflow/tracing/provider.py:794
safe_patchautolog 的地基:给第三方方法打补丁且保证不弄崩业务mlflow/utils/autologging_utils/safety.py:231
SqlAlchemyStore服务端落库与检索 trace / span / assessmentmlflow/store/tracking/sqlalchemy_store.py:5213:3713
evaluate离线评估入口mlflow/genai/evaluation/base.py:56
_run_pipeline评估的心脏:predict 池与 score 池边产边评mlflow/genai/evaluation/harness.py:521
Scorer所有打分器的统一契约mlflow/genai/scorers/base.py:290
JudgeScorer 的子类,专指 LLM 评审员mlflow/genai/judges/base.py:53
InstructionsJudge自然语言指令 judge 的实现体mlflow/genai/judges/instructions_judge/__init__.py:61
JudgeToolRegistryagentic judge 用来「翻阅 trace」的工具箱mlflow/genai/judges/tools/registry.py:21
OnlineScorerSampler生产监控的抽样器mlflow/genai/scorers/online/sampler.py:16

2.4 主线走一遍(不进代码)

  1. 你调了个被 @mlflow.trace 包住的函数。装饰器向 OTel tracer 要一个 span,压进当前上下文。
  2. span 一开始,processor 的 on_start 就触发。若这是根 span(没有父),它先去后端注册一条 trace,拿到 trace ID;然后把 span 包成 MLflow 的 LiveSpan 注册进 InMemoryTraceManagermlflow/tracing/processor/base_mlflow.py:210)。
  3. 函数体里的嵌套调用——你自己的 @mlflow.trace,或 autolog 补丁过的 openai.chat.completions.create——各自建子 span,父子关系由 OTel 上下文自动接上。
  4. 每个 span 结束,on_end 触发:填上耗时、状态,非根 span 就交给 exporter 增量上报mlflow/tracing/export/mlflow_v3.py:117)。
  5. 根 span 结束是分水岭:processor 把 token 用量、成本这些聚合信息回填到 trace 元数据,exporter 把整棵树从内存管理器里 pop 出来写进 store(mlflow/tracing/export/mlflow_v3.py:191)。
  6. 之后你跑 mlflow.genai.evaluate():harness 把每条 trace 当一个评测项,喂给一串 scorer。
  7. scorer 里如果是 LLM judge,它会读 trace(甚至反过来调工具翻这棵树),产出一个 Feedback
  8. Feedbackmlflow.log_assessment 写回同一条 tracemlflow/genai/evaluation/harness.py:1015 _log_assessments)。闭环完成——下次你看这条 trace,判分就贴在上面。

3. 阅读地图

六章分两段:01–03 是「数据怎么进来」,04–06 是「数据怎么被用」。建议顺序读;只想解决一个具体问题的话看最后一列。

顺序章节读完你会知道什么时候直奔这章
1数据模型与埋点 API:一次函数调用怎么变成一棵 traceTrace/TraceInfo/TraceData/Span 的分层、MLflow 私货怎么塞进 OTel attribute、三套埋点 API 的差别、生成器与异步怎么处理想自定义埋点、或搞不懂 span 里那些字段
2追踪运行时:span 从产生到落盘的完整管道processor/exporter 的分工、InMemoryTraceManager 为什么必须存在、异步与批量导出、超时与缓冲淘汰、多目的地路由trace 丢了、延迟高、或要接自己的后端
3自动埋点:一行 autolog 怎么把第三方 SDK 就地改造成埋点客户端safe_patch 的异常隔离契约、补丁函数怎么建 span、流式响应怎么拆成 chunk 事件、二十来个集成的共性骨架要给新框架写集成、或 autolog 没生效
4评估引擎:evaluate() 内部的双线程池流水线与 Scorer 抽象evaluate() 三种输入形态、predict 池与 score 池怎么边产边评、AIMD 限流、Scorer 的参数注入与序列化跑评估太慢、被限流、或要写自定义 scorer
5LLM judge 与对齐:从一句自然语言指令到会读 trace 的评审员make_judge 的模板变量、结构化输出、agentic 模式的工具循环、三种对齐优化器判分不准、想让 judge 更贴人类口味
6落库、查询与生产监控:服务端怎么存 trace、怎么被搜、怎么持续打分trace/span/assessment 的表结构、搜索语法怎么翻成 SQL、在线打分调度与 dense sampling要自建部署、写查询、或上线持续监控

4. 巧妙之处(可借鉴的技术)

这一节是精华。每条先说「妙在哪」,再给源码位置。

4.1 不自造 span,改造 OTel 的 span

妙在哪: MLflow 的 Span 不是新数据结构,而是 OTel ReadableSpan 的包装——构造函数直接把 OTel span 存进 self._spanmlflow/entities/span.py:109-117)。所有 MLflow 私有信息(输入、输出、span 类型、token 用量、成本)全部走 OTel 的 attribute,键名统一加 mlflow. 前缀(mlflow/tracing/constant.py:98 SpanAttributeKey)。

为什么这是对的选择: 换来两个方向的互通。别人用 OTel SDK 埋的点能直接进 MLflow;MLflow 录的 trace 也能原样 OTLP 导出到别的后端(mlflow/tracing/processor/otel.py)。代价是所有值都得能序列化成 attribute——这也是为什么输入输出在 attribute 里是 JSON 字符串。

4.2 拦一层内存管理器,把「流式 span」变回「一棵树」

妙在哪: OTel 的模型是 span 各自独立结束、独立导出,谁也不等谁。但评估需要完整的树:judge 要问「这次检索召回了什么、最后答了什么」。

MLflow 的解法是在 processor 和 exporter 之间插一个进程内单例:所有 span 先按 trace ID 堆进 InMemoryTraceManager,只有根 span 结束时才 pop_trace 把整棵树弹出去(mlflow/tracing/trace_manager.py:57:188)。

配套的代价也处理了: 树没聚完就崩了怎么办?管理器底下是带 TTL 的缓存,没等到根 span 的半棵树会过期淘汰(mlflow/tracing/utils/timeout.py:28 get_trace_cache_with_timeout)。

4.3 后台线程还在跑时,把 root span 的导出推迟

妙在哪: 有个刁钻场景——主流程结束了,但业务代码起的后台线程里还有子 span 没结束。如果这时按常规 pop 掉整棵树,那些迟到的 span 就找不到「OTel trace ID → MLflow trace ID」的映射,直接丢了。

exporter 的做法是:导出根 span 前先问一句「这棵树还有没开着的 span 吗」,有就把根 span 存进 _deferred_root_spans 挂起,等下一批导出时再检查(mlflow/tracing/export/mlflow_v3.py:191 _export_traces,配合 mlflow/tracing/trace_manager.py:177 has_open_spans)。

代码注释里连为什么用两把锁而不是一把都写了:先在 _deferred_lock 下拷贝一份 key,出锁之后再去问 has_open_spans——避免嵌套持有两个锁造成死锁。

4.4 评估期间绕过批处理

妙在哪: 平时用批量导出(攒一批再发)性能更好。但评估 harness 是先跑 predict 产生 trace,紧接着就要读这条 trace 来打分——batch 还压在队列里就读不到了。

MLflow 的处理是在 on_end 里加一个判断:检测到当前处于评估上下文,就退回逐条同步导出(mlflow/tracing/processor/base_mlflow.py:280maybe_get_request_id(is_evaluate=True))。异步日志那一侧也有同样的开关(mlflow/tracing/export/mlflow_v3.py:404)。

可借鉴的模式: 与其让评估代码去 sleep 等一致性,不如让写入侧感知调用方身份,按场景切换一致性级别。

4.5 flush 之前,先等所有 on_end 落进队列

妙在哪: 「flush 一下确保都写完了」是个常见的假象。force_flush() 只保证已经进了队列的东西被发走;如果这一刻还有别的线程正在 on_end 里往队列走,flush 就漏了它。

MLflow 给 processor 加了个在途计数 _pending_on_end_countmlflow/tracing/processor/base_mlflow.py:207),配一个 Condition。全局 flush 时先等这个计数归零,再调 force_flush:68 flush_all_batch_processors)——这是一道入队屏障,不是重试。

4.6 私有随机源,还要在 fork 之后重新播种

妙在哪: OTel 默认的 ID 生成器用全局 random 模块。用户代码里一句 random.seed(42)(做实验复现时很常见),就会让每次运行生成完全一样的 trace ID——数据直接串了。

MLflow 的生成器持有私有 random.Random() 实例,与全局状态隔离(mlflow/tracing/provider.py:90 _IsolatedRandomIdGenerator)。更细的一步是注册 os.register_at_fork(after_in_child=...):多进程 fork 之后子进程各自重新播种,否则父子会吐出同一串 ID(mlflow/tracing/provider.py:81 _reseed)。

4.7 打补丁的第一原则:埋点崩了不能连累业务

妙在哪: autolog 是往别人家的类上打猴子补丁(mlflow/utils/autologging_utils/safety.py:231 safe_patch)。补丁里出 bug 是必然的,问题是出 bug 之后谁死。

safe_patch 定义了一条严格的契约:原函数抛的异常照常向上传播;补丁自己那部分抛的异常全部吞掉转成 warning:319 safe_patch_function)。实现上靠传给补丁一个「增强版原函数」,用闭包变量记录它有没有被执行过、有没有抛异常,以此区分异常来源。

那个函数上方的注释直白得罕见:这段代码跑在默认路径上,任何 bug 都会在用户毫无操作的情况下弄崩他们的任务。这是所有「无侵入埋点」项目都该抄的纪律。

4.8 评估流水线:边产边评,两个池子互相限流

妙在哪: 评估有两种慢法——调你的应用慢(predict),调 judge 模型慢(score)。串行跑就是两倍时间;各开一个池子无脑并发,又会同时打爆两边的速率限制。

_run_pipeline 建了两个 ThreadPoolExecutor,用一个 wait(..., FIRST_COMPLETED) 的循环把它们串成流水线:某一项 predict 完成就立刻提交去 score,同时释放一个 predict 槽位(mlflow/genai/evaluation/harness.py:521)。predict 池的并发度是有背压的,不会一路跑到底把 score 池淹了。

两个池子各挂一个 AIMD 限流器:被限流就乘性下降速率,连续成功就加性上升mlflow/genai/evaluation/rate_limiter.py:70 RPSRateLimiter_beta = 0.5 / _alpha = 1.0)——TCP 拥塞控制的思路搬到 LLM 调用上。

4.9 judge 就是 scorer,而带 {{ trace }} 的 judge 会自己去翻 trace

妙在哪: 类型层面上 Judge 直接继承 Scorermlflow/genai/judges/base.py:53)——所以内置打分器、你写的 Python 函数、LLM 评审员在 harness 眼里是同一种东西,可以混在一个 scorers=[...] 里。

真正有意思的是 make_judge 的模板变量。你写 {{ inputs }} / {{ outputs }},judge 就是普通的一问一答;你写 {{ trace }},judge 切换成 agentic 模式——不再是把整棵树塞进 prompt,而是给模型一组工具让它自己翻:列 span、取某个 span、看根 span、正则搜 trace(mlflow/genai/judges/tools/,注册表在 registry.py:21)。

还有一条降级路径值得抄:如果模板要 {{ inputs }} 但根 span 里压根没有(OTel 原生 trace 常见),它会自动 fallback 到 agentic 模式,让模型用工具去把输入找出来(mlflow/genai/judges/instructions_judge/__init__.py:567-589)。

4.10 judge 自己也要被对齐

妙在哪: LLM judge 最大的问题不是不会判,是判得跟人不一样。MLflow 把这件事做成一个显式动作:judge.align(traces) 拿一批带人类反馈的 trace,返回一个新的 judgemlflow/genai/judges/base.py:107)。

默认优化器 MemAlign 走的是双记忆路线:语义记忆把反馈蒸馏成一条条通用准则,情景记忆把反馈存成带 embedding 的样例、判分时检索最相似的几条当上下文(mlflow/genai/judges/optimizers/memalign/optimizer.py:629)。另外还有基于 DSPy 的 SIMBA 与 GEPA 两种(mlflow/genai/judges/optimizers/__init__.py)。

这是整个闭环的最后一环: trace 被 judge 打分,人给 judge 的打分纠错,纠错又变成新 judge。

4.11 在线抽样用「条件概率瀑布」,而不是各抽各的

妙在哪: 生产上挂了 5 个 scorer,各自采样率不同。最省事的做法是每个 scorer 独立掷骰子——结果是几乎每条 trace 都被某个 scorer 打了分,但没有一条 trace 被所有 scorer 打过,横向比较无从谈起。

MLflow 的 dense sampling 反过来做:按采样率降序排 scorer,用条件概率往下走——上一个没被选中,后面更低采样率的直接全部跳过。抽中的 trace 因此获得密集覆盖mlflow/genai/scorers/online/sampler.py:57 sample,瀑布主体在 :89-101)。

举例:50% 和 25% 两个 scorer,按代码算出来的真实分布是「25% 的 trace 两个都打、25% 只打第一个、50% 都不打」。推导只有两步:第一个 scorer 的条件概率是 0.5 / 1.0 = 0.5;通过之后,第二个的条件概率是 0.25 / 0.5 = 0.5——两级各砍一半,所以两个都打的是 25%。

别照抄上游注释。 sample() 的 docstring 里那个举例写的是「50% 两个都打、25% 只打第一个、25% 都不打」(mlflow/genai/scorers/online/sampler.py:63-66),与它自己下面那段条件概率代码算出来的结果不符。设计意图(密集覆盖、可横向比较)是对的,数字是错的。

哈希用 sha256(trace_id + scorer_name) 归一到 [0,1] 而不是随机数——同一条 trace 重跑结果一致,调度器重启不会重复打分。


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

按主题跳源码。优先用符号名 grep——行号会随上游更新漂移,符号名一般还在。

5.1 数据模型(→ 第 01 章)

主题文件路径符号名
trace 顶层容器mlflow/entities/trace.pyTraceinfo + data 两半)
trace 元数据mlflow/entities/trace_info.pyTraceInfo
span 容器mlflow/entities/trace_data.pyTraceData.spans
span 本体(OTel 包装)mlflow/entities/span.pySpanLiveSpanNoOpSpancreate_mlflow_span
span 类型枚举mlflow/entities/span.pySpanType(LLM/CHAIN/AGENT/TOOL/RETRIEVER…共 15 个)
MLflow 私有 attribute 键mlflow/tracing/constant.pySpanAttributeKeyTokenUsageKeyTraceMetadataKey
判分结果实体mlflow/entities/assessment.pyAssessmentFeedbackExpectationIssueReference
落库位置抽象mlflow/entities/trace_location.pyTraceLocationMlflowExperimentLocationUCSchemaLocation

5.2 埋点 API(→ 第 01 章)

主题文件路径符号名
装饰器入口mlflow/tracing/fluent.pytrace
同步/异步函数包装mlflow/tracing/fluent.py_wrap_function
生成器包装与 chunk 事件mlflow/tracing/fluent.py_wrap_generator_record_chunk_event
上下文管理器mlflow/tracing/fluent.pystart_span
脱离上下文建 span(集成常用)mlflow/tracing/fluent.pystart_span_no_context
读写当前 tracemlflow/tracing/fluent.pyget_current_active_spanupdate_current_traceget_active_trace_id
检索 tracemlflow/tracing/fluent.pysearch_tracessearch_sessionsget_trace
外部 trace 合并mlflow/tracing/fluent.pyadd_tracelog_trace_merge_trace

5.3 追踪运行时(→ 第 02 章)

主题文件路径符号名
内存态 trace 聚合mlflow/tracing/trace_manager.pyInMemoryTraceManagerregister_traceregister_spanpop_tracehas_open_spans
processor 基类(OTel 钩子)mlflow/tracing/processor/base_mlflow.pyBaseMlflowSpanProcessoron_start_on_end_impl_update_trace_info
全局 flush 屏障mlflow/tracing/processor/base_mlflow.pyflush_all_batch_processors_pending_on_end_count
MLflow 后端 processormlflow/tracing/processor/mlflow_v3.pyMlflowV3SpanProcessor._start_trace
纯 OTLP processormlflow/tracing/processor/otel.pyOtelSpanProcessor
导出器主体mlflow/tracing/export/mlflow_v3.pyMlflowV3SpanExporter_export_spans_incrementally_export_traces_deferred_root_spans
异步导出队列mlflow/tracing/export/async_export_queue.pyAsyncTraceExportQueueTask
span 批处理mlflow/tracing/export/span_batcher.pySpanBatcher
tracer provider 装配mlflow/tracing/provider.py_initialize_tracer_provider_get_span_processors_get_mlflow_span_processor
ID 生成与 fork 安全mlflow/tracing/provider.py_IsolatedRandomIdGenerator_reseed
开关与目的地mlflow/tracing/provider.pydisableenabletrace_disabledset_destination
采样mlflow/tracing/sampling.pymlflow/tracing/provider.py_get_trace_samplerMLFLOW_TRACE_SAMPLING_RATIO
内存缓冲超时/淘汰mlflow/tracing/utils/timeout.pyget_trace_cache_with_timeoutMlflowTraceTimeoutCache
客户端 APImlflow/tracing/client.pyTracingClient
断言写回mlflow/tracing/assessment.pylog_assessmentlog_feedbacklog_expectation

5.4 自动埋点(→ 第 03 章)

主题文件路径符号名
补丁地基与异常隔离mlflow/utils/autologging_utils/safety.pysafe_patchsafe_patch_function
autolog 开关登记mlflow/utils/autologging_utils/__init__.pyautologging_integrationautologging_is_disabled
全局一键开启mlflow/tracking/fluent.pyautologGENAI_LIBRARY_TO_AUTOLOG_MODULELIBRARY_TO_AUTOLOG_MODULE
OpenAI 集成(最完整的范本)mlflow/openai/autolog.pyautologpatched_callasync_patched_call_get_span_type
LangChain 集成(callback 路线)mlflow/langchain/autolog.pyautolog
其余 GenAI 集成mlflow/{anthropic,gemini,bedrock,litellm,dspy,llama_index,crewai,smolagents,strands,haystack,groq,mistral,agno,ag2,autogen,pydantic_ai,semantic_kernel}/各自的 autolog
接收外部 OTel 数据mlflow/otel/__init__.pyautolog
分布式追踪头mlflow/tracing/distributed/_get_tracing_headers_from_span

5.5 评估引擎(→ 第 04 章)

主题文件路径符号名
对外入口mlflow/genai/evaluation/base.pyevaluate_run_harnessto_predict_fn
流水线主循环mlflow/genai/evaluation/harness.pyrun_run_pipeline
两个提交器mlflow/genai/evaluation/harness.py_PredictSubmitter_ScoreSubmitter_Heartbeat
单项打分与写回mlflow/genai/evaluation/harness.py_run_score_compute_eval_scores_log_assessments
trace 克隆判定mlflow/genai/evaluation/harness.py_should_clone_trace
AIMD 限流mlflow/genai/evaluation/rate_limiter.pyRPSRateLimiterreport_throttlereport_success
评估项与结果模型mlflow/genai/evaluation/entities.pyEvalItemEvalResultScorerStat
scorer 契约mlflow/genai/scorers/base.pyScorerScorer.runScorer.__call__ScorerKind
scorer 装饰器mlflow/genai/scorers/base.pyscorer
scorer 序列化(为了注册到服务端)mlflow/genai/scorers/base.pySerializedScorermodel_validate_json_reconstruct_decorator_scorer
内置打分器mlflow/genai/scorers/builtin_scorers.pyBuiltInScorerCorrectnessSafetyRetrievalGroundednessToolCallEfficiencyPIIDetection
会话级打分器mlflow/genai/scorers/builtin_scorers.pySessionLevelScorerUserFrustrationKnowledgeRetention
第三方打分器桥接mlflow/genai/scorers/{ragas,deepeval,phoenix,trulens,google_adk}/各自适配器

5.6 Judge 与对齐(→ 第 05 章)

主题文件路径符号名
judge 抽象mlflow/genai/judges/base.pyJudgeJudgeFieldAlignmentOptimizer
工厂入口mlflow/genai/judges/make_judge.pymake_judge_validate_feedback_value_type
指令 judge 实现mlflow/genai/judges/instructions_judge/__init__.pyInstructionsJudge_build_system_message_create_response_format_modeltemplate_variables
模型调用与适配器选择mlflow/genai/judges/utils/invocation_utils.pyinvoke_judge_model
各家模型适配mlflow/genai/judges/adapters/LiteLLMAdapterGatewayAdapterDatabricksManagedJudgeAdapter
agentic 工具循环mlflow/genai/judges/utils/tool_calling_utils.py_process_tool_calls_remove_oldest_tool_call_pairMLFLOW_JUDGE_MAX_ITERATIONS
judge 工具箱mlflow/genai/judges/tools/JudgeToolJudgeToolRegistrylist_spansget_spansearch_trace_regex
内置 judgemlflow/genai/judges/builtin.py各内置判据
对齐优化器mlflow/genai/judges/optimizers/MemAlignOptimizerSIMBAAlignmentOptimizerGEPAAlignmentOptimizer
prompt 模板mlflow/genai/judges/prompts/各判据 prompt

5.7 存储、查询与监控(→ 第 06 章)

主题文件路径符号名
存储接口mlflow/store/tracking/abstract_store.pystart_tracelog_spanslog_spans_asyncsearch_tracesset_trace_tag
SQL 实现:写 spanmlflow/store/tracking/sqlalchemy_store.pylog_spans_TraceAggregate
SQL 实现:检索mlflow/store/tracking/sqlalchemy_store.pysearch_tracesstart_trace
表结构mlflow/store/tracking/dbmodels/models.pySqlTraceInfoSqlSpanSqlAssessmentsSqlTraceTagSqlTraceMetricsSqlSpanMetrics
REST 后端mlflow/store/tracking/rest_store.pydatabricks_rest_store.pyRestStore
在线打分调度mlflow/genai/scorers/job.pyrun_online_scoring_schedulerrun_online_trace_scorer_jobrun_online_session_scorer_job
抽样策略mlflow/genai/scorers/online/sampler.pyOnlineScorerSampler.samplegroup_scorers_by_filter
增量处理与断点mlflow/genai/scorers/online/OnlineTraceCheckpointManagerOnlineSessionCheckpointManagerOnlineTraceLoader
定时任务运行器mlflow/server/jobs/_job_runner_periodic_tasks_consumer(基于 huey)
scorer 注册与生命周期mlflow/genai/scorers/registry.pybase.pyScorer.registerScorer.startScorer.stop
定时评估配置mlflow/genai/scheduled_scorers.pyScorerScheduleConfig

6. 需要知道的边界

诚实说明几处「读代码能看出来、但文档里容易被忽略」的地方:

  • trace 树的完整性依赖根 span 正常结束。 进程被 kill、根 span 永远不 end,那棵树就只能等 TTL 淘汰(mlflow/tracing/utils/timeout.py:28)。长驻服务里跑超长会话要留意这一点。
  • 采样只作用于根 span。 一旦根 span 被采中,整棵树都会被记录;sampling_ratio_override 对嵌套调用无效(mlflow/tracing/fluent.py:123 的参数文档里明说)。
  • 部分能力与 Databricks 强绑定。 Unity Catalog 落库、推理表导出、托管 judge 模型这几条路径在开源自托管场景下不可用,代码里以 is_databricks_uri / is_in_databricks_model_serving_environment 分叉(mlflow/tracing/provider.py:794mlflow/tracing/export/mlflow_v3.py:117)。
  • 在线监控依赖服务端的 job runner。 它不是客户端行为,需要 MLflow server 起着周期任务消费者(mlflow/server/jobs/_periodic_tasks_consumer.py)。