Metadata-Version: 2.4
Name: renbase
Version: 0.1.0a1
Summary: Python SDK for Renbase Context-as-a-Service
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-httpx>=0.30.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"

# Renbase Python SDK

Python SDK for Renbase Context-as-a-Service API.

[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![PyPI version](https://img.shields.io/pypi/v/renbase.svg)](https://pypi.org/project/renbase/)

> **Developer Preview** 🚧
>
> This library is currently in early alpha. The API is subject to change and requires a private invitation to the Renbase platform to function.

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Authentication](#authentication)
- [API Reference](#api-reference)
  - [Context](#context)
  - [Documents](#documents)
  - [Feedback](#feedback)
- [Async Usage](#async-usage)
- [Framework Integration](#framework-integration)
  - [FastAPI](#fastapi)
  - [Django](#django)
- [LangChain Integration](#langchain-integration)
- [Error Handling](#error-handling)
- [Configuration](#configuration)
- [Response Models](#response-models)

---

## Installation

```bash
pip install renbase
```

With LangChain support:

```bash
pip install renbase[langchain]
```

From source (development):

```bash
git clone https://github.com/renbase/renbase-python.git
cd renbase-python
pip install -e ".[dev]"
```

---

## Quick Start

```python
from renbase import RenbaseClient

client = RenbaseClient(api_key="rb_live_xxx")
result = client.context.query("How do I configure authentication?")

for chunk in result.chunks:
    print(f"[{chunk.score:.2f}] {chunk.content[:100]}...")
```

---

## Authentication

### Using API Key

```python
from renbase import RenbaseClient

client = RenbaseClient(api_key="rb_live_xxx")
```

### Using Environment Variable

```bash
export RENBASE_API_KEY=rb_live_xxx
```

```python
from renbase import RenbaseClient

client = RenbaseClient()  # Reads from RENBASE_API_KEY
```

> **Tip**: Never hardcode API keys in your code. Use environment variables or a secrets manager.

---

## API Reference

### Context

Query your knowledge base for relevant context.

#### `client.context.query(text, top_k=5)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `text` | `str` | required | Search query |
| `top_k` | `int` | `5` | Number of results (max: 100) |

**Returns**: `ContextResponse`

```python
result = client.context.query("How to setup SSO?", top_k=10)

for chunk in result.chunks:
    print(f"Score: {chunk.score}")
    print(f"Source: {chunk.source}")
    print(f"Title: {chunk.document_title}")
    print(f"Content: {chunk.content}")
    print("---")
```

---

### Documents

Upload, list, and delete documents.

#### `client.documents.upload(file_path, metadata=None)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | `str` | required | Path to file |
| `metadata` | `dict` | `None` | Optional metadata |

**Returns**: `Document`

```python
doc = client.documents.upload(
    "./report.pdf",
    metadata={"department": "engineering", "year": 2024}
)
print(f"Document ID: {doc.id}")
```

#### `client.documents.list(limit=50, offset=0, source_type=None)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | `int` | `50` | Results per page (max: 250) |
| `offset` | `int` | `0` | Pagination offset |
| `source_type` | `str` | `None` | Filter: `upload`, `confluence`, `google_drive` |

**Returns**: `ListDocumentsResponse`

```python
response = client.documents.list(limit=10, source_type="upload")

print(f"Total documents: {response.total}")
for doc in response.documents:
    print(f"[{doc.processing_status}] {doc.title} ({doc.mime_type})")
```

#### `client.documents.delete(document_id)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `document_id` | `str` | required | Document UUID |

**Returns**: `None` (202 Accepted)

```python
client.documents.delete("550e8400-e29b-41d4-a716-446655440000")
```

---

### Feedback

Submit feedback on query results.

#### `client.feedback.create(query_id, score, comment=None)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query_id` | `str` | required | Query UUID |
| `score` | `int` | required | `1` (positive) or `-1` (negative) |
| `comment` | `str` | `None` | Optional comment |

**Returns**: `Feedback`

```python
feedback = client.feedback.create(
    query_id="550e8400-e29b-41d4-a716-446655440000",
    score=1,
    comment="Very helpful result!"
)
print(f"Feedback ID: {feedback.id}")
```

---

## Async Usage

Use `RenbaseAsyncClient` for async/await support.

```python
import asyncio
from renbase import RenbaseAsyncClient

async def main():
    async with RenbaseAsyncClient(api_key="rb_live_xxx") as client:
        # Query context
        result = await client.context.query("authentication setup")
        print(f"Found {len(result.chunks)} chunks")

        # Upload document
        doc = await client.documents.upload("./guide.pdf")
        print(f"Uploaded: {doc.id}")

        # List documents
        docs = await client.documents.list(limit=5)
        for d in docs.documents:
            print(f"- {d.title}")

asyncio.run(main())
```

---

## Framework Integration

### FastAPI

```python
from fastapi import FastAPI, HTTPException
from renbase import RenbaseAsyncClient
from contextlib import asynccontextmanager

client: RenbaseAsyncClient = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global client
    client = RenbaseAsyncClient()
    yield
    await client.close()

app = FastAPI(lifespan=lifespan)

@app.get("/search")
async def search(q: str, top_k: int = 5):
    result = await client.context.query(q, top_k=top_k)
    return {
        "chunks": [
            {"content": c.content, "score": c.score}
            for c in result.chunks
        ]
    }

@app.post("/documents")
async def upload_document(file_path: str):
    doc = await client.documents.upload(file_path)
    return {"id": str(doc.id)}
```

### Django

```python
# views.py
from django.http import JsonResponse
from renbase import RenbaseClient

client = RenbaseClient()

def search(request):
    query = request.GET.get("q", "")
    top_k = int(request.GET.get("top_k", 5))

    result = client.context.query(query, top_k=top_k)

    return JsonResponse({
        "chunks": [
            {"content": c.content, "score": c.score}
            for c in result.chunks
        ]
    })

def list_documents(request):
    limit = int(request.GET.get("limit", 50))
    response = client.documents.list(limit=limit)

    return JsonResponse({
        "total": response.total,
        "documents": [
            {"id": str(d.id), "title": d.title, "status": d.processing_status}
            for d in response.documents
        ]
    })
```

---

## LangChain Integration

Install with LangChain support:

```bash
pip install renbase[langchain]
```

### Basic Usage

```python
from renbase import RenbaseClient
from renbase.integrations.langchain import RenbaseRetriever

client = RenbaseClient(api_key="rb_live_xxx")
retriever = RenbaseRetriever(client=client, top_k=5)

# Use as any LangChain retriever
docs = retriever.invoke("How to configure SSO?")
for doc in docs:
    print(doc.page_content)
```

### RAG Chain Example

```python
from renbase import RenbaseClient
from renbase.integrations.langchain import RenbaseRetriever
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA

client = RenbaseClient(api_key="rb_live_xxx")
retriever = RenbaseRetriever(client=client, top_k=5)
llm = ChatOpenAI(model="gpt-4")

chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever
)

answer = chain.invoke("How do I set up SSO?")
print(answer["result"])
```

---

## Error Handling

All exceptions inherit from `RenbaseError`.

| Exception | HTTP Status | Description |
|-----------|-------------|-------------|
| `BadRequestError` | 400 | Invalid request parameters |
| `AuthenticationError` | 401 | Invalid or missing API key |
| `PaymentRequiredError` | 402 | Insufficient credits |
| `NotFoundError` | 404 | Resource not found |
| `RateLimitError` | 429 | Too many requests |
| `APIError` | 5xx | Server error |

```python
from renbase import RenbaseClient
from renbase.exceptions import (
    RenbaseError,
    AuthenticationError,
    RateLimitError,
    NotFoundError,
)

client = RenbaseClient(api_key="rb_live_xxx")

try:
    result = client.context.query("test")
except AuthenticationError as e:
    print(f"Auth failed: {e.message}")
except RateLimitError as e:
    print(f"Rate limited: {e.message}")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except RenbaseError as e:
    print(f"API error ({e.status_code}): {e.message}")
```

---

## Configuration

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `api_key` | `str` | `RENBASE_API_KEY` env | API key |
| `base_url` | `str` | `https://api.renbase.ai` | API base URL |
| `timeout` | `float` | `30.0` | Request timeout (seconds) |

```python
from renbase import RenbaseClient

client = RenbaseClient(
    api_key="rb_live_xxx",
    base_url="https://api.renbase.ai",
    timeout=60.0
)
```

---

## Response Models

### ContextResponse

```python
class ContextResponse:
    query_id: UUID | None  # Not yet available
    chunks: list[ChunkResult]
```

### ChunkResult

```python
class ChunkResult:
    document_id: UUID
    content: str
    score: float           # 0.0 to 1.0
    source: str            # "upload", "confluence", "google_drive"
    document_title: str | None
    source_url: str | None
    project_name: str | None
```

### Document

```python
class Document:
    id: UUID
```

### DocumentSummary

```python
class DocumentSummary:
    id: UUID
    title: str
    mime_type: str
    source_type: str | None
    processing_status: str  # "pending", "processing", "completed", "failed"
    created_at: datetime
```

### ListDocumentsResponse

```python
class ListDocumentsResponse:
    documents: list[DocumentSummary]
    total: int
    limit: int
    offset: int
```

### Feedback

```python
class Feedback:
    id: UUID
    query_id: UUID
    score: int
    created_at: datetime
```
