Skip to content

feat: Governance runtime implementation#899

Open
viswa-uipath wants to merge 7 commits into
mainfrom
feat/governance
Open

feat: Governance runtime implementation#899
viswa-uipath wants to merge 7 commits into
mainfrom
feat/governance

Conversation

@viswa-uipath

@viswa-uipath viswa-uipath commented Jun 9, 2026

Copy link
Copy Markdown

TEST IN PROGRESS: Changes for lang-chain adapter to support governance

Development Package

  • Use uipath pack --nolock to get the latest dev build from this PR (requires version range).
  • Add this package as a dependency in your pyproject.toml:
[project]
dependencies = [
  # Exact version:
  "uipath-langchain==0.11.16.dev1008994855",

  # Any version from PR
  "uipath-langchain>=0.11.16.dev1008990000,<0.11.16.dev1009000000"
]

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.uv.sources]
uipath-langchain = { index = "testpypi" }

[tool.uv]
override-dependencies = [
    "uipath-langchain>=0.11.16.dev1008990000,<0.11.16.dev1009000000",
]

Copilot AI review requested due to automatic review settings June 9, 2026 13:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a LangChain/LangGraph governance integration layer to uipath-langchain, wiring UiPath’s governance evaluator into LangChain callbacks and exposing registration via an entry point so uipath-runtime can discover the adapter.

Changes:

  • Introduces a LangChainAdapter + governed proxy wrapper and a callback handler that calls EvaluatorProtocol hooks for model/tool events.
  • Adds governance adapter registration (uipath.governance.adapters entry point) with import-time idempotent self-registration.
  • Updates dependency/lock configuration to pull in updated uipath-core and a dev/test build of uipath-runtime (currently with non-portable lockfile sources).

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 9 comments.

File Description
src/uipath_langchain/governance/adapter.py Implements the LangChain/LangGraph adapter, wrapper, and callback handler that triggers governance evaluations.
src/uipath_langchain/governance/__init__.py Registers the governance adapter on import and exposes an entry-point registration function.
pyproject.toml Adds governance adapter entry point; bumps uipath-core; pins uipath-runtime to a dev build and adds a uv TestPyPI source override.
uv.lock Updates locked dependencies, but currently includes a machine-local Windows editable uipath-runtime source.

Comment on lines +8 to +13
Intercepts:

- ``on_chain_start`` / ``on_chain_end`` → BEFORE_AGENT / AFTER_AGENT
- ``on_llm_start`` / ``on_chat_model_start`` / ``on_llm_end`` → BEFORE_MODEL / AFTER_MODEL
- ``on_tool_start`` / ``on_tool_end`` → TOOL_CALL / AFTER_TOOL

Comment on lines +110 to +117
if hasattr(agent, "callbacks"):
existing = getattr(agent, "callbacks", None) or []
if isinstance(existing, list):
existing.append(callback)
agent.callbacks = existing
else:
agent.callbacks = [callback]
logger.debug("Injected governance callback via agent.callbacks")
Comment on lines +120 to +126
if hasattr(agent, "config"):
config = agent.config or {}
callbacks = config.get("callbacks", [])
callbacks.append(callback)
config["callbacks"] = callbacks
agent.config = config
logger.debug("Injected governance callback via agent.config")
Comment on lines +175 to +182
if config is None:
config = {}
if isinstance(config, dict):
callbacks = config.get("callbacks", [])
if self._callback not in callbacks:
callbacks.append(self._callback)
config["callbacks"] = callbacks
return config
Comment on lines +386 to +395
tool_name = serialized.get("name", "unknown")
tool_args = inputs or {"input": input_str}
self._evaluator.evaluate_tool_call(
tool_name=tool_name,
tool_args=tool_args,
agent_name=self._agent_name,
runtime_id=self._session_id,
trace_id=self._trace_id,
session_state=self._session_state,
)
Comment on lines +401 to +411
def on_tool_end(self, output: Any, **kwargs: Any) -> None:
"""Evaluate AFTER_TOOL rules at tool end."""
try:
tool_result = str(output) if output is not None else ""
self._evaluator.evaluate_after_tool(
tool_name="unknown",
tool_result=tool_result,
agent_name=self._agent_name,
runtime_id=self._session_id,
trace_id=self._trace_id,
)
Comment on lines +83 to +105
def attach(
self,
agent: Any,
agent_id: str,
session_id: str,
evaluator: EvaluatorProtocol,
) -> "GovernedLangChainAgent":
"""Attach governance to a LangChain / LangGraph agent."""
callback = GovernanceCallbackHandler(
evaluator=evaluator,
agent_name=agent_id,
session_id=session_id,
)
self._inject_callback(agent, callback)
return GovernedLangChainAgent(
agent=agent,
adapter=self,
agent_id=agent_id,
session_id=session_id,
evaluator=evaluator,
callback=callback,
)

Comment thread pyproject.toml
Comment on lines 8 to 13
"uipath>=2.10.79, <2.11.0",
"uipath-core>=0.5.17, <0.6.0",
"uipath-core>=0.5.18, <0.6.0",
"uipath-platform>=0.1.61, <0.2.0",
"uipath-runtime>=0.11.0, <0.12.0",
"uipath-runtime==0.11.0.dev1001180441",
"langgraph>=1.1.8, <2.0.0",
"langchain-core>=1.2.11, <2.0.0",
Comment thread pyproject.toml
Comment on lines +161 to +163
[tool.uv.sources]
uipath-runtime = { index = "testpypi" }

viswa-uipath and others added 2 commits June 9, 2026 19:36
Registers a LangChain/LangGraph adapter with the uipath-core adapter
registry so GovernanceRuntime can attach BEFORE_MODEL / AFTER_MODEL /
TOOL_CALL / AFTER_TOOL hooks via LangChain's callback system. Exposed
as a uipath.governance.adapters entry point and self-registers on
import. Bumps uipath-core to 0.6.x and uipath-runtime to 0.11.x to
pick up the new adapter contracts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
viswa-uipath and others added 5 commits June 9, 2026 19:46
uipath-runtime is pinned to a dev pre-release served from testpypi.
uipath==2.10.79 transitively pins uipath-runtime>=0.11.0,<0.12.0,
which excludes pre-releases per PEP 440 (0.11.0.dev* sorts below
0.11.0). uv sync was failing on every CI runner, so no tests ran and
the coverage report rendered as 0%.

Add prerelease = "allow" plus an override-dependencies entry for
uipath-runtime under [tool.uv] so the dev pin can satisfy the
umbrella's stable-only constraint. Re-lock so uipath-runtime resolves
from testpypi instead of the local editable Windows path that wasn't
portable to the Linux/macOS runners.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
caplog.at_level(level, logger=NAME) only sets the level on the named
logger; pytest's caplog handler is attached to root, so any message
that exceeds the root logger's effective level still gets filtered out
before reaching the handler. CI's root logger config blocked the
adapter logger's WARNING/DEBUG records — the messages emitted (they
appear in the test output), caplog.records was empty, the
``any(...)`` assertions failed.

Drop the logger= argument so at_level() adjusts the root logger and
caplog reliably captures every record at that severity regardless of
runner config. The tests only assert on the message content, not the
emitting logger, so the change is behaviour-preserving.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
caplog wasn't catching adapter logger records on the Linux CI runners
even though the message reached stderr via lastResort. Some interaction
between caplog's root-attached LogCaptureHandler and the package's
propagation chain on those runners — fighting it isn't worth it.

Switch the warning/debug assertions to mock.patch the adapter module's
logger and verify the calls directly. The behaviour under test
(exception swallowed, warning emitted, debug breadcrumb on the
attach path) is the same; the assertion is just more direct and
doesn't depend on logging handler topology.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's mypy config has disallow_any_generics=true and warn_unused_ignores=true,
which the test scaffolding tripped on every inline helper class:

- bare ``list`` / ``dict`` annotations needed parameterisation
- ``# type: ignore[no-untyped-def]`` on the duck-typed agent methods
  weren't actually needed (mypy lets untyped functions slide outside
  the package boundary)

Annotate ``list[Any]`` / ``dict[str, Any]`` and drop the noisy
type-ignore comments.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants