provared-langchain
A LangChain middleware that checks each tool call of an agent against the permission a person signed, runs the tool only if the permission allows it, and records the call with Provared.
Evidence, not a verdict.
Version 0.1.0, a draft, as Provared itself is. Published as it is, by one author in their own time; fixes as time allows.
What it does
Provared is a way to prove what an AI agent was allowed to do, and what it then did. A person signs a slip with a passkey: which agent, which actions, which limits, until when. Beside the agent, Provared's stub writer checks each action against the slip before it is taken and, if it is allowed, writes a signed stub for it into a book that anyone can check later.
ProvaredMiddleware puts every tool call of a LangChain agent
(create_agent) behind that stub writer:
- Allowed: the tool runs, and a stub is written. The stub names the action, the amount and the other party where they apply, and the SHA-256 fingerprint of the call's arguments, so the record fixes exactly what was asked for without holding it.
- Not allowed (over a limit, an action the slip does not name, after
the slip ends or was cancelled): the tool is not run, nothing is
written, and the model is handed a failed tool answer that says why,
for example:
The tool "order_supplies" was not run. With this action the total would be 280 GBP. The slip's limit is 100 GBP: over by 180. - A tool that is not described to the middleware is not run either,
unless you pass
unlisted="run": the model is told, and Python gives aRuntimeWarning, so that a misspelt name shows.
Install
pip install provared-langchain
You need Python 3.11 or later. It installs provared, langchain,
langchain-core and pydantic.
Use
from langchain.agents import create_agent
from provared import open_recorder
from provared_langchain import ProvaredMiddleware
# The stub writer, on the book that holds the person's signed slip.
writer = open_recorder(book_text, slip_fingerprint, agent_private_keys, issuer_keys=[passkey_thumbprint])
# Last in the list: any middleware that wraps tool calls goes before it.
agent = create_agent(model, tools=[order_supplies, send_message], middleware=[
ProvaredMiddleware(writer, {
'order_supplies': {'action': 'supplies.order', 'with': 'supplier',
'amount': lambda args: {'unit': 'GBP', 'value': args['total_gbp']}},
'send_message': 'provared.message.send',
}),
])
agent.invoke({'messages': [{'role': 'user', 'content': 'Order paper.'}]})
book_text, held = writer.snapshot() # keep both together
For each tool, give the action name the slip uses for it, or a dict as
for provared.record_tools, without run: action, and where they apply
with, amount, details, countersign and approve. Each is checked
when the middleware is made, so a mistake in a description, such as a
misspelt member, shows when the agent is built.
How to make a slip, a book and the agent's keys is in Provared's
README and
Python README.
What to know
- It must come last among the middleware that wrap tool calls (human
review, retries, error handling, caching): put those before it. Then a
reviewer's change, or each retry, is checked and recorded as the call
the tool receives, and nothing can answer in the tool's place after
the check. If it is not last, the first tool call raises
provared.Refusal. - The arguments are checked against the tool's schema first, as
LangChain checks them, and written in their JSON form. That form is
what is fingerprinted, what the functions in a tool's description
(
amount,withand so on) are handed, and what the tool is run with; LangChain then reads it as it reads a model's arguments. So"30"given for a whole number is recorded as30. A call that does not fit is not run, and the model is told what is wrong. - No stub without a result. A tool that raises leaves no stub, and its error is passed on, as LangChain passes it on. A tool that answers with an error leaves no stub either.
- Ordinary and asynchronous agents both work (
invokeandainvoke). In an asynchronous agent the stub writer, an ordinary function that may wait, runs in a thread of its own for each call, and the tool runs on the event loop. Cancelling a run cancels a tool that is waiting: no stub is written, and the writer goes on. An ordinary tool that LangChain is already running in a thread cannot be stopped: it finishes, and leaves no stub, because its call was cancelled. - In an asynchronous program, do not call the stub writer on the event
loop while an agent may be running, not even
writer.snapshot(): it waits for the action it is taking, and that action needs the event loop. Call it from a thread:await asyncio.to_thread(writer.snapshot). - One call at a time. The stub writer takes one call at a time and holds it while the tool runs, so tools that LangChain would run side by side run one after another.
- A tool must not call the stub writer. From the tool itself, such a call is refused at once. From a thread that the tool starts without its context, it waits until the tool ends, so the tool must not wait for it.
- It covers the tool calls that LangChain runs. A tool that the model's own service runs on its side, such as a built-in web search, never reaches LangChain's tools and is not recorded.
- A fingerprint can be guessed where the arguments are few and easy to guess, such as one short name.
- What the record shows, and what it does not, is set out in Provared's README and its list of threats. In short: it shows what was recorded under a slip the person signed, not what was left out, and not whether an action was wise or lawful.
Tests
In a copy of the repository (the tests use the invented world in
python/examples/), from this folder, with LangChain and Provared
installed:
PYTHONPATH=src python -m unittest discover -s tests
The tests run an agent with LangChain's own stand-in model, scripted in advance. No AI service is called, and a guard refuses any connection outside the program while they run.
Licences
As for the rest of the repository: the Apache License 2.0 for the code, and Creative Commons Attribution 4.0 for this page.
Metadata
Release files for provared-langchain 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| provared_langchain-0.1.0.tar.gz | 23.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| provared_langchain-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.6 kB
Release files / provared_langchain-0.1.0.tar.gz
| Download URL | provared_langchain-0.1.0.tar.gz |
|---|---|
| Size | 23.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3321a3dfb87ed5baa4affcf3b921ba8447062b292eb2fd868586c111daa2f043
|
|
BLAKE2b-256 checksum How to use checksums |
2255e7b02369d10b37a2218794ded5e8ff59a447ad2db903ad0f66e8dacddbcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / provared_langchain-0.1.0-py3-none-any.whl
| Download URL | provared_langchain-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e056839afc00265aea07faa9b1fdbd5a47ba7c86efaf3b6f3fa8effaf906177
|
|
BLAKE2b-256 checksum How to use checksums |
835e7807eed82fc660ae8a250bcb257977bd03f626a0b3bdec1bd45806ba8418
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|