Skip to content

Commit 4340697

Browse files
cosminachoclaude
andcommitted
Feat: implement BaseChatModel introspection methods on UiPathChat
`UiPathChat` extends `BaseChatModel` directly (not via a vendor SDK class like `ChatOpenAI`) and was missing the standard overrides used by LangChain for tracing, caching, and LangSmith integration. Add `_identifying_params`, `_get_invocation_params`, `_get_ls_params` (with `ls_provider="uipath"`), and `_combine_llm_outputs` to match the BaseChatOpenAI convention. `_identifying_params` also surfaces `byo_connection_id` and the `UiPathAPIConfig` routing fields so cached/traced runs are keyed on the full UiPath routing context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b83a8a0 commit 4340697

3 files changed

Lines changed: 69 additions & 1 deletion

File tree

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to `uipath_langchain_client` will be documented in this file.
44

5+
## [1.9.6] - 2026-04-22
6+
7+
### Added
8+
- `UiPathChat` (normalized) now implements `_identifying_params`, `_get_invocation_params`, `_get_ls_params`, and `_combine_llm_outputs` to match the BaseChatModel / ChatOpenAI conventions for tracing, caching, and LangSmith integration. `_identifying_params` surfaces `model_name`, `_default_params`, `byo_connection_id`, and the `UiPathAPIConfig` routing fields; `_get_ls_params` sets `ls_provider="uipath"`.
9+
510
## [1.9.5] - 2026-04-21
611

712
### Changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LangChain Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
3-
__version__ = "1.9.5"
3+
__version__ = "1.9.6"

packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
AsyncCallbackManagerForLLMRun,
3333
CallbackManagerForLLMRun,
3434
)
35+
from langchain_core.language_models import LangSmithParams
3536
from langchain_core.language_models.base import (
3637
LanguageModelInput,
3738
)
@@ -206,6 +207,68 @@ def _llm_type(self) -> str:
206207
"""Return type of chat model."""
207208
return "UiPath-Normalized"
208209

210+
@property
211+
def _identifying_params(self) -> dict[str, Any]:
212+
"""Parameters that uniquely identify this model for tracing and caching."""
213+
params: dict[str, Any] = {"model_name": self.model_name, **self._default_params}
214+
if self.byo_connection_id is not None:
215+
params["byo_connection_id"] = self.byo_connection_id
216+
if self.api_config.api_type is not None:
217+
params["uipath_api_type"] = str(self.api_config.api_type)
218+
if self.api_config.routing_mode is not None:
219+
params["uipath_routing_mode"] = str(self.api_config.routing_mode)
220+
if self.api_config.vendor_type is not None:
221+
params["uipath_vendor_type"] = str(self.api_config.vendor_type)
222+
if self.api_config.api_flavor is not None:
223+
params["uipath_api_flavor"] = str(self.api_config.api_flavor)
224+
return params
225+
226+
def _get_invocation_params(
227+
self, stop: list[str] | None = None, **kwargs: Any
228+
) -> dict[str, Any]:
229+
"""Return the parameters used to invoke the model."""
230+
return {
231+
"model": self.model_name,
232+
**self._default_params,
233+
**({"stop": stop} if stop is not None else {}),
234+
**kwargs,
235+
}
236+
237+
def _get_ls_params(self, stop: list[str] | None = None, **kwargs: Any) -> LangSmithParams:
238+
"""Standard params sent to LangSmith for tracing."""
239+
params = self._get_invocation_params(stop=stop, **kwargs)
240+
ls_params = LangSmithParams(
241+
ls_provider="uipath",
242+
ls_model_name=params.get("model", self.model_name),
243+
ls_model_type="chat",
244+
)
245+
if (ls_temperature := params.get("temperature", self.temperature)) is not None:
246+
ls_params["ls_temperature"] = ls_temperature
247+
if (ls_max_tokens := params.get("max_tokens", self.max_tokens)) is not None:
248+
ls_params["ls_max_tokens"] = ls_max_tokens
249+
if ls_stop := stop or params.get("stop", None):
250+
ls_params["ls_stop"] = ls_stop if isinstance(ls_stop, list) else [ls_stop]
251+
return ls_params
252+
253+
def _combine_llm_outputs(self, llm_outputs: list[dict[str, Any] | None]) -> dict[str, Any]:
254+
"""Merge per-generation llm_output dicts across a batch."""
255+
combined: dict[str, Any] = {"model_name": self.model_name}
256+
overall_usage: dict[str, int] = {}
257+
for output in llm_outputs:
258+
if output is None:
259+
continue
260+
if "id" in output and "id" not in combined:
261+
combined["id"] = output["id"]
262+
if "created" in output and "created" not in combined:
263+
combined["created"] = output["created"]
264+
usage = output.get("token_usage") or {}
265+
for k, v in usage.items():
266+
if isinstance(v, int):
267+
overall_usage[k] = overall_usage.get(k, 0) + v
268+
if overall_usage:
269+
combined["token_usage"] = overall_usage
270+
return combined
271+
209272
@property
210273
def _default_params(self) -> dict[str, Any]:
211274
"""Get the default parameters for the normalized API request."""

0 commit comments

Comments
 (0)