Skip to content

Commit a5a1993

Browse files
committed
init
0 parents  commit a5a1993

635 files changed

Lines changed: 32337 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.live.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Separate test accounts are supported. Do not commit real PATs.
2+
QODER_FORWARD_PAT=
3+
QODER_FORWARD_BASE_URL=https://api.qoder.com.cn/api/v1/forward
4+
QODER_FORWARD_MODEL=
5+
6+
QODER_MANAGED_PAT=
7+
QODER_MANAGED_BASE_URL=https://api.qoder.com.cn/api/v1/cloud
8+
QODER_MANAGED_MODEL=
9+
10+
# Optional fallback for either mode.
11+
QODER_ACCESS_TOKEN=
12+
13+
# pytest live tests require explicit opt-in in the process environment:
14+
# QODER_RUN_LIVE=1 python -m pytest examples -m live -v
15+
# Scenarios other than models create resources and may consume model credits.

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.DS_Store
2+
.venv/
3+
__pycache__/
4+
*.py[cod]
5+
*.egg-info/
6+
.pytest_cache/
7+
.mypy_cache/
8+
.ruff_cache/
9+
.coverage
10+
build/
11+
dist/
12+
.env
13+
.env.*
14+
!.env.live.example
15+
/tmp_api_tests/

MANIFEST.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include README.md Makefile
2+
include .env.live.example
3+
recursive-include src *.py py.typed
4+
recursive-include examples *.py *.md
5+
recursive-include docs *.md

Makefile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
PYTHON ?= python3
2+
LIVE_ENV_FILE ?= .env.live
3+
.DEFAULT_GOAL := test
4+
5+
.PHONY: test lint typecheck build test-live test-live-managed test-live-all
6+
7+
test:
8+
$(PYTHON) -m pytest -q
9+
10+
lint:
11+
$(PYTHON) -m ruff check src tests examples
12+
$(PYTHON) -m ruff format --check src tests examples
13+
14+
typecheck:
15+
$(PYTHON) -m mypy src/qca
16+
17+
build:
18+
$(PYTHON) -m build
19+
20+
test-live:
21+
QODER_RUN_LIVE=1 QODER_LIVE_ENV_FILE="$(LIVE_ENV_FILE)" $(PYTHON) -m pytest examples/forward -m live -v
22+
23+
test-live-managed:
24+
QODER_RUN_LIVE=1 QODER_LIVE_ENV_FILE="$(LIVE_ENV_FILE)" $(PYTHON) -m pytest examples/managed -m live -v
25+
26+
test-live-all:
27+
QODER_RUN_LIVE=1 QODER_LIVE_ENV_FILE="$(LIVE_ENV_FILE)" $(PYTHON) -m pytest examples -m live -v

README.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# Qoder Cloud Agents Python SDK
2+
3+
Python 3.10+,同步与原生异步客户端,支持类型化响应、自动分页、SSE 和文件传输。
4+
5+
完整 API 参考:[Forward API](docs/forward-api.md) · [Managed API](docs/managed-api.md),包含初始化、同步/异步调用、分页、SSE、文件传输,以及全部资源方法的参数、返回类型与 HTTP 路由。
6+
7+
## 安装与配置
8+
9+
```bash
10+
# 在本仓库中安装
11+
python -m pip install .
12+
# 开发环境
13+
python -m pip install -e '.[dev]'
14+
```
15+
16+
```python
17+
from qca import Forward, Managed
18+
19+
with Forward() as client:
20+
for model in client.models.list().data:
21+
if model.is_enabled:
22+
print(model.id)
23+
24+
with Managed() as client:
25+
for agent in client.agents.list(limit=20):
26+
print(agent.id, agent.name)
27+
```
28+
29+
也可使用 `from qca.forward import Client``from qca.managed import Client`。两种模式独立实例化,使用各自的资源与类型。
30+
31+
| 配置 | Forward | Managed |
32+
|---|---|---|
33+
| 令牌 | `QODER_ACCESS_TOKEN` | `QODER_ACCESS_TOKEN` |
34+
| API 根地址 | `QODER_FORWARD_BASE_URL` | `QODER_BASE_URL` |
35+
| 默认地址 | `https://api.qoder.com/api/v1/forward/` | `https://api.qoder.com/api/v1/cloud/` |
36+
37+
显式参数优先于环境变量。客户端不读取 `.env`;只有 examples 读取 `.env.live`。CN 环境需要显式配置对应根地址:
38+
39+
```python
40+
client = Forward(
41+
access_token="your-access-token",
42+
base_url="https://api.qoder.com.cn/api/v1/forward",
43+
timeout=30.0,
44+
max_retries=2,
45+
)
46+
```
47+
48+
## 会话
49+
50+
Forward 通过 Identity 和 Template 创建 Session,Managed 通过 Agent 和 Environment 创建 Session:
51+
52+
```python
53+
from qca import Forward
54+
55+
with Forward() as client:
56+
environment = client.environments.create(name="demo", config={"type": "cloud"})
57+
identity = client.identities.create(external_id="example-user", name="示例用户")
58+
template = client.templates.create(
59+
name="assistant", environment_id=environment.id,
60+
model="ultimate", # 使用当前账号已启用的模型
61+
system="根据可读取的资料回答问题。",
62+
tools=[{"type": "agent_toolset_20260401"}],
63+
)
64+
session = client.sessions.create(identity_id=identity.id, template_id=template.id)
65+
print(session.id)
66+
```
67+
68+
```python
69+
from qca import Managed
70+
71+
with Managed() as client:
72+
environment = client.environments.create(name="demo", config={"type": "cloud"})
73+
agent = client.agents.create(
74+
name="assistant", model={"id": "ultimate"},
75+
system="根据可读取的资料回答问题。",
76+
tools=[{"type": "agent_toolset_20260401"}],
77+
)
78+
session = client.sessions.create(environment_id=environment.id, agent=agent.id)
79+
print(session.id)
80+
```
81+
82+
片段会创建资源。包含执行断言和清理的完整用例见 [examples](examples/README.md)。Forward 还提供 Schedule、Batch、Channel;Managed 提供 Deployment、Dream、自托管环境 Work API。
83+
84+
## 消息与 SSE
85+
86+
下列代码适用于两种客户端。在同一段对话中复用 `session_id`,同一条逻辑消息的 HTTP 重试复用幂等键。
87+
88+
```python
89+
from uuid import uuid4
90+
91+
sent = client.sessions.events.send(
92+
session_id,
93+
events=[{"type": "user.message", "content": [{"type": "text", "text": "你好"}]}],
94+
extra_headers={"Idempotency-Key": uuid4().hex},
95+
)
96+
97+
with client.sessions.events.stream(
98+
session_id,
99+
extra_headers={"Last-Event-ID": sent.data[0].id},
100+
event_deltas=["agent.message"],
101+
) as stream:
102+
for event in stream:
103+
if event.type == "agent.message":
104+
print(event.to_json())
105+
elif event.type == "session.status_idle":
106+
print(event.stop_reason)
107+
break
108+
elif event.type in ("session.error", "session.status_terminated"):
109+
raise RuntimeError(f"Session stopped: {event.type}")
110+
```
111+
112+
SDK 不自动重连 SSE。保存 `stream.last_event_id`,重连时通过 `Last-Event-ID` 恢复,不要重发已被接收的消息。`event_start``event_delta` 是预览,最终事件会再次包含完整内容;同一个 ID 的增量事件不会被去重。idle 可能表示等待确认或达到预算,业务成功还需检查 `stop_reason` 和最终回复。
113+
114+
## 异步
115+
116+
异步客户端使用 `httpx.AsyncClient`,请求、重试等待、SSE 读取均为原生异步 I/O。
117+
118+
```python
119+
import asyncio
120+
from qca import AsyncManaged
121+
122+
async def main():
123+
async with AsyncManaged() as client:
124+
async for agent in client.agents.list(limit=20):
125+
print(agent.id)
126+
first_page = await client.sessions.list(limit=10)
127+
print(first_page.data)
128+
129+
asyncio.run(main())
130+
```
131+
132+
异步流使用 `async with await client.sessions.events.stream(...)`。完整片段见 [Forward 异步示例](examples/forward/async_session.py)[Managed 异步示例](examples/managed/async_session.py)。本地文件读取通过线程执行,网络请求直接使用异步 HTTP 客户端。
133+
134+
## 参数与响应
135+
136+
方法使用 snake_case、关键字参数和类型注解。嵌套资源的目标 ID 可以作为位置参数,祖先 ID 必须具名:
137+
138+
```python
139+
credential = client.vaults.credentials.retrieve("credential-id", vault_id="vault-id")
140+
memory = client.memory_stores.memories.retrieve("memory-id", memory_store_id="store-id")
141+
```
142+
143+
各 mode 的 `types/*_params.py` 使用 `TypedDict` 定义请求。嵌套参数直接传普通字典;联合类型直接传对应的字符串、字典或列表。响应是 Pydantic 模型,可以直接访问字段,未知字段也会保留。
144+
145+
```python
146+
from qca import NOT_GIVEN
147+
148+
client.identities.update("identity-id", name=NOT_GIVEN) # 不发送 name
149+
client.identities.update("identity-id", name=None) # 发送 null
150+
client.identities.update("identity-id", enabled=False) # 保留 false
151+
152+
identity = client.identities.retrieve("identity-id")
153+
print(identity.to_dict())
154+
print(identity.to_json())
155+
print(identity._request_id)
156+
print("name" in identity.model_fields_set) # 区分缺失与 null
157+
```
158+
159+
能否清空字段由服务端决定。方法均支持 `extra_headers``extra_query``extra_body``timeout`;extra 值优先于方法参数。空数组、空对象、0、false 均保留。
160+
161+
## 分页
162+
163+
```python
164+
page = client.sessions.list(limit=20)
165+
print(page.data) # 当前页
166+
for session in page: # 自动获取后续页
167+
print(session.id)
168+
for page in client.sessions.list().iter_pages():
169+
print(len(page.data))
170+
```
171+
172+
SDK 按 Go API 区分 `after_id` / `before_id``next_page` 分页,后续请求保留过滤条件。游标不前进或循环时抛出异常。非分页列表响应(例如 Models)通过 `.data` 访问。
173+
174+
## 错误、超时与重试
175+
176+
```python
177+
from qca import APIConnectionError, APIStatusError, APITimeoutError
178+
179+
try:
180+
session = client.sessions.retrieve("sess-id", timeout=10)
181+
except APITimeoutError:
182+
print("请求超时")
183+
except APIConnectionError:
184+
print("网络连接失败")
185+
except APIStatusError as exc:
186+
print(exc.status_code, exc.code, exc.type, exc.request_id)
187+
```
188+
189+
HTTP 状态分别对应 `BadRequestError``AuthenticationError``PermissionDeniedError``NotFoundError``ConflictError``UnprocessableEntityError``RateLimitError``InternalServerError`。非 JSON 错误正文保留在 `.body`。响应无法解码为声明类型时抛出 `APIResponseValidationError`
190+
191+
默认连接超时 10 秒,其余 HTTP 阶段 60 秒。可传浮点秒数、`httpx.Timeout``None`;超时按 HTTP 阶段和单次尝试计算。端到端任务期限由调用方管理,异步代码可用 `asyncio.wait_for`
192+
193+
默认最多重试 2 次:GET/HEAD 或携带 `Idempotency-Key` 的请求可对连接错误、408、429、5xx 重试;无幂等键的其他请求仅对 429 重试;409 不自动重试。在上述约束内遵循 `x-should-retry` 和有效的 `Retry-After-Ms` / `Retry-After`,否则指数退避。已建立的 SSE 不重试。
194+
195+
`client.with_options(max_retries=0, timeout=20)` 返回独立配置的客户端,复用同一个 HTTP 连接池;关闭任一客户端会关闭这个池。
196+
197+
## 文件和自定义 HTTP
198+
199+
```python
200+
from pathlib import Path
201+
202+
file = client.files.upload(file=Path("report.txt"))
203+
skill = client.skills.create(files=[("example/SKILL.md", b"---\nname: example\n---\nExample skill")])
204+
with client.files.download(file.id) as content:
205+
content.write_to_file("downloaded.txt")
206+
```
207+
208+
上传支持 bytes、二进制文件对象、Path、`(文件名, 内容[, MIME 类型])`。调用方传入的文件对象由调用方关闭;上传内容会缓存以便重试。metadata 使用 JSON 编码,Skill 相对路径保留在 multipart 文件名中。
209+
210+
Files 下载先获取临时链接,再流式读取存储地址;API 认证、默认请求头、Cookie 不会发送到存储主机。Skill Version 下载直接返回 API 的二进制响应。异步下载使用 `await client.files.download(...)``await response.write_to_file(...)`
211+
212+
```python
213+
import httpx
214+
from qca import Forward
215+
216+
with Forward(http_client=httpx.Client(proxy="http://localhost:8080")) as client:
217+
raw = client.models.with_raw_response.list()
218+
print(raw.status_code, raw.headers)
219+
models = raw.parse()
220+
```
221+
222+
异步客户端可传 `httpx.AsyncClient`,异步 raw response 使用 `await raw.parse()`。动态令牌提供者传到 `credential=`,每次 HTTP 尝试调用 `get_token()`;异步客户端也接受异步 `get_token()`。静态 access_token 优先于提供者,显式 Authorization 请求头优先于两者。
223+
224+
需要先查看响应头再读取正文时使用 `with_streaming_response`。退出上下文时关闭连接:
225+
226+
```python
227+
with client.models.with_streaming_response.list() as response:
228+
print(response.headers)
229+
models = response.parse()
230+
```
231+
232+
异步版本使用 `async with client.models.with_streaming_response.list()`,通过 `await response.parse()` 解析正文;也可以按块迭代 `iter_bytes()` / `iter_lines()`
233+
234+
## 目录
235+
236+
```text
237+
src/qca/
238+
__init__.py
239+
common/ # HTTP、鉴权、错误、分页、上传/下载、SSE
240+
forward/
241+
_client.py
242+
resources/ # identities/configs、sessions/events 等
243+
types/ # 请求 TypedDict、响应模型
244+
managed/
245+
_client.py
246+
resources/ # agents、deployments、environments/work 等
247+
types/
248+
tests/ # 资源面、文档契约、公共层与模拟执行场景
249+
examples/ # 每个场景一个文件;各 mode 含 6 个同步场景、异步片段及 live 测试
250+
docs/ # Forward / Managed 完整 API 参考
251+
```

0 commit comments

Comments
 (0)