Metadata-Version: 2.5
Name: purrcept_litellm
Version: 0.1.0
Summary: LiteLLM provider backend for purrcept_core.
Project-URL: Documentation, https://github.com/Windpicker-owo/Purrcept-LiteLLM#readme
Project-URL: Issues, https://github.com/Windpicker-owo/Purrcept-LiteLLM/issues
Project-URL: Source, https://github.com/Windpicker-owo/Purrcept-LiteLLM
Author: Purrcept contributors
License-Expression: MIT
License-File: LICENSE
Keywords: agents,litellm,llm,purrcept
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: litellm<2,>=1.94.1
Requires-Dist: purrcept-core<0.6.0,>=0.5.0
Provides-Extra: otel
Requires-Dist: opentelemetry-api<2,>=1.28; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.28; extra == 'otel'
Requires-Dist: opentelemetry-sdk<2,>=1.28; extra == 'otel'
Description-Content-Type: text/markdown

# Purrcept LiteLLM

`purrcept_litellm` 是 `purrcept_core` 的 LiteLLM Provider 插件。它实现
`ModelBackend` 协议，把 Core 的统一模型请求映射到 LiteLLM Chat Completions，并将
Provider 响应、工具调用、流式增量和错误重新归一化为 Core 类型。

当前版本：`0.1.0`，对应 `purrcept_core >=0.5.0,<0.6.0`。

## 安装

```bash
pip install purrcept_litellm
```

在本仓库开发：

```bash
uv sync
```

## 最小用法

LiteLLM 使用带 Provider 前缀的模型名，例如 `openai/gpt-4o-mini`、
`anthropic/claude-sonnet-4-5`。推荐让 LiteLLM 从对应 Provider 的标准环境变量读取
凭据：

```powershell
$env:OPENAI_API_KEY = "..."
$env:PURRCEPT_LITELLM_MODEL = "openai/gpt-4o-mini"
uv run python examples/01_chat.py
```

也可以显式传入凭据或接入 LiteLLM Proxy / OpenAI-compatible 服务：

```python
import os

from purrcept_core import AgentDriver, AgentFlow, InlineExecutor
from purrcept_core.models import Conversation, Model
from purrcept_litellm import LiteLLMBackend


def chat(conversation: Conversation) -> AgentFlow[str]:
    result = yield from conversation.ask("你好，请简短介绍自己。")
    return result.text


async def main() -> None:
    backend = LiteLLMBackend(
        api_key=os.environ["LLM_API_KEY"],
        api_base="https://llm.example.com/v1",
        timeout=60,
    )
    conversation = Model(backend, "openai/my-model").conversation(
        instructions="你是一名简洁、可靠的助手。",
    )
    answer = await AgentDriver(InlineExecutor()).run(
        chat(conversation),
        host=None,
    )
    print(answer)
```

`Generate` 默认会请求流式响应；插件会依次产生
`ModelStreamStarted`、`TextDelta` / `ToolCallDelta`、`UsageUpdate` 和
`ModelStreamCompleted`。禁用 Effect 的流式事件后，插件会自动使用普通响应。

完整的自动工具循环见 [`examples/02_tool_loop.py`](examples/02_tool_loop.py)。

## 配置边界

运行时拥有后端和凭据：

```python
backend = LiteLLMBackend(
    api_key="...",
    api_base="https://...",
    api_version="...",
    timeout=60,
    default_options={
        "drop_params": True,
        "num_retries": 2,
    },
)
```

一次请求的 LiteLLM 特有选项必须放在唯一命名空间中：

```python
conversation = model.conversation(
    provider_options={
        "purrcept_litellm": {
            "reasoning_effort": "medium",
            "drop_params": True,
        }
    }
)
```

`model`、`messages`、`stream`、`stream_options`、`tools`、`api_key`、
`api_base`、`api_version`、`callback(s)`、`success_callback` 和
`failure_callback` 是保留字段，不能由请求级选项覆盖。凭据只应进入后端配置或 Provider
环境变量，不应写进对话状态。

## OpenTelemetry 可观测性

OpenTelemetry 是可选依赖：

```bash
pip install "purrcept_litellm[otel]"
```

下面的 callback 使用 LiteLLM 自带的 OpenTelemetry 集成，但拥有私有
`TracerProvider`，并且只通过单次请求的 success/failure callback 参数执行。它不会写入
LiteLLM 或 OpenTelemetry 的进程全局 callback/provider：

```python
from purrcept_litellm import (
    LiteLLMBackend,
    LiteLLMOpenTelemetryConfig,
    create_opentelemetry_callback,
)

otel = create_opentelemetry_callback(
    LiteLLMOpenTelemetryConfig(
        exporter="otlp_http",
        endpoint="http://127.0.0.1:4318/v1/traces",
        service_name="purrcept-engine",
        environment="development",
        capture_message_content="SPAN_ONLY",
    )
)
backend = LiteLLMBackend(callbacks=(otel,))

# Runtime 停止接收新请求并等待在途请求结束后：
otel.force_flush()
otel.shutdown()
```

`NO_CONTENT` 是默认值，只记录模型、耗时、token、状态和错误等运行信息。
`SPAN_ONLY` 会额外把真实请求提示词和模型返回写入 span 属性；它可能包含个人信息和密钥
片段，只应发送到可信的本地或受控 OTLP Collector。`headers` 使用 LiteLLM/OTLP 的字符串
格式，并会从配置对象的 `repr` 中隐藏。

每个主请求 span 同时带有 OpenInference 的 `LLM` 分类。支持 OpenInference 的界面（例如
Phoenix）可以按 System、User、Assistant 和 Tool 展开完整消息，并用最后一条输入和首个
模型结果填充紧凑的 Input/Output 摘要。工具声明以标准 `llm.tools.*.tool.json_schema`
记录，因此也可以进入 Phoenix Span Replay。完整消息仍只由 LiteLLM 的 `gen_ai.*` 属性
承载；Adapter 不会再镜像整段消息序列，但 Input 摘要会包含最后一条消息的完整 content。
`NO_CONTENT` 只保留 `LLM` 分类，不会通过这些补充字段泄露提示词、返回或工具 schema。

使用默认 completion 时，Backend 会在返回或抛错前等待本次 request-local callback
完成，因此 Terminal 这类短生命周期事件循环不会在关闭时取消尚未记录的 span。HTTP
exporter 仍由 `BatchSpanProcessor` 异步发送；Runtime 关闭阶段负责最终的
`force_flush()` 和 `shutdown()`。

## 映射范围

| Core | LiteLLM |
|---|---|
| `SystemInstruction` | 前置 `system` message |
| `Message` / `TextBlock` | chat message / text content |
| `ImageUrl` / `ImageBytes` | `image_url` / data URI |
| `ToolSpec` | OpenAI-compatible function tool |
| `ToolCallBlock` / `ToolResultBlock` | assistant tool call / tool message |
| `ModelSettings` | temperature、max tokens、stop、tool choice、parallel calls |
| `TokenUsage` | prompt、completion、cache、reasoning tokens |
| `ModelStreamEvent` | LiteLLM chunk lifecycle |
| LiteLLM exceptions | Core 的认证、限流、上下文、临时、不可用、请求错误 |

未知但可 JSON 化的 Provider 字段会保留在 `ModelResponse.provider_metadata`，不会污染
Core 的稳定协议。

## Prompt control、缓存与 continuation

- Core 已编译好的 reminder 一律作为 `user` message 发送，不进入 `system`。插件不重新
  解释 scope、priority 或 replacement；生命周期仍由 `Conversation` / `PromptCompiler`
  管理。只有 `SystemInstruction` 使用 `system`。
- `INSTRUCTIONS` placement 仍插在对话历史之前，但角色是 `user`。`TAIL` / `AUTO` 跟在
  历史之后。相对顺序保持 Core 编译结果。
- `cache="prefer"` / `cache="explicit"` 会在稳定 instruction（其次为最后一个 tool）
  添加 LiteLLM `cache_control` 提示。Provider 是否支持和如何计费由 Provider 决定。
- strict cache 默认拒绝。只有确认目标 Provider 兼容后，才设置
  `allow_strict_prompt_cache=True`。
- LiteLLM Chat Completions 没有统一、可靠的服务器 continuation 句柄，因此
  `ModelContinuation` 不会默认发给 Provider。推荐使用 Core 默认的
  `continuation_policy="client_managed"`，由本地规范历史重建下一次请求。这也确保已过期
  reminder 不会因 Provider 保存了旧状态而泄漏到后续轮次。
- 自定义 LiteLLM Provider 若在 `provider_specific_fields.purrcept_continuation` 返回
  `{provider, data}`，插件会将其恢复为 Core 的不透明 continuation，供显式策略使用。

## 真实 Provider 验证

默认测试完全离线。启用真实流式调用：

```powershell
$env:PURRCEPT_LITELLM_RUN_INTEGRATION = "1"
$env:PURRCEPT_LITELLM_MODEL = "openai/gpt-4o-mini"
$env:OPENAI_API_KEY = "..."
uv run pytest tests/test_real_integration.py -m integration -q
```

若使用统一代理，可改用 `PURRCEPT_LITELLM_API_KEY` 和
`PURRCEPT_LITELLM_API_BASE`。

完整质量门：

```bash
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest --cov=purrcept_litellm --cov-branch
uv run python -m build
```

## 设计原则

`purrcept_core` 只定义 Provider 无关协议和 Agent 语义；本包只承担 LiteLLM
传输适配。具体模型凭据、网络客户端生命周期和部署策略归 Runtime 所有。这样 Core
不会依赖任何厂商 SDK，其他 Provider 也可以继续以独立 Python 插件实现。
