Metadata-Version: 2.4
Name: neatlogs
Version: 1.4.21
Summary: A Python package for extracting and managing LLM logs to build a collaborative workspace
Author-email: Neatlogs <hello@neatlogs.com>
License: MIT
Project-URL: Homepage, https://github.com/NeatLogs/neatlogs
Project-URL: Repository, https://github.com/NeatLogs/neatlogs.git
Project-URL: Issues, https://github.com/NeatLogs/neatlogs/issues
Project-URL: Documentation, https://docs.neatlogs.com/
Keywords: llm,tracking,monitoring,logging,ai,machine-learning,observability,collaboration
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: System :: Logging
Classifier: Topic :: System :: Monitoring
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openinference-instrumentation>=0.1.27
Requires-Dist: openinference-semantic-conventions>=0.1.13
Requires-Dist: opentelemetry-api>=1.35.0
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.35.0
Requires-Dist: opentelemetry-sdk>=1.35.0
Requires-Dist: requests>=2.31.0
Requires-Dist: agnost<0.2.0,>=0.1.8
Requires-Dist: opentelemetry-instrumentation>=0.56b0
Requires-Dist: opentelemetry-instrumentation-requests>=0.56b0
Requires-Dist: opentelemetry-instrumentation-httpx>=0.56b0
Requires-Dist: opentelemetry-instrumentation-urllib3>=0.56b0
Requires-Dist: opentelemetry-instrumentation-aiohttp-client>=0.56b0
Requires-Dist: opentelemetry-instrumentation-threading>=0.56b0
Requires-Dist: opentelemetry-instrumentation-logging>=0.56b0
Requires-Dist: wrapt>=1.0.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: openinference-instrumentation-openai>=0.1.32
Requires-Dist: openinference-instrumentation-anthropic>=0.1.20
Requires-Dist: openinference-instrumentation-langchain>=0.1.56
Requires-Dist: openinference-instrumentation-groq>=0.1.12
Requires-Dist: openinference-instrumentation-litellm>=0.1.28
Requires-Dist: openinference-instrumentation-google-genai>=0.1.8
Requires-Dist: openinference-instrumentation-bedrock>=0.1.32
Requires-Dist: openinference-instrumentation-vertexai>=0.1.11
Requires-Dist: openinference-instrumentation-mistralai>=1.3.4
Requires-Dist: openinference-instrumentation-crewai>=0.1.17
Requires-Dist: openinference-instrumentation-dspy>=0.1.32
Requires-Dist: openinference-instrumentation-agno>=0.1.25
Requires-Dist: openinference-instrumentation-openai-agents>=1.4.0
Requires-Dist: openinference-instrumentation-pydantic-ai>=0.1.9
Requires-Dist: openinference-instrumentation-smolagents>=0.1.21
Requires-Dist: openinference-instrumentation-guardrails>=0.1.10
Requires-Dist: openinference-instrumentation-haystack>=0.1.29
Requires-Dist: openinference-instrumentation-instructor>=0.1.12
Requires-Dist: openinference-instrumentation-mcp>=1.3.3
Requires-Dist: openinference-instrumentation-portkey>=0.1.7
Requires-Dist: openinference-instrumentation-google-adk>=0.1.8
Requires-Dist: openinference-instrumentation-autogen-agentchat>=0.1.6
Requires-Dist: openinference-instrumentation-llama-index>=4.3.9
Provides-Extra: azure-ai-inference
Requires-Dist: neatlogs-instrumentations[azure-ai-inference]>=0.1.1; extra == "azure-ai-inference"
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == "openai"
Provides-Extra: azure-openai
Requires-Dist: openai>=1.0.0; extra == "azure-openai"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.75.0; extra == "anthropic"
Provides-Extra: langchain
Requires-Dist: chromadb>=1.1.1; extra == "langchain"
Requires-Dist: langchain>=1.1.2; extra == "langchain"
Requires-Dist: langchain-classic>=1.0.0; extra == "langchain"
Requires-Dist: langchain-community>=0.4.1; extra == "langchain"
Requires-Dist: langchain-core>=0.3.0; extra == "langchain"
Requires-Dist: langchain-openai>=1.1.3; extra == "langchain"
Requires-Dist: qdrant-client<1.16; extra == "langchain"
Provides-Extra: langgraph
Requires-Dist: langgraph>=1.0.4; extra == "langgraph"
Requires-Dist: langchain-core>=0.3.0; extra == "langgraph"
Requires-Dist: langchain-openai>=1.1.3; extra == "langgraph"
Provides-Extra: crewai
Requires-Dist: crewai[azure-ai-inference]>=1.9.3; extra == "crewai"
Requires-Dist: litellm>=1.80.11; extra == "crewai"
Provides-Extra: llama-index
Requires-Dist: llama-index>=0.14.10; extra == "llama-index"
Provides-Extra: google-adk
Requires-Dist: google-adk>=1.14.1; extra == "google-adk"
Provides-Extra: groq
Requires-Dist: groq>=0.37.1; extra == "groq"
Provides-Extra: agno
Requires-Dist: agno>=2.3.13; extra == "agno"
Provides-Extra: bedrock
Requires-Dist: boto3>=1.42.11; extra == "bedrock"
Provides-Extra: dspy
Requires-Dist: dspy>=2.6.13; extra == "dspy"
Provides-Extra: litellm
Requires-Dist: litellm>=1.80.11; extra == "litellm"
Provides-Extra: google-genai
Requires-Dist: google-genai>=1.55.0; extra == "google-genai"
Provides-Extra: openai-agents
Requires-Dist: openai>=1.0.0; extra == "openai-agents"
Requires-Dist: openai-agents>=0.6.5; extra == "openai-agents"
Provides-Extra: guardrails
Requires-Dist: guardrails-ai>=0.4.0; extra == "guardrails"
Provides-Extra: haystack
Requires-Dist: haystack-ai>=2.0.0; extra == "haystack"
Provides-Extra: instructor
Requires-Dist: instructor>=1.0.0; extra == "instructor"
Provides-Extra: mcp
Requires-Dist: mcp>=1.0.0; extra == "mcp"
Provides-Extra: mistralai
Requires-Dist: mistralai>=1.0.0; extra == "mistralai"
Provides-Extra: portkey
Requires-Dist: portkey-ai>=1.0.0; extra == "portkey"
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai>=0.0.9; extra == "pydantic-ai"
Provides-Extra: smolagents
Requires-Dist: smolagents>=1.0.0; extra == "smolagents"
Provides-Extra: hermes
Requires-Dist: hermes-agent>=0.15.1; python_version >= "3.11" and extra == "hermes"
Provides-Extra: vertexai
Requires-Dist: google-cloud-aiplatform>=1.38.0; extra == "vertexai"
Provides-Extra: vertex-ai
Requires-Dist: google-genai>=1.55.0; extra == "vertex-ai"
Provides-Extra: autogen-agentchat
Requires-Dist: autogen-agentchat>=0.4.0; extra == "autogen-agentchat"
Provides-Extra: milvus
Requires-Dist: pymilvus<2.5.0,>=2.4.0; extra == "milvus"
Requires-Dist: milvus-lite<2.5.0,>=2.4.0; extra == "milvus"
Dynamic: license-file

<div align="center">

<h1>neatlogs</h1>

<p>LLM observability for AI agents.<br/>Instrument once. Inspect everything.</p>

<p>
  <a href="https://badge.fury.io/py/neatlogs"><img src="https://badge.fury.io/py/neatlogs.svg" alt="PyPI version" /></a>
  <img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+" />
  <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
</p>

<p>
  <a href="https://neatlogs.com">Website</a> &nbsp;·&nbsp;
  <a href="https://docs.neatlogs.com">Docs</a> &nbsp;·&nbsp;
  <a href="https://app.neatlogs.com">Get API key</a> &nbsp;·&nbsp;
  <a href="https://github.com/neatlogs/skills">AI Skill</a>
</p>

<br/>

<p><i>Agent failures don't throw exceptions — they produce wrong outputs, miss tool calls, or hallucinate.<br/>Neatlogs captures every trace so you can see exactly what the model was given, what it decided, and what each step returned.</i></p>

</div>

---

![neatlogs](assets/hero.png)

## Installation

```bash
pip install neatlogs
```

Optional extras install the underlying LLM / framework libraries:

```bash
pip install "neatlogs[openai]"
pip install "neatlogs[crewai]"
pip install "neatlogs[langchain,langgraph]"
pip install "neatlogs[google-genai]"
```

Requires **Python >= 3.10, < 3.14**.

Before initialization, run the local Doctor. It sends generated non-user workflow
and tool spans through an isolated instance of the installed SDK's normalization,
masking, capture, and flush pipeline. It performs no backend request and requires
no API key:

```bash
python -m neatlogs doctor --local --json
# or: neatlogs-doctor --local --json
```

A passing local result proves that this installed SDK pipeline works. It does not
prove that an application's own instrumentors or backend export are working.
Doctor capture is enabled only inside the isolated local/probe pipeline, is
strictly bounded, and is cleared during shutdown; normal application telemetry is
never retained for diagnostics. Use `doctor --probe` to export a controlled trace
through `/v1/traces` and read that exact trace back through
`/api/traces/v3/:traceId`; HTTP acceptance or exporter flush alone is not a pass.

---

## Quickstart

```python
import neatlogs
from neatlogs import span

neatlogs.init(
    api_key="your-api-key",       # or NEATLOGS_API_KEY env var
    workflow_name="my-agent",
    instrumentations=["openai"],
)

# Import instrumented libraries AFTER init()
from openai import OpenAI


@span(kind="WORKFLOW", name="quickstart")
def main():
    client = OpenAI()
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "What is AI?"}],
    )


main()
neatlogs.flush()
neatlogs.shutdown()
```

Auto-instrumentation captures LLM calls, tools, and retrievals as child spans. Use `@span(kind="WORKFLOW")` on your main function or request handler so each run shows up as a top-level trace in the dashboard.

Call `neatlogs.init()` **before** importing any instrumented library.

For long-running servers (FastAPI, Celery workers), call `init()` once at startup and decorate each request handler with `@span(kind="WORKFLOW")`. Do **not** call `flush()` / `shutdown()` per request.

Full walkthrough: [Your First Trace](https://docs.neatlogs.com/quickstart/your-first-trace).

### Payload delivery status

Normal OTLP batches are byte-limited and gzip-compressed. Deploy and verify gzip-aware trace
intake before releasing this SDK version. Captured text values are bounded to 100,000 UTF-8 bytes
and telemetry items to a 1 MB capture budget. A truncated value contains its original byte length,
SHA-256 digest, and `backend_upload_contract_unavailable`; spans/logs also carry
`neatlogs.capture.*` diagnostics, exposed through `get_delivery_diagnostics()`.

Typed-media metadata is detected without fetching remote content. Credential-bearing remote
locators are reduced to scheme/host/path, and large inline media is replaced in captured bodies by
typed unavailable metadata rather than a base64 prefix. Authenticated large-media and oversized
trace uploads are available behind the default-off `uploads_enabled=True` option (or
`NEATLOGS_UPLOADS_ENABLED=true`). The SDK masks first, prepares an API-key-authenticated upload,
PUTs only to the returned HTTPS object URL, completes backend validation, and exports only a small
canonical `ready` reference. Signed URLs and headers never enter telemetry. In-progress, rejected,
expired, or otherwise incomplete uploads fail explicitly and are exposed through
`get_delivery_diagnostics()`; an oversized item is never also sent through ordinary ingest.

### Prompt client boundary

Prompt-management requests are product-data API calls, not trace telemetry. The telemetry
`mask=` callback does not transform prompt content passed to `PromptClient`; apply any desired
prompt transformation before calling the prompt API. The prompt client authenticates with the
configured project API key and endpoint independently of the telemetry exporter.

Unpinned and label-based reads use a configurable 60-second cache by default. An expired entry is
returned immediately while one background refresh runs for that key; a refresh failure leaves the
stale entry available for the next retry. Explicitly versioned reads stay pinned to that immutable
version for the lifetime of the client. Each client retains at most 100 prompt selectors and runs
at most four background refreshes concurrently. Call `PromptClient.close()` when an explicitly
constructed client is no longer needed. `neatlogs.shutdown()` closes the module-level prompt
clients created from `init()` so a later SDK generation cannot reuse an earlier project's key or
cache. Prompt API failures do not flush, stop, or reconfigure the telemetry provider, and API
response bodies are not copied into SDK error messages.

---

## Integrate into your codebase (recommended)

The fastest way to add NeatLogs to an existing project is the official **Agent Skill** — it encodes import order, `@span` kinds, CrewAI prompt binding, and troubleshooting so your coding agent gets it right.

**Repo:** [github.com/neatlogs/skills](https://github.com/neatlogs/skills)

```bash
npx skills add neatlogs/skills --skill "neatlogs-py"
```

For **Cursor**:

```bash
npx skills add neatlogs/skills --skill "neatlogs-py" --agent "cursor"
```

No Node.js? Ask in chat: *"Install the NeatLogs AI skill from github.com/neatlogs/skills"*

Example prompts once installed:

- *"Add neatlogs tracing to my OpenAI calls"*
- *"Instrument my CrewAI agents with neatlogs"*
- *"Wrap my FastAPI handler so each request is a top-level trace"*

Full install options: [skills README](https://github.com/neatlogs/skills/blob/main/README.md).
The Wizard detects the project, downloads the needed Skill from the latest Skills
release, applies the documented instrumentation, and runs the applicable checks.

API reference: [docs.neatlogs.com](https://docs.neatlogs.com)

---

## Platform features

- **[Traces](https://docs.neatlogs.com/features/traces)**: Full span trees — LLM calls, tools, retrievals, reranking, guardrails — with inputs, outputs, tokens, cost, and latency.

- **[Timeline view](https://docs.neatlogs.com/features/traces#timeline)**: See which steps ran in parallel, where latency concentrated, and where the process was idle.

- **[AI assistant](https://docs.neatlogs.com/features/traces#ai-assistant)**: Ask questions grounded in the actual span data for a trace.

- **[AI Search](https://docs.neatlogs.com/features/ai-search)**: Query traces in plain English without writing SQL.

- **[Detections](https://docs.neatlogs.com/features/detections)**: Rules that flag matching spans — regex, numeric conditions, PII, or model classifiers.

- **[Prompt management](https://docs.neatlogs.com/features/experiments)**: Version prompts, promote labels, test in the Playground.

- **[Evals](https://app.neatlogs.com/evals)**: Human review campaigns — select traces or spans (or auto-collect future ones via filters), send custom rating forms to assigned reviewers, and track batch progress and scores.

- **[Comments & voting](https://docs.neatlogs.com/features/comments)**: Pin notes to spans, @mention teammates, and thumbs-up/down vote outputs while debugging a trace.

---

## Supported libraries

Pass keys to `instrumentations` in `neatlogs.init()`. Install extras when noted.

### LLM providers

| Provider | Key | Install |
|---|---|---|
| OpenAI | `openai` | `pip install "neatlogs[openai]"` |
| Anthropic | `anthropic` | `pip install "neatlogs[anthropic]"` |
| Google Gemini | `google_genai` | `pip install "neatlogs[google-genai]"` |
| Azure AI Inference | `azure_ai_inference` | `pip install "neatlogs[azure-ai-inference]"` |
| Azure OpenAI (`AzureOpenAI` client) | `openai` | `pip install "neatlogs[azure-openai]"` |
| AWS Bedrock | `bedrock` | `pip install "neatlogs[bedrock]"` |
| LiteLLM | `litellm` | `pip install "neatlogs[litellm]"` |
| Groq | `groq` | `pip install "neatlogs[groq]"` |
| Vertex AI | `vertexai` | `pip install "neatlogs[vertexai]"` |
| Vertex AI (google-genai SDK) | `vertex_ai` | `pip install "neatlogs[vertex-ai]"` |
| Mistral | `mistralai` | `pip install "neatlogs[mistralai]"` |
| Portkey | `portkey` | `pip install "neatlogs[portkey]"` |

### Agent frameworks

| Framework | Key | Install |
|---|---|---|
| LangChain | `langchain` | `pip install "neatlogs[langchain]"` |
| LangGraph | `langgraph` | `pip install "neatlogs[langgraph]"` |
| CrewAI | `crewai` | `pip install "neatlogs[crewai]"` |
| LlamaIndex | `llamaindex` | `pip install "neatlogs[llama-index]"` |
| Haystack | `haystack` | `pip install "neatlogs[haystack]"` |
| AutoGen | `autogen` | `pip install "neatlogs[autogen-agentchat]"` |
| DSPy | `dspy` | `pip install "neatlogs[dspy]"` |
| MCP | `mcp` | `pip install "neatlogs[mcp]"` |
| Agno | `agno` | `pip install "neatlogs[agno]"` |
| Google ADK | `google_adk` | `pip install "neatlogs[google-adk]"` |
| OpenAI Agents | `openai_agents` | `pip install "neatlogs[openai-agents]"` |
| Pydantic AI | `pydantic_ai` | `pip install "neatlogs[pydantic-ai]"` |
| smolagents | `smolagents` | `pip install "neatlogs[smolagents]"` |
| Hermes | `hermes` | `pip install "neatlogs[hermes]"` (Python 3.11+) |

### Structured output & guardrails

| Library | Key | Install |
|---|---|---|
| Instructor | `instructor` | `pip install "neatlogs[instructor]"` |
| Guardrails AI | `guardrails` | `pip install "neatlogs[guardrails]"` |

### Vector stores & HTTP

| Library | Key | Notes |
|---|---|---|
| ChromaDB | `chromadb` | Auto-instrumented when installed |
| Pinecone | `pinecone` | Auto-instrumented when installed |
| Qdrant | `qdrant` | Auto-instrumented when installed |
| Weaviate | `weaviate` | Auto-instrumented when installed |
| Milvus | `milvus` | `pip install "neatlogs[milvus]"` |
| Redis | `redis` | Auto-instrumented when installed |
| OpenSearch | `opensearch` | Auto-instrumented when installed |
| Elasticsearch | `elasticsearch` | Auto-instrumented when installed |
| Marqo | `marqo` | Auto-instrumented when installed |
| HTTP clients | `requests`, `httpx`, `urllib3`, `aiohttp` | Auto-instrumented when installed |

---

## Configuration

```bash
NEATLOGS_API_KEY=your-api-key
NEATLOGS_ENDPOINT=https://ingest.neatlogs.com   # optional — this is the default
```

Get your API key from the [NeatLogs dashboard](https://app.neatlogs.com). Full `init()` options: [reference](https://docs.neatlogs.com/reference/init-reference).

### Doctor diagnostics

Validate the SDK's final normalized and masked telemetry envelope without sending data:

```bash
neatlogs doctor --local
# or
neatlogs-doctor --local --json
```

To verify the complete ingestion path, export a controlled trace through the existing
`POST /v1/traces` route and read that exact trace back through
`GET /api/traces/v3/:traceId`:

```bash
NEATLOGS_API_KEY=your-api-key neatlogs doctor --probe --json
```

Local mode is network-free and uses generated diagnostic data. Probe mode reports the first failing backend stage with a stable reason code and never prints the API key.

---

## Examples

Runnable reference apps live in [`examples/sdk_examples/`](examples/sdk_examples/). Each folder has a `requirements.txt` (PyPI install) and `.env.example`.

| Example | Framework | Run |
|---------|-----------|-----|
| `anthropic_multiagent/` | Anthropic + Bedrock | `python main.py` |
| `openai_multiagent/` | OpenAI via Azure | `python main.py` |
| `google_genai_multiagent/` | Google GenAI | `python main.py` |
| `langchain_react/` | LangChain ReAct | `python react_agent.py` |
| `langgraph_multiagent/` | LangGraph | `python main.py` |
| `langgraph_research_assistant/` | LangGraph | `python main.py` |
| `marketing_strategy_demo/` | CrewAI + Gemini search | `python main.py` |
| `neatlogs_support_bot/` | CrewAI RAG bot | `python main.py` |
| `reasoning_model_workflow/` | Multi-provider reasoning | `python main.py` |
| `support_copilot_demo/` | Support agent demo traces | `RUN=A python support_copilot.py` |
| `support_copilot_demo_triaged/` | Post-Triage support demo | `SENDGRID_FAKE_SUCCESS=1 RUN=B python support_copilot.py` |

**Adding NeatLogs to your own code?** Use the [AI skill](#integrate-into-your-codebase-recommended) above — not copy-paste from this README.

---

## Best practices

1. **`init()` before LLM imports** — auto-instrumentation patches libraries at import time.
2. **Wrap script/server entry points in `@span(kind="WORKFLOW")`** — each run gets a clear top-level trace in the dashboard.
3. **Use auto-instrumentation first** — only add more `@span` decorators for custom orchestration.
4. **`trace()` for prompts and sessions** — not as a wrapper around `@span(kind="WORKFLOW")`.
5. **`workflow_name` = feature name** — put env/version/tech stack in `tags=`.
6. **Scripts:** `flush()` then `shutdown()` at exit. **Servers:** `init()` once, no per-request shutdown.

---

## License

MIT — see [LICENSE](LICENSE).
