Metadata-Version: 2.4
Name: aaas
Version: 0.2.0
Summary: Python SDK for the Agent as a Service protocol — declare an Agent on one connection, keep every function body in your own process.
Project-URL: Homepage, https://github.com/YY-Yifei/aaas
Project-URL: Repository, https://github.com/YY-Yifei/aaas
Project-URL: Specification, https://github.com/YY-Yifei/aaas/blob/main/PROTOCOL.md
Keywords: aaas,agent,function-calling,json-rpc,websocket,llm,protocol
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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 :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: websockets>=12.0
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.23; extra == "test"

# AaaS — Agent as a Service

**调用方在一条连接之内声明一个 Agent —— 它的职责，和它的能力（每个能力带完整参数 schema）——
服务端用模型驱动这个 Agent，而每个能力的执行体始终留在调用方进程内。**

「服务」这个词是字面的：调用方不部署任何东西、不注册任何回调地址、不持有任何后端，
只要有一条能连出去的 WebSocket，就能得到一个**此刻才存在、由它自己定义的** Agent。

这个仓库有两样东西，可以分开用：

| | 是什么 | 给谁 |
|---|---|---|
| **[`PROTOCOL.md`](https://github.com/YY-Yifei/aaas/blob/main/PROTOCOL.md)** | 协议规范：7 个方法、9 种事件、17 个错误码、15 条一致性断言 | 要实现这个协议的人 |
| **[`src/aaas/`](https://github.com/YY-Yifei/aaas/tree/main/src/aaas)** | Python SDK（`pip install aaas`）：调用方与服务端两侧的参考实现 | 要**用**这个协议的人 |

规范是源头，SDK 是它的实现。两者冲突时以规范为准，并视为缺陷。

---

## 安装

```bash
pip install aaas
```

唯一硬依赖是 `websockets`。Python ≥ 3.10。

## 10 分钟：一轮含反向 function calling

调用方侧 —— 你声明 Agent，并在**自己的进程里**执行能力：

```python
import asyncio, aaas

@aaas.function                     # 参数 schema 从签名推导，不用手写
async def search_local_index(query: str, page_size: int = 10) -> str:
    """在调用方的本地持仓索引里检索

    Args:
        query: 检索词
        page_size: 每页条数
    """
    return f"找到 3 条匹配「{query}」的记录"

async def main():
    async with aaas.Caller("wss://api.example.com/aaas", token="...") as caller:
        await caller.declare(aaas.AgentDefinition(
            revision="v1",
            instructions="你正在协助用户操作债券计算器。金额单位是万元。",
            functions=(search_local_index.declaration,),
        ), tools=[search_local_index])

        turn = await caller.send("帮我查一下持仓")
        async for event in turn:                 # 一轮不是一次请求-响应，是可迭代的事件流
            if event.kind == "message.delta":
                print(event.text, end="")

        final = await turn.end()
        assert final.status == "completed"

asyncio.run(main())
```

服务端侧 —— 你只需要写「用模型驱动这一轮」，协议层由 SDK 负责：

```python
import aaas

async def my_agent(ctx: aaas.TurnContext) -> None:
    ctx.reasoning("用户问本地索引，先调工具")       # 未协商 reasoning 时静默不发
    result = await ctx.call("search_local_index", {"query": ctx.text})
    ctx.reply(*(p.text for p in (result.parts or ()) if p.text))
    ctx.usage_of(output_tokens=42)

await aaas.Server(my_agent).serve("127.0.0.1", 8765)
```

`seq` 怎么排、调用怎么超时、取消时 `cancelCall` 与终态谁先、断连时怎么收尾 ——
都由 SDK 负责。它替你守的是 §5.3 那四条和 §5.8 那七条，那几处正是这类协议最容易写错的地方。

看不装也能跑的效果：

```bash
python -m aaas          # 起一个本地服务端 + 一个调用方，跑完一轮含反向 function calling
```

## SDK 覆盖了什么

| 模块 | 管什么 |
|---|---|
| `aaas.Caller` | 连接、声明、开轮、**接住服务端的反向调用**（执行体在本地） |
| `aaas.Server` + `TurnContext` | 7 个方法、事件序号、取消次序、断连收尾 |
| `aaas.function` | 从 Python 签名与类型注解推 `parameters`（消灭手写 schema） |
| `aaas.wire` | JSON-RPC 2.0 子集、`id` 分区（`c-*` / `s-*`）、batch 拒绝 |
| `aaas.errors` | 17 个错误码 + 5 个关闭码，两级形状（`code` 分类 / `reason` 具体） |
| `aaas.types` | 线上对象的 Python 投影，与 `schema/` 逐字段对齐 |

两侧都带**诊断**：调用方会在 `caller.diagnostics` 里记下它观测到的对端违规
（`id` 前缀不符、`seq` 缺口、方向不符、轮终态后的事件……）。规范里有大量 MUST 只能由
对端观测到，SDK 把这些变成结构化清单，而不是散落的 `print`。

## 仓库结构

```
PROTOCOL.md                     协议规范（唯一载体，1350+ 行）
schema/
  aaas.data.schema.json         9 个核心对象的 $defs，全封闭
  c2s.json / s2c.json           按**流动方向**切分的两个封闭世界
src/aaas/                       Python SDK
tests/                          37 个测试（单元 + 端到端）
tools/                          自检工具
```

`c2s` 与 `s2c` 按方向切分，不按方法：`invokeFunction.params` 只存在于 `s2c.json`，
它的 `result` 只存在于 `c2s.json`。于是「从错误方向发来一个形状合法的消息」在**结构上**
无处安放——它不是被某条规则禁止的，而是没有被定义过。

## 自检

```bash
python tools/validate_examples.py PROTOCOL.md   # 正文示例 vs schema：14 checked, 0 mismatched
python tools/check_refs.py PROTOCOL.md          # §X.Y / S1-S7 / C1-C15 悬空引用：0
python tools/check_leaks.py                     # 正文不得引用仓库外的材料
python tools/check_projection.py                # schema ↔ SDK dataclass 逐字段对齐
python -m pytest tests -q                       # 37 passed
```

`check_projection.py` 值得单独说一句：SDK 的 dataclass 是 schema 的第三份投影，
两份东西一定漂移。开发过程中它抓到过 `Event` 漏 `reason` 与 `elapsedMs` 两个字段——
读代码完全正常，只有真发出一条 `functionResult` 才撞上 `TypeError`。

## 协议现状

Draft `0.2.0`，**不承诺向后兼容**。15 条一致性断言见 §8；单层套件，每条都能判 PASS/FAIL，
SKIP 必须显式记录——静默跳过是最常见的「假一致性」。

还没写的：`LICENSE`。在那之前默认「保留所有权利」。

## 与 A2A / MCP 的关系

不是子集，不是超集，是**正交**。

- **A2A** 的方向是「我来之前 Agent 已经在那儿了」（服务端自述 `AgentCard`）；AaaS 是「我声明之后 Agent 才存在」。
  A2A 的 `AgentSkill` 有 `name`/`description`/`tags`/`examples`，**没有参数 schema**，所以 skill 无法直接变成一次 function calling。
- **MCP** 的工具提供方是**可被连入的服务器**；AaaS 的工具提供方是**发起连接的那一方**，方向相反。
- **OpenAI function calling** 是模型 API 的一个参数形状。AaaS 在其中 `name`/`description`/`parameters` 三格上同源，
  在此之上补了「谁来执行、怎么派发、失败怎么回」的整条链路。
