Metadata-Version: 2.5
Name: decionis-langchain
Version: 0.1.0
Summary: Gate LangChain tool calls and LangGraph nodes on a signed Decionis Decision Dossier.
Project-URL: Homepage, https://decionis.com
Project-URL: Documentation, https://decionis.ai/adapters
Author-email: Decionis <sdk@decionis.ai>
License: MIT
Keywords: agent,audit,decionis,execution-gate,governance,langchain,langgraph,policy,tool
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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 :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: decionis>=0.2
Requires-Dist: langchain-core>=0.3
Requires-Dist: pydantic>=2.7
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Provides-Extra: lint
Requires-Dist: mypy>=1.10; extra == 'lint'
Requires-Dist: ruff>=0.5; extra == 'lint'
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# `decionis-langchain`

Gate any **LangChain** tool call or **LangGraph** node on a signed
[Decionis](https://decionis.com) Decision Dossier. The agent picks the tool;
Decionis decides whether the call is allowed to fire — and records every
verdict as a verifiable proof artifact.

```
pip install decionis-langchain
```

## Why

LangChain agents can be jailbroken, prompt-injected, or simply hallucinated
into firing tools that move money, change pricing, send refunds, or delete
data. Wrapping the tool with Decionis means:

- **Every tool invocation gets a signed Decision Dossier** — the policy
  verdict (`ALLOW` / `BLOCK` / `REVIEW_REQUIRED` / `ESCALATE`), the agent
  identity, the call arguments, and a public verify URL.
- **Blocked calls short-circuit before the inner tool runs** — the LLM sees a
  structured refusal carrying the dossier id; the caller's audit log keeps
  the proof.
- **Shadow mode** records verdicts without blocking, so a team can roll out
  policy gradually and review the would-have-blocked rate before enforcing.

## Quick start — wrap a LangChain tool

```python
from decionis import DecionisClient
from decionis_langchain import DecionisGateTool
from langchain_core.tools import tool

@tool
def send_refund(customer_id: str, amount_usd: int) -> str:
    """Issue a refund. Idempotent on customer_id + amount + day."""
    ...

client = DecionisClient(api_key="…", base_url="https://api.decionis.com")

gated_refund = DecionisGateTool.wrap(
    inner_tool=send_refund,
    client=client,
    tenant_id="org-uuid",
    workflow_key="refund_execution",
    site_base_url="https://decionis.com",
)

agent.bind_tools([gated_refund])
```

When the LLM picks `send_refund`, Decionis evaluates the call first. On
`ALLOW` the inner tool runs and the dossier id rides along on the result. On
any blocking verdict the wrapper raises `DecionisGateRefusal` with the
dossier id, reason codes, and the public verify URL.

## Quick start — LangGraph node

```python
from langgraph.graph import StateGraph, END
from decionis_langchain import decionis_gate_node

graph = StateGraph(MyState)
graph.add_node("plan", plan_node)
graph.add_node(
    "gate",
    decionis_gate_node(
        client=client,
        tenant_id="org-uuid",
        workflow_key="refund_execution",
        site_base_url="https://decionis.com",
        # Pull the agent's chosen action out of the state to evaluate it.
        extract_call=lambda s: (s["proposed_tool"], s["proposed_args"]),
    ),
)
graph.add_node("execute", execute_node)
graph.add_node("refuse", refuse_node)

graph.add_edge("plan", "gate")
graph.add_conditional_edges(
    "gate",
    lambda s: s["decionis"]["outcome"],
    {"allowed": "execute", "blocked": "refuse"},
)
graph.add_edge("execute", END)
graph.add_edge("refuse", END)
```

The node writes a JSON-serializable record under `state["decionis"]` so the
graph stays checkpointable and the conditional edge can branch on
`allowed` or `blocked`. In enforcement an `ERROR` verdict routes to `blocked`.

## Shadow-mode rollout

Same pattern as the GitHub Action: ship in shadow first, review the verdict
distribution, then flip to enforce.

The end-to-end PLG funnel — pick a surface → install in shadow → watch verdicts → flip —
is walked at
[**decionis.com/shadow-mode**](https://decionis.com/shadow-mode).

```python
DecionisGateTool.wrap(
    inner_tool=send_refund,
    client=client,
    tenant_id="org-uuid",
    workflow_key="refund_execution",
    shadow_mode=True,   # ← every verdict recorded; inner tool always runs
)
```

In shadow mode the gate **never** raises — even on `BLOCK` — so existing
agent behaviour is unchanged. Verdicts still flow into the dossier ledger so
the rollout team can grade policy fit before enforcing.

That includes a failed decision request (a timeout, an HTTP error, no
connection): in shadow mode the tool runs anyway, the gate logs a warning, and
`on_decision` receives the failure as `error`, with `decision=None`. The
LangGraph node routes to `allowed` and records the failure under `error`. In
enforcement the request's error is raised and the tool does not run.

## Tunables

| Argument         | Default                                     | Purpose                                                                                                                                |
| ---------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `tenant_id`      | required                                    | Decionis org / tenant id.                                                                                                              |
| `workflow_key`   | required                                    | Policy bundle key (e.g. `refund_execution`).                                                                                           |
| `client`         | required                                    | A `decionis.DecionisClient` instance.                                                                                                  |
| `block_statuses` | `(BLOCK, REVIEW_REQUIRED, ESCALATE, ERROR)` | Which verdicts cause the gate to short-circuit. Pass `(BLOCK,)` to let review verdicts through with their dossier still recorded.      |
| `shadow_mode`    | `False`                                     | Run policy but never block.                                                                                                            |
| `site_base_url`  | `None`                                      | Override to build a public verify URL (`/verify/decision-dossiers/<id>?sig=…&source=langchain_agent`) when the SDK doesn't supply one. |
| `actor`          | `{"type": "ai_agent", "framework":"…"}`     | Extra actor metadata (model name, session id) for the dossier.                                                                         |
| `on_decision`    | `None`                                      | Observer callback `(GateResult) -> None`. Use for in-app telemetry. Exceptions in the observer never break the gate.                   |

## Honesty notes

- `shadow_mode=True` is the only switch that lets the inner tool run on a
  blocking verdict; the default never silently passes a `BLOCK`.
- `DecionisGateRefusal.verify_url` is the same artifact link Slack / Teams /
  LinkedIn unfurl with the OG card. Forward it to a reviewer when you want
  the proof one click away.
- The wrapper preserves the inner tool's name, description, and args schema
  unchanged so the LLM's tool selection behaviour does not drift.

## Compatibility

- Python ≥ 3.10
- `decionis` ≥ 0.2
- `langchain-core` ≥ 0.3
- `langgraph` ≥ 0.2 (only required if you use `decionis_gate_node`)
