Metadata-Version: 2.4
Name: rendfly
Version: 0.1.2
Summary: rendfly — agent observability. One line to see your agent's full execution tree.
Project-URL: Homepage, https://rendfly.com
Project-URL: Documentation, https://rendfly.com/docs/integrations/python-sdk
Author: rendfly
License-Expression: MIT
License-File: LICENSE
Keywords: agents,llm,observability,opentelemetry,tracing
Requires-Python: >=3.10
Requires-Dist: openinference-instrumentation-anthropic>=0.1.9
Requires-Dist: openinference-instrumentation-openai>=0.1.18
Requires-Dist: openinference-instrumentation>=0.1.18
Requires-Dist: openinference-semantic-conventions>=0.1.9
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0
Requires-Dist: opentelemetry-sdk>=1.27.0
Provides-Extra: all
Requires-Dist: openinference-instrumentation-crewai>=0.1.8; extra == 'all'
Requires-Dist: openinference-instrumentation-langchain>=0.1.29; extra == 'all'
Requires-Dist: openinference-instrumentation-llama-index>=4.0; extra == 'all'
Provides-Extra: crewai
Requires-Dist: openinference-instrumentation-crewai>=0.1.8; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff==0.15.22; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: openinference-instrumentation-langchain>=0.1.29; extra == 'langgraph'
Provides-Extra: llama-index
Requires-Dist: openinference-instrumentation-llama-index>=4.0; extra == 'llama-index'
Description-Content-Type: text/markdown

# rendfly

**See your agent's full execution tree. One line.**

rendfly is observability for LLM apps and AI agents. Add this SDK (or point your traffic at rendfly's proxy) and you get a live trace of every LLM call, tool call, and retrieval step your agent makes — with cost and latency at each step — so when something breaks in production you can see exactly where, and why.

Built on OpenTelemetry and OpenInference, so it plugs into infrastructure you likely already trust.

## Install

```bash
pip install rendfly
```

OpenAI and Anthropic are auto-instrumented out of the box — nothing extra to install. For CrewAI, LangChain/LangGraph and LlamaIndex support:

```bash
pip install "rendfly[all]"
```

(Or install just what you need: `pip install "rendfly[crewai]"` / `"rendfly[langgraph]"` / `"rendfly[llama-index]"`.)

## Quickstart

```python
import rendfly
rendfly.init()                          # reads RENDFLY_API_KEY; endpoint default https://proxy.rendfly.com

from openai import OpenAI               # auto-instrumented, nothing else to write
OpenAI().chat.completions.create(...)   # -> llm span: tokens, model, cost-ready, redacted body

@rendfly.agent                          # span kind=agent
def support(q: str) -> str:
    plan = make_plan(q)                 # span kind=task
    with rendfly.span("vector-search", kind="retrieval") as s:
        s.set_attribute("top_k", 8)     # a step your LLM provider can't see
        return db.search(q)

@rendfly.task
def make_plan(q): ...

with rendfly.session("conv-123"):       # ties everything below to one conversation
    support("...")
```

Set `RENDFLY_API_KEY` (an `rfk_proj_...` project key from your rendfly project) in your environment and you're capturing traces. No key set — the SDK quietly runs as a no-op and your app behaves exactly as before; a missing or invalid key never crashes startup.

## Merge with your proxy traffic

If you already route your LLM calls through rendfly's proxy (swap `base_url` to `https://proxy.rendfly.com`, one line, no other code changes), the proxy is already capturing every LLM call it forwards. Wrapping a block of code in `rendfly.session("conv-123")` — using the same conversation id you already track — tells the SDK to stamp that same id on every span it creates inside the block.

The result is one merged execution tree per conversation, one trace: the LLM calls the proxy already saw, plus the tool calls, retrieval steps, and agent/task structure that only your code knows about. You don't have to choose one integration over the other or migrate anything — the SDK is purely additive on top of the proxy. Skip `rendfly.session(...)` and the SDK still works fine; you just get a standalone trace instead of a merged one.

## What gets instrumented

`rendfly.init()` instruments the libraries it supports **that you actually have installed**: `openai`, `anthropic`, `crewai`, `langchain`, `llama_index`. Nothing is instrumented for a library that isn't there, and `rendfly.diagnostics()["instrumented"]` lists only what really attached.

One detail is worth knowing. LangChain and LlamaIndex record their own LLM span for a call the provider instrumentor also sees, so a single call shows up as two spans in the waterfall: the chain-level view and the HTTP-level view underneath it. Both are kept on purpose — dropping either would cost you real information — and rendfly counts the tokens and the cost **once**, on the outer span. Your totals are the totals, whichever frameworks you have installed.

If you would rather see one span per call, name the layer you want:

```python
rendfly.init(instrument=["langchain"])   # chain-level spans only
rendfly.init(instrument=["openai"])      # provider-level spans only
rendfly.init(instrument=False)           # decorators and rendfly.span() only
```

### Instrumentation rendfly doesn't ship

`rendfly.tracer_provider()` returns rendfly's OpenTelemetry provider. Pass it to any other instrumentor and its spans join the same traces:

```python
SomeOtherInstrumentor().instrument(tracer_provider=rendfly.tracer_provider())
```

`init()` also registers that provider as the process-wide OpenTelemetry provider when your application hasn't set one, so `trace.get_tracer(...)` spans of your own land in rendfly too. If you already run your own OpenTelemetry setup, rendfly leaves it alone and you wire it explicitly with the call above.

## Security & data handling

By default, rendfly captures message bodies and tool arguments but scrubs every value client-side, before anything leaves your process — PII and secrets never cross the wire in the first place. Control how much is captured with `capture_content` in `rendfly.init()`:

| `capture_content` | Message bodies / tool args | Client-side scrub | Use |
|---|---|---|---|
| `redacted` (default) | captured, scrubbed | yes | balanced — see what the agent did, PII is stripped before export |
| `metadata` | not sent | n/a | regulated environments — only kind, model, tokens, latency, status, and tool names leave your process |
| `raw` | captured verbatim | no (server still redacts) | maximum fidelity, explicit opt-in |

`redacted` is the secure default: unless you deliberately set `capture_content="raw"`, nothing goes out unscrubbed.

This table describes what the auto-instrumentation captures. Spans you create yourself — `@rendfly.agent` / `@rendfly.task` / `@rendfly.tool` and `rendfly.span(...)` — record name, kind, timing and status only; your function arguments are never read, so there is nothing to scrub. Attach what you want on the span explicitly with `set_attribute`, and it goes through the same scrubbing.

A few more things worth knowing:
- Your API key is sent only as an `Authorization` header on export — it is never written into a span, a log line, or an exception message, and is masked to its last 4 characters in any diagnostic output.
- Traffic requires `https://` by default; `http://` is accepted only for `localhost`, and only if you explicitly pass `allow_insecure=True`.
- `redact_patterns`, `redact_keys`, and `redactor` let you extend or fully replace the built-in scrubbing rules (emails, card numbers, tokens, and more) with your own.
- `drop_spans` and `sampler` let you exclude a whole subsystem or sample volume, so sensitive code paths never get captured at all.
- Every entry point (decorators, `span()`, instrumentation, export) fails safe: an internal SDK error is logged and swallowed, never raised into your application or your LLM call path.

## Configuration reference

Every argument is optional and falls back to an environment variable where noted; secure defaults apply if you pass nothing.

```python
rendfly.init(
    api_key=None,                       # env RENDFLY_API_KEY
    endpoint=None,                      # env RENDFLY_ENDPOINT; default https://proxy.rendfly.com
    project=None,                       # optional label; your api_key already scopes the project
    instrument=True,                    # True = auto-detect; or a list, e.g. ["langchain", "openai"]
    session_id=None,                    # optional default session id applied to every span
    capture_content="redacted",         # "redacted" (default) | "metadata" | "raw"
    redact_patterns=None,               # extra regex patterns for value scrubbing
    redact_keys=None,                   # extra attribute-key patterns to always drop
    redactor=None,                      # callable(str) -> str to fully override value scrubbing
    drop_spans=None,                    # callable(span) -> bool to exclude specific spans
    sampler=None,                       # OpenTelemetry sampler; default parent-based always-on
    max_attr_bytes=16384,               # per-value truncation cap, in bytes
    resource_attributes=None,           # extra resource attributes (never put secrets here)
    allow_insecure=False,               # permit http:// for localhost only — dev use only
    ca_bundle=None,                     # path to a private CA bundle, for on-prem collectors
    client_cert=None,                   # path to a client certificate, for mTLS
    console_debug=False,                # print post-redaction spans to console, key masked
)
```

## License

MIT — see [LICENSE](LICENSE).
