Metadata-Version: 2.4
Name: aiyoplane-langchain
Version: 1.0.0
Summary: AEAP Composition Boundary Adapter for LangChain. Thin wrapper that attaches the Runtime Decision Point to LangChain tool invocations.
Author-email: "Aiyoplane, Inc." <rashon@aiyoplane.com>
License: Apache-2.0
Project-URL: Homepage, https://aiyoplane.com
Project-URL: Documentation, https://github.com/aiyoplane/langchain-python
Project-URL: Repository, https://github.com/aiyoplane/langchain-python
Project-URL: Issues, https://github.com/aiyoplane/langchain-python/issues
Project-URL: Trust Surface, https://aiyoplane.com/trust
Project-URL: AEAP Specification, https://github.com/aiyoplane/aeap
Keywords: aiyo,aiyoplane,aeap,langchain,authorization,runtime-decision-point,rdp,execution-receipt,composition-boundary,agent-authorization,settlement-verified
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: aiyoplane-mcp-authz>=1.0.1
Requires-Dist: langchain-core<0.4,>=0.3
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.21; extra == "test"
Dynamic: license-file

# aiyoplane-langchain

**AEAP Composition Boundary Adapter for LangChain.**

A thin wrapper that attaches the AEAP Runtime Decision Point (RDP) to LangChain's tool-invocation surface. Consequential tool executions produce independently verifiable Execution Receipts. The adapter does not alter LangChain's agent, memory, prompt, or retrieval surfaces — only the specific boundary where a tool is about to execute.

```bash
pip install aiyoplane-langchain
```

## What this adapter is (and is not)

**It is:** the shape AEAP takes when attached to a LangChain `BaseTool`. The underlying authorization logic lives in [`aiyoplane-mcp-authz`](https://pypi.org/project/aiyoplane-mcp-authz/); this package distributes that logic into the LangChain ecosystem under LangChain-native idioms.

**It is not:** a new authorization product. It is not a LangChain plugin that adds features. It is not a LangSmith / LangFuse replacement. It has no feature backlog of its own — if the behavior needs changing, the change belongs in `aiyoplane-mcp-authz` so every AEAP adapter inherits it.

See the [AEAP specification](https://github.com/aiyoplane/aeap) §9 for the Composition Boundary primitive this adapter realizes.

## Install

```bash
pip install aiyoplane-langchain
# Peer dependencies installed automatically:
#   aiyoplane-mcp-authz >= 1.0.1
#   langchain-core >= 0.3, < 0.4
```

## Minimal usage

```python
from langchain_core.tools import tool
from aiyoplane_mcp_authz import create_aiyo_mcp_authz, create_local_rdp
from aiyoplane_langchain import wrap_tool

# 1. Define a LangChain tool as you normally would.
@tool
def transfer_funds(amount: int, to_account: str) -> str:
    """Transfer funds between accounts."""
    return _actually_transfer(amount, to_account)

# 2. Define the AEAP policy (local RDP for development; HostedRdp for production).
policy = {
    "rules": [
        {"action_type": "payment", "amount_max": 1_000, "effect": "allow"},
        {"action_type": "payment", "amount_max": 10_000, "effect": "escalate"},
        {"action_type": "payment", "effect": "block"},
    ]
}

authz = create_aiyo_mcp_authz(
    rdp=create_local_rdp(policy=policy),
    tool_config={
        "transfer_funds": {
            "aiyo_gated": True,
            "action": {"type": "payment"},
        },
    },
)

# 3. Wrap the tool at its Composition Boundary.
guarded_transfer = wrap_tool(
    transfer_funds,
    authz=authz,
    intent_builder=lambda args: {
        "type": "payment",
        "amount": args["amount"],
        "target": args["to_account"],
    },
)

# 4. Use the wrapped tool anywhere LangChain accepts a tool.
#    On ALLOW:    the inner tool runs normally.
#    On ESCALATE: aiyoplane_langchain.EscalationRequired is raised.
#    On BLOCK:    aiyoplane_langchain.BlockedByPolicy is raised; the tool never executes.
```

## Wrapping a whole tool list

The common case is "here are my agent's tools; some of them need AEAP gating."

```python
from aiyoplane_langchain import wrap_tools

all_tools = [search_docs, read_user, transfer_funds, delete_account]

guarded_tools = wrap_tools(
    all_tools,
    authz=authz,
    intent_builders={
        "transfer_funds": lambda args: {"type": "payment", "amount": args["amount"]},
        "delete_account": lambda args: {"type": "destructive", "target": args["user_id"]},
        # search_docs and read_user are omitted — they pass through un-wrapped.
    },
)
```

Tools whose name is not a key in `intent_builders` pass through unchanged. This is intentional: AEAP gating is an **explicit opt-in**, not a wholesale wrapper. The Protected Party's policy decides which actions are consequential.

## Composition Boundary — what the adapter actually does

The adapter is a wrapper around LangChain's `BaseTool._run` / `_arun`. On invocation:

1. **Intent construction.** The adapter calls the user-supplied `intent_builder(tool_args)` to produce an AEAP Intent payload. See [AEAP §3](https://github.com/aiyoplane/aeap/blob/main/AEAP.md).
2. **RDP evaluation.** The Intent is passed to the `AiyoAuthz` middleware from `aiyoplane-mcp-authz`, which calls the RDP (local or hosted).
3. **Verdict composition.**
   - **ALLOW** → the inner tool's `_run` / `_arun` executes with the original arguments. The verified Execution Receipt is available for inspection and optionally returned alongside the result (see `return_receipt=True`).
   - **ESCALATE** → `EscalationRequired` is raised. The exception carries an `escalation_id` for correlation. The LangChain agent / chain decides how to route the escalation.
   - **BLOCK** → `BlockedByPolicy` is raised. The inner tool never executes.
4. **Fail-closed default.** Any ambiguous condition — missing `intent_builder`, non-dict Intent, RDP unreachable, RDP exception, misconfigured `tool_config` — results in `BlockedByPolicy`. See [AEAP §2.2 invariant 5](https://github.com/aiyoplane/aeap/blob/main/AEAP.md).

## Local vs. hosted RDP

The adapter is RDP-agnostic — it delegates evaluation to the `AiyoAuthz` middleware, which accepts either a `LocalRdp` (in-process, for development and self-hosted deployments) or a `HostedRdp` (connects to `api.aiyoplane.com`, for managed deployments). Switching is a one-line change in the `AiyoAuthz` construction; the adapter and the LangChain tool are unchanged.

```python
# Development / self-hosted:
from aiyoplane_mcp_authz import create_local_rdp
authz = create_aiyo_mcp_authz(rdp=create_local_rdp(policy=policy), tool_config={...})

# Production / hosted:
from aiyoplane_mcp_authz import create_hosted_rdp
authz = create_aiyo_mcp_authz(
    rdp=create_hosted_rdp(api_key="aiyo_live_..."),
    tool_config={...},
)
```

## API reference

### `wrap_tool(tool, *, authz, intent_builder, tool_name_for_authz=None, return_receipt=False)`

Wrap a single `BaseTool`. Returns an `AiyoLangChainTool`.

- `tool` — the original LangChain tool
- `authz` — an `AiyoAuthz` instance
- `intent_builder` — a callable `(tool_args: dict) -> dict`
- `tool_name_for_authz` — override for the `authz.tool_config` key; defaults to `tool.name`
- `return_receipt` — if `True`, the wrapped tool returns `{"result": ..., "aeap_receipt": ...}` on ALLOW

### `wrap_tools(tools, *, authz, intent_builders, return_receipt=False)`

Wrap a collection in one call. Tools whose `name` is not a key in `intent_builders` pass through unchanged.

### `AiyoLangChainTool`

The `BaseTool` subclass produced by `wrap_tool`. You rarely need to construct this directly.

### Exceptions

- `AiyoLangChainError` — base class
- `AdapterConfigError` — adapter configuration is invalid (missing `tool_config` entry, bad `intent_builder`)
- `BlockedByPolicy` — RDP returned BLOCK; the tool did not execute
- `EscalationRequired` — RDP returned ESCALATE; the tool call is held pending resolution

## License

Apache License 2.0. See `LICENSE` for the full text.

## Links

- **AEAP specification:** https://github.com/aiyoplane/aeap
- **Underlying implementation:** [`aiyoplane-mcp-authz`](https://pypi.org/project/aiyoplane-mcp-authz/)
- **Node sibling:** `@aiyoplane/langchain` on npm
- **Related adapters:** `aiyoplane-langgraph` (same install model, graph-node composition)
- **Trust surface:** https://aiyoplane.com/trust
- **Issues:** https://github.com/aiyoplane/langchain-python/issues

LangChain is a trademark of LangChain, Inc. This package is an independent AEAP Composition Boundary Adapter and is not affiliated with or endorsed by LangChain, Inc.

**Verify First. Execute Second.**
