635 lines
21 KiB
Python
635 lines
21 KiB
Python
"""Utility functions for mlflow.langchain."""
|
|
|
|
import functools
|
|
import importlib
|
|
import json
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import types
|
|
from functools import lru_cache
|
|
from importlib.util import find_spec
|
|
from typing import Any, Callable, NamedTuple
|
|
|
|
import cloudpickle
|
|
import yaml
|
|
from packaging.version import Version
|
|
|
|
import mlflow
|
|
from mlflow.models.utils import _validate_and_get_model_code_path
|
|
from mlflow.utils.class_utils import _get_class_from_string
|
|
|
|
_AGENT_PRIMITIVES_FILE_NAME = "agent_primitive_args.json"
|
|
_AGENT_PRIMITIVES_DATA_KEY = "agent_primitive_data"
|
|
_AGENT_DATA_FILE_NAME = "agent.yaml"
|
|
_AGENT_DATA_KEY = "agent_data"
|
|
_TOOLS_DATA_FILE_NAME = "tools.pkl"
|
|
_TOOLS_DATA_KEY = "tools_data"
|
|
_LOADER_FN_FILE_NAME = "loader_fn.pkl"
|
|
_LOADER_FN_KEY = "loader_fn"
|
|
_LOADER_ARG_KEY = "loader_arg"
|
|
_PERSIST_DIR_NAME = "persist_dir_data"
|
|
_PERSIST_DIR_KEY = "persist_dir"
|
|
_MODEL_DATA_YAML_FILE_NAME = "model.yaml"
|
|
_MODEL_DATA_PKL_FILE_NAME = "model.pkl"
|
|
_MODEL_DATA_FOLDER_NAME = "model"
|
|
_MODEL_DATA_KEY = "model_data"
|
|
_MODEL_TYPE_KEY = "model_type"
|
|
_RUNNABLE_LOAD_KEY = "runnable_load"
|
|
_BASE_LOAD_KEY = "base_load"
|
|
_CONFIG_LOAD_KEY = "config_load"
|
|
_PICKLE_LOAD_KEY = "pickle_load"
|
|
_MODEL_LOAD_KEY = "model_load"
|
|
_UNSUPPORTED_MODEL_WARNING_MESSAGE = (
|
|
"MLflow does not guarantee support for Chains outside of the subclasses of LLMChain, found %s"
|
|
)
|
|
_UNSUPPORTED_LLM_WARNING_MESSAGE = (
|
|
"MLflow does not guarantee support for LLMs outside of HuggingFacePipeline and OpenAI, found %s"
|
|
)
|
|
|
|
|
|
try:
|
|
import langchain_community
|
|
|
|
# Since langchain-community 0.0.27, saving or loading a module that relies on the pickle
|
|
# deserialization requires passing `allow_dangerous_deserialization=True`.
|
|
IS_PICKLE_SERIALIZATION_RESTRICTED = Version(langchain_community.__version__) >= Version(
|
|
"0.0.27"
|
|
)
|
|
except ImportError:
|
|
IS_PICKLE_SERIALIZATION_RESTRICTED = False
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@lru_cache
|
|
def base_lc_types():
|
|
"""
|
|
Get base LangChain types (Chain, AgentExecutor, BaseRetriever).
|
|
|
|
Note: AgentExecutor was removed in langchain 1.0.0. Use LangGraph instead.
|
|
"""
|
|
from mlflow.langchain._compat import (
|
|
import_base_retriever,
|
|
try_import_agent_executor,
|
|
try_import_chain,
|
|
)
|
|
|
|
types = []
|
|
|
|
if chain_cls := try_import_chain():
|
|
types.append(chain_cls)
|
|
|
|
if agent_executor_cls := try_import_agent_executor():
|
|
types.append(agent_executor_cls)
|
|
|
|
types.append(import_base_retriever())
|
|
|
|
return tuple(types)
|
|
|
|
|
|
@lru_cache
|
|
def picklable_runnable_types():
|
|
"""
|
|
Runnable types that can be pickled and unpickled by cloudpickle.
|
|
"""
|
|
from mlflow.langchain._compat import (
|
|
import_chat_prompt_template,
|
|
import_runnable_lambda,
|
|
import_runnable_passthrough,
|
|
try_import_simple_chat_model,
|
|
)
|
|
|
|
types = [
|
|
import_chat_prompt_template(),
|
|
import_runnable_passthrough(),
|
|
import_runnable_lambda(),
|
|
]
|
|
|
|
if simple_chat_model := try_import_simple_chat_model():
|
|
types.insert(0, simple_chat_model)
|
|
|
|
return tuple(types)
|
|
|
|
|
|
@lru_cache
|
|
def lc_runnable_with_steps_types():
|
|
from mlflow.langchain._compat import import_runnable_parallel, import_runnable_sequence
|
|
|
|
return (import_runnable_parallel(), import_runnable_sequence())
|
|
|
|
|
|
def lc_runnable_assign_types():
|
|
from mlflow.langchain._compat import import_runnable_assign
|
|
|
|
return (import_runnable_assign(),)
|
|
|
|
|
|
def lc_runnable_branch_types():
|
|
from mlflow.langchain._compat import import_runnable_branch
|
|
|
|
return (import_runnable_branch(),)
|
|
|
|
|
|
def lc_runnable_binding_types():
|
|
from mlflow.langchain._compat import import_runnable_binding
|
|
|
|
return (import_runnable_binding(),)
|
|
|
|
|
|
def lc_runnables_types():
|
|
return (
|
|
picklable_runnable_types()
|
|
+ lc_runnable_with_steps_types()
|
|
+ lc_runnable_branch_types()
|
|
+ lc_runnable_assign_types()
|
|
+ lc_runnable_binding_types()
|
|
)
|
|
|
|
|
|
def langgraph_types():
|
|
try:
|
|
from langgraph.graph.state import CompiledStateGraph
|
|
|
|
return (CompiledStateGraph,)
|
|
except ImportError:
|
|
return ()
|
|
|
|
|
|
def supported_lc_types():
|
|
return base_lc_types() + lc_runnables_types() + langgraph_types()
|
|
|
|
|
|
# Wrapping as a function to avoid calling supported_lc_types() at import time
|
|
def get_unsupported_model_message(model_type):
|
|
return (
|
|
"MLflow langchain flavor only supports subclasses of "
|
|
f"{supported_lc_types()}, found {model_type}."
|
|
)
|
|
|
|
|
|
@lru_cache
|
|
def custom_type_to_loader_dict():
|
|
# helper function to load output_parsers from config
|
|
def _load_output_parser(config: dict[str, Any]) -> Any:
|
|
"""Load output parser."""
|
|
from mlflow.langchain._compat import import_str_output_parser
|
|
|
|
output_parser_type = config.pop("_type", None)
|
|
if output_parser_type == "default":
|
|
return import_str_output_parser()(**config)
|
|
else:
|
|
raise ValueError(f"Unsupported output parser {output_parser_type}")
|
|
|
|
return {"default": _load_output_parser}
|
|
|
|
|
|
class _SpecialChainInfo(NamedTuple):
|
|
loader_arg: str
|
|
|
|
|
|
def _get_special_chain_info_or_none(chain):
|
|
for (
|
|
special_chain_class,
|
|
loader_arg,
|
|
) in _get_map_of_special_chain_class_to_loader_arg().items():
|
|
if isinstance(chain, special_chain_class):
|
|
return _SpecialChainInfo(loader_arg=loader_arg)
|
|
|
|
|
|
@lru_cache
|
|
def _get_map_of_special_chain_class_to_loader_arg():
|
|
class_name_to_loader_arg = {
|
|
"langchain.chains.RetrievalQA": "retriever",
|
|
"langchain.chains.APIChain": "requests_wrapper",
|
|
"langchain.chains.HypotheticalDocumentEmbedder": "embeddings",
|
|
}
|
|
# SQLDatabaseChain is in langchain_experimental (since version 0.0.247+)
|
|
if find_spec("langchain_experimental"):
|
|
# Add this entry only if langchain_experimental is installed
|
|
class_name_to_loader_arg["langchain_experimental.sql.SQLDatabaseChain"] = "database"
|
|
|
|
class_to_loader_arg = {}
|
|
try:
|
|
from mlflow.langchain.retriever_chain import _RetrieverChain
|
|
|
|
class_to_loader_arg[_RetrieverChain] = "retriever"
|
|
except ImportError:
|
|
pass
|
|
|
|
for class_name, loader_arg in class_name_to_loader_arg.items():
|
|
try:
|
|
cls = _get_class_from_string(class_name)
|
|
class_to_loader_arg[cls] = loader_arg
|
|
except Exception:
|
|
logger.warning(
|
|
"Unexpected import failure for class '%s'. Please file an issue at"
|
|
" https://github.com/mlflow/mlflow/issues/.",
|
|
class_name,
|
|
exc_info=True,
|
|
)
|
|
|
|
return class_to_loader_arg
|
|
|
|
|
|
@lru_cache
|
|
def _get_supported_llms():
|
|
supported_llms = set()
|
|
|
|
def try_adding_llm(module, class_name):
|
|
if cls := getattr(module, class_name, None):
|
|
supported_llms.add(cls)
|
|
|
|
def safe_import_and_add(module_name, class_name):
|
|
"""Add conditional support for `partner` and `community` APIs in langchain"""
|
|
try:
|
|
module = importlib.import_module(module_name)
|
|
try_adding_llm(module, class_name)
|
|
except ImportError:
|
|
pass
|
|
|
|
safe_import_and_add("langchain.llms.openai", "OpenAI")
|
|
# HuggingFacePipeline is moved to langchain_huggingface since langchain 0.2.0
|
|
safe_import_and_add("langchain.llms", "HuggingFacePipeline")
|
|
safe_import_and_add("langchain.langchain_huggingface", "HuggingFacePipeline")
|
|
safe_import_and_add("langchain_openai", "OpenAI")
|
|
safe_import_and_add("langchain_databricks", "ChatDatabricks")
|
|
safe_import_and_add("databricks_langchain", "ChatDatabricks")
|
|
|
|
for llm_name in ["Databricks", "Mlflow"]:
|
|
safe_import_and_add("langchain.llms", llm_name)
|
|
|
|
for chat_model_name in [
|
|
"ChatDatabricks",
|
|
"ChatMlflow",
|
|
"ChatOpenAI",
|
|
"AzureChatOpenAI",
|
|
]:
|
|
safe_import_and_add("langchain.chat_models", chat_model_name)
|
|
|
|
return supported_llms
|
|
|
|
|
|
def _agent_executor_contains_unsupported_llm(lc_model, _SUPPORTED_LLMS):
|
|
from mlflow.langchain._compat import try_import_agent_executor
|
|
|
|
agent_executor_cls = try_import_agent_executor()
|
|
if agent_executor_cls is None:
|
|
return False
|
|
|
|
return (
|
|
isinstance(lc_model, agent_executor_cls)
|
|
# 'RunnableMultiActionAgent' object has no attribute 'llm_chain'
|
|
and hasattr(lc_model.agent, "llm_chain")
|
|
and not any(
|
|
isinstance(lc_model.agent.llm_chain.llm, supported_llm)
|
|
for supported_llm in _SUPPORTED_LLMS
|
|
)
|
|
)
|
|
|
|
|
|
# temp_dir is only required when lc_model could be a file path
|
|
def _validate_and_prepare_lc_model_or_path(lc_model, loader_fn, temp_dir=None):
|
|
if isinstance(lc_model, str):
|
|
return _validate_and_get_model_code_path(lc_model, temp_dir)
|
|
|
|
if not isinstance(lc_model, supported_lc_types()):
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
get_unsupported_model_message(type(lc_model).__name__)
|
|
)
|
|
|
|
_SUPPORTED_LLMS = _get_supported_llms()
|
|
|
|
from mlflow.langchain._compat import try_import_llm_chain
|
|
|
|
llm_chain_cls = try_import_llm_chain()
|
|
if (
|
|
llm_chain_cls
|
|
and isinstance(lc_model, llm_chain_cls)
|
|
and not any(isinstance(lc_model.llm, supported_llm) for supported_llm in _SUPPORTED_LLMS)
|
|
):
|
|
logger.warning(
|
|
_UNSUPPORTED_LLM_WARNING_MESSAGE,
|
|
type(lc_model.llm).__name__,
|
|
)
|
|
|
|
if _agent_executor_contains_unsupported_llm(lc_model, _SUPPORTED_LLMS):
|
|
logger.warning(
|
|
_UNSUPPORTED_LLM_WARNING_MESSAGE,
|
|
type(lc_model.agent.llm_chain.llm).__name__,
|
|
)
|
|
|
|
if special_chain_info := _get_special_chain_info_or_none(lc_model):
|
|
if loader_fn is None:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
f"For {type(lc_model).__name__} models, a `loader_fn` must be provided."
|
|
)
|
|
if not isinstance(loader_fn, types.FunctionType):
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
"The `loader_fn` must be a function that returns a {loader_arg}.".format(
|
|
loader_arg=special_chain_info.loader_arg
|
|
)
|
|
)
|
|
|
|
# If lc_model is a retriever, wrap it in a _RetrieverChain
|
|
from mlflow.langchain._compat import import_base_retriever
|
|
|
|
BaseRetriever = import_base_retriever()
|
|
if isinstance(lc_model, BaseRetriever):
|
|
try:
|
|
from mlflow.langchain.retriever_chain import _RetrieverChain
|
|
except ImportError:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
"_RetrieverChain is not available. It requires langchain<1.0.0. "
|
|
"For langchain>=1.0.0, please use LangGraph instead."
|
|
)
|
|
|
|
if loader_fn is None:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
f"For {type(lc_model).__name__} models, a `loader_fn` must be provided."
|
|
)
|
|
if not isinstance(loader_fn, types.FunctionType):
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
"The `loader_fn` must be a function that returns a retriever."
|
|
)
|
|
lc_model = _RetrieverChain(retriever=lc_model)
|
|
|
|
return lc_model
|
|
|
|
|
|
def _save_base_lcs(model, path, loader_fn=None, persist_dir=None):
|
|
from mlflow.langchain._compat import (
|
|
try_import_agent_executor,
|
|
try_import_base_chat_model,
|
|
try_import_chain,
|
|
try_import_llm_chain,
|
|
)
|
|
|
|
AgentExecutor = try_import_agent_executor()
|
|
Chain = try_import_chain()
|
|
LLMChain = try_import_llm_chain()
|
|
BaseChatModel = try_import_base_chat_model()
|
|
|
|
model_data_path = os.path.join(path, _MODEL_DATA_YAML_FILE_NAME)
|
|
model_data_kwargs = {
|
|
_MODEL_DATA_KEY: _MODEL_DATA_YAML_FILE_NAME,
|
|
_MODEL_LOAD_KEY: _BASE_LOAD_KEY,
|
|
}
|
|
|
|
is_llm_chain = LLMChain and isinstance(model, LLMChain)
|
|
is_base_chat_model = BaseChatModel and isinstance(model, BaseChatModel)
|
|
|
|
if is_llm_chain or is_base_chat_model:
|
|
model.save(model_data_path)
|
|
elif AgentExecutor and isinstance(model, AgentExecutor):
|
|
if model.agent and getattr(model.agent, "llm_chain", None):
|
|
model.agent.llm_chain.save(model_data_path)
|
|
|
|
if model.agent:
|
|
agent_data_path = os.path.join(path, _AGENT_DATA_FILE_NAME)
|
|
model.save_agent(agent_data_path)
|
|
model_data_kwargs[_AGENT_DATA_KEY] = _AGENT_DATA_FILE_NAME
|
|
|
|
if model.tools:
|
|
tools_data_path = os.path.join(path, _TOOLS_DATA_FILE_NAME)
|
|
try:
|
|
with open(tools_data_path, "wb") as f:
|
|
cloudpickle.dump(model.tools, f)
|
|
except Exception as e:
|
|
raise mlflow.MlflowException(
|
|
"Error when attempting to pickle the AgentExecutor tools. "
|
|
"This model likely does not support serialization."
|
|
) from e
|
|
model_data_kwargs[_TOOLS_DATA_KEY] = _TOOLS_DATA_FILE_NAME
|
|
else:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
"For initializing the AgentExecutor, tools must be provided."
|
|
)
|
|
|
|
key_to_ignore = ["llm_chain", "agent", "tools", "callback_manager"]
|
|
temp_dict = {k: v for k, v in model.__dict__.items() if k not in key_to_ignore}
|
|
|
|
agent_primitive_path = os.path.join(path, _AGENT_PRIMITIVES_FILE_NAME)
|
|
with open(agent_primitive_path, "w") as config_file:
|
|
json.dump(temp_dict, config_file, indent=4)
|
|
|
|
model_data_kwargs[_AGENT_PRIMITIVES_DATA_KEY] = _AGENT_PRIMITIVES_FILE_NAME
|
|
|
|
elif special_chain_info := _get_special_chain_info_or_none(model):
|
|
# Save loader_fn by pickling
|
|
loader_fn_path = os.path.join(path, _LOADER_FN_FILE_NAME)
|
|
with open(loader_fn_path, "wb") as f:
|
|
cloudpickle.dump(loader_fn, f)
|
|
model_data_kwargs[_LOADER_FN_KEY] = _LOADER_FN_FILE_NAME
|
|
model_data_kwargs[_LOADER_ARG_KEY] = special_chain_info.loader_arg
|
|
|
|
if persist_dir is not None:
|
|
if os.path.exists(persist_dir):
|
|
# Save persist_dir by copying into subdir _PERSIST_DIR_NAME
|
|
persist_dir_data_path = os.path.join(path, _PERSIST_DIR_NAME)
|
|
shutil.copytree(persist_dir, persist_dir_data_path)
|
|
model_data_kwargs[_PERSIST_DIR_KEY] = _PERSIST_DIR_NAME
|
|
else:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
"The directory provided for persist_dir does not exist."
|
|
)
|
|
|
|
# Save model
|
|
model.save(model_data_path)
|
|
elif Chain and isinstance(model, Chain):
|
|
logger.warning(get_unsupported_model_message(type(model).__name__))
|
|
model.save(model_data_path)
|
|
else:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
get_unsupported_model_message(type(model).__name__)
|
|
)
|
|
|
|
return model_data_kwargs
|
|
|
|
|
|
def _load_from_pickle(path):
|
|
with open(path, "rb") as f:
|
|
return cloudpickle.load(f)
|
|
|
|
|
|
def _load_from_json(path):
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
def _load_from_yaml(path):
|
|
with open(path) as f:
|
|
return yaml.safe_load(f)
|
|
|
|
|
|
def _get_path_by_key(root_path, key, conf):
|
|
key_path = conf.get(key)
|
|
return os.path.join(root_path, key_path) if key_path else None
|
|
|
|
|
|
def _patch_loader(loader_func: Callable[..., Any]) -> Callable[..., Any]:
|
|
"""
|
|
Patch LangChain loader function like load_chain() to handle pickle deserialization.
|
|
|
|
Since langchain-community 0.0.27, loading a module that relies on the pickle deserialization
|
|
requires the `allow_dangerous_deserialization` flag to be set to True, for security reasons.
|
|
|
|
Args:
|
|
loader_func: The LangChain loader function to be patched e.g. load_chain().
|
|
|
|
Returns:
|
|
The patched loader function.
|
|
"""
|
|
if not IS_PICKLE_SERIALIZATION_RESTRICTED:
|
|
return loader_func
|
|
|
|
# For LangChain >= 0.3.0, we can pass `allow_dangerous_deserialization` flag
|
|
# via the loader APIs. Since the model is serialized by the user (or someone who has
|
|
# access to the tracking server), it is safe to set this flag to True.
|
|
def patched_loader(*args, **kwargs):
|
|
return loader_func(*args, **kwargs, allow_dangerous_deserialization=True)
|
|
|
|
return patched_loader
|
|
|
|
|
|
def _load_base_lcs(
|
|
local_model_path,
|
|
conf,
|
|
):
|
|
lc_model_path = os.path.join(
|
|
local_model_path, conf.get(_MODEL_DATA_KEY, _MODEL_DATA_YAML_FILE_NAME)
|
|
)
|
|
|
|
agent_path = _get_path_by_key(local_model_path, _AGENT_DATA_KEY, conf)
|
|
tools_path = _get_path_by_key(local_model_path, _TOOLS_DATA_KEY, conf)
|
|
agent_primitive_path = _get_path_by_key(local_model_path, _AGENT_PRIMITIVES_DATA_KEY, conf)
|
|
loader_fn_path = _get_path_by_key(local_model_path, _LOADER_FN_KEY, conf)
|
|
persist_dir = _get_path_by_key(local_model_path, _PERSIST_DIR_KEY, conf)
|
|
|
|
model_type = conf.get(_MODEL_TYPE_KEY)
|
|
loader_arg = conf.get(_LOADER_ARG_KEY)
|
|
|
|
load_chain = None
|
|
try:
|
|
from langchain.chains.loading import load_chain
|
|
except ImportError:
|
|
pass
|
|
|
|
_RetrieverChain = None
|
|
try:
|
|
from mlflow.langchain.retriever_chain import _RetrieverChain
|
|
except ImportError:
|
|
pass
|
|
|
|
if loader_arg is not None:
|
|
if loader_fn_path is None:
|
|
raise mlflow.MlflowException.invalid_parameter_value(
|
|
"Missing file for loader_fn which is required to build the model."
|
|
)
|
|
loader_fn = _load_from_pickle(loader_fn_path)
|
|
kwargs = {loader_arg: loader_fn(persist_dir)}
|
|
if _RetrieverChain and model_type == _RetrieverChain.__name__:
|
|
model = _RetrieverChain.load(lc_model_path, **kwargs).retriever
|
|
else:
|
|
if load_chain is None:
|
|
raise mlflow.MlflowException(
|
|
"Cannot load model: langchain.chains.loading.load_chain is not available. "
|
|
"This may be because you're using langchain>=1.0.0. "
|
|
"Please use a model saved with langchain>=1.0.0."
|
|
)
|
|
model = _patch_loader(load_chain)(lc_model_path, **kwargs)
|
|
elif agent_path is None and tools_path is None:
|
|
if load_chain is None:
|
|
raise mlflow.MlflowException(
|
|
"Cannot load model: langchain.chains.loading.load_chain is not available. "
|
|
"This may be because you're using langchain>=1.0.0. "
|
|
"Please use a model saved with langchain>=1.0.0."
|
|
)
|
|
model = _patch_loader(load_chain)(lc_model_path)
|
|
else:
|
|
try:
|
|
from langchain.agents import initialize_agent
|
|
except ImportError:
|
|
raise mlflow.MlflowException(
|
|
"Cannot load AgentExecutor: langchain.agents.initialize_agent is not available. "
|
|
"AgentExecutor was removed in langchain 1.0.0. Please use LangGraph instead."
|
|
)
|
|
|
|
if load_chain is None:
|
|
raise mlflow.MlflowException(
|
|
"Cannot load model: langchain.chains.loading.load_chain is not available. "
|
|
"This may be because you're using langchain>=1.0.0. "
|
|
"Please use a model saved with langchain>=1.0.0."
|
|
)
|
|
|
|
llm = _patch_loader(load_chain)(lc_model_path)
|
|
tools = []
|
|
kwargs = {}
|
|
|
|
if os.path.exists(tools_path):
|
|
tools = _load_from_pickle(tools_path)
|
|
else:
|
|
raise mlflow.MlflowException(
|
|
"Missing file for tools which is required to build the AgentExecutor object."
|
|
)
|
|
|
|
if os.path.exists(agent_primitive_path):
|
|
kwargs = _load_from_json(agent_primitive_path)
|
|
|
|
model = initialize_agent(tools=tools, llm=llm, agent_path=agent_path, **kwargs)
|
|
return model
|
|
|
|
|
|
def patch_langchain_type_to_cls_dict(func):
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
def _load_chat_openai():
|
|
from langchain_community.chat_models import ChatOpenAI
|
|
|
|
return ChatOpenAI
|
|
|
|
def _load_azure_chat_openai():
|
|
from langchain_community.chat_models import AzureChatOpenAI
|
|
|
|
return AzureChatOpenAI
|
|
|
|
def _load_chat_databricks():
|
|
from databricks_langchain import ChatDatabricks
|
|
|
|
return ChatDatabricks
|
|
|
|
def _patched_get_type_to_cls_dict(original):
|
|
def _wrapped():
|
|
return {
|
|
**original(),
|
|
"openai-chat": _load_chat_openai,
|
|
"azure-openai-chat": _load_azure_chat_openai,
|
|
"chat-databricks": _load_chat_databricks,
|
|
}
|
|
|
|
return _wrapped
|
|
|
|
modules_to_patch = [
|
|
"langchain_databricks",
|
|
"langchain.llms",
|
|
"langchain_community.llms.loading",
|
|
]
|
|
originals = {}
|
|
for name in modules_to_patch:
|
|
try:
|
|
module = importlib.import_module(name)
|
|
originals[name] = module.get_type_to_cls_dict # Record original impl for cleanup
|
|
except (ImportError, AttributeError):
|
|
continue
|
|
module.get_type_to_cls_dict = _patched_get_type_to_cls_dict(originals[name])
|
|
|
|
try:
|
|
return func(*args, **kwargs)
|
|
finally:
|
|
# Clean up the patch
|
|
for module_name, original_impl in originals.items():
|
|
module = importlib.import_module(module_name)
|
|
module.get_type_to_cls_dict = original_impl
|
|
|
|
return wrapper
|