From 4d7bde5caa6d68d0f2fe61fc0cbeea8a4ab8795f Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 22 Jun 2026 14:33:31 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20(examples):=20Wire=20LlamaIndex?= =?UTF-8?q?=20example=20to=20the=20native=20SDK=20adapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the manual GovernedToolRunner bridge with the real agent_assembly.adapters.llamaindex adapter: register_hooks patches FunctionTool.call so every tool call is governed automatically. main.py reverts init_assembly's auto-detected no-op patch first so the offline LocalPolicyEngine is the live interceptor; a denied tool returns a [BLOCKED by governance policy] ToolOutput and its body never runs. Refs AAASM-3536 Co-Authored-By: Claude Opus 4.8 (1M context) --- python/llamaindex-tool-policy/src/main.py | 103 ++++++++++++-------- python/llamaindex-tool-policy/src/policy.py | 91 ++++++----------- 2 files changed, 92 insertions(+), 102 deletions(-) diff --git a/python/llamaindex-tool-policy/src/main.py b/python/llamaindex-tool-policy/src/main.py index 55c0845f..06a56c34 100644 --- a/python/llamaindex-tool-policy/src/main.py +++ b/python/llamaindex-tool-policy/src/main.py @@ -1,45 +1,57 @@ -""" -llamaindex-tool-policy: Agent Assembly governance demo with LlamaIndex. - -LlamaIndex does not yet have a native Agent Assembly adapter, so this example -shows how to add governance by wrapping each FunctionTool with GovernedToolRunner. -This pattern works for ANY Python callable, not just LlamaIndex. - -Run (offline mode, no gateway or API key required): +"""llamaindex-tool-policy: Agent Assembly governance demo with LlamaIndex. + +LlamaIndex has a native Agent Assembly adapter +(``agent_assembly.adapters.llamaindex``): it monkey-patches the concrete +``FunctionTool.call`` / ``acall`` execution methods, so once the adapter's hooks +are registered, **every** LlamaIndex tool call is governed automatically — the +exact method a ``FunctionAgent`` / ``ReActAgent`` invokes to run a tool. A denied +tool's body never executes; the adapter returns a ``ToolOutput`` flagged +``is_error=True`` carrying a ``[BLOCKED by governance policy]`` message so the +agent loop can react instead of crashing. + +This is an **offline** demo: ``LocalPolicyEngine`` simulates the gateway's +allow/deny verdict in-process, so no gateway or API key is required. The tools +and their ``FunctionTool.call`` invocations are real — only the policy decision +is local. + +Run: uv run python src/main.py """ + from __future__ import annotations import os import sys -from typing import Any sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from agent_assembly import init_assembly -from agent_assembly.exceptions import ToolExecutionBlockedError +from agent_assembly.adapters.llamaindex import LlamaIndexAdapter, LlamaIndexPatch -from src.policy import GovernedToolRunner, LocalPolicyEngine -from src.tools import _execute_sql_fn, _query_index_fn, _summarize_docs_fn +from src.policy import LocalPolicyEngine +from src.tools import execute_sql, query_index, summarize_docs -_DEMO_CALLS: list[tuple[str, Any, dict]] = [ - ("query_index", _query_index_fn, {"query": "what is Agent Assembly?"}), - ("summarize_docs", _summarize_docs_fn, {"topic": "policy enforcement"}), - ("execute_sql", _execute_sql_fn, {"sql": "DROP TABLE users; --"}), +#: The adapter's deny short-circuit marker. The LlamaIndex tool patch returns a +#: ToolOutput carrying this string on a deny rather than raising. +_BLOCKED_MARKER = "[BLOCKED by governance policy]" + +_DEMO_CALLS = [ + (query_index, {"query": "what is Agent Assembly?"}), + (summarize_docs, {"topic": "policy enforcement"}), + (execute_sql, {"sql": "DROP TABLE users; --"}), ] -def _run_governed_call( - runner: GovernedToolRunner, - label: str, - kwargs: dict, -) -> None: - print(f" → {label}({kwargs})") - try: - result = runner.run(**kwargs) - print(f" ✅ ALLOWED — {result}") - except ToolExecutionBlockedError as exc: - print(f" ❌ BLOCKED — {exc}") +def _run_governed_call(tool: object, kwargs: dict) -> None: + name = tool.metadata.get_name() # type: ignore[attr-defined] + print(f" → {name}({kwargs})") + # The adapter has patched FunctionTool.call, so this single call is governed. + output = tool.call(**kwargs) # type: ignore[attr-defined] + content = str(getattr(output, "content", output)) + if _BLOCKED_MARKER in content: + print(f" ❌ BLOCKED — {content}") + else: + print(f" ✅ ALLOWED — {content}") print() @@ -65,31 +77,36 @@ def main() -> None: print(f" Mode: {ctx.network_mode} (offline demo)") print() - policy = LocalPolicyEngine() - print("Policy rules (local simulation of gateway policy):") print(" DENY — execute_sql, run_shell_command (arbitrary execution)") print(" ALLOW — everything else") print() - print("Wrapping LlamaIndex tools with GovernedToolRunner...") - runners = { - name: GovernedToolRunner(name, fn, policy) - for name, fn, _ in _DEMO_CALLS - } - print(" Tools wrapped: query_index, summarize_docs, execute_sql") + # Register the native LlamaIndex adapter against the local policy engine. + # This patches FunctionTool.call so every tool call below is governed + # automatically — no per-tool wrapper needed. + # + # init_assembly() in sdk-only mode already auto-detected LlamaIndex and + # patched FunctionTool.call against a no-op interceptor (there is no + # gateway offline). Revert that first so this example's LocalPolicyEngine + # is the live interceptor; in production init_assembly wires the adapter + # to the gateway and this manual step is unnecessary. + print("Registering the native LlamaIndex governance adapter...") + LlamaIndexPatch(callback_handler=None).revert() + adapter = LlamaIndexAdapter() + adapter.register_hooks(LocalPolicyEngine()) + print(" FunctionTool.call / acall are now governed by Agent Assembly.") print() - print("Running governed tool calls:") - print("-" * 44) - for tool_name, fn, kwargs in _DEMO_CALLS: - _run_governed_call(runners[tool_name], tool_name, kwargs) + try: + print("Running governed tool calls:") + print("-" * 44) + for tool, kwargs in _DEMO_CALLS: + _run_governed_call(tool, kwargs) + finally: + adapter.unregister_hooks() print("Assembly context shut down.") - print() - print("Note: When a native LlamaIndex adapter is available,") - print(" GovernedToolRunner will no longer be needed — governance") - print(" will be applied automatically by init_assembly().") if __name__ == "__main__": diff --git a/python/llamaindex-tool-policy/src/policy.py b/python/llamaindex-tool-policy/src/policy.py index 79ba6483..5e724d32 100644 --- a/python/llamaindex-tool-policy/src/policy.py +++ b/python/llamaindex-tool-policy/src/policy.py @@ -1,77 +1,50 @@ """Local offline policy engine for the llamaindex-tool-policy example. -LlamaIndex does not yet have a native Agent Assembly adapter, so governance -is applied by explicitly calling GovernedToolRunner.run() before each -LlamaIndex FunctionTool invocation. - -This module defines: - - LocalPolicyEngine — simulates gateway policy (allow / deny) - - GovernedToolRunner — wraps any callable with AssemblyCallbackHandler - so every tool invocation passes through governance before execution +LlamaIndex now has a native Agent Assembly adapter +(``agent_assembly.adapters.llamaindex``). It monkey-patches the concrete +``FunctionTool.call`` / ``acall`` execution methods so every tool invocation +passes through governance automatically — no per-call wrapper is needed. + +The adapter calls ``interceptor.check_tool_start(...)`` before a tool body runs +and blocks on a ``deny`` (returning a ``[BLOCKED by governance policy]`` +``ToolOutput`` rather than raising, so an agent loop can react). This module +provides ``LocalPolicyEngine`` — the interceptor that simulates gateway policy +offline (allow / deny) — which is what ``main.py`` hands to the adapter. + +The ``_enforce = True`` flag puts the interceptor in fail-closed posture: an +unknown / malformed verdict denies rather than silently allowing (AAASM-3107). """ + from __future__ import annotations from typing import Any -from uuid import uuid4 -from agent_assembly.adapters.langchain import AssemblyCallbackHandler -from agent_assembly.exceptions import ToolExecutionBlockedError - -DENIED_TOOLS: frozenset[str] = frozenset({ - "execute_sql", - "run_shell_command", -}) +DENIED_TOOLS: frozenset[str] = frozenset( + { + "execute_sql", + "run_shell_command", + } +) class LocalPolicyEngine: - """Simulates Agent Assembly gateway policy enforcement in offline mode.""" + """Simulates Agent Assembly gateway policy enforcement in offline mode. + + Implements the ``check_tool_start`` interceptor contract the LlamaIndex + adapter calls before a governed ``FunctionTool`` runs. Returns an + ``{"status": "allow"}`` / ``{"status": "deny", "reason": ...}`` verdict. + """ + + #: Fail-closed posture: the adapter denies an unknown verdict under enforce. + _enforce = True - def check_tool_start( - self, - serialized: dict[str, Any], - **kwargs: Any, - ) -> dict[str, str]: - tool_name = serialized.get("name", "") + def check_tool_start(self, **kwargs: Any) -> dict[str, str]: + tool_name = str(kwargs.get("tool_name") or "") if tool_name in DENIED_TOOLS: return { "status": "deny", "reason": ( - f"Tool '{tool_name}' is blocked by policy rule " - "'deny_arbitrary_execution'." + f"Tool '{tool_name}' is blocked by policy rule 'deny_arbitrary_execution'." ), } return {"status": "allow"} - - -class GovernedToolRunner: - """Wraps a callable with Agent Assembly governance. - - Use this as a bridge when a framework does not have a native adapter. - Call ``run()`` instead of invoking the tool directly; governance runs first. - - Example:: - - runner = GovernedToolRunner("query_index", query_index_fn, policy) - result = runner.run(query="What is Agent Assembly?") - """ - - def __init__( - self, - tool_name: str, - fn: Any, - policy: LocalPolicyEngine, - ) -> None: - self._tool_name = tool_name - self._fn = fn - self._handler = AssemblyCallbackHandler(interceptor=policy) - - def run(self, **kwargs: Any) -> Any: - import json - - input_str = json.dumps(kwargs) - self._handler.on_tool_start( - serialized={"name": self._tool_name, "type": "tool"}, - input_str=input_str, - run_id=uuid4(), - ) - return self._fn(**kwargs) From cca0d2f12964636eccf994f72dc5cc0597509042 Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 22 Jun 2026 14:33:31 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=85=20(examples):=20Test=20LlamaIndex?= =?UTF-8?q?=20example=20via=20the=20real=20adapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drive the native adapter through real FunctionTool.call: an allowed tool runs, a denied tool's body never executes (blocked ToolOutput), and revert restores ungoverned behavior. Refs AAASM-3536 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tests/test_smoke.py | 99 +++++++++++++------ 1 file changed, 67 insertions(+), 32 deletions(-) diff --git a/python/llamaindex-tool-policy/tests/test_smoke.py b/python/llamaindex-tool-policy/tests/test_smoke.py index dd7bb42b..bff32213 100644 --- a/python/llamaindex-tool-policy/tests/test_smoke.py +++ b/python/llamaindex-tool-policy/tests/test_smoke.py @@ -1,59 +1,94 @@ -"""Smoke tests for llamaindex-tool-policy — offline, no gateway or API key required.""" +"""Smoke tests for llamaindex-tool-policy — offline, no gateway or API key required. + +These drive the **real** LlamaIndex adapter: ``LlamaIndexAdapter.register_hooks`` +patches ``FunctionTool.call``, so an allowed tool runs and a denied tool's body +never executes (the adapter returns a ``[BLOCKED by governance policy]`` +``ToolOutput`` instead of raising). Each test reverts the patch so the global +``FunctionTool`` class is left clean. +""" + from __future__ import annotations -from unittest.mock import patch +from collections.abc import Iterator import pytest +from llama_index.core.tools import FunctionTool + +from agent_assembly.adapters.llamaindex import LlamaIndexAdapter -from agent_assembly.exceptions import ToolExecutionBlockedError +from src.policy import LocalPolicyEngine -from src.policy import GovernedToolRunner, LocalPolicyEngine +_BLOCKED_MARKER = "[BLOCKED by governance policy]" @pytest.fixture -def policy() -> LocalPolicyEngine: - return LocalPolicyEngine() +def governed() -> Iterator[None]: + """Register the LlamaIndex adapter against the local policy; revert after.""" + adapter = LlamaIndexAdapter() + adapter.register_hooks(LocalPolicyEngine()) + try: + yield + finally: + adapter.unregister_hooks() -def test_query_index_is_allowed(policy: LocalPolicyEngine) -> None: - runner = GovernedToolRunner("query_index", lambda query: f"results for {query}", policy) - result = runner.run(query="agent assembly docs") - assert "results for" in result +def _make_tool(name: str, calls: list[str]) -> FunctionTool: + def fn(value: str = "") -> str: + calls.append(value) + return f"ran {name}({value})" + return FunctionTool.from_defaults(fn=fn, name=name) -def test_summarize_docs_is_allowed(policy: LocalPolicyEngine) -> None: - runner = GovernedToolRunner("summarize_docs", lambda topic: f"summary of {topic}", policy) - result = runner.run(topic="governance") - assert "summary of" in result +def test_query_index_is_allowed(governed: None) -> None: + calls: list[str] = [] + tool = _make_tool("query_index", calls) + output = tool.call(value="agent assembly docs") + assert calls == ["agent assembly docs"] + assert _BLOCKED_MARKER not in str(output.content) -def test_execute_sql_is_denied(policy: LocalPolicyEngine) -> None: - runner = GovernedToolRunner("execute_sql", lambda sql: "rows", policy) - with pytest.raises(ToolExecutionBlockedError, match="deny_arbitrary_execution"): - runner.run(sql="SELECT * FROM secrets") +def test_execute_sql_is_denied(governed: None) -> None: + calls: list[str] = [] + tool = _make_tool("execute_sql", calls) + output = tool.call(value="SELECT * FROM secrets") + assert calls == [], "denied tool body must not execute" + assert _BLOCKED_MARKER in str(output.content) + assert "deny_arbitrary_execution" in str(output.content) -def test_run_shell_command_is_denied(policy: LocalPolicyEngine) -> None: - runner = GovernedToolRunner("run_shell_command", lambda cmd: "output", policy) - with pytest.raises(ToolExecutionBlockedError): - runner.run(cmd="rm -rf /") +def test_run_shell_command_is_denied(governed: None) -> None: + calls: list[str] = [] + tool = _make_tool("run_shell_command", calls) + output = tool.call(value="rm -rf /") + assert calls == [] + assert _BLOCKED_MARKER in str(output.content) -def test_unknown_tool_is_allowed(policy: LocalPolicyEngine) -> None: - runner = GovernedToolRunner("list_indexes", lambda: "idx-1, idx-2", policy) - result = runner.run() - assert "idx" in result +def test_unknown_tool_is_allowed(governed: None) -> None: + calls: list[str] = [] + tool = _make_tool("list_indexes", calls) + output = tool.call(value="x") + assert calls == ["x"] + assert _BLOCKED_MARKER not in str(output.content) -def test_governed_runner_does_not_call_fn_when_denied(policy: LocalPolicyEngine) -> None: - called = [] - runner = GovernedToolRunner("execute_sql", lambda sql: called.append(sql), policy) - with pytest.raises(ToolExecutionBlockedError): - runner.run(sql="DROP TABLE users") - assert called == [], "fn must not be called when governance denies" + +def test_revert_restores_ungoverned_behavior() -> None: + """After unregister, a previously-denied tool name runs normally again.""" + calls: list[str] = [] + adapter = LlamaIndexAdapter() + adapter.register_hooks(LocalPolicyEngine()) + adapter.unregister_hooks() + + tool = _make_tool("execute_sql", calls) + output = tool.call(value="SELECT 1") + assert calls == ["SELECT 1"], "tool must run once governance hooks are reverted" + assert _BLOCKED_MARKER not in str(output.content) def test_init_assembly_sdk_only_requires_no_gateway() -> None: + from unittest.mock import patch + from agent_assembly import init_assembly from agent_assembly.core import assembly as _core From cd3df09af8f5a824df16f4861b7f79952951f56c Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 22 Jun 2026 14:33:31 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=9D=20(examples):=20Document=20Lla?= =?UTF-8?q?maIndex=20native=20adapter=20+=20gate=20SDK=20floor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the README to describe the native adapter (no manual wrapper) and bump the agent-assembly floor to >=0.0.1b5, the release that ships the LlamaIndex adapter (AAASM-3536). Refs AAASM-3536 Co-Authored-By: Claude Opus 4.8 (1M context) --- python/llamaindex-tool-policy/README.md | 31 ++++++++------------ python/llamaindex-tool-policy/pyproject.toml | 7 +++-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/python/llamaindex-tool-policy/README.md b/python/llamaindex-tool-policy/README.md index c7a4506d..b54f4ebc 100644 --- a/python/llamaindex-tool-policy/README.md +++ b/python/llamaindex-tool-policy/README.md @@ -1,17 +1,16 @@ # llamaindex-tool-policy -Demonstrates how to add [Agent Assembly](https://github.com/ai-agent-assembly/agent-assembly-examples) governance to [LlamaIndex](https://docs.llamaindex.ai/) tool calls when no native adapter is available. +Demonstrates [Agent Assembly](https://github.com/ai-agent-assembly/agent-assembly-examples) governance for [LlamaIndex](https://docs.llamaindex.ai/) tool calls using the **native LlamaIndex adapter**. -Because LlamaIndex does not yet have a native Agent Assembly adapter, this example shows the **manual wrapper pattern** — wrapping each `FunctionTool` with `GovernedToolRunner` so governance is enforced before every tool invocation. This pattern works for any Python callable. +LlamaIndex has a native Agent Assembly adapter (`agent_assembly.adapters.llamaindex`). It monkey-patches the concrete `FunctionTool.call` / `acall` execution methods, so once the adapter's hooks are registered, **every** LlamaIndex tool call is governed automatically — the exact method a `FunctionAgent` / `ReActAgent` invokes to run a tool. A denied tool's body never executes; the adapter returns a `ToolOutput` flagged `is_error=True` carrying a `[BLOCKED by governance policy]` message so the agent loop can react instead of crashing. ## What this example demonstrates - Initializing Agent Assembly with `init_assembly()`. -- Applying governance to `FunctionTool` calls using `GovernedToolRunner`. -- Running an **allowed** tool call (`query_index`). +- Registering the native `LlamaIndexAdapter` against a local policy interceptor. +- Running an **allowed** tool call (`query_index`) through the patched `FunctionTool.call`. - Running another **allowed** tool call (`summarize_docs`). -- Running a **denied** tool call (`execute_sql` — blocked by `deny_arbitrary_execution`). -- How to add governance to any framework that lacks a native adapter. +- Running a **denied** tool call (`execute_sql` — blocked by `deny_arbitrary_execution`); its body never runs. ## Prerequisites @@ -19,7 +18,7 @@ Because LlamaIndex does not yet have a native Agent Assembly adapter, this examp |---|---| | Python | >= 3.12 | | [uv](https://github.com/astral-sh/uv) | latest | -| Agent Assembly Python SDK | >= 0.0.1b2 | +| Agent Assembly Python SDK | >= 0.0.1b2 (with the LlamaIndex adapter) | No API key or running gateway is required for the offline demo. @@ -52,8 +51,8 @@ Policy rules (local simulation of gateway policy): DENY — execute_sql, run_shell_command (arbitrary execution) ALLOW — everything else -Wrapping LlamaIndex tools with GovernedToolRunner... - Tools wrapped: query_index, summarize_docs, execute_sql +Registering the native LlamaIndex governance adapter... + FunctionTool.call / acall are now governed by Agent Assembly. Running governed tool calls: -------------------------------------------- @@ -64,7 +63,7 @@ Running governed tool calls: ✅ ALLOWED — 📝 Summary for 'policy enforcement': Agent Assembly provides governance... → execute_sql({'sql': 'DROP TABLE users; --'}) - ❌ BLOCKED — Tool 'execute_sql' is blocked by policy rule 'deny_arbitrary_execution'. + ❌ BLOCKED — [BLOCKED by governance policy] Tool 'execute_sql' is blocked by policy rule 'deny_arbitrary_execution'. ... ``` ## Run tests @@ -77,13 +76,9 @@ uv run pytest tests/ -v 1. Start an Agent Assembly gateway or use your SaaS workspace URL. 2. Copy `.env.example` to `.env` and fill in credentials. -3. Replace `LocalPolicyEngine` with the gateway-backed `GatewayClient`: - -```python -with init_assembly(gateway_url="http://localhost:8080", agent_id="my-agent") as ctx: - runner = GovernedToolRunner("query_index", query_fn, ctx.client) - result = runner.run(query="...") -``` +3. Let `init_assembly()` auto-detect and register the LlamaIndex adapter against + the live gateway interceptor instead of the local `LocalPolicyEngine` — the + tool-call code does not change; only the policy source does. ## Troubleshooting @@ -91,7 +86,7 @@ with init_assembly(gateway_url="http://localhost:8080", agent_id="my-agent") as |---|---| | `ModuleNotFoundError: agent_assembly` | Run `uv sync` first | | `ModuleNotFoundError: llama_index` | Run `uv sync` — `llama-index-core` is a required dependency | -| `ToolExecutionBlockedError` in tests | Expected — the deny policy rule for `execute_sql` is intentional | +| `ImportError: cannot import name 'LlamaIndexAdapter'` | Your installed SDK predates the LlamaIndex adapter — upgrade `agent-assembly` | ## Links diff --git a/python/llamaindex-tool-policy/pyproject.toml b/python/llamaindex-tool-policy/pyproject.toml index 23406bfd..843649d1 100644 --- a/python/llamaindex-tool-policy/pyproject.toml +++ b/python/llamaindex-tool-policy/pyproject.toml @@ -1,10 +1,13 @@ [project] name = "llamaindex-tool-policy" version = "0.1.0" -description = "Agent Assembly governance demo with LlamaIndex tool wrapping" +description = "Agent Assembly governance demo with the native LlamaIndex adapter" requires-python = ">=3.12" dependencies = [ - "agent-assembly>=0.0.1b2", + # Floor bumped to the release that ships the native LlamaIndex adapter + # (agent_assembly.adapters.llamaindex, AAASM-3536). Earlier SDK releases + # lack it, so this example's CI is release-gated on that publish. + "agent-assembly>=0.0.1b5", "llama-index-core>=0.14.22", ] From 41e5941f582733b1d2ea7ca5e775b1f2c7645355 Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 22 Jun 2026 16:22:35 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=94=A7=20(llamaindex-example):=20Inst?= =?UTF-8?q?all=20agent-assembly=20from=20git=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin the SDK to python-sdk master via [tool.uv.sources] so the example builds against the merged LlamaIndex adapter ahead of the 0.0.1b5 PyPI publish. Revert to the PyPI pin once that release ships. Co-Authored-By: Claude Opus 4.8 (1M context) --- python/llamaindex-tool-policy/pyproject.toml | 5 +++++ python/llamaindex-tool-policy/uv.lock | 13 +++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/python/llamaindex-tool-policy/pyproject.toml b/python/llamaindex-tool-policy/pyproject.toml index 843649d1..bfcb8cf0 100644 --- a/python/llamaindex-tool-policy/pyproject.toml +++ b/python/llamaindex-tool-policy/pyproject.toml @@ -20,3 +20,8 @@ dev = [ [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["."] + +[tool.uv.sources] +# Track the SDK's git master so the example validates against the merged +# adapter until 0.0.1b5 publishes it to PyPI. Revert to the PyPI pin then. +agent-assembly = { git = "https://github.com/ai-agent-assembly/python-sdk.git", branch = "master" } diff --git a/python/llamaindex-tool-policy/uv.lock b/python/llamaindex-tool-policy/uv.lock index f1ac92a4..d0cb4ef0 100644 --- a/python/llamaindex-tool-policy/uv.lock +++ b/python/llamaindex-tool-policy/uv.lock @@ -4,8 +4,8 @@ requires-python = ">=3.12" [[package]] name = "agent-assembly" -version = "0.0.1b2" -source = { registry = "https://pypi.org/simple" } +version = "0.0.1b4" +source = { git = "https://github.com/ai-agent-assembly/python-sdk.git?branch=master#7181dc693192bc29d7bf85a6cf6eac5a8f78c4bf" } dependencies = [ { name = "grpcio" }, { name = "httpx" }, @@ -13,13 +13,6 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/09/536043927316fad0abb592a0d26d4730d29dfdb34f00ea3109ee6d0aaae0/agent_assembly-0.0.1b2.tar.gz", hash = "sha256:53d28a4f7f58280fe2f7f24f08fb65b58a94030689e41520cfea54156be3ba65", size = 70392, upload-time = "2026-06-15T02:23:04.366Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/cb/95ce75263c627017a510bb2445a051b78c18b2d78e2d01517d78c79f6f9a/agent_assembly-0.0.1b2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:87b7de97fbbf487603841de7b977d3380f8970e45f90bb9da14b91c3730bf2a6", size = 10524759, upload-time = "2026-06-15T02:22:55.846Z" }, - { url = "https://files.pythonhosted.org/packages/03/50/91b8ace11dadeb9b497deecc7a4637b32d213e5adf917cfd05b481ed867a/agent_assembly-0.0.1b2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca679b465e4bcbdf39b4c7d6e59dd09a7fae781de43eb822fe2f7b58c310b4d1", size = 9171842, upload-time = "2026-06-15T02:22:58.115Z" }, - { url = "https://files.pythonhosted.org/packages/70/51/88447dab6e0e55b75551d31f78fb6f2d4b326c5c3303b382cffa5f00aad1/agent_assembly-0.0.1b2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7cb3ab63e5e5b852d404c619471daef8b6607b82b46ef703c56673d2863ea9", size = 10007357, upload-time = "2026-06-15T02:23:00.301Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0b/808cd9a52b858a7da8bc6f7d0c0d8a79465cdc8ecf3af584825d37c494c3/agent_assembly-0.0.1b2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e15643aa3cc8c19288eb8b47ae59636b81530efe9cdae0c14aef3623db195ae", size = 11094577, upload-time = "2026-06-15T02:23:02.414Z" }, -] [[package]] name = "aiohappyeyeballs" @@ -746,7 +739,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "agent-assembly", specifier = ">=0.0.1b2" }, + { name = "agent-assembly", git = "https://github.com/ai-agent-assembly/python-sdk.git?branch=master" }, { name = "llama-index-core", specifier = ">=0.14.22" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.14.0" }, From 5570a0e7c012e2009977ceaae74257d0dc11d512 Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 22 Jun 2026 16:40:27 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=8E=A8=20(llamaindex-example):=20Appl?= =?UTF-8?q?y=20ruff=20format=20+=20mypy=20--strict=20cleanups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add blank line after src/tools.py module docstring (ruff format) and give _run_governed_call's kwargs a precise dict[str, str] annotation so mypy --strict no longer flags a bare generic. The only remaining mypy error is the expected unresolved import of agent_assembly.adapters.llamaindex (adapter not yet on SDK master). Co-Authored-By: Claude Opus 4.8 (1M context) --- python/llamaindex-tool-policy/src/main.py | 2 +- python/llamaindex-tool-policy/src/tools.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/llamaindex-tool-policy/src/main.py b/python/llamaindex-tool-policy/src/main.py index 06a56c34..69416c8d 100644 --- a/python/llamaindex-tool-policy/src/main.py +++ b/python/llamaindex-tool-policy/src/main.py @@ -42,7 +42,7 @@ ] -def _run_governed_call(tool: object, kwargs: dict) -> None: +def _run_governed_call(tool: object, kwargs: dict[str, str]) -> None: name = tool.metadata.get_name() # type: ignore[attr-defined] print(f" → {name}({kwargs})") # The adapter has patched FunctionTool.call, so this single call is governed. diff --git a/python/llamaindex-tool-policy/src/tools.py b/python/llamaindex-tool-policy/src/tools.py index 6eecf849..39606b10 100644 --- a/python/llamaindex-tool-policy/src/tools.py +++ b/python/llamaindex-tool-policy/src/tools.py @@ -5,6 +5,7 @@ - summarize_docs — safe summarisation tool (ALLOWED by policy) - execute_sql — arbitrary SQL execution (DENIED by policy) """ + from __future__ import annotations from llama_index.core.tools import FunctionTool From 71eead99d1e927c5e6eb54e271f6380f4a5f737b Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 22 Jun 2026 16:52:53 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=85=20(llamaindex-example):=20Refresh?= =?UTF-8?q?=20lock=20so=20llamaindex=20adapter=20resolves;=20ruff=20+=20my?= =?UTF-8?q?py=20--strict=20clean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump uv.lock to the python-sdk master tip (ae2d557) that ships agent_assembly.adapters.llamaindex; the prior pin (7181dc6) predated the merged adapter so the import failed. Tests were already fully annotated with no ANN suppressions; ruff, mypy --strict, and pytest are clean over src and tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- python/llamaindex-tool-policy/uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/llamaindex-tool-policy/uv.lock b/python/llamaindex-tool-policy/uv.lock index d0cb4ef0..05371e51 100644 --- a/python/llamaindex-tool-policy/uv.lock +++ b/python/llamaindex-tool-policy/uv.lock @@ -5,7 +5,7 @@ requires-python = ">=3.12" [[package]] name = "agent-assembly" version = "0.0.1b4" -source = { git = "https://github.com/ai-agent-assembly/python-sdk.git?branch=master#7181dc693192bc29d7bf85a6cf6eac5a8f78c4bf" } +source = { git = "https://github.com/ai-agent-assembly/python-sdk.git?branch=master#ae2d557f7e446532d53f49e7b5a67293fa9b1799" } dependencies = [ { name = "grpcio" }, { name = "httpx" },