Metadata-Version: 2.4
Name: qunivex
Version: 1.0.0
Summary: Official Python SDK for the Qunivex API — run AI agents, semantic search, and manage projects.
Project-URL: Homepage, https://qunivex.com
Project-URL: Documentation, https://qunivex.com/docs/api
Project-URL: API Keys, https://qunivex.com/api-keys
Author-email: Qunivex <support@qunivex.com>
License: MIT
License-File: LICENSE
Keywords: agents,ai,chatbot,llm,openai-compatible,qunivex,rag,semantic-search,vector-search
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.8
Classifier: Programming Language :: Python :: 3.9
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 :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: requests>=2.25.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.23; extra == 'dev'
Description-Content-Type: text/markdown

# qunivex

Official Python SDK for the [Qunivex](https://qunivex.com) API — run your AI agents, search your knowledge base, and manage your projects from Python.

```bash
pip install qunivex
```

```python
from qunivex import Qunivex

qx = Qunivex(api_key="qvx-live-...")

print(qx.chat("QXA-8MQ7KN2A", "What plans do you offer?").content)
```

Create a key on the [API page](https://qunivex.com/api-keys) in your dashboard. Full reference: **[qunivex.com/docs/api](https://qunivex.com/docs/api)**

---

## `model` is an agent, not a foundation model

This is the one thing to know before you start. A Qunivex **agent** is a whole configured pipeline — system prompt, knowledge base, tools, MCP servers, sub-agents, guardrails, workflow — built in your dashboard. *Which* LLM runs inside it is the agent's own setting, not something the caller picks.

So `model` takes a `QXA-` agent id. A `QXP-` project id also works and resolves to that project's main agent.

| Prefix  | Names                       | Example         |
| ------- | --------------------------- | --------------- |
| `QXP-`  | Project                     | `QXP-1DM2J8K0`  |
| `QXA-`  | Agent (main or sub-agent)   | `QXA-8MQ7KN2A`  |

Both are on your project's Overview page.

## Setup

```python
qx = Qunivex(api_key="qvx-live-...")
```

Or set `QUNIVEX_API_KEY` in the environment and pass nothing — worth preferring, since a key in source is a key in version control.

```python
qx = Qunivex()                                  # reads QUNIVEX_API_KEY
qx = Qunivex(timeout=60, max_retries=4)         # tune transport
with Qunivex() as qx: ...                        # closes the HTTP session
```

Rate limits and transient server errors are retried automatically with exponential backoff and jitter, honouring `Retry-After`. A *quota* 429 is not retried — waiting will not help.

## Chat

```python
reply = qx.chat("QXA-8MQ7KN2A", "What plans do you offer?")

reply.content            # the text
reply.total_tokens       # usage for this turn
reply.blocked            # True if a guardrail stopped it
reply.raw                # the untouched API payload
```

Stream it:

```python
for piece in qx.stream("QXA-8MQ7KN2A", "Tell me about Pro"):
    print(piece, end="", flush=True)
```

### Multi-turn

The API is **stateless** — every request carries the full history, exactly like OpenAI's. That makes each call reproducible and leaves the context window under your control.

Manage the transcript yourself:

```python
history = [{"role": "user", "content": "What plans do you offer?"}]
r = qx.chat(AGENT, history[-1]["content"])
history.append({"role": "assistant", "content": r.content})
```

…or let a `Conversation` do it:

```python
convo = qx.conversation("QXA-8MQ7KN2A", system="Be concise.")

convo.send("What plans do you offer?")
convo.send("Which suits two people?")     # remembers the first turn

convo.messages          # the running transcript — mutable, trim it if you like
convo.reset()           # start over, keeping the system message
```

A `Conversation` also generates one `conversation_id` and sends it with every turn, so the whole exchange is grouped together in your dashboard's Logs.

### Raw OpenAI shape

When you need a field the wrappers do not surface:

```python
resp = qx.completions.create(
    model="QXA-8MQ7KN2A",
    messages=[{"role": "user", "content": "Hello"}],
    temperature=0.2,
)
resp["choices"][0]["message"]["content"]
```

## Semantic search

Retrieval only — no model call, so it is far cheaper and faster than a completion. Ideal for adding search to your own site over content already in Qunivex.

```python
for hit in qx.search("QXP-1DM2J8K0", "refund policy", top_k=3):
    print(round(hit.score, 3), hit.source, hit.text[:100])
```

`score` is a relevance figure in `(0, 1]` — higher is better. Pass `agent=` to search a sub-agent's own knowledge base instead of the project's.

If the embedding provider is down this raises `ServiceUnavailableError` rather than returning an empty list, so "nothing matched" and "search is broken" never look the same.

## Projects

```python
for p in qx.projects.list():
    print(p.id, p.name)

project = qx.projects.retrieve("QXP-1DM2J8K0")

project.name                        # 'Acme Support'
project.main_agent.system_prompt
project.main_agent.model
project.sub_agents                  # [AgentInfo, ...]
project.knowledge.documents         # 12

project.update(
    name="Acme Support",
    main_agent={"system_prompt": "Be concise and always cite a source.",
                "temperature": 0.3},
)

project.chat("What plans do you offer?")     # via the main agent
project.search("refund policy")
```

Editable at the top level: `name`, `description`. Under `main_agent`: `name`, `system_prompt`, `model`, `temperature`, `max_tokens`, `memory`, `widget_enabled`. Only fields you pass are changed.

## Knowledge base

```python
docs = project.documents

docs.upload("handbook.pdf")
docs.add_text("faq.txt", "Q: Do you ship internationally? A: Yes…")

for d in docs.list():
    print(d.filename, d.status, d.chunks, d.tokens)

docs.delete(doc_id)
```

Indexing happens before the response returns, so the `Document` you get back already carries its final `status` and chunk count — nothing to poll. Supported: PDF, DOCX, TXT, MD, HTML, JSON, XML, RTF, YAML and other plain-text formats.

Uploads count against your plan's knowledge-base token budget.

## Discovery & usage

```python
for m in qx.models():
    print(m.id, m.name, m.project_name, m.model_name)

u = qx.usage()
print(f"{u.api_calls_used}/{u.api_calls_limit} calls, {u.api_calls_remaining} left")
```

Neither counts against your quota.

## Errors

```python
from qunivex import (
    QunivexError, AuthenticationError, PermissionDeniedError,
    NotFoundError, RateLimitError, QuotaExceededError,
)

try:
    qx.chat(AGENT, "hello")
except QuotaExceededError:
    ...                     # monthly allowance spent — upgrade or wait
except RateLimitError as e:
    ...                     # burst limit; e.retry_after has the wait
except AuthenticationError:
    ...                     # bad or revoked key
except QunivexError as e:
    print(e.status, e.code, e.message)
```

Every error subclasses `QunivexError`, so that last clause is a complete catch.

`NotFoundError` is also what you get for a project this key was **scoped away from** — the API does not distinguish the two, so key scoping cannot be used to probe what sits behind it.

## Keys & security

- Keys look like `qvx-live-` plus 28 characters, and the full value is shown **once**, when you create it. Only a hash is stored, so it cannot be recovered — lose it, revoke it, mint a new one.
- A key belongs to your **account**, and can optionally be narrowed to specific projects (or to none) when you create or edit it.
- A key carries the full rights of your account within its scope, including editing an agent's prompt and deleting documents. **Never ship one to a browser, a mobile app, or a public repo.** For a chat widget on a website, use the embed snippet on your project's Deployments page — that uses a separate, domain-locked public key designed to be visible.

## Requirements

Python 3.8+ and `requests`. That is the whole dependency list.

## License

MIT
