教程:使用 ChatModel 创建自定义 GenAI 模型
从 MLflow 3.0.0 开始,我们推荐使用 ResponsesAgent 而非 ChatModel。更多详情请参阅 ResponsesAgent 简介。
快速发展的生成式人工智能 (GenAI) 领域带来了令人兴奋的机遇和集成挑战。为了有效利用最新的 GenAI 进步,开发人员需要一个平衡灵活性和标准化的框架。MLflow 通过在 2.11.0 版本中引入的 mlflow.pyfunc.ChatModel 类解决了这一需求,为 GenAI 应用程序提供了一致的接口,同时简化了部署和测试。
在 ChatModel 和 PythonModel 之间进行选择
在 MLflow 中构建 GenAI 应用程序时,选择正确的模型抽象至关重要,该抽象需要在易用性和所需的定制程度之间取得平衡。MLflow 为此目的提供了两个主要的类:mlflow.pyfunc.ChatModel 和 mlflow.pyfunc.PythonModel。每个类都有其优点和权衡,因此了解哪一个最适合您的用例至关重要。
| ChatModel | PythonModel | |
|---|---|---|
| 何时使用 | 当您想开发和部署一个与 OpenAI 规范兼容的**标准**聊天模式的对话模型时使用。 | 当您想要对模型的接口拥有完全控制权或定制模型的每个方面行为时使用。 |
| 接口 | 固定为 OpenAI 的聊天模式。 | 对模型的输入和输出模式具有完全控制权。 |
| 设置 | 快速。开箱即用地适用于对话应用程序,具有预定义的模型签名和输入示例。 | **自定义**。您需要自己定义模型签名或输入示例。 |
| 复杂性 | **低**。标准化接口简化了模型部署和集成。 | 高。部署和集成自定义 PythonModel 可能不直观。例如,模型需要处理 Pandas DataFrame,因为 MLflow 会在将输入数据传递给 PythonModel 之前将其转换为 DataFrame。 |
本教程的目的
本教程将指导您完成使用 MLflow 的 mlflow.pyfunc.ChatModel 类创建自定义聊天代理的过程。
在本教程结束时,您将
- 将 MLflow 跟踪集成到自定义
mlflow.pyfunc.ChatModel实例中。 - 在
mlflow.pyfunc.log_model()中使用model_config参数来自定义您的模型。 - 利用标准化的签名接口以简化部署。
- 了解并避免扩展
mlflow.pyfunc.ChatModel类时遇到的常见陷阱。
先决条件
- 熟悉 MLflow 日志记录 API 和 GenAI 概念。
- 安装了 MLflow 2.11.0 或更高版本,以便使用
mlflow.pyfunc.ChatModel。 - 安装了 MLflow 2.14.0 或更高版本,以便使用 MLflow 跟踪。
本教程仅以与外部服务交互的示例形式使用 Databricks 基础模型 API。您可以轻松地将提供商示例替换为使用任何托管式 LLM 托管服务(例如 Amazon Bedrock、Azure AI Studio、OpenAI、Anthropic 等等)。
核心概念
- 追踪
- 定制化
- 标准化
- 陷阱
GenAI 的跟踪定制化
MLflow 跟踪允许您监控和记录模型方法执行情况,在调试和性能优化期间提供有价值的见解。
在我们的 BasicAgent 示例实现中,我们利用两个单独的 API 来启动跟踪跨度:装饰器 API 和流式 API。
装饰器 API
@mlflow.trace
def _get_system_message(self, role: str) -> Dict:
if role not in self.models:
raise ValueError(f"Unknown role: {role}")
instruction = self.models[role]["instruction"]
return ChatMessage(role="system", content=instruction).to_dict()
使用 @mlflow.trace 跟踪装饰器是向函数和方法添加跟踪功能的最简单方法。默认情况下,从应用此装饰器生成的跨度将使用函数的名称作为跨度的名称。可以按如下方式覆盖此命名以及与跨度相关的其他参数
@mlflow.trace(name="custom_span_name", attributes={"key": "value"}, span_type="func")
def _get_system_message(self, role: str) -> Dict:
if role not in self.models:
raise ValueError(f"Unknown role: {role}")
instruction = self.models[role]["instruction"]
return ChatMessage(role="system", content=instruction).to_dict()
始终建议为生成的任何跨度设置人类可读的名称,特别是当您正在检测私有或通用命名的函数或方法时。MLflow 跟踪 UI 默认会显示函数或方法的名称,如果您的函数和方法命名不明确,这可能会令人困惑。
流式 API
启动跨度的 流式 API 上下文管理器实现,当您需要对跨度数据的每个方面进行完整的日志记录控制时,它非常有用。
下面显示了我们应用程序中的示例,用于确保我们捕获通过 load_context 方法加载模型时设置的参数。我们从实例属性 self.models_config 和 self.models 中提取信息以设置跨度的属性。
with mlflow.start_span("Audit Agent") as root_span:
root_span.set_inputs(messages)
attributes = {**params.to_dict(), **self.models_config, **self.models}
root_span.set_attributes(attributes)
# More span manipulation...
MLflow UI 中的跟踪
运行包含这些组合跟踪跨度生成和检测用法模式的示例后,

GenAI 的模型定制化
为了控制我们 BasicAgent 模型的行为,而无需将配置值直接硬编码到我们的模型逻辑中,在记录模型时在 model_config 参数中指定配置可以为我们的模型定义带来一定的灵活性和多功能性。
此功能允许我们
- 快速测试不同的配置,而无需更改源代码
- 直接在 MLflow UI 中查看记录不同迭代时使用的配置
- 简化模型代码,将配置与实现分离
在我们的示例模型中,我们设置了一组标准配置来控制 BasicAgent 的行为。代码所需的配置结构是一个包含以下组件的字典
models:定义每个代理的配置。(model_name):代表代理的角色。此部分包含endpoint:代理使用的特定模型类型。instruction:提供给模型的提示,描述其角色和职责。temperature:控制响应可变性的温度设置。max_tokens:生成响应的最大令牌限制。
configuration:包含代理应用程序的杂项设置。user_response_instruction:通过模拟基于第一个代理输出的用户响应来为第二个代理提供上下文。
此配置结构定义将
- 在记录模型时定义并构建以支持模型行为需求
- 在
load_context方法中使用并在加载时应用于模型 - 在 MLflow 文件中记录,并将在 MLflow UI 的工件查看器中可见
本教程中为我们的 BasicAgent 示例提交的 model_config 值可以在 UI 中记录的模型的 MLmodel 文件中看到

GenAI 模型的标准化
使用 MLflow 部署 GenAI 应用程序时,更复杂的任务之一出现于尝试构建基于 mlflow.pyfunc.PythonModel 抽象的自定义实现时。
虽然 PythonModel 推荐用于自定义深度学习和传统机器学习模型(例如需要除基础模型外的额外处理逻辑的 sklearn 或 torch 模型),但服务这些模型时会对输入数据进行内部操作,这对 GenAI 应用程序引入了不必要的复杂性。
由于深度学习和传统 ML 模型主要依赖结构化数据,当通过 REST 接口传递输入数据进行模型服务时,PythonModel 实现会将 JSON 数据转换为 pandas.DataFrame 或 numpy 对象。在使用 GenAI 模型时,这种转换会产生令人困惑且难以调试的场景。GenAI 实现通常只处理符合 JSON 的数据结构,没有直观的表格表示,因此需要一个令人沮丧且复杂的转换接口才能使应用程序部署正常工作。
为了简化这个问题,创建了 mlflow.pyfunc.ChatModel 类,以提供一个更简单的接口来处理对自定义 Python 模型(服务 GenAI 用例)的 predict() 方法的调用所传入和传出的数据。
在下面的示例教程代码中,我们对 ChatModel 进行子类化,以利用这种具有不可变输入和输出格式的简化接口。由于这种不可变性,我们不必考虑模型签名,而是可以直接使用在整个 GenAI 行业中被广泛接受的 API 标准。
为了说明为什么倾向于使用 ChatModel 作为 MLflow 中自定义 GenAI 实现的超类,以下是截至 2024 年 9 月,为符合 OpenAI API 规范而在记录模型期间需要定义和提供的签名
输入模式为 dict
[
{
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {"type": "string", "required": True},
"name": {"type": "string", "required": False},
"role": {"type": "string", "required": True},
},
},
"name": "messages",
"required": True,
},
{"type": "double", "name": "temperature", "required": False},
{"type": "long", "name": "max_tokens", "required": False},
{"type": "array", "items": {"type": "string"}, "name": "stop", "required": False},
{"type": "long", "name": "n", "required": False},
{"type": "boolean", "name": "stream", "required": False},
{"type": "double", "name": "top_p", "required": False},
{"type": "long", "name": "top_k", "required": False},
{"type": "double", "name": "frequency_penalty", "required": False},
{"type": "double", "name": "presence_penalty", "required": False},
]
基于代理(工具调用)的模式比上面显示的更简单的聊天界面示例复杂得多。随着 GenAI 框架和服务的演进,其功能和特性变得越来越复杂,这些接口的复杂性也将增加,使得手动定义模式成为一项具有挑战性和耗时的任务。MLflow mlflow.pyfunc.ChatModel 接口提供的结构化输入验证减轻了手动定义和管理这些复杂签名的负担。通过利用这些预定义的模式,您可以获得强大的输入类型安全性和验证,确保您的部署应用程序在无需额外工作的情况下始终如一且正确地处理输入。这种方法不仅降低了出错的风险,还简化了开发过程,让您可以专注于构建有影响力的 GenAI 解决方案,而无需管理复杂输入规范的开销。
通过以上 mlflow.pyfunc.ChatModel 为自定义实现奠定基础,我们不必考虑这个复杂的签名。它已为我们提供。
与 ChatModel 的静态签名交互时,需要注意的两个方面是
- 如果您的自定义实现所连接的服务不遵守
OpenAI规范,您需要从mlflow.types.llm.ChatMessage和mlflow.types.llm.ChatParams的标准结构中提取数据,并确保其符合您的服务所期望的内容。 predict返回的响应应遵守ChatModel输出签名中定义的输出结构:mlflow.types.llm.ChatCompletionResponse。
MLflow 中常见的 GenAI 陷阱
为 GenAI 用例构建自定义实现有许多可能令人沮丧或不直观的方式。以下是我们从用户那里听到的一些最常见的问题
未使用受支持的组件
如果您正在使用 MLflow 原生支持的库,利用内置支持来记录和加载您的实现将始终比实现自定义模型更容易。建议查看支持的 GenAI 组件,以查看是否存在满足您用例需求的内置解决方案,这些解决方案在许多可用集成中都有体现。
误解 load_context 的作用
当对自定义模型的基类模型类型进行子类化时,类定义可能看起来像一个“所见即所得”的标准 Python 类。然而,在加载自定义模型实例时,load_context 方法实际上是由另一个加载器对象调用的。
由于实现的原因,您不能在 load_context 中直接分配未定义的实例属性。
例如,这行不工作
from mlflow.pyfunc import ChatModel
class MyModel(ChatModel):
def __init__(self):
self.state = []
def load_context(self, context):
# This will fail on load as the instance attribute self.my_model_config is not defined
self.my_model_config = context.get("my_model_config")
相反,请确保 load_context 方法设置的任何实例属性都在类构造函数中定义了一个占位符值
from mlflow.pyfunc import ChatModel
class MyModel(ChatModel):
def __init__(self):
self.state = []
self.my_model_config = None # Define the attribute here
def load_context(self, context):
self.my_model_config = context.get("my_model_config")
未能安全处理机密信息
为了简化模型的部署,您可能会倾向于在配置中指定身份验证机密信息。但是,在 model_config 参数中定义的任何配置数据都会直接显示在 MLflow UI 中,并且不会被安全存储。
处理敏感配置数据(如 API 密钥或访问令牌)的推荐方法是使用机密管理器。关于从机密管理系统中获取什么的配置可以存储在 model_config 定义中,并且您的部署环境可以利用安全的方式来访问机密管理服务的密钥引用。
处理机密分配(通常设置为环境变量或作为请求头的一部分传递)的一个有效位置是在 load_context 中处理获取和每个会话的设置。如果您有轮换的令牌,值得将获取机密以及在 predict 的调用堆栈中作为重试机制的一部分重新获取它们的操作嵌入其中。
未能使用 input_example
虽然在 MLflow 中记录模型时提供 input_example 似乎纯粹是为了 MLflow UI 中工件视图显示的装饰目的,但它还有一项额外功能,使提供此数据非常有用,特别是对于 GenAI 用例。
当提供 input_example 时,MLflow 将使用示例数据调用模型的 predict 方法,以验证输入与您正在记录的模型对象兼容。如果发生任何故障,您将收到一条错误消息,说明输入语法存在什么问题。这对于确保在记录时,您期望的输入接口结构与已部署模型允许的结构一致非常有利,从而节省了您稍后尝试部署解决方案时花费数小时进行调试和故障排除的时间。
强烈建议在记录时提供此示例。
未能处理因达到速率限制而导致的重试
几乎所有的 GenAI 提供商服务都会施加速率限制和基于令牌的使用限制,以防止其服务中断或帮助保护用户免受意外计费的影响。当达到限制时,重要的是您的预测逻辑对处理这些失败具有鲁棒性,以确保您的部署应用程序的用户了解其请求为何不成功。
引入重试逻辑来处理某些错误(特别是涉及瞬态连接问题或每时间单位请求限制的错误)可能会有所帮助。
在部署前未能验证
部署 GenAI 应用程序的过程可能会花费大量时间。当实现最终准备好提交到服务环境时,您最不希望遇到的是由于提交到模型 predict() 方法的已解码 JSON 有效负载存在某些问题而无法提供服务(Serving)的模型。
MLflow 提供了 mlflow.models.validate_serving_input() API,以确保您记录的模型能够通过模拟部署模型时发生的数据处理与之交互。
要使用此 API,只需导航到 MLflow UI 的工件查看器中的记录模型。工件查看器右侧的模型显示窗格包含您可以在交互式环境中执行的代码片段,以确保您的模型已准备好部署。
对于本教程中的示例,这是从工件查看器显示中复制的生成代码
from mlflow.models import validate_serving_input
model_uri = "runs:/8935b7aff5a84f559b5fcc2af3e2ea31/model"
# The model is logged with an input example. MLflow converts
# it into the serving payload format for the deployed model endpoint,
# and saves it to 'serving_input_payload.json'
serving_payload = """{
"messages": [
{
"role": "user",
"content": "What is a good recipe for baking scones that doesn't require a lot of skill?"
}
],
"temperature": 1.0,
"n": 1,
"stream": false
}"""
# Validate the serving payload works on the model
validate_serving_input(model_uri, serving_payload)
我们示例中的关键类和方法
BasicAgent:扩展ChatModel的自定义聊天代理类。_get_system_message:检索特定角色的系统消息配置。get_agent_response:将消息发送到终结点并检索响应。_call_agent:管理代理角色之间的对话流程。prepare_message_list:准备要发送的消息列表。load_context:初始化模型上下文和配置。predict:处理聊天模型的预测逻辑。
在上面列出的方法中,load_context 和 predict 方法覆盖了 ChatModel 的基础抽象实现。为了定义 ChatModel 的子类,您必须(至少)实现 predict 方法。仅当您实现(如下所示)自定义加载逻辑时,才会使用 load_context 方法,其中需要加载静态配置以使模型对象正常工作,或者需要执行额外的依赖逻辑以使对象实例化正确运行。
自定义 ChatModel 示例
在下面的完整示例中,我们通过子类化 mlflow.pyfunc.ChatModel 来创建一个自定义聊天代理。这个名为 BasicAgent 的代理利用了几个重要特性,有助于简化 GenAI 应用程序的开发、部署和跟踪。通过子类化 ChatModel,我们确保了处理对话代理的一致接口,同时避免了与更通用模型相关的常见陷阱。
以下实现强调了以下关键方面
- 跟踪:我们利用 MLflow 的跟踪功能来跟踪和记录关键操作,同时使用装饰器 API 和流式 API 上下文处理方法。
- 装饰器 API:用于轻松跟踪 _get_agent_response 和 _call_agent 等方法,以自动创建跨度。
- 流式 API:在 predict 方法中,提供对跨度创建的细粒度控制,用于审核代理交互期间的关键输入和输出。
- 提示:我们确保了人类可读的跨度名称,以便在 MLflow 跟踪 UI 中和通过客户端 API 获取记录的跟踪时更容易调试。
- 对话管理:
- 模型配置:通过在模型记录期间传递自定义配置(使用 model_config 参数),我们将模型行为与硬编码值分离开来。这允许快速测试不同的代理配置,而无需修改源代码。
- load_context 方法:确保在运行时加载配置,用必要的设置初始化代理,并防止因缺少配置而导致的运行时失败。
- 提示:我们避免在 load_context 中直接设置未定义的实例属性。相反,所有属性都在类构造函数中用默认值初始化,以确保正确加载我们的模型。
- 对话管理:
- 我们使用 _get_system_message、_get_agent_response 和 _call_agent 等方法实现多步代理交互模式。这些方法管理多个代理之间的通信流程,例如“神谕”和“裁判”角色,每个角色都配置有特定的指令和参数。
- 静态输入/输出结构:通过遵循
ChatModel所需的输入(List[ChatMessage])和输出(ChatCompletionResponse)格式,我们消除了与转换 JSON 或表格数据相关的复杂性,这在像PythonModel这样的通用模型中很常见。
- 避免的常见陷阱:
- 通过输入示例进行模型验证:我们在模型记录期间提供一个输入示例,允许 MLflow 在记录过程中使用此示例验证输入接口,并尽早捕获任何结构问题。
- 代码中的模型:
- MLflow 建议在编写 GenAI 代理或应用程序时使用代码中的模型,以实现稳健的日志记录和包含任意 Python 代码的代理的轻松部署。
import mlflow
from mlflow.types.llm import ChatCompletionResponse, ChatMessage, ChatParams, ChatChoice
from mlflow.pyfunc import ChatModel
from mlflow import deployments
from typing import List, Optional, Dict
class BasicAgent(ChatModel):
def __init__(self):
"""Initialize the BasicAgent with placeholder values."""
self.deploy_client = None
self.models = {}
self.models_config = {}
self.conversation_history = []
def load_context(self, context):
"""Initialize the connectors and model configurations."""
self.deploy_client = deployments.get_deploy_client("databricks")
self.models = context.model_config.get("models", {})
self.models_config = context.model_config.get("configuration", {})
def _get_system_message(self, role: str) -> Dict:
"""
Get the system message configuration for the specified role.
Args:
role (str): The role of the agent (e.g., "oracle" or "judge").
Returns:
dict: The system message for the given role.
"""
if role not in self.models:
raise ValueError(f"Unknown role: {role}")
instruction = self.models[role]["instruction"]
return ChatMessage(role="system", content=instruction).to_dict()
@mlflow.trace(name="Raw Agent Response")
def _get_agent_response(
self, message_list: List[Dict], endpoint: str, params: Optional[dict] = None
) -> Dict:
"""
Call the agent endpoint to get a response.
Args:
message_list (List[Dict]): List of messages for the agent.
endpoint (str): The agent's endpoint.
params (Optional[dict]): Additional parameters for the call.
Returns:
dict: The response from the agent.
"""
response = self.deploy_client.predict(
endpoint=endpoint, inputs={"messages": message_list, **(params or {})}
)
return response["choices"][0]["message"]
@mlflow.trace(name="Agent Call")
def _call_agent(
self, message: ChatMessage, role: str, params: Optional[dict] = None
) -> Dict:
"""
Prepares and sends the request to a specific agent based on the role.
Args:
message (ChatMessage): The message to be processed.
role (str): The role of the agent (e.g., "oracle" or "judge").
params (Optional[dict]): Additional parameters for the call.
Returns:
dict: The response from the agent.
"""
system_message = self._get_system_message(role)
message_list = self._prepare_message_list(system_message, message)
# Fetch agent response
agent_config = self.models[role]
response = self._get_agent_response(
message_list, agent_config["endpoint"], params
)
# Update conversation history
self.conversation_history.extend([message.to_dict(), response])
return response
@mlflow.trace(name="Assemble Conversation")
def _prepare_message_list(
self, system_message: Dict, user_message: ChatMessage
) -> List[Dict]:
"""
Prepare the list of messages to send to the agent.
Args:
system_message (dict): The system message dictionary.
user_message (ChatMessage): The user message.
Returns:
List[dict]: The complete list of messages to send.
"""
user_prompt = {
"role": "user",
"content": self.models_config.get(
"user_response_instruction", "Can you make the answer better?"
),
}
if self.conversation_history:
return [system_message, *self.conversation_history, user_prompt]
else:
return [system_message, user_message.to_dict()]
def predict(
self, context, messages: List[ChatMessage], params: Optional[ChatParams] = None
) -> ChatCompletionResponse:
"""
Predict method to handle agent conversation.
Args:
context: The MLflow context.
messages (List[ChatMessage]): List of messages to process.
params (Optional[ChatParams]): Additional parameters for the conversation.
Returns:
ChatCompletionResponse: The structured response object.
"""
# Use the fluent API context handler to have added control over what is included in the span
with mlflow.start_span(name="Audit Agent") as root_span:
# Add the user input to the root span
root_span.set_inputs(messages)
# Add attributes to the root span
attributes = {**params.to_dict(), **self.models_config, **self.models}
root_span.set_attributes(attributes)
# Initiate the conversation with the oracle
oracle_params = self._get_model_params("oracle")
oracle_response = self._call_agent(messages[0], "oracle", oracle_params)
# Process the response with the judge
judge_params = self._get_model_params("judge")
judge_response = self._call_agent(
ChatMessage(**oracle_response), "judge", judge_params
)
# Reset the conversation history and return the final response
self.conversation_history = []
output = ChatCompletionResponse(
choices=[ChatChoice(index=0, message=ChatMessage(**judge_response))],
usage={},
model=judge_params.get("endpoint", "unknown"),
)
root_span.set_outputs(output)
return output
def _get_model_params(self, role: str) -> dict:
"""
Retrieves model parameters for a given role.
Args:
role (str): The role of the agent (e.g., "oracle" or "judge").
Returns:
dict: A dictionary of parameters for the agent.
"""
role_config = self.models.get(role, {})
return {
"temperature": role_config.get("temperature", 0.5),
"max_tokens": role_config.get("max_tokens", 500),
}
# IMPORTANT: specifies the Python ChatModel instance to use for inference requests when
# the model is loaded back
agent = BasicAgent()
mlflow.models.set_model(agent)
上面的代码片段将我们的代理定义为 ChatModel 的子类。对于代码中的模型方法,我们调用
mlflow.models.set_model,传入 BasicAgent 的一个实例,以指示在重新加载代理时使用哪个模型对象进行推理。
将代理代码保存在一个 Python 文件中,例如 basic_agent.py。这是代码中的模型的关键部分——它允许记录包含模型代码的文件,而不是序列化的模型对象,从而避免序列化问题。
模型定义在代码文件中后,只剩下最后一步就可以记录它了:我们需要定义初始化模型的配置。这是通过定义我们的 model_config 配置来完成的。
设置我们的 model_config 值
在记录模型之前,我们需要定义控制我们模型代理行为的配置。这种配置与模型核心逻辑的分离允许我们轻松测试和比较不同的代理行为,而无需修改模型实现。通过使用灵活的配置系统,我们可以高效地试验不同的设置,从而更容易迭代和微调我们的模型。
为什么要分离配置?
在生成式人工智能 (GenAI) 的背景下,代理行为会根据提供给每个代理的指令集和参数(如 temperature 或 max_tokens)而有很大不同。如果我们直接将这些配置硬编码到模型逻辑中,每次新测试都需要更改模型的源代码,这将导致
- 效率低下:每次测试更改源代码都会减慢实验过程。
- 错误风险增加:不断修改源代码会增加引入错误或意外副作用的可能性。
- 缺乏可重现性:如果没有代码和配置之间的清晰分离,跟踪和重现用于特定结果的确切配置将变得很困难。
通过在外部通过 model_config 参数设置这些值,我们使模型能够灵活地适应不同的测试场景。这种方法还与 MLflow 的评估工具(如 mlflow.genai.evaluate())无缝集成,该工具允许您系统地比较不同配置下的模型输出。
定义模型配置
配置主要包括两个部分
-
模型:此部分定义特定于代理的配置,例如此示例中的
judge和oracle角色。每个代理都有- 一个终结点 (endpoint):指定用于此代理的模型类型或服务。
- 一条指令 (instruction):定义代理的角色和职责(例如,回答问题、评估响应)。
- 温度和最大令牌 (Temperature and Max Tokens):控制生成变化性(
temperature)和响应的令牌限制。
-
一般配置 (General Configuration):关于模型整体行为的其他设置,例如如何为后续代理构建用户响应。
设置模型配置有两种选择:直接在记录代码中设置(如下所示),或者将 yaml 格式的配置文件写入本地位置,其路径可以在记录时定义 model_config 参数时指定。要了解有关 model_config 参数如何使用的更多信息,请参阅有关 model_config 用法的指南。
以下是我们如何为代理设置配置
model_config = {
"models": {
"judge": {
"endpoint": "databricks-meta-llama-3-1-405b-instruct",
"instruction": (
"You are an evaluator of answers provided by others. Based on the context of both the question and the answer, "
"provide a corrected answer if it is incorrect; otherwise, enhance the answer with additional context and explanation."
),
"temperature": 0.5,
"max_tokens": 2000,
},
"oracle": {
"endpoint": "databricks-mixtral-8x7b-instruct",
"instruction": (
"You are a knowledgeable source of information that excels at providing detailed, but brief answers to questions. "
"Provide an answer to the question based on the information provided."
),
"temperature": 0.9,
"max_tokens": 5000,
},
},
"configuration": {
"user_response_instruction": "Can you evaluate and enhance this answer with the provided contextual history?"
},
}
外部配置的好处
- 灵活性:分离的配置允许我们轻松切换或调整模型行为,而无需修改核心逻辑。例如,我们可以更改模型的指令或调整
temperature以测试不同程度的创造力。 - 可扩展性:随着向系统中添加更多代理或引入新角色,我们可以扩展此配置,而不会使模型代码混乱。这种分离使代码库更整洁、更易于维护。
- 可重现性和比较:通过将配置保持在外部,我们可以将每次运行使用的特定设置记录在 MLflow 中。这使得重现结果和比较不同实验变得更容易,确保了强大的评估和裁定过程来选择性能最佳的配置。
配置就位后,我们就可以记录模型并使用这些设置运行实验了。通过利用 MLflow 强大的跟踪和记录功能,我们将能够高效地管理实验并从代理的响应中提取有价值的见解。
定义输入示例
在记录模型之前,提供一个演示如何与模型交互的 input_example 非常重要。此示例有几个关键用途
- 记录时的验证:在记录过程中包含
input_example允许 MLflow 使用此示例执行predict方法。这有助于验证模型可以处理预期的输入格式,并尽早发现任何问题。 - UI 表示:
input_example将显示在 MLflow UI 中模型的工件下。这为用户了解与部署模型交互时预期的输入结构提供了方便的参考。
通过提供输入示例,您可以确保模型使用真实数据进行测试,从而增加它在部署时按预期行为的信心。
当使用 mlflow.pyfunc.ChatModel 定义 GenAI 应用程序时,如果未提供,将使用默认的占位符输入示例。如果您在 MLflow UI 的工件查看器中注意到不熟悉或通用的输入示例,那很可能是系统分配的默认占位符。为避免这种情况,请确保在保存模型时指定自定义输入示例。
这是我们将使用的输入示例
input_example = {
"messages": [
{
"role": "user",
"content": "What is a good recipe for baking scones that doesn't require a lot of skill?",
}
]
}
此示例代表用户请求一个简单的烤饼食谱。它与我们的 BasicAgent 模型期望的输入结构一致,该模型处理包含 role 和 content 的消息列表。
提供输入示例的好处
- 执行和验证:MLflow 将在记录期间把这个
input_example传递给模型的predict方法,以确保它可以无错误地处理输入。任何关于输入处理的问题,例如不正确的数据类型或缺失的字段,都将在此时被捕获,从而节省您稍后调试时间。 - 用户界面显示:
input_example将显示在 MLflow UI 中模型工件视图部分内。这有助于用户了解模型期望的输入数据格式,使部署模型后更容易与之交互。 - 部署信心:通过预先用示例输入验证模型,您可以获得额外的保证,即模型在生产环境中能够正常工作,从而降低部署后出现意外行为的风险。
包含 input_example 是一个简单而强大的步骤,用于验证您的模型已准备好部署,并在接收用户输入时按预期运行。
记录和加载我们的自定义代理
要使用 MLflow 记录和加载模型,请使用
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
name="model",
# If needed, update `python_model` to the Python file containing your agent code
python_model="basic_agent.py",
model_config=model_config,
input_example=input_example,
)
loaded = mlflow.pyfunc.load_model(model_info.model_uri)
response = loaded.predict(
{
"messages": [
{
"role": "user",
"content": "What is the best material to make a baseball bat out of?",
}
]
}
)
结论
在本教程中,您探索了使用 MLflow 的 mlflow.pyfunc.ChatModel 类创建自定义 GenAI 聊天代理的过程。我们演示了如何实施一种灵活、可扩展和标准化的方法来管理 GenAI 应用程序的部署,使您能够利用最新的 AI 进步,即使对于尚未通过命名组件在 MLflow 中原生支持的库和框架也是如此。
通过使用 ChatModel 而不是更通用的 PythonModel,您可以避免部署 GenAI 时遇到的许多常见陷阱,方法是利用不可变的签名接口的优势,这些接口在您部署的任何 GenAI 接口中都是一致的,从而通过提供一致的体验简化了所有解决方案的使用。
本教程的关键要点包括
- 跟踪和监控:通过将跟踪直接集成到模型中,您可以获得对应用程序内部工作原理的有价值的见解,使调试和优化更加直接。装饰器 API 和流式 API 方法都提供了管理关键操作跟踪的通用方法。
- 灵活的配置管理:将配置与模型代码分离,确保您可以快速测试和迭代,而无需修改源代码。这种方法不仅简化了实验,还提高了应用程序发展的可重现性和可扩展性。
- 标准化的输入和输出结构:利用
ChatModel的静态签名简化了部署和提供 GenAI 模型的复杂性。通过遵循既定标准,您减少了集成和验证输入/输出格式时通常出现的摩擦。 - 避免常见陷阱:在整个实现过程中,我们强调了为避免常见问题而应遵循的最佳实践,例如正确处理密钥、验证输入示例以及理解加载上下文的细微差别。遵循这些实践可确保您的模型在生产环境中保持安全、健壮和可靠。
- 验证和部署准备:在部署之前验证模型的重要性怎么强调都不为过。通过使用
mlflow.models.validate_serving_input()等工具,您可以尽早发现并解决潜在的部署问题,从而在生产部署过程中节省时间和精力。
随着生成式 AI 领域不断发展,构建适应性强和标准化的模型对于利用未来几个月和几年内将解锁的令人兴奋和强大的功能至关重要。本教程中介绍的方法为您提供了一个强大的框架,用于在 MLflow 中集成和管理 GenAI 技术,使您能够轻松开发、跟踪和部署复杂的 AI 解决方案。
我们鼓励您扩展和定制此基础示例以满足您的特定需求,并探索进一步的增强。通过利用 MLflow 不断增长的功能,您可以继续完善您的 GenAI 模型,确保它们在任何应用中都能提供有影响力和可靠的结果。