-
Notifications
You must be signed in to change notification settings - Fork 69
feat: Add support for serialization and tracing metadata params #452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ | |
| from databricks import sdk | ||
| from langchain_core.callbacks import AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun | ||
| from langchain_core.language_models import BaseChatModel | ||
| from langchain_core.language_models.base import LanguageModelInput | ||
| from langchain_core.language_models.base import LangSmithParams, LanguageModelInput | ||
| from langchain_core.messages import ( | ||
| AIMessage, | ||
| AIMessageChunk, | ||
|
|
@@ -66,7 +66,7 @@ | |
| from openai.types.chat import ChatCompletion, ChatCompletionChunk | ||
| from openai.types.completion_usage import CompletionUsage | ||
| from openai.types.responses import Response, ResponseStreamEvent, ResponseUsage | ||
| from pydantic import BaseModel, ConfigDict, Field, model_validator | ||
| from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator | ||
| from typing_extensions import override | ||
|
|
||
| from databricks_langchain.utils import get_async_openai_client, get_openai_client | ||
|
|
@@ -272,10 +272,29 @@ | |
|
|
||
| model_config = ConfigDict(populate_by_name=True) | ||
|
|
||
| @classmethod | ||
| def is_lc_serializable(cls) -> bool: | ||
| """Return whether this model can be serialized by LangChain.""" | ||
| return True | ||
|
|
||
| @classmethod | ||
| def get_lc_namespace(cls) -> List[str]: | ||
| """Get the namespace of the LangChain object.""" | ||
| return ["databricks_langchain", "chat_models"] | ||
|
|
||
| @property | ||
| def lc_secrets(self) -> Dict[str, str]: | ||
| """Map the Databricks token field to its environment variable.""" | ||
| return {"databricks_token": "DATABRICKS_TOKEN"} | ||
|
|
||
| model: str = Field(alias="endpoint") | ||
| """Name of Databricks Model Serving endpoint to query.""" | ||
| target_uri: Optional[str] = None | ||
| """The target MLflow deployment URI to use. Deprecated: use workspace_client instead.""" | ||
| databricks_host: Optional[str] = None | ||
| """Databricks workspace URL. If omitted, the SDK uses its default authentication.""" | ||
| databricks_token: Optional[SecretStr] = Field(default=None, repr=False) | ||
| """Databricks personal access token. Serialized as the ``DATABRICKS_TOKEN`` secret.""" | ||
| workspace_client: Optional[sdk.WorkspaceClient] = Field(default=None, exclude=True) | ||
| """Optional WorkspaceClient instance to use for authentication. If not provided, uses default authentication.""" | ||
| temperature: Optional[float] = None | ||
|
|
@@ -366,6 +385,18 @@ | |
| def __init__(self, **kwargs: Any): | ||
| super().__init__(**kwargs) | ||
|
|
||
| if self.workspace_client is None and ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we raise an error if both |
||
| self.databricks_host is not None or self.databricks_token is not None | ||
| ): | ||
| self.workspace_client = sdk.WorkspaceClient( | ||
| host=self.databricks_host, | ||
| token=( | ||
| self.databricks_token.get_secret_value() | ||
| if self.databricks_token is not None | ||
| else None | ||
| ), | ||
| ) | ||
|
|
||
| # Handle deprecated target_uri parameter | ||
| if self.target_uri: | ||
| warnings.warn( | ||
|
|
@@ -547,7 +578,7 @@ | |
| content_blocks.append(item_dict) | ||
|
|
||
| try: | ||
| args = json.loads(item.arguments, strict=False) # ty:ignore[unresolved-attribute, invalid-argument-type]: astral-sh/ty#1479 should fix this | ||
|
Check warning on line 581 in integrations/langchain/src/databricks_langchain/chat_models.py
|
||
| error = None | ||
| except json.JSONDecodeError as e: | ||
| error = str(e) | ||
|
|
@@ -575,8 +606,8 @@ | |
| content_blocks.append( | ||
| { | ||
| "role": "tool", | ||
| "content": item.output, # ty:ignore[unresolved-attribute]: astral-sh/ty#1479 should fix this | ||
|
Check warning on line 609 in integrations/langchain/src/databricks_langchain/chat_models.py
|
||
| "tool_call_id": item.call_id, # ty:ignore[unresolved-attribute]: astral-sh/ty#1479 should fix this | ||
|
Check warning on line 610 in integrations/langchain/src/databricks_langchain/chat_models.py
|
||
| } | ||
| ) | ||
| elif item.type in ( | ||
|
|
@@ -1392,14 +1423,40 @@ | |
| def _identifying_params(self) -> Dict[str, Any]: | ||
| return self._default_params | ||
|
|
||
| def _get_databricks_host(self) -> Optional[str]: | ||
| """Get the workspace host without exposing authentication state.""" | ||
| if self.databricks_host: | ||
| return self.databricks_host | ||
| if self.workspace_client is not None: | ||
| host = getattr(self.workspace_client.config, "host", None) | ||
| if isinstance(host, str): | ||
| return host | ||
| return None | ||
|
|
||
| def _get_invocation_params( | ||
| self, stop: Optional[List[str]] = None, **kwargs: Any | ||
| ) -> Dict[str, Any]: | ||
| """Get the parameters used to invoke the model FOR THE CALLBACKS.""" | ||
| return { | ||
| **self._default_params, | ||
| **super()._get_invocation_params(stop=stop, **kwargs), | ||
| """Get safe model and workspace configuration for callbacks and tracing.""" | ||
| params = super()._get_invocation_params(stop=stop, **kwargs) | ||
| optional_params = { | ||
| "databricks_host": self._get_databricks_host(), | ||
| "timeout": self.timeout, | ||
| "max_retries": self.max_retries, | ||
| } | ||
| params.update({key: value for key, value in optional_params.items() if value is not None}) | ||
| if self.use_ai_gateway: | ||
| params["use_ai_gateway"] = True | ||
| if self.use_ai_gateway_native_api: | ||
| params["use_ai_gateway_native_api"] = True | ||
| if self.use_responses_api: | ||
| params["use_responses_api"] = True | ||
| return params | ||
|
|
||
| def _get_ls_params(self, stop: Optional[List[str]] = None, **kwargs: Any) -> LangSmithParams: | ||
| """Get standard LangSmith trace metadata for ChatDatabricks.""" | ||
| params = super()._get_ls_params(stop=stop, **kwargs) | ||
| params["ls_provider"] = "databricks" | ||
| return params | ||
|
|
||
| @property | ||
| def _llm_type(self) -> str: | ||
|
|
@@ -1733,15 +1790,15 @@ | |
| item = chunk.item # ty:ignore[unresolved-attribute]: astral-sh/ty#1479 should fix this | ||
| if item.type == "function_call_output": | ||
| lc_chunk = ToolMessageChunk( | ||
| content=item.output, # ty:ignore[unresolved-attribute, invalid-argument-type]: astral-sh/ty#1479 should fix this | ||
|
Check warning on line 1793 in integrations/langchain/src/databricks_langchain/chat_models.py
|
||
| tool_call_id=item.call_id, # ty:ignore[unresolved-attribute, invalid-argument-type]: astral-sh/ty#1479 should fix this | ||
|
Check warning on line 1794 in integrations/langchain/src/databricks_langchain/chat_models.py
|
||
| ) | ||
| elif item.type == "function_call": | ||
| id = item.call_id # ty:ignore[unresolved-attribute]: astral-sh/ty#1479 should fix this | ||
| tool_call_chunks.append( | ||
| tool_call_chunk( | ||
| name=item.name, # ty:ignore[unresolved-attribute]: astral-sh/ty#1479 should fix this | ||
| args=item.arguments, # ty:ignore[unresolved-attribute, invalid-argument-type]: astral-sh/ty#1479 should fix this | ||
|
Check warning on line 1801 in integrations/langchain/src/databricks_langchain/chat_models.py
|
||
| id=item.call_id, # ty:ignore[unresolved-attribute]: astral-sh/ty#1479 should fix this | ||
| ) | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We ask users to pass in the workspace_client, is there a specific reason to explicitly pass in databricks_host and token ?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our service must recreate chat model instances from serialized chat model runs. We cannot easily represent complex objects like client instances, so it's convenient here to add an initialization path that uses only simple primitives like strings.