Metadata-Version: 2.5
Name: evolink-sdk
Version: 0.1.0
Summary: Async Python SDK for the Evolink memory and RAG API
Project-URL: Documentation, https://github.com/sireto/evolink/blob/master/sdk/README.md
Project-URL: Repository, https://github.com/sireto/evolink
Author: Evolink
License: MIT
Requires-Python: >=3.12
Requires-Dist: httpx<1,>=0.27
Description-Content-Type: text/markdown

# evolink-sdk

Async Python HTTP client for the Evolink API.

This package is intentionally a remote client, not the Evolink engine. It sends HTTPS requests and
does not contain ingestion, memory, RAG, prompts, database access, provider SDKs, or LLM logic.
Use it from another product calling a deployed Evolink API.

## Install

```bash
pip install evolink-sdk
```

## Configuration

The SDK needs the public API URL and the API key generated from the Evolink admin dashboard:

```env
EVOLINK_API_URL=https://sdk.evolink.example.com/api/v1
EVOLINK_API_KEY=sk_generated-from-admin-dashboard
EVOLINK_TIMEOUT=60
```

`EVOLINK_API_KEY` is the caller-to-Evolink-API credential. It is sent as `Authorization: Bearer ...`;
it is not an OpenAI/LLM key. `LLM_API_KEY` and `OCR_API_KEY` remain server-side settings and are
never bundled into this client. Generate an `sk_...` secret key for the target workspace using the
SDK.

## Before you use the SDK

The SDK requires:

1. A deployed Evolink API reachable from the application, including the `/api/v1` URL prefix.
2. An API key generated for the target workspace in the Evolink dashboard's **API Keys** section.
   Copy the `sk_...` key when
   it is created; the plaintext value is shown only once.

The API key is sent as `Authorization: Bearer ...`. Store it as a server-side application secret;
never expose it in browser code or commit it to source control. Python and other server-side SDK
consumers do not need CORS configuration.

The capabilities available to your application depend on how the connected Evolink deployment is
configured. For example, memory extraction and answer generation require an enabled language model,
while semantic retrieval requires embeddings. If a capability is unavailable, the API returns an
error rather than requiring additional packages in the client.

The SDK package does not start the API, create workspaces, generate keys, or contain provider
credentials. Those setup tasks are completed before configuring the client.

## Usage

```python
from evolink_sdk import EvolinkClient

async with EvolinkClient(
    api_url="https://sdk.evolink.example.com/api/v1",
    api_key="sk_generated-from-admin-dashboard",
) as client:
    document = await client.add(
        content="The team standard is PostgreSQL.",
        task_type="memory",
    )

    answer = await client.rag.query(
        query="What is the team database standard?",
        document_id=document["id"],
    )
```

Document ingestion supports two task types:

- `task_type="memory"` (default): extracts atomic facts, creates workspace memories, and updates profiles/graph relationships when the server LLM is configured.
- `task_type="superrag"`: extracts and chunks content for retrieval, but intentionally does not generate memories.

Memory ingestion also accepts `dreaming="dynamic"` (default) or `dreaming="instant"`.
`dynamic` gives the extractor related workspace memory context for reconsolidation and updates;
`instant` processes the document independently without previous-memory context. This option is
available on `client.add()`, `client.documents.create()`, and `client.documents.upload_file()`.

Memory extraction processes document chunks in batches of five by default. Pass `batch_size` to
`client.add()`, `client.documents.create()`, or `client.documents.upload_file()` to customize the
batch size to any positive integer. The same option is available on the stateless
`client.memories.generate()` and `client.profiles.generate()` methods.

Use the same option with `client.add()`, `client.documents.create()`, and
`client.documents.upload_file()`:

```python
reference = await client.documents.upload_file(
    file="architecture.pdf",
    task_type="superrag",
)

facts = await client.add(
    content="The team standard is PostgreSQL.",
    task_type="memory",
)
```

The typed SDK alias is available as `evolink_sdk.TaskType` and accepts only
`"memory"` or `"superrag"`.

Generated memories are classified as `semantic`, `episodic`, or `preference` by the configured
memory extractor. Direct memory writes can select the type explicitly:

```python
memory = await client.memories.add(
    content="The user prefers PostgreSQL.",
    memory_type="preference",
)
```

The typed SDK alias `evolink_sdk.MemoryType` accepts `"semantic"`, `"episodic"`, or
`"preference"`.

The client exposes ingestion, documents, memories, profiles, stateless memory/profile generation,
stateless embeddings and reranking, safe configuration, and RAG operations. It does not expose
workspace provisioning, admin settings, dashboards, graph administration, or API-key generation.
See the repository [SDK documentation](../docs/SDK.md) for the complete task-by-task reference.

RAG retrieval supports `search_mode="memory"` for extracted memories only,
`search_mode="document"` for original document chunks only, and
`search_mode="hybrid"` (default) for both sources:

```python
results = await client.rag.retrieve(
    query="What database does the team use?",
    search_mode="memory",
    rerank_limit=5,
)
answer = await client.rag.query(
    query="How do I configure the database?",
    search_mode="document",
)
```

`client.config()` fetches safe published project configuration. `client.rag.query()` returns the
complete answer and retrieval evidence. The admin panel is not an SDK consumer and uses the admin
API directly.

## Complete client reference

All methods are asynchronous and must be called with `await`.

### Client setup

```python
from evolink_sdk import EvolinkClient

client = EvolinkClient(
    api_url="https://sdk.example.com/api/v1",  # required
    api_key="sk_live_...",                     # required
    timeout=60.0,                               # optional seconds; default: 60
)
try:
    config = await client.config()
finally:
    await client.close()
```

`EvolinkClient.from_env()` reads `EVOLINK_API_URL` (required), `EVOLINK_API_KEY` (required), and
`EVOLINK_TIMEOUT` (optional, default `60`).
The async context-manager form closes the HTTP connection automatically.

### `client.add(...)`

Adds text or a URL as a document. The server determines whether `content` is a URL or text.

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `content` | Yes | — | Text or URL to ingest; must not be empty. |
| `metadata` | No | `{}` | JSON-compatible application metadata. |
| `task_type` | No | `"memory"` | `"memory"` extracts facts; `"superrag"` indexes content without creating memories. |
| `dreaming` | No | `"dynamic"` | `"dynamic"` uses related memory context; `"instant"` processes independently. |
| `batch_size` | No | `5` | Number of document chunks sent to the memory extractor per batch; must be positive. |
| `name` | No | `"content"` | Document display name. |

```python
document = await client.add(
    content="https://example.com/architecture",
    name="Architecture reference",
    task_type="superrag",
    metadata={"source_system": "docs", "team": "platform"},
)
```

### `client.config`

Returns safe server configuration for a workspace, such as enabled providers and model names.
Provider credentials are never returned. The client workspace is used automatically by every
service.

### `client.documents`

#### `documents.create(...)`

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `name` | Yes | — | Non-empty document name, for example `"meeting-notes.md"`. |
| `content` | Yes | — | Text content or a URL. |
| `source` | No | `"upload"` | Source label, for example `"notion"`, `"url"`, or `"upload"`. |
| `content_type` | No | `"text/plain"` | MIME type, for example `"text/markdown"` or `"text/html"`. |
| `task_type` | No | `"memory"` | `"memory"` or `"superrag"`. |
| `dreaming` | No | `"dynamic"` | `"dynamic"` or `"instant"`. |
| `batch_size` | No | `5` | Number of document chunks sent to the memory extractor per batch; must be positive. |
| `metadata` | No | `{}` | JSON-compatible metadata. |

```python
document = await client.documents.create(
    name="team-preferences.md",
    content="The team prefers PostgreSQL for transactional workloads.",
    source="internal-wiki",
    content_type="text/markdown",
    task_type="memory",
    dreaming="dynamic",
    metadata={"department": "engineering", "quarter": "2026-Q1"},
)
```

#### `documents.upload_file(...)`

`file` is required and accepts raw `bytes`, a `bytearray`, a binary file object, or a filesystem
path. All other parameters are optional. `name` defaults to the path filename, or `"upload"` for
raw bytes. `content_type` defaults to `"application/octet-stream"`.

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `file` | Yes | — | Raw bytes, a binary file object, or a filesystem path. |
| `name` | No | Path filename or `"upload"` | Document display name. |
| `content_type` | No | `"application/octet-stream"` | MIME type of the uploaded file. |
| `task_type` | No | `"memory"` | `"memory"` extracts facts; `"superrag"` indexes without creating memories. |
| `dreaming` | No | `"dynamic"` | `"dynamic"` uses related memory context; `"instant"` processes independently. |
| `batch_size` | No | `5` | Number of document chunks sent to the memory extractor per batch; must be positive. |
| `metadata` | No | `{}` | JSON-compatible application metadata. |

```python
document = await client.documents.upload_file(
    file="./handbook.pdf",
    name="engineering-handbook.pdf",
    content_type="application/pdf",
    task_type="superrag",
    dreaming="instant",
    metadata={"source": "handbook", "version": 3},
)
```

#### Document lookup and lifecycle methods

`documents.list()` returns lightweight document summaries containing identity, source, type, and
processing status; it intentionally excludes large `content` and `metadata` fields.
`documents.get(document_id)` returns one full document. `documents.retry(document_id)` retries a
failed document. `documents.chunks(document_id)` returns its indexed chunks.
`documents.memories(document_id)` returns memories linked to it.
`documents.delete(document_id)` permanently deletes it and returns `None`.
`document_id` is always required; the client workspace is used automatically.

```python
details = await client.documents.get(document["id"])
chunks = await client.documents.chunks(document["id"])
if details["status"] == "failed":
    await client.documents.retry(document["id"])
```

### `client.memories`

#### `memories.add(...)`

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `content` | Yes | — | The fact or event to store. |
| `document_id` | No | — | Optional source document UUID. |
| `summary` | No | — | Short human-readable summary. |
| `memory_type` | No | `"semantic"` | `"semantic"`, `"episodic"`, or `"preference"`. |
| `importance` | No | `0.5` | Number from `0.0` to `1.0`. |
| `metadata` | No | `{}` | JSON-compatible metadata. |

```python
memory = await client.memories.add(
    content="The user prefers concise technical explanations.",
    memory_type="preference",
    importance=0.85,
    metadata={"source": "onboarding", "confidence": 0.94},
)
```

`memories.list(memory_type=None, document_id=None)` lists shared workspace
memories. `memory_type` and `document_id` are optional filters; `memory_type` accepts
the same three values as `memories.add`.

`memories.search(content)` searches shared workspace memories. The content is
required and the client workspace is used automatically.

`memories.update(memory_id, content=None, summary=None, importance=None,
metadata=None)` updates only supplied fields. `memory_id` is required. `importance`, when supplied,
must be between `0.0` and `1.0`; `None` means leave the field unchanged.

`memories.delete(memory_id)` deletes one memory. `memory_id` is required and
the method returns `None` on success.

#### `memories.generate(...)` (stateless)

Extracts memory drafts from supplied content using the configured server-side LLM. It returns
atomic facts with their type, importance, and relationship hints, but does not create memory
records or modify the database. Optional `existing_memories` are supplied only for comparison.
`batch_size` controls how many generated content chunks are sent to the extractor per LLM call and
defaults to `5`; it must be a positive integer.

```python
drafts = await client.memories.generate(
    content="The user prefers PostgreSQL and attended PyCon last month.",
    existing_memories=["The user uses MySQL."],
    batch_size=10,
)
```


### `client.profiles`

`profiles.list(document_id=None)` lists the workspace/document-derived profile,
optionally limited to a source document. `profiles.get(document_id=None)` returns
that profile, and `profiles.refresh(document_id=None)` rebuilds it. Profiles are
workspace/document projections and do not require a user ID.

```python
profile = await client.profiles.get(
    document_id="22222222-2222-2222-2222-222222222222",
)
await client.profiles.refresh(document_id="22222222-2222-2222-2222-222222222222")
```

#### `profiles.generate(...)` (stateless)

Generates memory drafts from supplied content and builds a profile from those drafts. It does not
load persisted memories, write profile data, create memory records, or create relationships.
`batch_size` controls how many generated content chunks are sent to the extractor per LLM call. It
defaults to `5` and must be a positive integer.

```python
profile = await client.profiles.generate(
    workspace_id="workspace-123",
    document_id="22222222-2222-2222-2222-222222222222",
    content="The team prefers PostgreSQL and attended PyCon last month.",
    batch_size=10,
)
```

The response contains the generated `profile`, the extracted `memories`, and `persisted: false`.

### `client.embeddings`

#### `embeddings.generate(...)` (stateless)

Generates vectors for caller-provided texts using the configured embedding provider. Vectors and
input text are not stored in the database.

```python
embeddings = await client.embeddings.generate(
    texts=[
        "The team prefers PostgreSQL.",
        "Redis is used for caching.",
    ],
    input_type="document",  # or "query"
)
```

The request accepts up to 256 texts. The response contains one embedding per input text.

### `client.reranking`

#### `reranking.rerank(...)` (stateless)

Scores and orders only the contexts supplied by the caller. It does not retrieve additional
contexts and does not write reranking results to the database.

```python
ranked = await client.reranking.rerank(
    query="Which database does the team prefer?",
    contexts=[
        {"id": "redis", "content": "The team uses Redis for caching."},
        {"id": "postgres", "content": "The team prefers PostgreSQL."},
    ],
    top_k=1,
)
```

The request accepts up to 100 contexts. Each context can include an optional `id`, `content`,
and `metadata`. The response includes the original index, context, and reranking score.

### `client.rag`

#### `rag.retrieve(...)`

Retrieves evidence without generating an answer.

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `query` | Yes | — | Search question or phrase. |
| `document_id` | No | — | Restrict retrieval to one document. |
| `limit` | No | `10` | Number of candidates, from `1` to `100`. |
| `search_mode` | No | `"hybrid"` | `"memory"`, `"document"`, or `"hybrid"`. |
| `rerank` | No | `False` | Apply the configured optional reranker. |
| `rewrite_query` | No | `False` | Rewrite/expand the query before retrieval while preserving the original. |
| `rerank_limit` | No | — | Candidate count for reranking, from `1` to `100`. |

```python
evidence = await client.rag.retrieve(
    query="Which database does the platform team standardize on?",
    search_mode="hybrid",
    limit=20,
    rerank=True,
    rerank_limit=8,
    rewrite_query=True,
)
```

#### `rag.query(...)`

Retrieves evidence and generates an answer using the server-configured LLM.

| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `query` | Yes | — | User question. |
| `document_id` | No | — | Restrict retrieval to one document. |
| `top_k` | No | `10` | Retrieved candidates, from `1` to `50`. |
| `rephrasing_enabled` | No | `False` | Enable query rephrasing. |
| `rephrasing_mode` | No | `"rewrite"` | Server rephrasing strategy; use `"rewrite"` for the standard mode. |
| `search_mode` | No | `"hybrid"` | `"memory"`, `"document"`, or `"hybrid"`. |
| `rerank` | No | `False` | Apply the configured optional reranker. |
| `rerank_top_k` | No | — | Reranking candidate count, from `1` to `50`. |

```python
result = await client.rag.query(
    query="What is the team's database standard and why?",
    search_mode="hybrid",
    top_k=12,
    rephrasing_enabled=True,
    rephrasing_mode="rewrite",
    rerank=True,
    rerank_top_k=6,
)
print(result["answer"])
print(result["sources"])
```

`rag.history` returns stored RAG query results for the workspace. The response
contains the original query, rewritten query when used, answer, sources, retrieved chunks, scores,
and creation timestamp.

### Errors and validation

Successful methods return decoded JSON, except delete methods, which return `None` for HTTP 204.
Failures raise `EvolinkError` with `status_code`, `message`, and the original response `payload`.
Typical statuses are `400` for invalid parameters, `401` for a missing/revoked API key, `404` for
an unknown workspace/document/memory, and `422` for schema validation errors. UUID values may be
passed as either `uuid.UUID` objects or strings.
