Metadata-Version: 2.4
Name: error-logging-agent
Version: 1.0.0
Summary: Plug-n-play AI error-triage agent: captures Python tracebacks, has an LLM explain them, and reports to Slack + JIRA. Never auto-fixes — humans fix.
Author: Smartfinancial
License: MIT
Keywords: error,traceback,llm,ollama,groq,slack,jira,triage,observability
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28
Requires-Dist: pydantic>=2.0
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"

# error-logging-agent

A **plug-n-play AI error-triage agent** for Python. It captures every error
traceback, asks an LLM to explain it (what caused it, which files are involved,
and how a human might fix it), and reports the result to **Slack** and **JIRA**.

**It never fixes your code.** By design, the agent only *diagnoses and advises* —
a human applies the fix. There is no code path that edits files or runs commands.

This is **Version A**: a deterministic pipeline (capture → LLM → dedup → notify).
It's built so the "analyze" step can later be upgraded to an agentic investigator
(reading repo files, git blame, etc.) without touching the rest.

---

## Install

```bash
pip install error_logging_agent-1.0.0-py3-none-any.whl      # from the built wheel
# or, from source:  pip install .    (add -e for editable/dev)
```

Requires Python 3.9+. Dependencies are light: `requests`, `pydantic`, `PyYAML`.
Ollama, Groq, Slack, and JIRA are all reached over plain HTTP — no heavy SDKs.

## How configuration & Ollama work (read this first)

This package is a **client**, not a host. It does **not** run Ollama and it does
**not** own your environment — the application that installs it does. You point
the library at services that already exist. Configuration resolves in this order
(highest priority first):

1. **Arguments to `install()`** — inline overrides in code
2. **Environment variables** — the usual way to supply secrets/endpoints in prod
3. **A `config.yaml`** — if you pass `config_path=` or set `ERROR_AGENT_CONFIG`
4. **Built-in defaults**

A `.env` file in the working directory is auto-loaded (no extra dependency), so
local dev "just works" without exporting anything.

### Ollama

Ollama runs as a **separate service you already operate** — the library only
makes HTTP calls to it. Nothing about installing this package installs or starts
Ollama. You tell the library where it is and which model to use:

```bash
export LLM_PROVIDER=ollama
export OLLAMA_BASE_URL=http://localhost:11434     # or a remote host
export OLLAMA_MODEL=qwen2.5-coder:7b              # any model you've `ollama pull`ed
```

If Ollama lives on another machine, just point `OLLAMA_BASE_URL` at it. If you'd
rather use the hosted Groq fallback instead of (or in addition to) Ollama, set
`GROQ_API_KEY` and select it — see below.

### All recognized environment variables

| Variable | Purpose |
|---|---|
| `LLM_PROVIDER` | `ollama` (default) or `groq` |
| `OLLAMA_BASE_URL` | Ollama server URL (default `http://localhost:11434`) |
| `OLLAMA_MODEL` | Ollama model name (default `qwen2.5-coder:7b`) |
| `GROQ_API_KEY` | Groq key (enables the cloud provider) |
| `GROQ_MODEL` | Groq model (e.g. `openai/gpt-oss-120b`) |
| `SLACK_WEBHOOK_URL` | Slack incoming webhook (simplest) |
| `SLACK_BOT_TOKEN` + `SLACK_CHANNEL` | Slack bot posting (alternative) |
| `JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_API_TOKEN`, `JIRA_PROJECT_KEY` | JIRA Cloud |
| `ERROR_AGENT_ENV`, `ERROR_AGENT_SERVICE` | environment / service labels |
| `ERROR_AGENT_CONFIG` | path to a `config.yaml` |

### Configure entirely in code (no YAML needed)

Everything can be passed to `install()` — handy when your app already has its own
settings system:

```python
import error_logging_agent

error_logging_agent.install(
    service="smarthub",
    environment="production",
    llm={
        "primary":  {"kind": "ollama", "model": "qwen2.5-coder:7b",
                     "base_url": "http://localhost:11434"},
        "fallback": {"kind": "groq", "model": "openai/gpt-oss-120b"},  # api_key via GROQ_API_KEY
    },
    slack={"enabled": True},   # webhook via SLACK_WEBHOOK_URL
    jira={"enabled": True, "base_url": "https://yourorg.atlassian.net",
          "project_key": "SHA"},   # email/token via env
)
```

Secrets (webhook, JIRA token, Groq key) are best left in environment variables
even when configuring in code — the library reads them automatically.

## Turn it on (one line)

```python
import error_logging_agent
error_logging_agent.install()          # reads config.yaml / env vars
```

From that point on:

- any **uncaught exception** (main thread, other threads, process crash) is captured
- anything sent via **`logger.exception(...)` / `logger.error(..., exc_info=True)`** is captured
- you can also report manually: `error_logging_agent.report_exception(customer_id="acct_123")`

Everything runs on a **background worker**, so your app is never blocked waiting on
the LLM, and a failure inside the agent can never crash your app.

## Configure

Copy `src/error_logging_agent/config.example.yaml` to `config.yaml`, then either set
`ERROR_SENTINEL_CONFIG=/path/to/config.yaml` or call
`install(config_path="config.yaml")`. Secrets are best passed via env vars:

| Env var | Purpose |
|---|---|
| `GROQ_API_KEY` | Groq API key (for the cloud fallback provider) |
| `SLACK_BOT_TOKEN` + `SLACK_CHANNEL` | Slack bot posting (preferred) |
| `SLACK_WEBHOOK_URL` | Slack incoming webhook (simplest alternative) |
| `JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_API_TOKEN`, `JIRA_PROJECT_KEY` | JIRA Cloud |

### LLM providers (plug-n-play)

The LLM layer is provider-agnostic. Pick a `primary` and an optional `fallback`:

```yaml
llm:
  primary:                       # local & private — recommended default
    kind: ollama
    model: qwen2.5-coder:7b
    base_url: http://localhost:11434
  fallback:                      # used automatically if primary errors/times out
    kind: groq
    model: openai/gpt-oss-120b   # api_key via GROQ_API_KEY
```

Switching Ollama ↔ Groq is a **config change, never a code change**. Adding a new
provider (OpenAI, Anthropic, …) is one small subclass of `LLMProvider`.

## Testing — from zero-setup to full integration

**Level 0 — unit tests (no services, ~1s).** Proves the capture → fingerprint →
dedup → analyze → notify logic and the FastAPI middleware:

```bash
pip install -e ".[dev]"
pytest -q
```

**Level 1 — dry-run selftest (no Slack/JIRA needed).** Fires a synthetic error and
prints the analysis to your terminal. With Ollama down you'll see an
"analysis unavailable" block (proves the plumbing); with Ollama up you'll see a
real diagnosis:

```bash
# start Ollama and pull a model first, e.g.:
ollama serve &            # if not already running
ollama pull qwen2.5-coder:7b

error-logging-agent --config config.yaml selftest --dry-run
```

`dry_run: true` (or a bare install with no notifier configured) routes results to
the console notifier, so you can iterate on models/prompts before touching Slack.

**Level 2 — real Slack (still no JIRA).** In `config.yaml` set `slack.enabled: true`
and export `SLACK_WEBHOOK_URL` (or `SLACK_BOT_TOKEN` + `SLACK_CHANNEL`), then:

```bash
error-logging-agent --config config.yaml selftest
```

A formatted message should land in your channel.

**Level 3 — FastAPI end to end.** Run the example app and trigger a route error:

```bash
uvicorn examples.fastapi_app:app --port 8000
curl http://localhost:8000/boom      # 500 to the client; triage in your logs/Slack
```

**Test dedup:** hit `/boom` (or run selftest) several times quickly — you should
get exactly **one** notification, with the occurrence count rising on repeats.

---

## How it works

```
exception ──► capture (fast, non-blocking) ──► queue ──► worker
                                                            │
   scrub secrets ─► fingerprint ─► dedup/rate-limit ─► LLM analyze ─► notify
                                                                       ├─ JIRA (create/update)
                                                                       └─ Slack (Block Kit)
```

Key design points:

- **Dedup + rate-limiting first.** The same bug is fingerprinted (exception type +
  normalized stack) so repeats collapse into one ticket/alert. A per-minute cap
  stops error *storms* from flooding Slack/JIRA. Dedup runs *before* the LLM, so you
  also don't re-pay to analyze the same error.
- **Secret scrubbing.** Emails, API keys, tokens, and card numbers are redacted
  before anything reaches a cloud LLM or JIRA. Local Ollama is the default so
  tracebacks can stay entirely on your infrastructure.
- **Graceful degradation.** If the LLM is unreachable, you still get the raw
  traceback reported with an "analysis unavailable" note. If a notifier fails, the
  worker keeps running.
- **No auto-fix.** The `Analysis` model has no field for a patch or command. The
  agent describes a fix; a human applies it.

## Module map

| File | Responsibility |
|---|---|
| `__init__.py` | public API: `install`, `report_exception`, `shutdown` |
| `capture.py` | logging handler + excepthooks → `ErrorEvent` |
| `worker.py` | background thread + queue (swap for Redis/RQ later) |
| `pipeline.py` | scrub → fingerprint → dedup → analyze → notify |
| `analyzer.py` | prompt + provider call + JSON validation + fallback |
| `providers/` | `OllamaProvider`, `GroqProvider` behind one interface |
| `notifiers/` | `SlackNotifier`, `JiraNotifier` |
| `dedup.py` | SQLite fingerprint store + rate limiter |
| `fingerprint.py` | stable hashing of errors |
| `scrub.py` | PII/secret redaction |
| `config.py` | YAML + env config schema |

## FastAPI integration (SmartHub)

The logging handler already covers any app using standard `logging`, but the
FastAPI integration adds **request context** (method, path, client, safe headers)
to every captured error. Two lines at startup:

```python
from fastapi import FastAPI
import error_logging_agent
from error_logging_agent.integrations.fastapi import instrument

app = FastAPI()
error_logging_agent.install()      # start the sentinel (reads config/env)
instrument(app)               # capture request errors + context, then re-raise
```

The middleware is pure ASGI: it never touches successful responses, and on an
unhandled error it reports to the sentinel and then **re-raises**, so FastAPI's
normal 500 handling is completely unchanged. Only an allow-list of headers is
captured (`user-agent`, `x-request-id`, …) — `Authorization`/`Cookie` are never
collected. See `examples/fastapi_app.py`.

Other frameworks follow the same pattern (Django middleware / `got_request_exception`,
Flask `@app.errorhandler`) — call `error_logging_agent.report_exception(**context)`.

## Running without JIRA (Slack-only)

JIRA is **off by default** — you don't need a project key to start. With
`jira.enabled: false` (or simply omitting the block), errors are analyzed and
posted to Slack only. When you're ready for tickets, create a JIRA project (its
**key** is the short prefix in issue ids, e.g. `SMART-123` → key `SMART`), then
set `jira.enabled: true` with `base_url` / `project_key` and the token env vars.
Nothing else changes.

## Roadmap to Version B (agentic)

The `Analyzer` is the single seam to upgrade. Replace its one-shot LLM call with a
tool-using loop (read the files named in the traceback, run git blame, search past
errors) and the rest of the pipeline — capture, dedup, Slack, JIRA — stays exactly
the same.
