Metadata-Version: 2.5
Name: opscopilot-core
Version: 0.2.1
Summary: An agentic SRE engine: screenshot-grounded bug triage, code-aware RAG diagnosis, and human-in-the-loop remediation.
Project-URL: Homepage, https://github.com/RaushanAlpha23/opscopilot-core
Project-URL: Repository, https://github.com/RaushanAlpha23/opscopilot-core
Project-URL: Issues, https://github.com/RaushanAlpha23/opscopilot-core/issues
Project-URL: Changelog, https://github.com/RaushanAlpha23/opscopilot-core/blob/main/CHANGELOG.md
Author: Raushan Alpha
License: MIT License
        
        Copyright (c) 2026 Ops-Copilot contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: agent,devops,incident,langgraph,rag,sre,triage
Classifier: Development Status :: 4 - Beta
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: Topic :: Software Development :: Bug Tracking
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: pydantic-settings>=2.2
Requires-Dist: pydantic>=2.6
Provides-Extra: all
Requires-Dist: langchain-core>=0.3; extra == 'all'
Requires-Dist: langchain-mistralai>=0.2; extra == 'all'
Requires-Dist: langgraph>=0.2; extra == 'all'
Requires-Dist: openai>=1.30; extra == 'all'
Requires-Dist: qdrant-client>=1.9; extra == 'all'
Requires-Dist: redis>=5.0; extra == 'all'
Requires-Dist: requests>=2.31; extra == 'all'
Requires-Dist: sentence-transformers>=3.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: langchain-core>=0.3; extra == 'dev'
Requires-Dist: langchain-mistralai>=0.2; extra == 'dev'
Requires-Dist: langgraph>=0.2; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: openai>=1.30; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: qdrant-client>=1.9; extra == 'dev'
Requires-Dist: redis>=5.0; extra == 'dev'
Requires-Dist: requests>=2.31; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: sentence-transformers>=3.0; extra == 'dev'
Requires-Dist: twine>=5.1; extra == 'dev'
Provides-Extra: github
Requires-Dist: requests>=2.31; extra == 'github'
Provides-Extra: graph
Requires-Dist: langgraph>=0.2; extra == 'graph'
Provides-Extra: local-embeddings
Requires-Dist: sentence-transformers>=3.0; extra == 'local-embeddings'
Provides-Extra: mistral
Requires-Dist: langchain-core>=0.3; extra == 'mistral'
Requires-Dist: langchain-mistralai>=0.2; extra == 'mistral'
Provides-Extra: openai
Requires-Dist: openai>=1.30; extra == 'openai'
Provides-Extra: qdrant
Requires-Dist: qdrant-client>=1.9; extra == 'qdrant'
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == 'redis'
Description-Content-Type: text/markdown

# opscopilot-core

An agentic SRE engine: screenshot-grounded bug triage, code-aware RAG diagnosis, and human-in-the-loop remediation.

Point it at a repository, hand it a bug report and a screenshot, and it will read the screen, retrieve the code most likely responsible, propose a root cause with a confidence score, ask a clarifying question when it isn't sure, and file a reviewed GitHub issue once a human approves.

```bash
pip install opscopilot-core
```

---

## Quickstart

The base install pulls no LLM SDK, no vector database and no torch, so this runs offline in a couple of seconds:

```python
from opscopilot import OpsCopilot

cop = OpsCopilot.from_env(llm="mistral:mistral-small-latest")
cop.index_repository("./my_app")

state = cop.submit(
    user_note="Cart total shows NaN after removing an item",
    screenshot_path="bug.png",
    api_response={"items": [], "total": None},
)

print(state.status)               # awaiting_approval
print(state.diagnosis.summary)    # "get_total_price divides by len(items)..."
print(state.diagnosis.confidence) # 0.86
print(state.remediation.suggested_fix)

cop.approve(state.incident_id)    # files the GitHub issue
```

If the engine isn't confident enough, it asks instead of guessing:

```python
if state.status is IncidentStatus.awaiting_evidence:
    print(state.pending_question)   # "Can you paste the response from GET /api/cart?"
    state = cop.provide_evidence(state.incident_id, "total is null, items is []")
```

### From the command line

```bash
export OPSCOPILOT_MISTRAL_API_KEY=...

opscopilot --vector-store memory://./index.json index ./my_app
opscopilot submit --note "cart total is NaN" --screenshot bug.png
opscopilot evidence <incident-id> "GET /api/cart returns total: null"
opscopilot approve <incident-id>
```

---

## How it works

```
submit()
   │
   ├─ triage ............ severity + category from the written report
   ├─ vision ............ structured read of the screenshot, cross-checked
   │                      against the API response
   ├─ retrieval ......... query built from the vision model's grounded
   │                      symptom, not the reporter's wording
   ├─ diagnosis ......... one call correlating symptom + code + schema,
   │                      returning a root cause and a confidence score
   │
   └─ confidence gate
        ├─ below threshold ─→ ask ONE specific question ──→ awaiting_evidence
        │                     (provide_evidence resumes from diagnosis)
        └─ at/above ────────→ remediation ────────────────→ awaiting_approval
                                                              │
                                             approve() ───────┴──→ ticketed
                                             reject()  ──────────→ rejected
```

Both pauses are durable. State lives in the state store, not in a Python variable, so an incident parked on a human decision survives a restart or a deploy — which matters, because that gap is measured in hours.

---

## Configuration

Every backend is chosen by a string, so swapping one is a config change rather than a code change.

| Setting | Env var | Default | Options |
|---|---|---|---|
| `llm` | `OPSCOPILOT_LLM` | `mistral:mistral-small-latest` | `mistral:*`, `openai:*`, `fake:*` |
| `vision_llm` | `OPSCOPILOT_VISION_LLM` | falls back to `llm` | same |
| `embeddings` | `OPSCOPILOT_EMBEDDINGS` | `hash:384` | `hash:N`, `st:<model>`, `openai:<model>` |
| `vector_store` | `OPSCOPILOT_VECTOR_STORE` | `memory://` | `memory://`, `memory://path.json`, `http://host:6333` |
| `state_store` | `OPSCOPILOT_STATE_STORE` | `memory://` | `memory://`, `redis://host:6379/0` |
| `confidence_threshold` | `OPSCOPILOT_CONFIDENCE_THRESHOLD` | `0.6` | |
| `max_evidence_rounds` | `OPSCOPILOT_MAX_EVIDENCE_ROUNDS` | `2` | questions asked before proceeding anyway |
| `github_token` / `github_repo` | `OPSCOPILOT_GITHUB_TOKEN` / `_REPO` | unset | ticketing is skipped when unset |

A production setup:

```bash
OPSCOPILOT_LLM=mistral:mistral-small-latest
OPSCOPILOT_EMBEDDINGS=st:sentence-transformers/all-MiniLM-L6-v2
OPSCOPILOT_VECTOR_STORE=http://localhost:6333
OPSCOPILOT_STATE_STORE=redis://localhost:6379/0
OPSCOPILOT_GITHUB_REPO=your-org/your-repo
```

```bash
pip install 'opscopilot-core[mistral,qdrant,redis,local-embeddings,github]'
```

### Defaults worth knowing

`hash:384` is a dependency-free lexical embedder. It exists so the package installs and runs instantly, and so the test suite exercises real retrieval rather than a mock. It matches identifiers well and has no semantic understanding — `get_total_price` will match a query naming it, but "prices look wrong" will not find `calculate_subtotal`. **Use `st:` or `openai:` embeddings for real retrieval quality.**

`memory://` holds the index in the process. Use `memory://./index.json` to persist across CLI invocations, or Qdrant for anything concurrent.

---

## Extras

| Extra | Brings | Needed for |
|---|---|---|
| `mistral` | `langchain-mistralai` | Mistral models (incl. vision) |
| `openai` | `openai` | OpenAI chat + embeddings |
| `qdrant` | `qdrant-client` | Qdrant vector store |
| `redis` | `redis` | durable incident state |
| `local-embeddings` | `sentence-transformers` | local semantic embeddings (pulls torch) |
| `github` | `requests` | filing issues |
| `graph` | `langgraph` | the optional `StateGraph` wiring |
| `all` | everything above | |

---

## Extending it

Everything is a protocol, and the engine takes injected implementations:

```python
from opscopilot import OpsCopilot
from opscopilot.models import Ticket

class LinearTickets:
    name = "linear"
    def create(self, state) -> Ticket:
        ...

cop = OpsCopilot(ticket_provider=LinearTickets())
```

The same pattern works for `llm`, `embedder`, `vector_store` and `state_store`. Prompts live in one module (`opscopilot.nodes.prompts`) so they can be overridden without touching node logic.

### Progress events

```python
cop.on_event(lambda e: print(e.type, e.data))
# incident.received / node.started / node.finished / awaiting_approval / ticket.created
```

The engine never assumes where events go. Subscribe a handler that publishes to Redis pub/sub, writes a log line, or appends to a trace document — see `examples/fastapi_app.py` for streaming them to a browser over SSE.

---

## Testing against it

`FakeLLM` ships in the package so downstream tests need no API keys:

```python
from opscopilot import OpsCopilot
from opscopilot.llm import FakeLLM

cop = OpsCopilot.from_env(llm="fake:test", embeddings="hash:256")
cop._llm = cop._vision_llm = FakeLLM(handler=my_scripted_responses)
```

---

## Development

```bash
pip install -e '.[dev]'
pytest
ruff check src tests
mypy src
```

## License

MIT
