# Thryve

A modular Python framework for multi-LLM agents, tool calling, context management, and DAG workflows. Thryve unifies multiple backends (OpenAI, Ollama, etc.), tools, memory, and agents behind a single API.

## Features

- **Multi-backend LLM/VLM**: OpenAI (chat, vision, function calling, embeddings), Ollama; stubs for llama.cpp and Transformers
- **Unified messages**: Text + image (multimodal), tool calls and results
- **Tool system**: Define tools with `@tool` or `Tool`, register and execute; OpenAI/Anthropic format converters
- **Agent loop**: Reactive cycle (LLM -> tool calls -> observe -> repeat) with doom-loop detection
- **Context management**: Message history, token budget, checkpoints, truncate/summarize compression
- **Memory**: Short-term + long-term storage (SQLite), hybrid search (keyword + optional vector)
- **DAG workflows**: Graph with topological sort, `LLMNode` / `AgentNode` / `ToolNode`, layer-parallel execution

## Installation

```bash
pip install thryve
```

Optional extras:

```bash
pip install thryve[dev]        # pytest, pytest-asyncio
pip install thryve[all]        # aiosqlite, tiktoken
pip install thryve[transformers]  # HuggingFace Transformers backend
pip install thryve[llama]       # llama-cpp-python backend
```

## Quick Start

### 1. Basic chat

Set your API key and use the default config:

```bash
export OPENAI_API_KEY=sk-...
```

```python
from thryve import Thryve, ThryveConfig

config = ThryveConfig.from_env()  # reads OPENAI_API_KEY, THRYVE_* vars
thryve = Thryve(config)
reply = thryve.chat("What is 2 + 2?")  # synchronous, returns str
print(reply)
```

### 2. Chat with explicit config

```python
from thryve import Thryve, ThryveConfig, LLMConfig

config = ThryveConfig(
    llm=LLMConfig(
        backend="openai",
        model="glm-4.7",
        api_key="56f57f83af5c4ed894892ad9dcc717d9.2m1A9psRh0jlGr4p",
        base_url="https://open.bigmodel.cn/api/coding/paas/v4",
        temperature=0.7,
    )
)
thryve = Thryve(config)
reply = thryve.chat("Hello!")
```

### 3. Agent with tools

Register tools and run an agent that can call them:

```python
from thryve import Thryve, ThryveConfig, tool

@tool()
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

thryve = Thryve(ThryveConfig.from_env())
thryve.register_tool(add)
result = thryve.chat_with_agent("What is 3 + 5?")
print(result.final_response)
print(result.stop_reason)  # e.g. COMPLETED
```

### 4. DAG workflow

Build a graph and run it:

```python
from thryve import Thryve, ThryveConfig, Graph, FunctionNode

async def step_a(inputs):
    return inputs.get("x", 0) + 1

async def step_b(inputs):
    return inputs.get("step_a", 0) * 2

thryve = Thryve(ThryveConfig.from_env())
g = Graph()
g.chain(
    FunctionNode("step_a", step_a),
    FunctionNode("step_b", step_b),
)
outputs = thryve.execute_graph(g, {"x": 10})
print(outputs["step_a"])  # 11
print(outputs["step_b"])  # 22
```

### 5. Memory and introspection

```python
thryve.add_to_memory("User prefers dark mode.", permanent=False)
chunks = thryve.search_memory("dark mode", top_k=5)
info = thryve.get_llm_info()  # provider, model, supports_vision, supports_tools
```

## Sync vs Async

所有公开方法**默认是同步**的，可以在 REPL、普通脚本中直接使用。异步版本加 `_async` 后缀。

| 同步（默认） | 异步 | 说明 |
|---|---|---|
| `chat(message)` | `chat_async(message)` | 对话 |
| `chat_stream(message, callback=...)` | `chat_stream_async(message)` | 流式对话 |
| `chat_with_agent(message)` | `chat_with_agent_async(message)` | Agent 对话 |
| `execute_graph(graph, inputs)` | `execute_graph_async(graph, inputs)` | DAG 工作流 |

```python
# 同步（默认，直接用）
reply = thryve.chat("你好")

# 异步（在 async def 中用）
reply = await thryve.chat_async("你好")
```

## Streaming（流式输出）

`chat()` / `chat_async()` 会等完整回复再返回。需要边收边打时用流式方法：

**同步流式**（默认）：

```python
reply = thryve.chat_stream(
    "什么是大语言模型",
    callback=lambda c: print(c, end="", flush=True),
)
print()  # 换行
# reply 仍是完整回复字符串
```

**异步流式**：

```python
async for chunk in thryve.chat_stream_async("什么是大语言模型"):
    print(chunk, end="", flush=True)
```

## Configuration

- **From env**: `ThryveConfig.from_env()` uses `OPENAI_API_KEY`, `THRYVE_LLM_MODEL`, `THRYVE_LLM_BACKEND`, `THRYVE_MEMORY_PATH`, etc.
- **From file**: `ThryveConfig.from_file("config.json")` for JSON config.
- **Merge**: `config.merge(other)` to override with another config.

### EmbeddingConfig（记忆向量搜索）

Embedding 用于**记忆系统**的语义搜索：写入记忆时会生成向量，搜索时用「向量 + 关键词」混合召回。不配置或初始化失败时，记忆会退化为**仅关键词搜索**，仍可正常使用。

**通过 ThryveConfig 传入：**

```python
from thryve import Thryve, ThryveConfig, LLMConfig, EmbeddingConfig

config = ThryveConfig(
    llm=LLMConfig(backend="openai", model="gpt-4o-mini", api_key="sk-..."),
    embedding=EmbeddingConfig(
        backend="openai",
        model="text-embedding-3-small",
        api_key="sk-...",           # 可与 LLM 共用
        base_url=None,              # 可选，自定义 API 地址
        dimensions=None,            # 可选，向量维度
    ),
)
thryve = Thryve(config)
# 此时 search_memory() 会使用向量 + 关键词混合搜索
```

**环境变量**（`ThryveConfig.from_env()` 会读取）：

| 变量 | 说明 | 默认 |
|------|------|------|
| `THRYVE_EMBEDDING_BACKEND` | 后端，如 `openai` | `openai` |
| `THRYVE_EMBEDDING_MODEL` | 模型名 | `text-embedding-3-small` |
| `THRYVE_EMBEDDING_API_KEY` | API Key，未设时用 `OPENAI_API_KEY` | - |
| `THRYVE_EMBEDDING_BASE_URL` | 自定义 base URL | - |

**JSON 配置示例**（`ThryveConfig.from_file("config.json")`）：

```json
{
  "llm": { "backend": "openai", "model": "gpt-4o-mini" },
  "embedding": {
    "backend": "openai",
    "model": "text-embedding-3-small",
    "api_key": "sk-..."
  }
}
```

不设置 `embedding` 或对应后端不可用时，Thryve 会打一条 WARNING，记忆仍可用，只是 `search_memory()` 仅做关键词匹配。

## Project structure

```
src/thryve/
  thryve.py       # Thryve main entry
  llm.py          # LLM facade
  config.py       # ThryveConfig, LLMConfig, EmbeddingConfig, MemoryConfig, AgentConfig
  core/
    backends/     # OpenAI, Ollama, llama_capp (stub), transformers (stub)
    tools/        # Tool, ToolRegistry, ToolExecutor, @tool
    agent/        # Agent, AgentLoop, MultiAgentOrchestrator
    context/      # ContextManager, checkpoints, compression
    memory/       # MemoryManager, SQLiteStorage, HybridSearcher
    graph/        # Graph, Node, GraphExecutor
```

## License

MIT License
