Metadata-Version: 2.4
Name: saf3ai-sdk
Version: 0.2.5
Summary: Observability and security scanning SDK for supported Python AI agent frameworks
Project-URL: PyPI, https://pypi.org/project/saf3ai-sdk/
Author-email: Saf3AI Team <team@saf3ai.com>
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT 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
Requires-Python: >=3.9
Requires-Dist: opentelemetry-api>=1.20.0
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
Requires-Dist: opentelemetry-instrumentation-dbapi>=0.40b0
Requires-Dist: opentelemetry-instrumentation-mysql>=0.40b0
Requires-Dist: opentelemetry-instrumentation-psycopg2>=0.40b0
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.40b0
Requires-Dist: opentelemetry-instrumentation>=0.40b0
Requires-Dist: opentelemetry-sdk>=1.20.0
Requires-Dist: opentelemetry-semantic-conventions>=0.40b0
Requires-Dist: packaging<25.0,>=21.0
Requires-Dist: psutil<7.0.0,>=5.9.0
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: requests<3.0.0,>=2.28.0
Requires-Dist: termcolor<2.5.0,>=2.3.0
Requires-Dist: wrapt<2.0.0,>=1.14.0
Provides-Extra: dev
Requires-Dist: black>=22.0.0; extra == 'dev'
Requires-Dist: isort>=5.10.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

<p align="center">
  <strong>Saf3AI SDK for Python</strong><br/>
  Observability, security scanning, and data lineage for production AI agents
</p>

<p align="center">
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/pypi/pyversions/saf3ai-sdk?label=Python" alt="Python versions"/></a>
  <a href="https://pypi.org/project/saf3ai-sdk/"><img src="https://img.shields.io/pypi/v/saf3ai-sdk?label=PyPI" alt="PyPI version"/></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License MIT"/></a>
</p>

---

## Overview

**saf3ai-sdk** is the official Python client for the **Saf3AI platform**. It combines:

1. **Observability** — Saf3AI-native tracing with rich attributes (conversation IDs, models, usage, lineage).
2. **Security** — Integration with the Saf3AI **Scanner** for prompt and optional response analysis, with policy callbacks.
3. **Governance** — Automatic capture of data sources (HTTP, tools, DB access) onto traces for audit and compliance workflows.

Call **`init()` once** at process startup. The SDK detects your agent framework, configures the **Saf3AI TraceProvider**, connects to your Saf3AI platform account, and enables framework-aware instrumentation.

---

## Table of contents

- Why use this SDK
- Platform capabilities
- Architecture
- Requirements
- Installation
- Quick start
- Configuration
- Supported frameworks
- Observability conventions
- Security scanning
- Production checklist
- API surface
- Troubleshooting
- Recent changes
- Support & license

---

## Why use this SDK

| Need | How the SDK helps |
|------|-------------------|
| **Single pane for agents** | One initialization path sends trace data to Saf3AI with consistent service and semantic attributes. |
| **Safe defaults** | Scanner and platform auth can share **`SAF3AI_API_KEY`** unless explicitly overridden. |
| **Framework drift** | Optional imports: install only ADK, LangChain, CrewAI, OpenAI Agents, AutoGen, or Bedrock; detection order is documented and predictable. |
| **Operational clarity** | Conversation stitching and cross-framework capture improve troubleshooting in production. |

---

## Platform capabilities

### Observability

- Saf3AI **`TraceProvider`** owned by the SDK (set during **`init()`**).
- Trace delivery to your **Saf3AI platform workspace**.
- Framework tracers: `saf3ai-adk`, `saf3ai-langchain`, `saf3ai-crewai`, `saf3ai-rest`.
- Optional **console** export for local development.

### Security

- Prompt scanning and optional response scanning via callbacks or direct helpers.
- Policy hooks return allow/block; results can be attached to traces when enabled.

### Data lineage

- **HTTP** — Outbound request sources can be attached to active traces.
- **MCP** — Tool and resource usage can be attached as lineage entries.
- **Databases** — Database operations can be captured for governance and compliance contexts.

---

## Architecture

```mermaid
flowchart TB
  classDef box fill:#f8fbff,stroke:#2b5876,stroke-width:1.5px,color:#0f172a;
  classDef platform fill:#eefbf3,stroke:#1f7a4a,stroke-width:1.5px,color:#0f172a;

  subgraph App["Application Layer"]
    A[Your Agent Experience]
  end

  subgraph SDK["Saf3AI SDK Layer"]
    I[Initialize SDK]
    T[Saf3AI TraceProvider]
    F[Framework Integrations]
    G[Security and Governance Controls]
  end

  subgraph Platform["Saf3AI Platform Layer"]
    P[Unified Saf3AI Platform]
  end

  A --> I
  I --> T
  I --> F
  I --> G
  T --> P
  F --> P
  G --> P

  class A,I,T,F,G box;
  class P platform;
```

---

## Requirements

| Item | Detail |
|------|--------|
| **Python** | 3.9, 3.10, 3.11, or 3.12 |
| **Network** | Outbound HTTPS access to Saf3AI platform endpoints |
| **Credentials** | Organization API key and Saf3AI account endpoint |
| **Frameworks** | Optional: Google ADK, LangChain / LangFlow, CrewAI, OpenAI Agents, Microsoft AutoGen, or Amazon Bedrock — install only what you run |

---

## Installation

```bash
pip install saf3ai-sdk
```

This command installs the version currently published on PyPI. This checkout prepares **0.2.5**; to verify it before publication, install it with `pip install -e .` and check `python -m pip show saf3ai-sdk`. Once published, deploy the tested version explicitly with `pip install saf3ai-sdk==0.2.5`.

**Development dependencies** (formatting, tests, types):

```bash
pip install saf3ai-sdk[dev]
```

---

## Quick start

Initialize **before** you construct agents or LLM clients.

```python
import os
from saf3ai_sdk import init, get_detected_framework

init(
    agent_id=os.environ["SAF3AI_AGENT_ID"],
    api_key=os.environ["SAF3AI_API_KEY"],
    safeai_collector_agent=os.environ["SAF3AI_COLLECTOR_AGENT"],
    service_name=os.getenv("SAF3AI_SERVICE_NAME"),
    scanner_endpoint=os.getenv("SAF3AI_SCANNER_ENDPOINT"),
    # Only needed when the policy's registered agent identifier differs from agent_id:
    scanner_agent_identifier=os.getenv("SAF3AI_SCANNER_AGENT_IDENTIFIER"),
)

print(get_detected_framework())  # e.g. "adk", "langchain", "crewai", "openai-agents", "autogen", "bedrock", "custom"
```

For richer configuration, pass an **`SDKConfig`** object into **`init()`** (see **`saf3ai_sdk.config.sdk_config`**).

---

## Configuration

### Required parameters or environment

| Variable / parameter | Purpose |
|----------------------|--------|
| **`agent_id`** | Stable identifier for this agent instance in trace data |
| **`scanner_agent_identifier`** | Registered agent identifier used by agent-scoped scanner rules; defaults to `agent_id` |
| **`api_key`** / **`SAF3AI_API_KEY`** | Authenticates platform and scanner access |
| **`safeai_collector_agent`** | Saf3AI endpoint base URL (required if not injected by your runtime wrapper) |

> **Security:** Never commit API keys. Use secret managers or CI-injected variables in production.

### Frequently used optional variables

| Variable | Purpose |
|----------|---------|
| `SAF3AI_SERVICE_NAME` | Human-readable `service.name` when different from `agent_id` |
| `SAF3AI_SCANNER_ENDPOINT` | Scanner API base URL |
| `SAF3AI_SCANNER_AGENT_IDENTIFIER` | Example application variable passed explicitly to `scanner_agent_identifier`; the SDK does not read it on its own |
| `SAF3AI_SCAN_RESPONSES` | Scan model outputs (`true` / `false`) |
| `SAF3AI_CAPTURE_RESPONSES` | Attach response text to traces when permitted by policy |
| `SAF3AI_DEBUG_MODE` | Verbose SDK logging |
| `SAF3AI_CONSOLE_OUTPUT` | Emit trace data to stdout (development) |
| `SAF3AI_CLIENT_DATA_CAPTURE` | Client context enrichment where supported |

### Example: shell

```bash
export SAF3AI_API_KEY="your-key"
export SAF3AI_COLLECTOR_AGENT="https://collector.example.com"
export SAF3AI_SCANNER_ENDPOINT="https://scanner.example.com"
export SAF3AI_AGENT_ID="claims-agent-prod"
```

---

## Supported frameworks

Detection order: **ADK -> CrewAI -> LangChain -> OpenAI Agents -> AutoGen -> Bedrock -> custom**.

| Framework | Auto-detected | Built-in instrumentation after `init()` |
|-----------|--------------|-------------------------------------------|
| **Google ADK** | Yes | Yes (`saf3ai-adk`) |
| **LangChain** / LangFlow | Yes | Yes (`saf3ai-langchain`) |
| **CrewAI** | Yes | Yes (`saf3ai-crewai`) |
| **OpenAI Agents** | Yes | Yes (`saf3ai-openai-agents`) |
| **Microsoft AutoGen** | Yes | Yes (`saf3ai-autogen`) |
| **Amazon Bedrock** | Yes | Yes (`saf3ai-bedrock`) |
| **Custom / REST** | Fallback | Yes (`saf3ai-rest`) |

Install only the stack your service imports; the SDK selects the first matching framework.

---

## Observability conventions

- Traces include standardized Saf3AI metadata such as **`service.name`**, **`saf3ai.sdk.version`**, and environment.
- Conversation stitching uses **`gen_ai.conversation.id`** and **`conversation_id`** helpers (`get_conversation_id_unified`, `set_conversation_id_unified`, `reset_conversation`).
- Third-party spans may omit conversation attributes; the SDK aligns them with the unified conversation where possible.

---

## Security scanning

### Agent-scoped rules in 0.2.5

An agent-scoped rule matches only when the `/scan` request includes the exact registered identifier as `metadata.agent_identifier`. Automatic `@traceable` prompt **and** response scans now send it. The value comes from `scanner_agent_identifier` in `init()` (default: `agent_id`); `@traceable(agent_identifier="...")` overrides it for one decorated agent. Use the registered identifier, not a display name or function name. This is especially important when one process hosts multiple agents.

```python
from saf3ai_sdk import init, traceable

init(
    agent_id="it-service-agent",
    api_key="...",  # Load from a secret manager or environment in production.
    safeai_collector_agent="https://your-collector.example",
    scanner_endpoint="https://your-scanner.example",
    scanner_agent_identifier="registered-policy-id",  # Must exactly match the policy identifier.
)

@traceable(name="it-service")
def answer(prompt: str) -> str:
    return call_your_model(prompt)
```

Replace the placeholder with the identifier actually registered for your agent, including its exact spelling. In the reported test this was `it service agetn`; a corrected-looking name would not match that rule. For a multi-agent process, put `agent_identifier="..."` on each `@traceable` decorator instead of relying on one process-wide default. Direct `scan_prompt()` and `scan_response()` calls still accept `metadata={"agent_identifier": "..."}`.

**Detection and blocking are different.** Automatic `@traceable` scanning records scan results; it does not, by itself, interpret every match as a block. To prevent a prompt reaching the model, register a policy callback that returns `False` on a block decision. The prompt callback now runs **before** the decorated function; the response callback runs before its result is returned. A framework adapter callback can scan again, so choose one scan/enforcement path per interaction where possible.

For a custom agent that needs blocking, use `create_framework_security_callbacks(framework="custom", agent_identifier=..., on_scan_complete=...)` before invoking the decorated function. `on_scan_complete(text, scan_results, text_type)` should implement your organization's policy and return `True` to allow or `False` to block. ADK, LangChain, CrewAI, and REST adapters also support explicit agent identifiers in their callback configuration. Test both a matching rule and a benign prompt before production.

```python
def security_policy(text: str, scan_results: dict, text_type: str) -> bool:
    """text_type is 'prompt' or 'response' where applicable."""
    detections = scan_results.get("detection_results") or {}
    for _, result in detections.items():
        if isinstance(result, dict) and result.get("result") == "MATCH_FOUND":
            return False
    return True
```

---

## Intentional policy blocks

Intentional policy blocks in custom agents should use the shared SDK exception:

```python
from saf3ai_sdk import PolicyBlocked, traceable

@traceable(name="support-agent")
def answer(prompt: str) -> str:
    if policy_denies(prompt):  # Your application's policy decision.
        raise PolicyBlocked("Request denied by policy", phase="prompt", blocked_by="support-policy")
    return call_your_model(prompt)
```

Catch `PolicyBlocked` at the application boundary to return your blocked-request response. Replace any locally defined exception of that name with this import. Both synchronous and asynchronous `@traceable` functions propagate it while retaining block evidence, without recording a runtime failure. SDK-owned tracing contexts and `call_llm_with_saf3ai()` follow the same rule. Other exceptions still record errors; matching a threat in Monitor Only does not itself raise `PolicyBlocked`. When using a third-party tracing context around the SDK, its exception-recording policy remains under that library's control.

## Production checklist

- [ ] **`init()`** called once at cold start, before any tracer-backed client.
- [ ] **Secrets** from environment or vault; never in source control.
- [ ] **Platform endpoint** reachable from runtime network (VPC / egress rules).
- [ ] **Scanner** endpoint and scan flags match organizational policy (prompt-only vs prompt+response).
- [ ] **Agent identity:** capture one `/scan` request and verify `metadata.agent_identifier` exactly matches the registered rule identifier for each agent.
- [ ] **Enforcement:** verify that a blocked prompt never enters the model/tool call and a blocked response is never returned to the caller. Automatic scanning alone is observational.
- [ ] **Failure policy:** decide whether a scanner outage should allow or reject requests. Current callback error paths may fail open; test this against your security requirements.
- [ ] **`agent_id`** / **`service_name`** stable per deployment unit for cost and ownership dashboards.
- [ ] **Log level** appropriate (`INFO` or **`WARNING`** in production; **`DEBUG`** only when diagnosing).
- [ ] **Conversation IDs** for multi-tenant apps: use explicit session-scoped IDs (`reset_conversation()` between unrelated chats).

---

## API surface

| Export | Role |
|--------|------|
| **`init(...)`** | Configure trace provider, auth, framework and instrumentation wiring |
| **`get_detected_framework()`** | Resolved framework string |
| **`get_tracer(name)`** | Tracer from the SDK **`TraceProvider`** |
| **`traceable`** | Trace decorator / wrapper for custom code paths |
| **`PolicyBlocked`** | Intentional policy rejection; propagates without marking SDK-owned spans as runtime failures |
| **`call_llm_with_saf3ai(...)`** | Model-call wrapper with prompt/response scan callbacks and enforcement evidence |
| **`set_scanner_config`**, **`set_global_security_callbacks`** | Scanner wiring for traceable flows |
| **`scan_prompt`**, **`scan_response`**, **`scan_prompt_and_response`** | Direct scanner HTTP APIs |
| **`set_custom_attributes`**, **`get_custom_attributes`**, **`clear_custom_attributes`**, **`reset_conversation`** | Context and attribute helpers |
| **`create_security_callback`** | ADK-oriented security callbacks (lazy import) |
| **`SDKConfig`** | Typed configuration object for **`init()`** |

Full parameter lists and behaviors live in docstrings in **`saf3ai_sdk/__init__.py`** and **`saf3ai_sdk.config`**.

---

## Troubleshooting

| Symptom | What to verify |
|---------|----------------|
| No traces | `SAF3AI_API_KEY`, platform endpoint, firewall, and logs immediately after **`init()`** |
| Wrong framework | Import order and installed packages (CrewAI is checked before LangChain) |
| Scanner not invoked | **`scanner_endpoint`**, **`SAF3AI_API_KEY`**, and callbacks attached to the LLM or ADK agent |
| Rule matches only with manual metadata | Install the fixed 0.2.5 release or this checkout, verify `metadata.agent_identifier` in the outgoing `/scan` payload, and check exact spelling against the registered policy identifier |
| Match appears but request was not blocked | Configure and test an enforcement callback; automatic `@traceable` scanning records results but does not automatically turn every match into a block |
| Duplicate or fragmented conversations | Ensure conversation ID helpers are used consistently per user session |

---

## Recent changes

> **0.2.5 release candidate (publish and deploy separately)**
>
> - Agent identity is included in automatic prompt and response scans.
> - `@traceable` prompt-policy callbacks run before agent execution.
> - AutoGen and Bedrock automatic scan configuration paths are repaired.
> - Public `PolicyBlocked` preserves intentional block evidence without classifying the rejection as a runtime failure in sync/async decorators, SDK-owned contexts, and the model-call wrapper.
> - REST/custom callbacks propagate explicitly raised policy blocks.
> - AutoGen accepts generated callback signatures, records completed-scan markers, and enforces response blocks.
> - Automatic response scans respect `SAF3AI_SCAN_RESPONSES`; prompt text and attribute values are no longer printed at INFO level by `@traceable`.
> - Wheel and source archives use core metadata 2.4 for package-tool compatibility.

### Local release verification

Run the local regression suite and build both distribution formats before publication:

```bash
python -m pytest -q tests
python -m build
python -m twine check dist/*
```

Tests, caches, and generated packages are intentionally Git-ignored and excluded from distribution archives. Keep the local test suite available in the release workspace; a fresh clone does not include those tests. Release notes remain version-controlled in this README and `CHANGELOG.md`. The sample-policy checks use mocked scanner/model calls; live framework and scanner validation must be performed against the deployment configuration.

The final local regression run passed **119 tests**. Archive-content and installed-wheel smoke checks also verify that the packaged SDK exposes the expected API and reports version **0.2.5**. Python 3.9 syntax was checked; the full regression run used Python 3.13 and does not replace runtime checks on your deployment's Python/framework versions.

---

## Support & license

- **Issues & features:** [GitHub Issues](https://github.com/saf3ai/saf3ai_sdk/issues)  
- **Email:** team@saf3ai.com  
- **License:** [MIT](LICENSE)

For security-sensitive reports, contact the maintainers directly rather than using public issues until a disclosure process is published.

---

*Saf3AI and the Saf3AI logo are trademarks of their respective owners. This README describes the open-source **saf3ai-sdk** Python package.*
