Metadata-Version: 2.4
Name: flowlines
Version: 0.2.2
Summary: Automatic instrumentation for LLM provider APIs — capture requests, responses, timing, and errors with minimal code changes.
Project-URL: Homepage, https://github.com/flowlines/flowlines-sdk
Project-URL: Repository, https://github.com/flowlines/flowlines-sdk
Author: Flowlines
License-Expression: MIT
License-File: LICENSE
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: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: opentelemetry-api>=1.20
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20
Requires-Dist: opentelemetry-sdk>=1.20
Requires-Dist: opentelemetry-semantic-conventions-ai<0.5.0,>=0.4.15
Provides-Extra: agno
Requires-Dist: opentelemetry-instrumentation-agno>=0.53; extra == 'agno'
Provides-Extra: alephalpha
Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.53; extra == 'alephalpha'
Provides-Extra: all
Requires-Dist: opentelemetry-instrumentation-agno>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-chromadb>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-cohere>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-crewai>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-groq>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-haystack>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-lancedb>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-langchain>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-marqo>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-mcp>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-milvus>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-mistralai>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-ollama>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-openai-agents>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-openai>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-pinecone>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-qdrant>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-replicate>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-sagemaker>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-together>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-transformers>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-vertexai>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-voyageai>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-watsonx>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-weaviate>=0.53; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-writer>=0.53; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.53; extra == 'anthropic'
Provides-Extra: bedrock
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.53; extra == 'bedrock'
Provides-Extra: chromadb
Requires-Dist: opentelemetry-instrumentation-chromadb>=0.53; extra == 'chromadb'
Provides-Extra: cohere
Requires-Dist: opentelemetry-instrumentation-cohere>=0.53; extra == 'cohere'
Provides-Extra: crewai
Requires-Dist: opentelemetry-instrumentation-crewai>=0.53; extra == 'crewai'
Provides-Extra: google-generativeai
Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.53; extra == 'google-generativeai'
Provides-Extra: groq
Requires-Dist: opentelemetry-instrumentation-groq>=0.53; extra == 'groq'
Provides-Extra: haystack
Requires-Dist: opentelemetry-instrumentation-haystack>=0.53; extra == 'haystack'
Provides-Extra: lancedb
Requires-Dist: opentelemetry-instrumentation-lancedb>=0.53; extra == 'lancedb'
Provides-Extra: langchain
Requires-Dist: opentelemetry-instrumentation-langchain>=0.53; extra == 'langchain'
Provides-Extra: llamaindex
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.53; extra == 'llamaindex'
Provides-Extra: marqo
Requires-Dist: opentelemetry-instrumentation-marqo>=0.53; extra == 'marqo'
Provides-Extra: mcp
Requires-Dist: opentelemetry-instrumentation-mcp>=0.53; extra == 'mcp'
Provides-Extra: milvus
Requires-Dist: opentelemetry-instrumentation-milvus>=0.53; extra == 'milvus'
Provides-Extra: mistralai
Requires-Dist: opentelemetry-instrumentation-mistralai>=0.53; extra == 'mistralai'
Provides-Extra: ollama
Requires-Dist: opentelemetry-instrumentation-ollama>=0.53; extra == 'ollama'
Provides-Extra: openai
Requires-Dist: opentelemetry-instrumentation-openai>=0.53; extra == 'openai'
Provides-Extra: openai-agents
Requires-Dist: opentelemetry-instrumentation-openai-agents>=0.53; extra == 'openai-agents'
Provides-Extra: pinecone
Requires-Dist: opentelemetry-instrumentation-pinecone>=0.53; extra == 'pinecone'
Provides-Extra: qdrant
Requires-Dist: opentelemetry-instrumentation-qdrant>=0.53; extra == 'qdrant'
Provides-Extra: replicate
Requires-Dist: opentelemetry-instrumentation-replicate>=0.53; extra == 'replicate'
Provides-Extra: sagemaker
Requires-Dist: opentelemetry-instrumentation-sagemaker>=0.53; extra == 'sagemaker'
Provides-Extra: together
Requires-Dist: opentelemetry-instrumentation-together>=0.53; extra == 'together'
Provides-Extra: transformers
Requires-Dist: opentelemetry-instrumentation-transformers>=0.53; extra == 'transformers'
Provides-Extra: vertexai
Requires-Dist: opentelemetry-instrumentation-vertexai>=0.53; extra == 'vertexai'
Provides-Extra: voyageai
Requires-Dist: opentelemetry-instrumentation-voyageai>=0.53; extra == 'voyageai'
Provides-Extra: watsonx
Requires-Dist: opentelemetry-instrumentation-watsonx>=0.53; extra == 'watsonx'
Provides-Extra: weaviate
Requires-Dist: opentelemetry-instrumentation-weaviate>=0.53; extra == 'weaviate'
Provides-Extra: writer
Requires-Dist: opentelemetry-instrumentation-writer>=0.53; extra == 'writer'
Description-Content-Type: text/markdown

# Flowlines SDK for Python

Observability for LLM-powered applications. The Flowlines SDK instruments LLM provider APIs using OpenTelemetry — it captures requests, responses, timing, and errors, and exports them to the Flowlines backend via OTLP/HTTP.

Supported providers: **OpenAI**, **Anthropic**, **Google Generative AI (Gemini)**, **AWS Bedrock**, **Cohere**, **Vertex AI**, **Together AI**. Also instruments **LangChain**, **LlamaIndex**, **MCP**, **Pinecone**, **ChromaDB**, and **Qdrant**.

## Requirements

- Python 3.11+

## Installation

```bash
pip install flowlines
```

Then install instrumentation extras for the providers you use:

```bash
# Single provider
pip install flowlines[openai]

# Multiple providers
pip install flowlines[openai,anthropic]

# All supported providers
pip install flowlines[all]
```

Available extras: `openai`, `anthropic`, `google-generativeai`, `bedrock`, `cohere`, `vertexai`, `together`, `pinecone`, `chromadb`, `qdrant`, `langchain`, `llamaindex`, `mcp`.

## AI coding agent integration

If you use an AI coding agent, you can install the Flowlines skill so your agent knows how to integrate the SDK into your project:

```bash
npx skills add flowlines-ai/skills
```

Then, just ask your agent to integrate Flowlines into your project.

## Quick start

If you don't have an existing OpenTelemetry setup, this is all you need:

```python
import flowlines

flowlines.init(api_key="your-flowlines-api-key")
```

That's it. Every LLM call made through an installed provider is now automatically captured and exported to Flowlines. The SDK:

1. Creates an OpenTelemetry `TracerProvider`
2. Detects which LLM libraries are installed and instruments them
3. Filters spans to only export LLM-related telemetry
4. Sends data to the Flowlines backend via OTLP/HTTP

### User, session, and agent tracking

Tag LLM calls with a user ID, session ID, and/or agent ID using the `context()` context manager:

```python
with flowlines.context(user_id="user-42", session_id="sess-abc"):
    client.chat.completions.create(...)  # this span gets user_id and session_id
    client.chat.completions.create(...)  # same
```

You can also attach an `agent_id` to identify which agent produced the spans:

```python
with flowlines.context(user_id="user-42", session_id="sess-abc", agent_id="agent-1"):
    client.chat.completions.create(...)
```

For cases where a context manager doesn't fit (e.g. across request boundaries), use the imperative API. `set_context()` returns a token — pass it to `clear_context()` to restore the previous state:

```python
token = flowlines.set_context(user_id="user-42", session_id="sess-abc", agent_id="agent-1")
try:
    client.chat.completions.create(...)
finally:
    flowlines.clear_context(token)
```

Context tracking is thread-safe and async-safe.

### Custom endpoint

By default, data is sent to `https://ingest.flowlines.ai`. You can override this:

```python
flowlines.init(
    api_key="your-flowlines-api-key",
    ingest_endpoint="https://your-custom-endpoint.example.com",
)
```

You can also override the API endpoint used for memory retrieval and session management:

```python
flowlines.init(
    api_key="your-flowlines-api-key",
    ingest_endpoint="https://your-custom-ingest.example.com",
    api_endpoint="https://your-custom-api.example.com",
)
```

Both endpoints must use HTTPS, unless they target `localhost` / `127.0.0.1` / `::1` (useful for local development).

### Memory retrieval

Retrieve user memory from the Flowlines backend with `get_memory()` (sync) or `aget_memory()` (async):

```python
memory = flowlines.get_memory("user-42")
memory = flowlines.get_memory("user-42", session_id="sess-abc", agent_id="agent-1", view="summary")
```

```python
memory = await flowlines.aget_memory("user-42")
```

Both return a JSON string of the memory object, or `None` if no memory was found (HTTP 404). On other errors, they raise `FlowlinesMemoryError`.

### Session management

Signal that a session has ended with `end_session()` (sync) or `aend_session()` (async). This flushes pending spans before notifying the backend:

```python
flowlines.end_session("user-42", session_id="sess-abc")
```

```python
await flowlines.aend_session("user-42", session_id="sess-abc")
```

On errors, they raise `FlowlinesSessionError`.

## Usage with an existing OpenTelemetry setup

If your application already has its own `TracerProvider`, pass `has_external_otel=True` to prevent the SDK from creating a second one:

```python
import flowlines

flowlines.init(
    api_key="your-flowlines-api-key",
    has_external_otel=True,
)
```

In this mode, the SDK does **not** create a `TracerProvider` or register instrumentors. You are responsible for wiring things up yourself:

```python
from opentelemetry.sdk.trace import TracerProvider

provider = TracerProvider()

# 1. Add the Flowlines span processor to your provider
processor = flowlines.create_span_processor()
provider.add_span_processor(processor)

# 2. Instrument providers using the Flowlines instrumentor registry
for instrumentor in flowlines.get_instrumentors():
    instrumentor.instrument(tracer_provider=provider)
```

- `create_span_processor()` returns a span processor that filters and exports LLM spans to Flowlines. Call it exactly once.
- `get_instrumentors()` returns instrumentor instances for every supported provider library that is currently installed. You can also skip this and register instrumentors yourself.

## Troubleshooting

### Verbose mode

Pass `verbose=True` to enable debug logging. This prints detailed information about initialization, instrumentor discovery, span filtering, and export results to stderr:

```python
flowlines.init(api_key="your-flowlines-api-key", verbose=True)
```

Example output:

```
[flowlines] Initializing Flowlines SDK (endpoint=https://ingest.flowlines.ai)
[flowlines] Mode A: creating TracerProvider and registering instrumentors
[flowlines] Instrumentor loaded: OpenAIInstrumentor
[flowlines] Instrumentor skipped: anthropic (library not installed)
[flowlines] Total instrumentors loaded: 1
[flowlines] Flowlines SDK initialized successfully
[flowlines] Export: 2/5 span(s) are LLM-related — sending to backend
[flowlines] Export: succeeded
```

### No spans appearing in Flowlines

- **Enable verbose mode.** Pass `verbose=True` to see exactly what the SDK is doing — which instrumentors are loaded, how many spans are captured, and whether exports succeed.
- **Missing instrumentation extras.** The SDK only instruments providers whose instrumentation package is installed. For example, if you use OpenAI, make sure you installed `flowlines[openai]`. Check your installed packages with `pip list | grep opentelemetry-instrumentation`.
- **Flowlines initialized too late.** `flowlines.init()` must run **before** any LLM calls. If the provider client is created before instrumentation is set up, those calls won't be captured.
- **Wrong API key.** Verify that the `api_key` you pass is valid. The SDK will export spans, but the backend will reject them silently if the key is invalid.

### `ValueError: Endpoint must use HTTPS`

The SDK requires HTTPS for all endpoints except loopback addresses (`localhost`, `127.0.0.1`, `::1`). If you're testing locally, use `http://localhost:<port>`.

### Spans are missing `user_id` / `session_id`

Make sure the LLM call happens **inside** the `flowlines.context()` block or between `set_context()` and `clear_context()`. If you're using threads or async tasks, note that context does not propagate automatically to child threads — set it in each task.

### Duplicate spans or conflicting `TracerProvider`

If you already have an OpenTelemetry setup, you **must** pass `has_external_otel=True`. Otherwise the SDK creates its own `TracerProvider`, which conflicts with yours. See [Usage with an existing OpenTelemetry setup](#usage-with-an-existing-opentelemetry-setup).

## Examples

See the [`examples/`](examples/) directory for working sample applications:

- **[OpenAI conversational agent](examples/openai/)** — Interactive agent with tool calling, demonstrating Mode A auto-instrumentation and context propagation.

## License

MIT
