Metadata-Version: 2.4
Name: langchain-fairseal
Version: 0.1.0
Summary: LangChain callback handler that notarizes every tool call on FairSeal (on-chain non-repudiation)
Author-email: FairSeal <hello@fairseal.io>
License: MIT
Project-URL: Homepage, https://fairseal.io
Project-URL: Repository, https://github.com/ned-del/fairseal
Project-URL: Documentation, https://fairseal.io/docs/notarize
Project-URL: Bug Tracker, https://github.com/ned-del/fairseal/issues
Keywords: langchain,fairseal,x402,blockchain,audit,non-repudiation
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.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: langchain-core>=0.3
Requires-Dist: requests>=2.28
Provides-Extra: langsmith
Requires-Dist: langsmith>=0.4; extra == "langsmith"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-mock>=3.12; extra == "dev"
Requires-Dist: responses>=0.25; extra == "dev"
Requires-Dist: langsmith>=0.4; extra == "dev"
Requires-Dist: langchain>=0.3; extra == "dev"
Dynamic: license-file

# langchain-fairseal

> **LangSmith tells you what the agent did. FairSeal proves it actually happened — on-chain.**

`langchain-fairseal` is a LangChain callback handler that notarizes every tool call
on [FairSeal](https://fairseal.io): a cryptographic receipt layer anchored to Base mainnet.
Receipts are independently verifiable at [verify.fairseal.io](https://verify.fairseal.io)
by any party without trusting LangChain, AWS, or your infrastructure.

---

## Quick Start

```bash
pip install langchain-fairseal
```

```python
import os
from langchain_fairseal import FairSealCallbackHandler

# Handler: notarize every tool call, write receipt to LangSmith spans
handler = FairSealCallbackHandler(
    api_key=os.environ["FAIRSEAL_NOTARY_API_KEY"],  # fsn_... key
    agent_id="my-agent/prod-v1",                   # stable agent identifier
    sample_rate=1.0,                               # 100% notarized; 0.1 = 10% sampled
    fail_open=True,                                # notarize failures never block agent
    attach_to_langsmith=True,                      # write receipt_id to LangSmith spans
)

# Pass to any LangChain chain / agent / tool via config
result = my_chain.invoke(
    {"input": "What is the weather in Taipei?"},
    config={"callbacks": [handler]},
)

# Inspect receipts
print(handler.summary())
# {'total_notarized': 2, 'successful': 2, 'failed': 0, 'receipt_ids': ['nr_...', 'nr_...']}

last = handler.last_receipt
print(f"Latest receipt: {last.receipt_id}")
print(f"Verify: {last.verify_url}")
```

---

## Architecture

```
LangChain Agent / Chain
        │
        ▼
  on_tool_start()  ──────────────────────────────────────────────────────┐
        │                                                                 │
        │  sha256(inputs) → input_hash                                   │
        │  (raw inputs NEVER leave agent runtime)                        │
        ▼                                                                 │
  [tool executes]                                                         │
        │                                                                 │
        ▼                                                                 │
  on_tool_end()                                                           │
        │                                                                 │
        │  sha256(output) → decision_hash                                │
        │                                                                 │
        ▼                                                                 │
  POST api.fairseal.io/v1/notarize                                        │
    { schema: "agent_decision",                                          │
      metadata: { agent_id, input_hash, decision_hash, decided_at } }   │
        │                                                                 │
        ▼                                                                 │
  receipt_id = "nr_..."  ◄──────────────────────────────────────────────┘
        │
        ├──► LangSmith run span metadata (fairseal_receipt_id)
        └──► handler.receipts  [in-memory list]

  verify.fairseal.io/nr_...  ← independent verification, no LangChain/AWS trust
```

### Extension Point Note (LangChain 0.3.30)

The LangChain/AgentCore Payments blog post (2026-08-18) refers to an
`AgentMiddleware.wrap_tool_call` interface. **This class does not exist in LangChain
0.3.30 / langchain-core 0.3.86** (confirmed by import inspection).

This package uses the stable, documented callback system:
- `BaseCallbackHandler.on_tool_start` — capture tool name + input hash
- `BaseCallbackHandler.on_tool_end` — capture output hash + fire notarize
- `BaseCallbackHandler.on_tool_error` — fail-open cleanup + error notarize

When/if `AgentMiddleware` ships as a stable API, the handler can be refactored
to use it without changing the public interface.

---

## Configuration

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `api_key` | str | env `FAIRSEAL_NOTARY_API_KEY` | FairSeal notary key (`fsn_...`) |
| `agent_id` | str | env `FAIRSEAL_AGENT_ID` or `"langchain-agent"` | Stable agent identifier |
| `sample_rate` | float | `1.0` | Fraction of tool calls to notarize (0.0–1.0) |
| `fail_open` | bool | `True` | If True, notarize errors never block the agent |
| `attach_to_langsmith` | bool | `True` | Write receipt to LangSmith run span metadata |
| `model` | str | None | Optional model tag in receipt metadata |
| `operator` | str | None | Optional operator tag in receipt metadata |

### Environment Variables

```bash
FAIRSEAL_NOTARY_API_KEY=fsn_...   # Primary auth key
FAIRSEAL_API_KEY=fsn_...          # Fallback (also accepted)
FAIRSEAL_AGENT_ID=my-agent/prod   # Default agent_id
FAIRSEAL_API_URL=https://api.fairseal.io  # Override API base (for testing)
FAIRSEAL_TIMEOUT_S=10             # HTTP timeout in seconds
LANGCHAIN_API_KEY=ls_...          # For LangSmith span attachment
```

---

## PII Safety

Only SHA-256 hashes of tool inputs and outputs are transmitted to FairSeal.
**Raw payload text never leaves the agent runtime.** This is enforced in
`langchain_fairseal.notarize.sha256_of()` and tested in `tests/test_notarize.py`.

---

## LangSmith Integration

When `attach_to_langsmith=True` and `LANGCHAIN_API_KEY` is set, each successful
receipt is written to the LangSmith run span as:

```json
{
  "fairseal_receipt_id": "nr_...",
  "fairseal_verify_url": "https://verify.fairseal.io/nr_...",
  "fairseal_tool_name": "search_tool",
  "fairseal_agent_id": "my-agent/prod-v1"
}
```

Visible in the LangSmith trace UI under run metadata.

---

## Development

```bash
pip install -e ".[dev]"
pytest -v                     # unit tests (mocked HTTP)
pytest tests/test_e2e.py -v   # e2e (requires FAIRSEAL_NOTARY_API_KEY)
```

---

## Positioning

| Concern | LangSmith | FairSeal |
|---------|-----------|----------|
| What did the agent do? | ✅ Full trace | ❌ Not designed for this |
| Prove it actually happened? | ❌ Internal log only | ✅ On-chain, third-party verifiable |
| Third-party verification? | ❌ Requires LangChain trust | ✅ Independent, no trust needed |
| Real-time observability? | ✅ | ❌ Batch anchoring (~2 min) |

Use both: LangSmith for debugging and observability, FairSeal for compliance and
non-repudiation.

---

## License

MIT © 2026 FairSeal
