265 lines
7.2 KiB
Markdown
265 lines
7.2 KiB
Markdown
# 示例(curl / Python / JS / Flutter)
|
||
|
||
本文提供“最小可用”的端到端示例:创建对话 → 发送消息 → 轮询输出 →(可选)停止任务 → 文件上传/下载。
|
||
|
||
请先阅读 `auth.md` 并准备好 token。
|
||
|
||
## 0. 统一变量
|
||
|
||
- `BASE_URL`:例如 `http://localhost:8091`
|
||
- `TOKEN`:你的 Bearer Token(明文)
|
||
|
||
---
|
||
|
||
## 1) curl:完整对话流程
|
||
|
||
### 1.1 创建对话
|
||
|
||
```bash
|
||
BASE_URL="http://localhost:8091"
|
||
TOKEN="<TOKEN>"
|
||
|
||
curl -sS -X POST \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
"$BASE_URL/api/v1/conversations"
|
||
```
|
||
|
||
假设返回:
|
||
|
||
```json
|
||
{ "success": true, "conversation_id": "conv_20260123_234245_036" }
|
||
```
|
||
|
||
### 1.2 发送消息(创建任务)
|
||
|
||
```bash
|
||
curl -sS -X POST \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"conversation_id": "conv_20260123_234245_036",
|
||
"message": "请用中文简要介绍《明日方舟:终末地》",
|
||
"run_mode": "fast",
|
||
"max_iterations": 100
|
||
}' \
|
||
"$BASE_URL/api/v1/messages"
|
||
```
|
||
|
||
假设返回:
|
||
|
||
```json
|
||
{ "success": true, "task_id": "60322db3-...", "status": "running", "conversation_id": "conv_...", "created_at": 1769182965.30 }
|
||
```
|
||
|
||
### 1.3 轮询(建议脚本处理 next_offset)
|
||
|
||
```bash
|
||
TASK_ID="60322db3-..."
|
||
OFFSET=0
|
||
|
||
curl -sS -H "Authorization: Bearer $TOKEN" \
|
||
"$BASE_URL/api/v1/tasks/$TASK_ID?from=$OFFSET"
|
||
```
|
||
|
||
---
|
||
|
||
## 2) Python:轮询并实时拼接文本
|
||
|
||
依赖:`pip install requests`
|
||
|
||
```python
|
||
import time
|
||
import requests
|
||
|
||
BASE_URL = "http://localhost:8091"
|
||
TOKEN = "<TOKEN>"
|
||
H = {"Authorization": f"Bearer {TOKEN}"}
|
||
|
||
def post_json(path, payload):
|
||
r = requests.post(BASE_URL + path, json=payload, headers={**H, "Content-Type":"application/json"}, timeout=30)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
def get_json(path, params=None):
|
||
r = requests.get(BASE_URL + path, params=params or {}, headers=H, timeout=30)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
# 1) create conversation
|
||
conv = requests.post(BASE_URL + "/api/v1/conversations", headers=H, timeout=30).json()
|
||
assert conv["success"]
|
||
conversation_id = conv["conversation_id"]
|
||
|
||
# 2) send message
|
||
task = post_json("/api/v1/messages", {
|
||
"conversation_id": conversation_id,
|
||
"message": "请用中文简要介绍《明日方舟:终末地》",
|
||
"run_mode": "fast",
|
||
"max_iterations": 100
|
||
})
|
||
task_id = task["task_id"]
|
||
|
||
# 3) poll events
|
||
offset = 0
|
||
text_buf = []
|
||
think_buf = []
|
||
|
||
while True:
|
||
data = get_json(f"/api/v1/tasks/{task_id}", params={"from": offset})
|
||
if not data["success"]:
|
||
raise RuntimeError(data.get("error"))
|
||
info = data["data"]
|
||
events = info["events"]
|
||
offset = info["next_offset"]
|
||
|
||
for ev in events:
|
||
t = ev["type"]
|
||
d = ev["data"] or {}
|
||
if t == "text_chunk":
|
||
text_buf.append(d.get("content",""))
|
||
print(d.get("content",""), end="", flush=True)
|
||
elif t == "text_end":
|
||
print("\\n--- text_end ---\\n")
|
||
elif t == "thinking_chunk":
|
||
think_buf.append(d.get("content",""))
|
||
elif t == "tool_start":
|
||
print(f\"\\n[tool_start] {d.get('name')}\\n\")
|
||
elif t == "update_action":
|
||
# 工具状态更新
|
||
st = d.get("status") or ""
|
||
msg = d.get("message") or ""
|
||
if st or msg:
|
||
print(f\"\\n[update_action] {st} {msg}\\n\")
|
||
elif t == "error":
|
||
print(f\"\\n[error] {d.get('message')}\\n\")
|
||
|
||
if info["status"] != "running":
|
||
break
|
||
|
||
time.sleep(1.0)
|
||
|
||
final_text = "".join(text_buf)
|
||
final_thinking = "".join(think_buf)
|
||
print("final status:", info["status"])
|
||
print("final text length:", len(final_text))
|
||
print("final thinking length:", len(final_thinking))
|
||
```
|
||
|
||
---
|
||
|
||
## 3) JavaScript(浏览器/Node)要点
|
||
|
||
浏览器端直接跨域请求时,请确保服务端允许 CORS(当前服务端已启用 CORS)。请求示例:
|
||
|
||
```js
|
||
const BASE_URL = "http://localhost:8091";
|
||
const TOKEN = "<TOKEN>";
|
||
|
||
async function api(path, options = {}) {
|
||
const resp = await fetch(BASE_URL + path, {
|
||
...options,
|
||
headers: {
|
||
"Authorization": `Bearer ${TOKEN}`,
|
||
...(options.headers || {})
|
||
}
|
||
});
|
||
const data = await resp.json();
|
||
if (!resp.ok || !data.success) throw new Error(data.error || resp.statusText);
|
||
return data;
|
||
}
|
||
|
||
const conv = await api("/api/v1/conversations", { method: "POST" });
|
||
const msg = await api("/api/v1/messages", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
conversation_id: conv.conversation_id,
|
||
message: "请用中文简要介绍《明日方舟:终末地》",
|
||
run_mode: "fast",
|
||
max_iterations: 100
|
||
})
|
||
});
|
||
|
||
let offset = 0;
|
||
let text = "";
|
||
while (true) {
|
||
const poll = await api(`/api/v1/tasks/${msg.task_id}?from=${offset}`);
|
||
const info = poll.data;
|
||
for (const ev of info.events) {
|
||
if (ev.type === "text_chunk") text += ev.data.content || "";
|
||
if (ev.type === "tool_start") console.log("tool:", ev.data.name);
|
||
}
|
||
offset = info.next_offset;
|
||
if (info.status !== "running") break;
|
||
await new Promise(r => setTimeout(r, 1000));
|
||
}
|
||
console.log("done:", text);
|
||
```
|
||
|
||
---
|
||
|
||
## 4) Flutter(Dart)轮询示例(伪代码)
|
||
|
||
依赖:`http` 包或 `dio` 包均可,这里用 `http` 表达逻辑。
|
||
|
||
```dart
|
||
import 'dart:convert';
|
||
import 'dart:async';
|
||
import 'package:http/http.dart' as http;
|
||
|
||
const baseUrl = "http://localhost:8091";
|
||
const token = "<TOKEN>";
|
||
|
||
Map<String,String> headersJson() => {
|
||
"Authorization": "Bearer $token",
|
||
"Content-Type": "application/json",
|
||
};
|
||
|
||
Future<String> createConversation() async {
|
||
final resp = await http.post(Uri.parse("$baseUrl/api/v1/conversations"),
|
||
headers: {"Authorization":"Bearer $token"});
|
||
final data = jsonDecode(resp.body);
|
||
if (resp.statusCode != 200 || data["success"] != true) throw Exception(data["error"]);
|
||
return data["conversation_id"];
|
||
}
|
||
|
||
Future<String> sendMessage(String convId, String message) async {
|
||
final resp = await http.post(Uri.parse("$baseUrl/api/v1/messages"),
|
||
headers: headersJson(),
|
||
body: jsonEncode({
|
||
"conversation_id": convId,
|
||
"message": message,
|
||
"run_mode": "fast",
|
||
"max_iterations": 100,
|
||
}));
|
||
final data = jsonDecode(resp.body);
|
||
if (resp.statusCode != 202 || data["success"] != true) throw Exception(data["error"]);
|
||
return data["task_id"];
|
||
}
|
||
|
||
Stream<String> pollText(String taskId) async* {
|
||
int offset = 0;
|
||
while (true) {
|
||
final resp = await http.get(Uri.parse("$baseUrl/api/v1/tasks/$taskId?from=$offset"),
|
||
headers: {"Authorization":"Bearer $token"});
|
||
final data = jsonDecode(resp.body);
|
||
if (resp.statusCode != 200 || data["success"] != true) throw Exception(data["error"]);
|
||
final info = data["data"];
|
||
final events = (info["events"] as List);
|
||
for (final ev in events) {
|
||
if (ev["type"] == "text_chunk") {
|
||
yield (ev["data"]["content"] ?? "");
|
||
}
|
||
}
|
||
offset = info["next_offset"];
|
||
if (info["status"] != "running") break;
|
||
await Future.delayed(Duration(milliseconds: 800));
|
||
}
|
||
}
|
||
```
|
||
|
||
提示:
|
||
|
||
- Flutter UI 展示建议:把 `pollText()` 的输出 append 到一个 `StringBuffer`,并用 `setState()`/状态管理更新。
|
||
- 同时可以订阅 tool_* 事件在 UI 中显示“正在搜索/正在执行工具”等状态。
|