Metadata-Version: 2.4
Name: magicorb
Version: 0.1.0
Summary: LLM-powered Python function enhancement decorator library (@as_magic).
Author-email: Qixuan Wang <magicorb@qixuan.wang>
License: MIT
Project-URL: Homepage, https://github.com/magicorb/magicorb
Project-URL: Issues, https://github.com/magicorb/magicorb/issues
Keywords: llm,decorator,ai,glm,function-enhancement,magic
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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 :: Software Development :: Libraries
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jsonpickle>=3.0
Provides-Extra: sync
Requires-Dist: requests>=2.28; extra == "sync"
Provides-Extra: async
Requires-Dist: aiohttp>=3.8; extra == "async"
Provides-Extra: glm
Requires-Dist: requests>=2.28; extra == "glm"
Requires-Dist: aiohttp>=3.8; extra == "glm"
Provides-Extra: pydantic
Requires-Dist: pydantic>=2.0; extra == "pydantic"
Provides-Extra: all
Requires-Dist: requests>=2.28; extra == "all"
Requires-Dist: aiohttp>=3.8; extra == "all"
Requires-Dist: pydantic>=2.0; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# magicorb

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![Version](https://img.shields.io/badge/version-0.1.0-green.svg)](./pyproject.toml)

一个基于 LLM 的 Python 函数增强装饰器库。通过 `@as_magic` 装饰器，把函数调用参数自动序列化、交给大模型处理，并把模型输出包装为 `MagicOut` 对象注入回业务流程，实现「AI 即函数逻辑」的极简开发模式。

```python
from magicorb import as_magic

@as_magic
def sentence_condensation(sentence: str, magic_out=None):
    """把用户给出的句子进行缩句
    Returns:
        result: 缩句之后的结果
    """
    if magic_out is not None:
        if magic_out.error:
            raise RuntimeError(magic_out.message)
        return magic_out.get("result")
    return None

print(sentence_condensation("我超级喜欢可爱机智聪明漂亮的悦悦"))
# >>> "我喜欢悦悦"
```

## 目录

- [核心特性](#核心特性)
- [安装](#安装)
- [快速开始](#快速开始)
- [工作原理](#工作原理)
- [API 文档](#api-文档)
  - [`as_magic`](#as_magic)
  - [`MagicOut`](#magicout)
  - [`Field`](#field)
- [使用方式](#使用方式)
- [自定义 Provider](#自定义-provider)
- [sample_return 协议](#sample_return-协议)
- [LLM 输出协议](#llm-输出协议)
- [设计限制与注意事项](#设计限制与注意事项)
- [FAQ](#faq)
- [运行样例](#运行样例)
- [项目结构](#项目结构)
- [依赖](#依赖)
- [对比与定位](#对比与定位)
- [协议](#协议)
- [AI 合规声明](#ai-合规声明)
- [合作开发](#合作开发)
- [变更日志](#变更日志)

## 核心特性

- **装饰器驱动**：`@as_magic` 一行装饰，无侵入接入 LLM
- **同步 / 异步双支持**：原生 `def` 与 `async def`
- **MagicOut 注入**：LLM 输出包装为 `MagicOut` 安全对象，提供 `.error` / `.message` / `.data` / `.get()` 接口，**`__bool__` 永真**，从根上消除 `if magic_out:` 在 LLM 返回 `{}`/`""`/`0` 时的 falsy 陷阱
- **自动序列化清洗**：基于 [jsonpickle](https://github.com/jsonpickle/jsonpickle)，原生处理循环引用、pydantic 模型、自定义对象
- **基础类型 fast-path**：`int` / `str` / `bool` / `None` / 有限 `float` 直通，跳过序列化往返
- **NaN/Inf 净化**：递归把非有限浮点替换为 `null`，保证发给 LLM 的是合法 JSON
- **失败参数占位**：不可序列化的参数以 `{_unserializable: true, _type: ...}` 占位告知 LLM，不静默丢弃
- **可插拔 Provider**：默认 GLM（智谱 `glm-4-flash`），支持自定义任意 LLM provider
- **结构化输出协议**：通过 `sample_return` + `Field` 描述期望返回结构，**支持装饰后赋值**
- **参数排除**：`exclude_params` 跳过敏感参数，不进入 LLM 上下文

## 安装

### 从源码安装（开发模式）

```bash
git clone <your-repo-url>
cd magicorb
pip install -e .            # 基础安装（仅 jsonpickle）
pip install -e .[glm]       # + GLM provider 所需的 requests + aiohttp
pip install -e .[all]       # + pydantic
```

### 直接使用

```bash
pip install jsonpickle requests aiohttp
# 可选：pydantic 模型支持
pip install pydantic
```

### 配置智谱 API Key

默认 GLM provider 通过环境变量读取 key：

```bash
# Windows PowerShell
$env:ZHIPU_API_KEY = "your_api_key_here"
# Linux / macOS
export ZHIPU_API_KEY="your_api_key_here"
```

## 快速开始

```python
from magicorb import as_magic

@as_magic
def sentence_condensation(sentence: str, magic_out=None):
    """把用户给出的句子进行缩句
    Args:
        sentence: 用户给出的句子
    Returns:
        result: 缩句之后的结果
    """
    if magic_out is not None:
        if magic_out.error:                          # 自动识别 _error
            raise RuntimeError(magic_out.message)
        return magic_out.get("result")
    return None


print(sentence_condensation("我超级喜欢可爱机智聪明漂亮的悦悦"))
# LLM 返回缩句结果，例如 "我喜欢悦悦"
```

> 装饰阶段必须保证 `magic_out=None`（无默认值或非 `None` 默认值会抛 `TypeError`）。

## 工作原理

调用 `@as_magic` 装饰的函数时，内部按以下顺序执行：

1. **参数绑定** — `sig.bind(*args, **kwargs)` + `apply_defaults()`，统一成参数字典
2. **magic_out 拦截** — 若用户显式传了 `magic_out`（值非 `None`）立即抛 `TypeError`；否则从参数字典中删除，不进入 LLM 上下文
3. **参数清洗** — `safe_clean_params` 逐参数走 `jsonpickle_clean_object`：
   - 基础类型（`int`/`str`/`bool`/`None`/有限 `float`）直通
   - pydantic 模型优先 `model_dump()`
   - `callable` 一律丢弃（不送 LLM）
   - 其他对象走 jsonpickle 编码-解码往返 + 递归净化 NaN/Inf
   - 失败参数填占位 `{_unserializable: true, _type: ...}`
4. **文档组装** — `base_doc`（函数 docstring，装饰时固定）+ `sample_return` 渲染（每次调用时基于 `final_wrap.sample_return` 动态拼接）
5. **Provider 调度** — `provider(doc, params)` 调用 LLM，返回结构化 JSON dict
6. **结果注入** —
   - 若函数声明了 `magic_out` 参数：LLM 输出包装为 `MagicOut` 对象注入到 `magic_out`，业务函数读取 `magic_out.error` / `.message` / `.data` / `.get()` 处理
   - 否则：LLM 输出作为候选返回值，函数返回 `None` 时兜底用 LLM 输出，返回非 `None` 时业务覆盖

## API 文档

### `as_magic`

```python
from magicorb import as_magic
```

**重载签名**：

```python
# 写法 1：裸装饰（无括号）
@as_magic
def f(...): ...

# 写法 2：带配置（全部关键字参数）
@as_magic(
    provider: Callable[[str, dict], dict] | None = None,
    async_provider: Callable[[str, dict], Awaitable[dict]] | None = None,
    exclude_params: list[str] | None = None,
)
def f(...): ...
```

**参数**：

| 参数 | 类型 | 说明 |
|------|------|------|
| `provider` | `(func_doc: str, call_args: dict) -> dict` | 同步 LLM provider，未指定时取全局 `_GLOBAL_MAGIC_PROVIDER` |
| `async_provider` | `async (func_doc, call_args) -> dict` | 异步 LLM provider，未指定时取全局 `_GLOBAL_ASYNC_MAGIC_PROVIDER` |
| `exclude_params` | `list[str]` | 不进入 LLM 上下文的参数名列表 |

**装饰阶段校验**：

- `magic_out` 参数必须声明为 `magic_out=None`，否则抛 `TypeError`
- `@as_magic(some_callable)` 会被当成裸装饰处理（`some_callable` 当被装饰函数）—— 这是 overload 显式声明的契约，配置参数必须用关键字传

**运行时行为**：

- 业务函数返回 `None` → 用 LLM 输出作为函数返回值（兜底语义）
- 业务函数返回非 `None` → 业务返回值覆盖 LLM 输出
- 业务函数声明了 `magic_out` → LLM 输出包装为 `MagicOut` 注入到 `magic_out` 参数

### `MagicOut`

```python
from magicorb import MagicOut
```

LLM 输出的安全包装对象。`__bool__` **永远为 `True`**，消除 `if magic_out:` 在 LLM 返回 `{}`/`""`/`0` 时的 falsy 陷阱。

```python
m = MagicOut({"value": 42, "name": "alice", "_note": "ok", "_error": False})
```

| 接口 | 返回 | 说明 |
|------|------|------|
| `m.error` | `bool` | 是否 `_error: true` |
| `m.message` | `str / None` | `_message` 字段 |
| `m.note` | `str / None` | `_note` 字段 |
| `m.data` | `dict` | 业务字段（剥离 `_` 前缀协议字段） |
| `m.raw` | `dict` | 原始 LLM 输出（含协议字段） |
| `m.get(key, default)` | `Any` | 兼容 dict 接口，老代码无需改动 |
| `m[key]` | `Any` | `__getitem__` |
| `key in m` | `bool` | `__contains__` |
| `bool(m)` | `True` | **永远 True**，消除 `if magic_out:` 在 LLM 返回 `{}`/`""`/`0` 时的 falsy 陷阱 |
| `iter(m)` | iter | 迭代 keys |
| `repr(m)` | `str` | `MagicOut({...})` |

provider 返回非 dict 时会自动包装为 `{_error: True, _message: "provider 返回非 dict: ...", _raw_value: <原值>}`，业务侧统一用 `magic_out.error` 处理。

**关键场景：永远为真**

```python
# 老代码如果这样写，在 LLM 返回 {} 时会走错分支
if magic_out:           # ❌ 老的 dict 接口下，{} 为 falsy
    process(magic_out)

# 用 MagicOut 后，永远走 True 分支，必须用 magic_out.error 判错
if magic_out.error:     # ✅ 推荐写法
    raise RuntimeError(magic_out.message)
process(magic_out.data)
```

### `Field`

```python
from magicorb import Field
```

声明 `sample_return` 中每个字段的类型与描述。

```python
Field(field_type: type, description: str)
```

| 属性 | 类型 | 说明 |
|------|------|------|
| `Field.type` | `type` | 字段类型，如 `str` / `int` / `tuple` |
| `Field.description` | `str` | 字段描述，会渲染为 `# 描述` 注释 |

详见 [sample_return 协议](#sample_return-协议)。

## 使用方式

### 1. 裸装饰器（使用全局 GLM provider）

```python
@as_magic
def summarize(text: str, magic_out=None):
    """
    总结一段文本
    Args:
        text: 待总结文本
    Returns:
        summary: 总结结果
    """
    if magic_out is not None:
        if magic_out.error:
            raise RuntimeError(magic_out.message)
        return magic_out.get("summary")
    return None
```

### 2. 带配置装饰器

```python
@as_magic(
    provider=my_custom_provider,        # 自定义 provider
    exclude_params=["secret_token"],    # 不送入 LLM
)
def analyze(text: str, secret_token: str, magic_out=None):
    ...
```

### 3. 异步函数

```python
@as_magic
async def async_translate(text: str, magic_out=None):
    """
    翻译文本为英文
    """
    if magic_out is not None:
        if magic_out.error:
            raise RuntimeError(magic_out.message)
        return magic_out.get("translated")
    return None

# asyncio.run(async_translate("你好世界"))
```

异步函数装饰时建议显式传 `async_provider=`，否则会从全局 `_GLOBAL_ASYNC_MAGIC_PROVIDER` 取（默认是 GLM 的 `aglm_magic_provider`，依赖 `aiohttp`）。

### 4. 方法装饰（self 注入）

```python
class Calculator:
    def __init__(self):
        self.saved_value = 0

    @as_magic
    def add(self, a: int, magic_out=None):
        """
        基于 self.saved_value + a，返回结果并更新 self.saved_value
        :return: value 结果；self 待更新成员变量字典
        """
        if magic_out is not None:
            updates = magic_out.get("self") or {}
            for k, v in updates.items():
                setattr(self, k, v)
            return magic_out.get("value")
        return None
```

> **注意**：装饰方法时整个 `self.__dict__` 会序列化送入 LLM 上下文。这是 by design（示例 4 就依赖此机制），但敏感字段需用 `exclude_params=["field_name"]` 排除。

### 5. 声明返回结构（sample_return + Field）

`sample_return` 支持**装饰后赋值**——会被每次调用时动态读取并拼接到 prompt：

```python
from magicorb import as_magic, Field

@as_magic
def extract_info(text: str, magic_out=None):
    """
    从文本中抽取信息
    """
    if magic_out is not None:
        return magic_out.data          # 直接拿业务字段 dict
    return None

# 装饰后赋值，仍然生效
extract_info.sample_return = {
    "name": Field(str, "人物姓名"),
    "age":  Field(int, "人物年龄"),
    "tags": [Field(str, "标签")],
}
```

详细写法见 [sample_return 协议](#sample_return-协议)。

## 自定义 Provider

Provider 是一个签名为 `(func_doc: str, call_args: dict) -> dict` 的可调用对象：

```python
def my_provider(func_doc: str, call_args: dict) -> dict:
    # 自行调用任意 LLM (OpenAI / Claude / 本地模型)
    ...
    return parsed_json_dict
```

**两种注入方式**：

```python
# 方式 1：装饰器参数（推荐，作用域清晰）
@as_magic(provider=my_provider)
def f(...): ...

# 方式 2：全局槽位（运行时生效，影响所有未指定 provider 的 @as_magic）
from magicorb.LLMprovider import glm_provider as glm
glm._GLOBAL_MAGIC_PROVIDER = my_provider
```

异步 provider 签名为 `async (func_doc, call_args) -> dict`，通过 `async_provider=` 装饰器参数或 `_GLOBAL_ASYNC_MAGIC_PROVIDER` 设置。

**返回值约定**：provider 必须返回 dict；返回非 dict 会被 `MagicOut` 自动包装为 `{_error: True, _message: "provider 返回非 dict: ...", _raw_value: 原值}`，业务侧用 `magic_out.error` 统一处理。

## sample_return 协议

`sample_return` 是装饰后赋值的属性，描述期望的返回结构。装饰器在**每次调用时**基于 `final_wrap.sample_return` 重新拼接 prompt，所以支持装饰后赋值。

### 两种写法

| 场景 | 写法 | 渲染效果 |
|------|------|------|
| 明确知道元素结构 | 容器装 Field：`[Field(str, "tag")]` / `(Field(str, "tag"),)` / `{Field(str, "tag")}` | 展开元素结构：`[\n  str  # tag\n]` |
| 不展开，只标类型 | `Field(tuple, "tuple of tags")` | 标类型名：`tuple  # tuple of tags` |

容器写法支持 `list` / `tuple` / `set` / `frozenset`，统一渲染为 `[...]` 形式展开首个元素。两者可嵌套混用。

### 渲染示例

```python
# 写法 A：容器装 Field
sample_return = {"tags": [Field(str, "标签")]}
# 渲染为：
# {
#   tags: [
#     str  # 标签
#   ]
# }

# 写法 B：Field + type
sample_return = {"tags": Field(tuple, "tuple of tags")}
# 渲染为：
# {
#   tags: tuple  # tuple of tags
# }
```

**何时选哪种**：

- 知道元素是同构列表/集合，希望 LLM 输出每个元素 → 写法 A（容器装 Field）
- 不关心内部结构，只标类型名（如 `tuple`/`set`/`frozenset`） → 写法 B（Field+type）

## LLM 输出协议

LLM 必须返回**裸 JSON 对象**（顶层 dict），约定字段：

| 字段 | 含义 |
|------|------|
| 业务字段 | 业务正常输出（**不得以 `_` 开头**） |
| `_error: true` | 校验/参数非法 |
| `_message: str` | 错误原因 |
| `_note: str` | 补充说明 |
| `_unserializable: true` | （装饰器侧注入）某入参不可序列化，已用占位 |
| `_type: str` | （装饰器侧注入）该入参的原始类型名 |

`_` 前缀字段为协议层元信息，业务字段不应使用此前缀。详见 [prompt.py](magicorb/prompt.py) 中的 `SUPER_PROMPT`。

**入参清洗约定**（LLM 在 user message 中看到的）：

- 普通基础类型、列表、字典、业务对象均原样呈现
- 非有限浮点数（NaN/Infinity）已被递归替换为 `null`
- 不可序列化的参数以占位对象 `{"_unserializable": true, "_type": "原始类型名"}` 保留
- 循环引用通过 jsonpickle 元字段表达：
  - `py/object`：对象的类名
  - `py/id`：对象唯一编号
  - `py/ref`：循环引用，指向前面 `py/id` 对应的对象
- 这些仅用于描述对象结构，**LLM 输出禁止返回任何 `py/*` 开头的字段**

## 设计限制与注意事项

> 以下问题经评估后**维持现状**，原因附后。使用前请阅读并按约定规避。

### `magic_out` 必须声明为 `magic_out=None`

装饰阶段校验，无默认值或非 `None` 默认值会立即抛 `TypeError`。理由：`magic_out` 是装饰器内部通道，禁止外部介入。

### `return None` 兜底语义无法区分两种意图

`magic.py` 的 `return magic_ret if res is None else res`：业务函数返回 `None` 表示「请用 LLM 输出」。**无法区分「故意返回 None」与「异常返回 None」**，约定由业务侧保证不返回有业务意义的 `None`。

**规避**：业务侧需要返回 `None` 时，改为返回 `magic_out.data`（即使为空 dict 也不是 None）；或抛异常表达错误状态。

### 递归调用无保护

`@as_magic` 函数内部递归调自身会触发多次 LLM 请求，N 次递归 N 次 LLM 调用。装饰器不做递归栈跟踪。

**规避**：递归调用应改为先取 LLM 输出后再走纯 Python 递归；或把递归逻辑拆出为独立的非装饰函数。

### 单位置 callable 参数判定歧义

`@as_magic(some_callable)` 会被当成裸装饰处理（`some_callable` 当被装饰函数）。这是 overload 显式声明的契约。

**规避**：`provider` / `async_provider` / `exclude_params` 必须通过**关键字参数**传，禁止位置传。

### `self`/`cls` 默认进入 LLM 上下文

装饰方法时整个 `self.__dict__` 会序列化送 LLM。这是 by design（示例 4 就依赖此机制）。

**规避**：敏感字段用 `exclude_params=["field_name"]` 排除；如果不想注入 self，把方法拆为模块级函数。

### 基础类型 fast-path 范围

`int`/`str`/`bool`/`None`/有限 `float` 直通；非有限 `float`（NaN/Inf）会被净化为 `None`，不丢参数。`IntEnum` 等 `int` 子类也会走 fast-path（丢元信息，但 JSON 输出无害）。

### jsonpickle 元字段

循环引用会用 `py/object` / `py/id` / `py/ref` 标记。SUPER_PROMPT 明确告诉 LLM 输出禁止带 `py/*` 字段。

## FAQ

**Q: 为什么 `magic_out` 必须声明为 `=None`？**
A: 这是装饰器内部通道。装饰阶段校验默认值是为了避免业务代码误传值。如果想用 `magic_out` 模式，就声明 `magic_out=None`；如果不想用，就不要在签名里加这个参数。

**Q: `bool(magic_out)` 为什么永远为 True？**
A: 因为 LLM 可能合法地返回空 dict `{}` 或 `{value: 0}`，老代码 `if magic_out:` 会在这些情况走错分支。强制永真后，业务侧必须用 `magic_out.error` 判错，语义更清晰。

**Q: 业务函数返回 None 时会发生什么？**
A: 装饰器会用 LLM 输出作为函数返回值（兜底语义）。所以业务侧需要返回 `None` 时，应该改返回 `magic_out.data`，或抛异常表达错误状态。

**Q: 如何排除敏感参数？**
A: 用 `exclude_params=["field_name"]`，被排除的参数不会进入 LLM 上下文。

**Q: 如何切换全局 provider？**
A: 覆盖 `magicorb.LLMprovider.glm_provider._GLOBAL_MAGIC_PROVIDER`（同步）或 `_GLOBAL_ASYNC_MAGIC_PROVIDER`（异步）。

**Q: 装饰方法时 `self` 会进 LLM 吗？**
A: 会，整个 `self.__dict__` 会被序列化送入。这是 by design，示例 4 就依赖此机制。敏感字段用 `exclude_params` 排除。

**Q: 支持哪些容器类型在 sample_return 里？**
A: `list` / `tuple` / `set` / `frozenset`，统一渲染为 `[...]` 形式展开首个元素。

**Q: 如何处理 LLM 报错？**
A: 在业务函数里检查 `magic_out.error`，抛业务异常或返回安全默认值。**不要直接 `return None`**，否则会触发兜底语义走 LLM 输出。

**Q: 装饰后能给函数赋 `sample_return` 吗？**
A: 可以，装饰器在每次调用时基于 `final_wrap.sample_return` 动态拼接 prompt，所以装饰后赋值仍然生效。

## 运行样例

`test.py` 是一套覆盖所有特性的样例集，使用 mock provider，无需真实 LLM 即可运行：

```bash
python test.py
```

样例索引：

| # | 样例 | 演示特性 |
|---|------|---------|
| 1 | 快速开始 | 最小可运行示例 |
| 2 | 自定义 provider | 注入 mock provider 替换全局 |
| 3 | 裸装饰器 + 全局 provider | 全局 GLM provider 槽位 |
| 4 | 带配置装饰器 | provider= / exclude_params= |
| 5 | 异步函数 | async def + async_provider |
| 6 | 方法装饰 | self 注入，Calculator 示例 |
| 7 | 声明返回结构 | sample_return + Field，装饰后赋值 |
| 8 | sample_return 两种写法 | 容器装 Field / Field+type |
| 9 | MagicOut 接口 | error/message/note/data/raw/get/bool |
| 10 | MagicOut 永真 | 消除 if magic_out: 的 falsy 陷阱 |
| 11 | _error 处理 | LLM 报错时业务侧统一处理 |
| 12 | 基础类型 fast-path | int/str/bool/None/有限 float 直通 |
| 13 | NaN/Inf 净化 | 非有限浮点替换为 null |
| 14 | 不可序列化占位 | callable 参数 -> {_unserializable, _type} |
| 15 | 循环引用 | jsonpickle py/id/py/ref 元字段 |
| 16 | provider 返回非 dict | 自动包装为 _error 结构 |
| 17 | magic_out 默认值校验 | 必须声明 magic_out=None |

## 项目结构

```
magicorb/                          # 项目根
├── magicorb/                      # 包根
│   ├── __init__.py                  # 暴露 as_magic, MagicOut, Field
│   ├── magic.py                     # @as_magic 装饰器核心、MagicOut、序列化、参数清洗
│   ├── field.py                     # Field + render_sample_structure 结构渲染
│   ├── prompt.py                    # SUPER_PROMPT 协议
│   ├── logger.py                    # 库内日志 logger
│   └── LLMprovider/
│       ├── __init__.py
│       └── glm_provider.py          # 默认 GLM 同步/异步 provider
├── legacy/
│   ├── magicorb.py                # 早期单文件实现（已拆分，仅供参考）
│   └── a.py
├── test.py                          # 用法样例集（mock provider，无 LLM 也能跑）
├── pyproject.toml                   # 包元数据 + 依赖
├── LICENSE                          # MIT
└── README.md
```

## 依赖

| 依赖 | 用途 | 必需 |
|------|------|------|
| jsonpickle | 对象序列化、循环引用 | 是 |
| requests | 同步 GLM 调用 | 同步必选 |
| aiohttp | 异步 GLM 调用 | 异步必选 |
| pydantic | BaseModel 自动 dump | 可选 |

`pyproject.toml` 提供以下 optional-dependencies 组：

```bash
pip install -e .               # 仅 jsonpickle
pip install -e .[sync]        # + requests
pip install -e .[async]       # + aiohttp
pip install -e .[glm]         # + requests + aiohttp
pip install -e .[pydantic]    # + pydantic
pip install -e .[all]         # 全部
pip install -e .[dev]         # + pytest
```

## 对比与定位

### 设计哲学：尽可能确定性 + 开发期固化

magicorb 的设计目标可以用一句话概括：**把 LLM 调用做成尽可能确定性的、在开发期就能完全约束的函数调用**。

- **Prompt 在开发期固化**：装饰器的 docstring、`sample_return` 在装饰阶段（或装饰后赋值阶段）就已确定，运行时不被任何机制自动改写、调优或扩展。Prompt 是工程产物，写出来什么样，运行时就什么样。
- **运行时单次结构化调用**：每次调用 = 一次序列化 + 一次 LLM 调用 + 一次包装注入。不存在运行时多步推理、循环、工具链调度——这些都会引入运行时行为的不确定性。
- **LLM 输出通过协议字段受控**：`_error` / `_message` / `_note` / `_unserializable` / `_type` 等显式字段协议让 LLM 的输出行为在协议层被约束，业务侧通过 `MagicOut` 包装类读取，避免裸 dict / 裸对象的不可预期访问。
- **`return None` 兜底 / 非 None 覆盖**：业务函数可以在拿到 LLM 输出后选择"用 LLM 输出兜底"或"业务覆盖"，保留人工兜底通道，不把最终返回值完全交给 LLM。

### 设计边界（不做 ≠ 缺陷）

下列能力 magicorb **不做**，这是定位选择，不是能力缺失。对应场景请选用更合适的工具：

| 能力 | magicorb 立场 | 推荐工具 |
|---|---|---|
| 运行时 prompt 自动优化 / 调优 | 不做。Prompt 应在开发期固化，运行时调优破坏函数契约的确定性 | [DSPy](https://github.com/stanfordnlp/dspy) |
| 多步推理 / agent 循环 | 不做。magicorb 是单次 AI 增强，不是 agent 框架 | DSPy / LangChain / SimpleLLMFunc |
| 流式 token 输出 | 不做。magicorb 返回完整结构化对象，不面向"边生成边显示"的 UX 场景 | magentic / LangChain |
| 工具调用 / Function-Calling | 不做。magicorb 是 "AI-as-Function"（把 LLM 包装成函数），方向与 "Function-Calling"（让 LLM 调函数）相反 | LangChain @tool / Qwen-Agent |

### niche 定位

magicorb 与下列库同属 "AI-as-Function 装饰器" niche，但各自聚焦不同，没有优劣之分：

| 库 | 聚焦点 |
|---|---|
| **magentic** | 类型注解直接驱动结构化输出，pydantic 必选，生态最成熟 |
| **SimpleLLMFunc** | docstring 即系统提示词，原生 agent 循环与代码沙盒 |
| **llm-as-function** | 极简装饰器，类型注解生成 prompt，代码量最小 |
| **MiniAI** | `@ai.function` + `{var}` 占位，依赖少 |
| **simplemind** | 自动根据函数签名生成 tool schema，多模型兼容 |
| **magicorb** | **注入式 pipeline + 方法装饰 + `MagicOut` 包装 + 开发期确定性** |

magicorb 在这个 niche 里独占以下三个方向：

| 差异化方向 | 说明 |
|---|---|
| **注入式 pipeline** | LLM 输出不是函数返回值，而是注入到业务函数的 `magic_out` 参数。业务函数做后处理（判错、setattr、组合、覆盖）→ 最终返回值。其他库是 "LLM 输出 = 函数返回值" 直连模式 |
| **方法装饰 + self 状态注入** | 支持装饰类方法，让 LLM 通过返回字段驱动 `self` 的状态更新。把 LLM 当对象方法用，不限于纯函数调用 |
| **`MagicOut` 包装 + `__bool__` 永真** | LLM 输出统一包装为 `MagicOut`，强制业务侧用 `magic_out.error` 判错而非 `if magic_out:`，消除 LLM 返回 `{}` / `""` / `0` 时的 falsy 陷阱 |

### 选型建议

| 场景 | 推荐 |
|---|---|
| 要把 LLM 输出做业务后处理、装饰类方法、修改对象状态 | magicorb |
| 要类型注解直接驱动输出、生态最成熟 | magentic |
| 要运行时 prompt 调优、多步推理 | DSPy |
| 要 agent 循环、代码沙盒 | SimpleLLMFunc / LangChain |
| 要流式 UX、工具调用 | magentic / LangChain |
| 要完整 agent 生态 / RAG | LangChain |
| 国内 GLM-first、不想强制 pydantic | magicorb |

## 协议

本项目基于 [MIT License](./LICENSE) 开源。

```
MIT License

Copyright (c) 2026 Qixuan Wang <magicorb@qixuan.wang>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

## AI 合规声明

本着透明、可追溯、负责任的工程原则，本项目就 AI 工具的使用做如下披露：

### AI 工具使用情况

- **开发工具**：本项目在开发过程中使用了 [TRAE](https://www.trae.cn/)（Trae CN，字节跳动旗下的 AI 原生 IDE）作为开发辅助工具。
- **设计与实现分工**：
  - **设计原型由作者提供**：`@as_magic` 装饰器语义（裸装饰 / 带配置双写法）、`MagicOut` 包装类接口（`.error` / `.data` / `__bool__` 永真等）、`sample_return` + `Field` 结构渲染协议、LLM 输出字段族（`_error` / `_message` / `_note` / `_unserializable` / `_type`）、序列化清洗策略（基础类型 fast-path、NaN/Inf 净化、不可序列化占位）等核心设计决策与原型实现由作者 王琪璇 (Qixuan Wang) 提出
  - **后续经 AI 多轮重构成型**：在作者设计原型的基础上，由 TRAE 内嵌 AI 助手经过多轮迭代重构，逐步完成包化拆分、内部 import 调整、边界 case 修复、文档撰写、测试样例编写等工程化工作
- **使用范围**：
  - **代码生成与重构**：装饰器逻辑、序列化清洗、MagicOut 包装、包结构调整等环节的代码主要由 TRAE 内嵌 AI 助手生成
  - **文档撰写**：本 README、API 文档、FAQ、变更日志等内容由 AI 起草
  - **测试样例**：`test.py` 中的样例设计与编写由 AI 完成
  - **问题排查**：在调试序列化、循环引用、MagicOut 包装等边界行为时借助 AI 辅助分析
- **模型来源**：TRAE 内嵌 AI 助手使用的模型由 TRAE 平台提供（如 GLM 系列等），具体模型版本随开发时间而异

### 代码审阅与责任

- **本项目实现代码未经人工代码审阅**：设计原型由作者提供，但具体实现主要由 AI 助手经多轮重构成型，未经过人工逐行审阅
- 项目最终的所有代码、文档、设计决策**由本项目维护者 王琪璇 (Qixuan Wang)（magicorb@qixuan.wang）负责**
- 已通过 `test.py` 的 17 个样例对核心行为进行了回归验证（这是目前唯一的验证手段）
- **风险提示**：未经人工审阅意味着可能存在 AI 生成代码的潜在缺陷、未考虑的边界场景、与文档描述不符的实现细节；使用者请自行评估风险
- 如发现 AI 生成内容存在问题或潜在风险，欢迎通过下方合作邮箱反馈

### 用户须知

- 本项目作为一个 LLM 函数增强装饰器库，其运行时**会调用外部 LLM 服务**（默认为智谱 GLM）。运行时产生的 AI 调用、数据传输、计费等行为由使用者的 provider 配置决定，与开发期使用的 TRAE AI 助手无关
- 使用本项目调用 LLM 时，请遵守对应 LLM 服务商的使用条款与隐私政策

## 合作开发

欢迎对本项目提出建议、报告问题或参与协作开发。

- **合作邮箱**：[magicorb@qixuan.wang](mailto:magicorb@qixuan.wang)
- **欢迎的贡献类型**：
  - 新的 LLM provider 实现（OpenAI / Claude / 本地模型等）
  - 序列化清洗的边界 case 反馈与修复
  - 文档改进、样例补充、使用经验分享
  - 性能优化与跨平台兼容性测试
- **反馈建议**：邮件主题请加 `[magicorb]` 前缀，便于分类；附带最小可复现样例会更高效

## 变更日志

### 0.1.0 (2026)

- 初版发布，包化为 `magicorb` 包
- `as_magic` 装饰器：同步/异步双支持、裸装饰/带配置双写法
- `MagicOut` 包装类：`.error` / `.message` / `.note` / `.data` / `.raw` / `.get()` / `__bool__` 永真
- `Field` + `sample_return`：声明返回结构，**支持装饰后赋值**
- 序列化清洗：基础类型 fast-path、NaN/Inf 净化、不可序列化占位、jsonpickle 循环引用
- LLM 输出协议：`_error` / `_message` / `_note` 字段族 + `_unserializable` / `_type` 占位
- 默认 GLM provider（智谱 `glm-4-flash`），可插拔自定义 provider
- MIT 协议开源
- AI 合规声明：披露开发期使用 TRAE 作为辅助工具，诚实声明代码未经人工审阅
- 新增「对比与定位」章节：明确「尽可能确定性 + 开发期固化」设计哲学，划定不做 prompt 自动优化 / agent 循环 / 流式 / 工具调用的设计边界
- 合作开发渠道：合作邮箱 `magicorb@qixuan.wang`
