flowlines 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. flowlines-0.1.0/.claude/settings.local.json +31 -0
  2. flowlines-0.1.0/.github/workflows/ci.yml +69 -0
  3. flowlines-0.1.0/.gitignore +13 -0
  4. flowlines-0.1.0/.pre-commit-config.yaml +23 -0
  5. flowlines-0.1.0/.python-version +1 -0
  6. flowlines-0.1.0/CLAUDE.md +71 -0
  7. flowlines-0.1.0/LICENSE +21 -0
  8. flowlines-0.1.0/PKG-INFO +258 -0
  9. flowlines-0.1.0/PUBLISH.md +60 -0
  10. flowlines-0.1.0/README.md +194 -0
  11. flowlines-0.1.0/examples/openai/.env.example +2 -0
  12. flowlines-0.1.0/examples/openai/README.md +42 -0
  13. flowlines-0.1.0/examples/openai/agent.py +204 -0
  14. flowlines-0.1.0/flowlines/__init__.py +11 -0
  15. flowlines-0.1.0/flowlines/_context.py +76 -0
  16. flowlines-0.1.0/flowlines/_exporter.py +58 -0
  17. flowlines-0.1.0/flowlines/_filter.py +20 -0
  18. flowlines-0.1.0/flowlines/_init.py +366 -0
  19. flowlines-0.1.0/flowlines/_patches.py +54 -0
  20. flowlines-0.1.0/flowlines/py.typed +0 -0
  21. flowlines-0.1.0/integration_tests/__init__.py +0 -0
  22. flowlines-0.1.0/integration_tests/conftest.py +383 -0
  23. flowlines-0.1.0/integration_tests/test_basic.py +159 -0
  24. flowlines-0.1.0/integration_tests/test_basic_anthropic.py +114 -0
  25. flowlines-0.1.0/integration_tests/test_basic_gemini.py +114 -0
  26. flowlines-0.1.0/integration_tests/test_noconflict_with_otel.py +202 -0
  27. flowlines-0.1.0/integration_tests/test_noconflict_with_traceloop.py +84 -0
  28. flowlines-0.1.0/pyproject.toml +102 -0
  29. flowlines-0.1.0/pyrightconfig.json +4 -0
  30. flowlines-0.1.0/tests/__init__.py +0 -0
  31. flowlines-0.1.0/tests/test_context.py +418 -0
  32. flowlines-0.1.0/tests/test_exporter.py +145 -0
  33. flowlines-0.1.0/tests/test_filter.py +55 -0
  34. flowlines-0.1.0/tests/test_init.py +739 -0
  35. flowlines-0.1.0/uv.lock +3052 -0
@@ -0,0 +1,31 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv sync:*)",
5
+ "Bash(uv run pytest:*)",
6
+ "Bash(uv run:*)",
7
+ "Bash(uv pip index versions:*)",
8
+ "WebSearch",
9
+ "WebFetch(domain:pypi.org)",
10
+ "Bash(git add:*)",
11
+ "Bash(git commit:*)",
12
+ "WebFetch(domain:github.com)",
13
+ "Bash(uv pip list:*)",
14
+ "Bash(pip index:*)",
15
+ "Bash(uv add:*)",
16
+ "Bash(ANTHROPIC_API_KEY=dummy GEMINI_API_KEY=dummy uv run pytest:*)",
17
+ "Bash(OPENAI_API_KEY=dummy ANTHROPIC_API_KEY=dummy GEMINI_API_KEY=dummy uv run pytest:*)",
18
+ "WebFetch(domain:platform.openai.com)",
19
+ "WebFetch(domain:cookbook.openai.com)",
20
+ "WebFetch(domain:community.openai.com)",
21
+ "WebFetch(domain:developers.openai.com)",
22
+ "WebFetch(domain:docs.windsurf.com)",
23
+ "WebFetch(domain:docs.cline.bot)",
24
+ "WebFetch(domain:aider.chat)",
25
+ "WebFetch(domain:docs.github.com)",
26
+ "WebFetch(domain:antigravity.google)",
27
+ "WebFetch(domain:codelabs.developers.google.com)",
28
+ "Bash(GEMINI_API_KEY=AIzaSyD-cuHx5wvLOEhOrJbLJLJx4xNLJbg1DyM uv run:*)"
29
+ ]
30
+ }
31
+ }
@@ -0,0 +1,69 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ - uses: astral-sh/setup-uv@v7
15
+ - uses: actions/setup-python@v6
16
+ with:
17
+ python-version: "3.13"
18
+
19
+ - name: Install dependencies
20
+ run: uv sync
21
+
22
+ - name: Check formatting
23
+ run: uv run ruff format --check flowlines/ tests/ integration_tests/
24
+
25
+ - name: Lint
26
+ run: uv run ruff check flowlines/ tests/ integration_tests/
27
+
28
+ - name: Type check (mypy)
29
+ run: uv run mypy flowlines/
30
+
31
+ - name: Type check (pyright)
32
+ run: uv run pyright
33
+
34
+ test:
35
+ runs-on: ubuntu-latest
36
+ strategy:
37
+ matrix:
38
+ python-version: ["3.11", "3.12", "3.13"]
39
+ steps:
40
+ - uses: actions/checkout@v6
41
+ - uses: astral-sh/setup-uv@v7
42
+ - uses: actions/setup-python@v6
43
+ with:
44
+ python-version: ${{ matrix.python-version }}
45
+
46
+ - name: Install dependencies
47
+ run: uv sync
48
+
49
+ - name: Run tests
50
+ run: uv run pytest tests/
51
+
52
+ integration-test:
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@v6
56
+ - uses: astral-sh/setup-uv@v7
57
+ - uses: actions/setup-python@v6
58
+ with:
59
+ python-version: "3.13"
60
+
61
+ - name: Install dependencies
62
+ run: uv sync --group integration
63
+
64
+ - name: Run integration tests
65
+ env:
66
+ OPENAI_API_KEY: ${{ secrets.IT_OPENAI_API_KEY }}
67
+ ANTHROPIC_API_KEY: ${{ secrets.IT_ANTHROPIC_API_KEY }}
68
+ GEMINI_API_KEY: ${{ secrets.IT_GEMINI_API_KEY }}
69
+ run: uv run pytest integration_tests/
@@ -0,0 +1,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ .env
13
+ *.jsonl
@@ -0,0 +1,23 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.1
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: local
10
+ hooks:
11
+ - id: pyright
12
+ name: pyright
13
+ entry: uv run pyright
14
+ language: system
15
+ types: [python]
16
+ pass_filenames: false
17
+
18
+ - id: mypy
19
+ name: mypy
20
+ entry: uv run mypy
21
+ language: system
22
+ types: [python]
23
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,71 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
7
+ Flowlines SDK for Python — an observability SDK that instruments LLM provider APIs using OpenTelemetry. It captures requests, responses, timing, and errors, filtering to only LLM-related spans and exporting them via OTLP/HTTP to the Flowlines backend.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ # Install dependencies
13
+ uv sync
14
+
15
+ # Run unit tests
16
+ uv run pytest tests/
17
+
18
+ # Run a single test
19
+ uv run pytest tests/test_init.py::TestClassName::test_method_name
20
+
21
+ # Run integration tests (requires OPENAI_API_KEY env var)
22
+ uv run pytest integration_tests/
23
+
24
+ # Lint
25
+ uv run ruff check flowlines/ tests/ integration_tests/
26
+
27
+ # Format
28
+ uv run ruff format flowlines/ tests/ integration_tests/
29
+
30
+ # Type checking
31
+ uv run mypy flowlines/
32
+ uv run pyright
33
+ ```
34
+
35
+ ## Architecture
36
+
37
+ All source is in `flowlines/`. The public API exports `Flowlines` and `FlowlinesExporter` from `__init__.py`.
38
+
39
+ ### Core modules
40
+
41
+ - **`_init.py`** — `Flowlines` singleton with three initialization modes:
42
+ - **Mode A** (default): Creates its own `TracerProvider`, registers all available instrumentors automatically
43
+ - **Mode B1** (`has_external_otel=True`): User manages their own `TracerProvider`; Flowlines provides `create_span_processor()` and `get_instrumentors()` for manual integration
44
+ - **Mode B2** (`has_traceloop=True`): Traceloop already initialized; Flowlines adds its span processor to the existing provider
45
+ - **`_context.py`** — `FlowlinesSpanProcessor` (decorator over `BatchSpanProcessor`) that injects `user_id`/`session_id`/`agent_id` from `ContextVar` into spans at start time. Context is set via `flowlines.context()` context manager or `Flowlines.set_context()`/`clear_context()` imperative API.
46
+ - **`_exporter.py`** — `FlowlinesExporter` wraps `OTLPSpanExporter`, filtering exported spans to only those with `gen_ai.*` or `ai.*` attributes (via `_filter.py`)
47
+ - **`_filter.py`** — `is_llm_span()` function that identifies LLM-related spans by attribute prefixes
48
+
49
+ ### Key patterns
50
+
51
+ - **Singleton**: `Flowlines` enforces single instance; `_reset()` classmethod exists for test teardown
52
+ - **Decorator pattern**: Both `FlowlinesSpanProcessor` and `FlowlinesExporter` wrap inner OpenTelemetry objects
53
+ - **Dynamic instrumentor discovery**: Registry of `(library_name, module_path, class_name)` tuples; each instrumentor is loaded only if its library is importable
54
+
55
+ ## Code Conventions
56
+
57
+ - Private modules prefixed with underscore (`_init.py`, `_context.py`, etc.)
58
+ - `from __future__ import annotations` in all modules
59
+ - Strict type checking enabled (mypy strict + pyright strict)
60
+ - All function signatures have type annotations including return types
61
+ - Google-style docstrings with Args/Returns/Raises sections
62
+ - Ruff lint rules: `E`, `F`, `W`, `I`, `UP`, `B`, `SIM`, `TCH`
63
+
64
+ ## Test Conventions
65
+
66
+ - Unit tests in `tests/` use `unittest.mock.patch` and `MagicMock` extensively
67
+ - `autouse` fixtures reset the `Flowlines` singleton before each test
68
+ - Tests organized in classes by feature/mode
69
+ - All test functions have type annotations (`-> None`) and docstrings
70
+ - Integration tests in `integration_tests/` use an in-process OTLP capture server
71
+ - `pytest-asyncio` with `asyncio_mode = "auto"`
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flowlines
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: flowlines
3
+ Version: 0.1.0
4
+ Summary: Automatic instrumentation for LLM provider APIs — capture requests, responses, timing, and errors with minimal code changes.
5
+ Project-URL: Homepage, https://github.com/flowlines/flowlines-sdk
6
+ Project-URL: Repository, https://github.com/flowlines/flowlines-sdk
7
+ Author: Flowlines
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: opentelemetry-api>=1.20
21
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20
22
+ Requires-Dist: opentelemetry-sdk>=1.20
23
+ Provides-Extra: all
24
+ Requires-Dist: opentelemetry-instrumentation-anthropic>=0.40; extra == 'all'
25
+ Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == 'all'
26
+ Requires-Dist: opentelemetry-instrumentation-chromadb>=0.40; extra == 'all'
27
+ Requires-Dist: opentelemetry-instrumentation-cohere>=0.40; extra == 'all'
28
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.40; extra == 'all'
29
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == 'all'
30
+ Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == 'all'
31
+ Requires-Dist: opentelemetry-instrumentation-mcp>=0.40; extra == 'all'
32
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == 'all'
33
+ Requires-Dist: opentelemetry-instrumentation-pinecone>=0.40; extra == 'all'
34
+ Requires-Dist: opentelemetry-instrumentation-qdrant>=0.40; extra == 'all'
35
+ Requires-Dist: opentelemetry-instrumentation-together>=0.40; extra == 'all'
36
+ Requires-Dist: opentelemetry-instrumentation-vertexai>=0.40; extra == 'all'
37
+ Provides-Extra: anthropic
38
+ Requires-Dist: opentelemetry-instrumentation-anthropic>=0.40; extra == 'anthropic'
39
+ Provides-Extra: bedrock
40
+ Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == 'bedrock'
41
+ Provides-Extra: chromadb
42
+ Requires-Dist: opentelemetry-instrumentation-chromadb>=0.40; extra == 'chromadb'
43
+ Provides-Extra: cohere
44
+ Requires-Dist: opentelemetry-instrumentation-cohere>=0.40; extra == 'cohere'
45
+ Provides-Extra: google-generativeai
46
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.40; extra == 'google-generativeai'
47
+ Provides-Extra: langchain
48
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == 'langchain'
49
+ Provides-Extra: llamaindex
50
+ Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == 'llamaindex'
51
+ Provides-Extra: mcp
52
+ Requires-Dist: opentelemetry-instrumentation-mcp>=0.40; extra == 'mcp'
53
+ Provides-Extra: openai
54
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == 'openai'
55
+ Provides-Extra: pinecone
56
+ Requires-Dist: opentelemetry-instrumentation-pinecone>=0.40; extra == 'pinecone'
57
+ Provides-Extra: qdrant
58
+ Requires-Dist: opentelemetry-instrumentation-qdrant>=0.40; extra == 'qdrant'
59
+ Provides-Extra: together
60
+ Requires-Dist: opentelemetry-instrumentation-together>=0.40; extra == 'together'
61
+ Provides-Extra: vertexai
62
+ Requires-Dist: opentelemetry-instrumentation-vertexai>=0.40; extra == 'vertexai'
63
+ Description-Content-Type: text/markdown
64
+
65
+ # Flowlines SDK for Python
66
+
67
+ 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.
68
+
69
+ Supported providers: **OpenAI**, **Anthropic**, **AWS Bedrock**, **Cohere**, **Vertex AI**, **Together AI**. Also instruments **LangChain**, **LlamaIndex**, **MCP**, **Pinecone**, **ChromaDB**, and **Qdrant**.
70
+
71
+ ## Requirements
72
+
73
+ - Python 3.11+
74
+
75
+ ## Installation
76
+
77
+ ```bash
78
+ pip install flowlines
79
+ ```
80
+
81
+ Then install instrumentation extras for the providers you use:
82
+
83
+ ```bash
84
+ # Single provider
85
+ pip install flowlines[openai]
86
+
87
+ # Multiple providers
88
+ pip install flowlines[openai,anthropic]
89
+
90
+ # All supported providers
91
+ pip install flowlines[all]
92
+ ```
93
+
94
+ Available extras: `openai`, `anthropic`, `bedrock`, `cohere`, `vertexai`, `together`, `pinecone`, `chromadb`, `qdrant`, `langchain`, `llamaindex`, `mcp`.
95
+
96
+ ## AI coding agent integration
97
+
98
+ 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:
99
+
100
+ ```bash
101
+ npx skills add flowlines-ai/skills
102
+ ```
103
+
104
+ Then, just ask your agent to integrate Flowlines into your project.
105
+
106
+ ## Quick start
107
+
108
+ If you don't have an existing OpenTelemetry setup, this is all you need:
109
+
110
+ ```python
111
+ from flowlines import Flowlines
112
+
113
+ flowlines = Flowlines(api_key="your-flowlines-api-key")
114
+ ```
115
+
116
+ That's it. Every LLM call made through an installed provider is now automatically captured and exported to Flowlines. The SDK:
117
+
118
+ 1. Creates an OpenTelemetry `TracerProvider`
119
+ 2. Detects which LLM libraries are installed and instruments them
120
+ 3. Filters spans to only export LLM-related telemetry
121
+ 4. Sends data to the Flowlines backend via OTLP/HTTP
122
+
123
+ ### User, session, and agent tracking
124
+
125
+ Tag LLM calls with a user ID, session ID, and/or agent ID using the `context()` context manager:
126
+
127
+ ```python
128
+ with flowlines.context(user_id="user-42", session_id="sess-abc"):
129
+ client.chat.completions.create(...) # this span gets user_id and session_id
130
+ client.chat.completions.create(...) # same
131
+ ```
132
+
133
+ You can also attach an `agent_id` to identify which agent produced the spans:
134
+
135
+ ```python
136
+ with flowlines.context(user_id="user-42", session_id="sess-abc", agent_id="agent-1"):
137
+ client.chat.completions.create(...)
138
+ ```
139
+
140
+ The `session_id` and `agent_id` are optional — you can track just the user:
141
+
142
+ ```python
143
+ with flowlines.context(user_id="user-42"):
144
+ client.chat.completions.create(...)
145
+ ```
146
+
147
+ 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:
148
+
149
+ ```python
150
+ token = Flowlines.set_context(user_id="user-42", session_id="sess-abc", agent_id="agent-1")
151
+ try:
152
+ client.chat.completions.create(...)
153
+ finally:
154
+ Flowlines.clear_context(token)
155
+ ```
156
+
157
+ Context tracking is thread-safe and async-safe.
158
+
159
+ ### Custom endpoint
160
+
161
+ By default, data is sent to `https://ingest.flowlines.ai`. You can override this:
162
+
163
+ ```python
164
+ flowlines = Flowlines(
165
+ api_key="your-flowlines-api-key",
166
+ endpoint="https://your-custom-endpoint.example.com",
167
+ )
168
+ ```
169
+
170
+ The endpoint must use HTTPS, unless it targets `localhost` / `127.0.0.1` / `::1` (useful for local development).
171
+
172
+ ## Usage with an existing OpenTelemetry setup
173
+
174
+ If your application already has its own `TracerProvider`, pass `has_external_otel=True` to prevent the SDK from creating a second one:
175
+
176
+ ```python
177
+ from flowlines import Flowlines
178
+
179
+ flowlines = Flowlines(
180
+ api_key="your-flowlines-api-key",
181
+ has_external_otel=True,
182
+ )
183
+ ```
184
+
185
+ In this mode, the SDK does **not** create a `TracerProvider` or register instrumentors. You are responsible for wiring things up yourself:
186
+
187
+ ```python
188
+ from opentelemetry.sdk.trace import TracerProvider
189
+
190
+ provider = TracerProvider()
191
+
192
+ # 1. Add the Flowlines span processor to your provider
193
+ processor = flowlines.create_span_processor()
194
+ provider.add_span_processor(processor)
195
+
196
+ # 2. Instrument providers using the Flowlines instrumentor registry
197
+ for instrumentor in flowlines.get_instrumentors():
198
+ instrumentor.instrument(tracer_provider=provider)
199
+ ```
200
+
201
+ - `create_span_processor()` returns a span processor that filters and exports LLM spans to Flowlines. Call it exactly once.
202
+ - `get_instrumentors()` returns instrumentor instances for every supported provider library that is currently installed. You can also skip this and register instrumentors yourself.
203
+
204
+ ## Troubleshooting
205
+
206
+ ### Verbose mode
207
+
208
+ Pass `verbose=True` to enable debug logging. This prints detailed information about initialization, instrumentor discovery, span filtering, and export results to stderr:
209
+
210
+ ```python
211
+ flowlines = Flowlines(api_key="your-flowlines-api-key", verbose=True)
212
+ ```
213
+
214
+ Example output:
215
+
216
+ ```
217
+ [flowlines] Initializing Flowlines SDK (endpoint=https://ingest.flowlines.ai)
218
+ [flowlines] Mode A: creating TracerProvider and registering instrumentors
219
+ [flowlines] Instrumentor loaded: OpenAIInstrumentor
220
+ [flowlines] Instrumentor skipped: anthropic (library not installed)
221
+ [flowlines] Total instrumentors loaded: 1
222
+ [flowlines] Flowlines SDK initialized successfully
223
+ [flowlines] Export: 2/5 span(s) are LLM-related — sending to backend
224
+ [flowlines] Export: succeeded
225
+ ```
226
+
227
+ ### No spans appearing in Flowlines
228
+
229
+ - **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.
230
+ - **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`.
231
+ - **Flowlines initialized too late.** The `Flowlines()` constructor must run **before** any LLM calls. If the provider client is created before instrumentation is set up, those calls won't be captured.
232
+ - **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.
233
+
234
+ ### `RuntimeError: Flowlines is a singleton`
235
+
236
+ `Flowlines` enforces a single instance. You're calling `Flowlines(...)` more than once. Store the instance and reuse it, or initialize it once at application startup.
237
+
238
+ ### `ValueError: Endpoint must use HTTPS`
239
+
240
+ 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>`.
241
+
242
+ ### Spans are missing `user_id` / `session_id`
243
+
244
+ 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.
245
+
246
+ ### Duplicate spans or conflicting `TracerProvider`
247
+
248
+ 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).
249
+
250
+ ## Examples
251
+
252
+ See the [`examples/`](examples/) directory for working sample applications:
253
+
254
+ - **[OpenAI conversational agent](examples/openai/)** — Interactive agent with tool calling, demonstrating Mode A auto-instrumentation and context propagation.
255
+
256
+ ## License
257
+
258
+ MIT
@@ -0,0 +1,60 @@
1
+ # Publishing to PyPI
2
+
3
+ ## Prerequisites
4
+
5
+ 1. Install the build tools:
6
+
7
+ ```bash
8
+ uv tool install twine
9
+ ```
10
+
11
+ 2. Create accounts and API tokens:
12
+ - **TestPyPI**: https://test.pypi.org/account/register/ — create an API token at https://test.pypi.org/manage/account/
13
+ - **PyPI**: https://pypi.org/account/register/ — create an API token at https://pypi.org/manage/account/
14
+
15
+ ## Bump the version
16
+
17
+ Edit `version` in `pyproject.toml`:
18
+
19
+ ```toml
20
+ version = "0.2.0"
21
+ ```
22
+
23
+ ## Build
24
+
25
+ ```bash
26
+ rm -rf dist/
27
+ uv build
28
+ ```
29
+
30
+ This produces `dist/flowlines-<version>.tar.gz` and `dist/flowlines-<version>-py3-none-any.whl`.
31
+
32
+ ## Publish to TestPyPI
33
+
34
+ ```bash
35
+ uvx twine upload --repository testpypi dist/*
36
+ ```
37
+
38
+ You will be prompted for credentials. Use `__token__` as the username and your TestPyPI API token as the password.
39
+
40
+ Verify the result at `https://test.pypi.org/project/flowlines/`.
41
+
42
+ To install from TestPyPI and check it works:
43
+
44
+ ```bash
45
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ flowlines
46
+ ```
47
+
48
+ (`--extra-index-url` is needed so that dependencies are still fetched from the real PyPI.)
49
+
50
+ ## Publish to PyPI
51
+
52
+ Once you're satisfied with the TestPyPI release:
53
+
54
+ ```bash
55
+ uvx twine upload dist/*
56
+ ```
57
+
58
+ Use `__token__` as the username and your PyPI API token as the password.
59
+
60
+ Verify the result at `https://pypi.org/project/flowlines/`.