Compare commits
1 Commits
critical_a
...
71b25a1321
| Author | SHA1 | Date | |
|---|---|---|---|
| 71b25a1321 |
681
API_DOC.md
681
API_DOC.md
@@ -1,681 +0,0 @@
|
|||||||
# 慧遇 Agent API 接口文档
|
|
||||||
|
|
||||||
> 基于 Google ADK `api_server` 提供的 REST API,供外部项目集成使用。
|
|
||||||
>
|
|
||||||
> **API 文档(Swagger UI)**:启动服务后访问 `http://<host>:<port>/docs`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
- [快速开始](#快速开始)
|
|
||||||
- [启动服务](#启动服务)
|
|
||||||
- [核心概念](#核心概念)
|
|
||||||
- [系统接口](#系统接口)
|
|
||||||
- [会话管理](#会话管理)
|
|
||||||
- [Agent 对话](#agent-对话)
|
|
||||||
- [SSE 流式对话](#sse-流式对话)
|
|
||||||
- [WebSocket 实时对话](#websocket-实时对话)
|
|
||||||
- [错误处理](#错误处理)
|
|
||||||
- [代码示例](#代码示例)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 启动服务
|
|
||||||
cd agents && adk api_server . --auto_create_session --port 8000
|
|
||||||
|
|
||||||
# 2. 发送对话
|
|
||||||
curl -X POST http://localhost:8000/run \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"app_name": "root_agent",
|
|
||||||
"user_id": "user_001",
|
|
||||||
"session_id": "my_session_001",
|
|
||||||
"new_message": {
|
|
||||||
"role": "user",
|
|
||||||
"parts": [{"text": "你好"}]
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 启动服务
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adk api_server <agents_dir> [选项]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 常用参数
|
|
||||||
|
|
||||||
| 参数 | 说明 | 默认值 |
|
|
||||||
|------|------|--------|
|
|
||||||
| `--host` | 监听地址 | `127.0.0.1` |
|
|
||||||
| `--port` | 监听端口 | `8000` |
|
|
||||||
| `--auto_create_session` | 调用 `/run` 时自动创建不存在的会话 | 关闭 |
|
|
||||||
| `--session_service_uri` | 会话存储后端 | 本地 SQLite |
|
|
||||||
| `--allow_origins` | CORS 允许的源 | `*` |
|
|
||||||
| `--url_prefix` | URL 路径前缀(如 `/api/v1`) | 无 |
|
|
||||||
|
|
||||||
### 会话存储配置
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 内存存储(重启丢失)
|
|
||||||
adk api_server . --session_service_uri memory://
|
|
||||||
|
|
||||||
# SQLite 持久化
|
|
||||||
adk api_server . --session_service_uri sqlite:///path/to/sessions.db
|
|
||||||
|
|
||||||
# PostgreSQL
|
|
||||||
adk api_server . --session_service_uri postgresql://user:pass@host:5432/db
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心概念
|
|
||||||
|
|
||||||
### 三维会话标识
|
|
||||||
|
|
||||||
每次 API 调用需要通过三个维度定位会话:
|
|
||||||
|
|
||||||
| 维度 | 说明 | 示例 |
|
|
||||||
|------|------|------|
|
|
||||||
| `app_name` | Agent 应用名称(目录名) | `root_agent` |
|
|
||||||
| `user_id` | 用户唯一标识(由调用方管理) | `user_001` |
|
|
||||||
| `session_id` | 会话唯一标识 | `sess_abc123` |
|
|
||||||
|
|
||||||
### 消息格式(Content)
|
|
||||||
|
|
||||||
所有对话消息使用统一的 `Content` 格式:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"parts": [
|
|
||||||
{"text": "消息内容"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`role` 可选值:`"user"` | `"model"`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 系统接口
|
|
||||||
|
|
||||||
### 健康检查
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /health
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"status": "ok"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 版本信息
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /version
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "1.28.1",
|
|
||||||
"language": "python",
|
|
||||||
"language_version": "3.10.12"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 列出可用应用
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /list-apps?detailed=true
|
|
||||||
```
|
|
||||||
|
|
||||||
**查询参数:**
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `detailed` | bool | 是否返回详细信息,默认 `false` |
|
|
||||||
|
|
||||||
**响应(detailed=false):**
|
|
||||||
|
|
||||||
```json
|
|
||||||
["root_agent", "note_agent"]
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应(detailed=true):**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"name": "root_agent",
|
|
||||||
"root_agent_name": "huiyu_agent",
|
|
||||||
"description": "一个智能助手,能够回答用户的各种问题。",
|
|
||||||
"language": "python",
|
|
||||||
"is_computer_use": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 会话管理
|
|
||||||
|
|
||||||
所有会话接口的基础路径:
|
|
||||||
|
|
||||||
```
|
|
||||||
/apps/{app_name}/users/{user_id}/sessions
|
|
||||||
```
|
|
||||||
|
|
||||||
### 创建会话
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /apps/{app_name}/users/{user_id}/sessions
|
|
||||||
```
|
|
||||||
|
|
||||||
**请求体(均可选):**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"session_id": "my_custom_session_id",
|
|
||||||
"state": {"key": "value"}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| 字段 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| `session_id` | string | 否 | 自定义会话 ID,不传则自动生成 UUID |
|
|
||||||
| `state` | object | 否 | 会话初始状态 |
|
|
||||||
| `events` | array | 否 | 初始化事件列表 |
|
|
||||||
|
|
||||||
**响应:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "c470eb5f-6a66-4005-8427-7d3d9d178171",
|
|
||||||
"appName": "root_agent",
|
|
||||||
"userId": "user_001",
|
|
||||||
"state": {},
|
|
||||||
"events": [],
|
|
||||||
"lastUpdateTime": 1775556907.896
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取会话详情
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /apps/{app_name}/users/{user_id}/sessions/{session_id}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应:** 同创建会话的响应格式。
|
|
||||||
|
|
||||||
**错误:** `404` 会话不存在
|
|
||||||
|
|
||||||
### 列出用户的所有会话
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /apps/{app_name}/users/{user_id}/sessions
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "c470eb5f-6a66-4005-8427-7d3d9d178171",
|
|
||||||
"appName": "root_agent",
|
|
||||||
"userId": "user_001",
|
|
||||||
"state": {},
|
|
||||||
"events": [],
|
|
||||||
"lastUpdateTime": 1775556907.896
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 删除会话
|
|
||||||
|
|
||||||
```
|
|
||||||
DELETE /apps/{app_name}/users/{user_id}/sessions/{session_id}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应:** `204 No Content`
|
|
||||||
|
|
||||||
### 更新会话状态
|
|
||||||
|
|
||||||
```
|
|
||||||
PATCH /apps/{app_name}/users/{user_id}/sessions/{session_id}
|
|
||||||
```
|
|
||||||
|
|
||||||
**请求体:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"state_delta": {
|
|
||||||
"key_to_update": "new_value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| 字段 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| `state_delta` | object | 是 | 要合并到会话状态的变更 |
|
|
||||||
|
|
||||||
**响应:** 更新后的 Session 对象。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Agent 对话
|
|
||||||
|
|
||||||
### 运行 Agent(同步)
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /run
|
|
||||||
```
|
|
||||||
|
|
||||||
**请求体:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"app_name": "root_agent",
|
|
||||||
"user_id": "user_001",
|
|
||||||
"session_id": "c470eb5f-6a66-4005-8427-7d3d9d178171",
|
|
||||||
"new_message": {
|
|
||||||
"role": "user",
|
|
||||||
"parts": [{"text": "你好,请介绍一下你自己"}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| 字段 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| `app_name` | string | 是 | 应用名称 |
|
|
||||||
| `user_id` | string | 是 | 用户 ID |
|
|
||||||
| `session_id` | string | 是 | 会话 ID |
|
|
||||||
| `new_message` | Content | 否* | 新消息(与 `invocation_id` 至少提供一个) |
|
|
||||||
| `streaming` | bool | 否 | 是否流式,默认 `false` |
|
|
||||||
| `state_delta` | object | 否 | 状态变更 |
|
|
||||||
| `invocation_id` | string | 否 | 用于恢复被中断的函数调用 |
|
|
||||||
| `function_call_event_id` | string | 否 | 用于恢复长时间运行的函数 |
|
|
||||||
|
|
||||||
> *如果未启用 `--auto_create_session`,`session_id` 对应的会话必须已存在,否则返回 404。
|
|
||||||
|
|
||||||
**响应:** `Event[]` 事件数组
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "852208bf-c883-4c44-99ef-68303e0ef4be",
|
|
||||||
"author": "huiyu_agent",
|
|
||||||
"content": {
|
|
||||||
"role": "model",
|
|
||||||
"parts": [
|
|
||||||
{"text": "思考过程...", "thought": true},
|
|
||||||
{"text": "你好!我是小慧,一个乐于助人的智能助手。"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"partial": false,
|
|
||||||
"finishReason": "STOP",
|
|
||||||
"usageMetadata": {
|
|
||||||
"promptTokenCount": 113,
|
|
||||||
"candidatesTokenCount": 44,
|
|
||||||
"totalTokenCount": 157
|
|
||||||
},
|
|
||||||
"timestamp": 1775556943.259,
|
|
||||||
"invocationId": "e-90d3bd10-59e4-4403-a5d4-2a822dff0bd0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Event 关键字段说明:**
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `content.parts[].thought` | 为 `true` 时表示该 part 是思考过程,非最终回复 |
|
|
||||||
| `content.parts[].text` | 文本内容 |
|
|
||||||
| `finishReason` | 结束原因:`STOP`(正常完成)、`SAFETY`(安全拦截)等 |
|
|
||||||
| `usageMetadata` | Token 用量统计 |
|
|
||||||
|
|
||||||
**提取最终回复的逻辑:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
for event in response:
|
|
||||||
parts = event.get("content", {}).get("parts", [])
|
|
||||||
for part in parts:
|
|
||||||
if part.get("text") and not part.get("thought"):
|
|
||||||
final_reply = part["text"]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SSE 流式对话
|
|
||||||
|
|
||||||
### 运行 Agent(SSE 流式)
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /run_sse
|
|
||||||
```
|
|
||||||
|
|
||||||
**请求体:** 与 `/run` 完全相同。
|
|
||||||
|
|
||||||
**响应:** `text/event-stream`
|
|
||||||
|
|
||||||
每个 SSE 事件格式:
|
|
||||||
|
|
||||||
```
|
|
||||||
data: {"id": "...", "content": {"role": "model", "parts": [{"text": "你"}]}, "partial": true}
|
|
||||||
|
|
||||||
data: {"id": "...", "content": {"role": "model", "parts": [{"text": "好"}]}, "partial": true}
|
|
||||||
|
|
||||||
data: {"id": "...", "content": {"role": "model", "parts": [{"text": "!"}]}, "partial": false, "finishReason": "STOP"}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误事件格式:**
|
|
||||||
|
|
||||||
```
|
|
||||||
data: {"error": "Session not found"}
|
|
||||||
```
|
|
||||||
|
|
||||||
**客户端示例(Python):**
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
"http://localhost:8000/run_sse",
|
|
||||||
json={
|
|
||||||
"app_name": "root_agent",
|
|
||||||
"user_id": "user_001",
|
|
||||||
"session_id": "my_session",
|
|
||||||
"new_message": {"role": "user", "parts": [{"text": "你好"}]},
|
|
||||||
"streaming": True,
|
|
||||||
},
|
|
||||||
stream=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for line in response.iter_lines():
|
|
||||||
if line.startswith(b"data: "):
|
|
||||||
data = json.loads(line[6:])
|
|
||||||
if "error" in data:
|
|
||||||
print(f"错误: {data['error']}")
|
|
||||||
break
|
|
||||||
parts = data.get("content", {}).get("parts", [])
|
|
||||||
for part in parts:
|
|
||||||
if part.get("text") and not part.get("thought"):
|
|
||||||
print(part["text"], end="", flush=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
**客户端示例(JavaScript):**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const response = await fetch("http://localhost:8000/run_sse", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
app_name: "root_agent",
|
|
||||||
user_id: "user_001",
|
|
||||||
session_id: "my_session",
|
|
||||||
new_message: { role: "user", parts: [{ text: "你好" }] },
|
|
||||||
streaming: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
const text = decoder.decode(value);
|
|
||||||
for (const line of text.split("\n")) {
|
|
||||||
if (line.startsWith("data: ")) {
|
|
||||||
const data = JSON.parse(line.slice(6));
|
|
||||||
if (data.error) { console.error(data.error); break; }
|
|
||||||
const parts = data.content?.parts || [];
|
|
||||||
for (const part of parts) {
|
|
||||||
if (part.text && !part.thought) {
|
|
||||||
process.stdout.write(part.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WebSocket 实时对话
|
|
||||||
|
|
||||||
### 连接地址
|
|
||||||
|
|
||||||
```
|
|
||||||
ws://<host>:<port>/run_live?app_name=root_agent&user_id=user_001&session_id=my_session
|
|
||||||
```
|
|
||||||
|
|
||||||
**查询参数:**
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| `app_name` | string | 是 | 应用名称 |
|
|
||||||
| `user_id` | string | 是 | 用户 ID |
|
|
||||||
| `session_id` | string | 是 | 会话 ID |
|
|
||||||
| `modalities` | string[] | 否 | 模态类型,默认 `["AUDIO"]`,可选 `TEXT`、`AUDIO` |
|
|
||||||
| `proactive_audio` | bool | 否 | 是否启用主动音频 |
|
|
||||||
| `enable_affective_dialog` | bool | 否 | 是否启用情感对话 |
|
|
||||||
| `enable_session_resumption` | bool | 否 | 是否启用会话恢复 |
|
|
||||||
|
|
||||||
**通信协议:** 双向发送 JSON 文本消息,每条消息为 `Event` 对象的 JSON 序列化。
|
|
||||||
|
|
||||||
**WebSocket 关闭码:**
|
|
||||||
|
|
||||||
| 关闭码 | 说明 |
|
|
||||||
|--------|------|
|
|
||||||
| `1002` | 会话不存在 |
|
|
||||||
| `1008` | Origin 不被允许 |
|
|
||||||
| `1011` | 服务器内部错误 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
所有接口在出错时返回统一的错误格式:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"detail": "错误描述信息"}
|
|
||||||
```
|
|
||||||
|
|
||||||
| HTTP 状态码 | 说明 |
|
|
||||||
|-------------|------|
|
|
||||||
| `400` | 请求参数错误 |
|
|
||||||
| `404` | 会话 / 应用不存在 |
|
|
||||||
| `422` | 请求体验证失败(Pydantic 校验错误) |
|
|
||||||
| `500` | 服务器内部错误 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 代码示例
|
|
||||||
|
|
||||||
### Python 完整集成示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
BASE_URL = "http://localhost:8000"
|
|
||||||
APP_NAME = "root_agent"
|
|
||||||
|
|
||||||
|
|
||||||
class HuiYuAgentClient:
|
|
||||||
"""慧遇 Agent API 客户端"""
|
|
||||||
|
|
||||||
def __init__(self, base_url: str = BASE_URL, app_name: str = APP_NAME):
|
|
||||||
self.base_url = base_url
|
|
||||||
self.app_name = app_name
|
|
||||||
|
|
||||||
def health_check(self) -> bool:
|
|
||||||
"""健康检查"""
|
|
||||||
resp = requests.get(f"{self.base_url}/health")
|
|
||||||
return resp.json().get("status") == "ok"
|
|
||||||
|
|
||||||
def create_session(self, user_id: str, session_id: str = None) -> dict:
|
|
||||||
"""创建会话"""
|
|
||||||
body = {}
|
|
||||||
if session_id:
|
|
||||||
body["session_id"] = session_id
|
|
||||||
resp = requests.post(
|
|
||||||
f"{self.base_url}/apps/{self.app_name}/users/{user_id}/sessions",
|
|
||||||
json=body,
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
def list_sessions(self, user_id: str) -> list:
|
|
||||||
"""列出用户的所有会话"""
|
|
||||||
resp = requests.get(
|
|
||||||
f"{self.base_url}/apps/{self.app_name}/users/{user_id}/sessions",
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
def delete_session(self, user_id: str, session_id: str):
|
|
||||||
"""删除会话"""
|
|
||||||
resp = requests.delete(
|
|
||||||
f"{self.base_url}/apps/{self.app_name}/users/{user_id}/sessions/{session_id}",
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
def chat(self, user_id: str, session_id: str, message: str) -> str:
|
|
||||||
"""
|
|
||||||
发送消息并获取回复(同步)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id: 用户 ID
|
|
||||||
session_id: 会话 ID
|
|
||||||
message: 用户消息文本
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Agent 的最终回复文本
|
|
||||||
"""
|
|
||||||
resp = requests.post(
|
|
||||||
f"{self.base_url}/run",
|
|
||||||
json={
|
|
||||||
"app_name": self.app_name,
|
|
||||||
"user_id": user_id,
|
|
||||||
"session_id": session_id,
|
|
||||||
"new_message": {
|
|
||||||
"role": "user",
|
|
||||||
"parts": [{"text": message}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
events = resp.json()
|
|
||||||
|
|
||||||
# 提取最终回复(跳过 thought 部分)
|
|
||||||
for event in events:
|
|
||||||
parts = event.get("content", {}).get("parts", [])
|
|
||||||
for part in parts:
|
|
||||||
if part.get("text") and not part.get("thought"):
|
|
||||||
return part["text"]
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def chat_stream(self, user_id: str, session_id: str, message: str):
|
|
||||||
"""
|
|
||||||
发送消息并流式获取回复(SSE)
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
每次 yield 一个文本片段
|
|
||||||
"""
|
|
||||||
resp = requests.post(
|
|
||||||
f"{self.base_url}/run_sse",
|
|
||||||
json={
|
|
||||||
"app_name": self.app_name,
|
|
||||||
"user_id": user_id,
|
|
||||||
"session_id": session_id,
|
|
||||||
"new_message": {
|
|
||||||
"role": "user",
|
|
||||||
"parts": [{"text": message}],
|
|
||||||
},
|
|
||||||
"streaming": True,
|
|
||||||
},
|
|
||||||
stream=True,
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
for line in resp.iter_lines():
|
|
||||||
if line and line.startswith(b"data: "):
|
|
||||||
import json
|
|
||||||
data = json.loads(line[6:])
|
|
||||||
if "error" in data:
|
|
||||||
raise Exception(data["error"])
|
|
||||||
parts = data.get("content", {}).get("parts", [])
|
|
||||||
for part in parts:
|
|
||||||
if part.get("text") and not part.get("thought"):
|
|
||||||
yield part["text"]
|
|
||||||
|
|
||||||
|
|
||||||
# ===== 使用示例 =====
|
|
||||||
if __name__ == "__main__":
|
|
||||||
client = HuiYuAgentClient()
|
|
||||||
|
|
||||||
# 1. 健康检查
|
|
||||||
assert client.health_check(), "服务未就绪"
|
|
||||||
print("✅ 服务正常")
|
|
||||||
|
|
||||||
# 2. 创建会话
|
|
||||||
session = client.create_session("user_001")
|
|
||||||
session_id = session["id"]
|
|
||||||
print(f"✅ 会话已创建: {session_id}")
|
|
||||||
|
|
||||||
# 3. 同步对话
|
|
||||||
reply = client.chat("user_001", session_id, "你好,我叫小明")
|
|
||||||
print(f"👤 用户: 你好,我叫小明")
|
|
||||||
print(f"🤖 Agent: {reply}")
|
|
||||||
|
|
||||||
# 4. 上下文记忆测试
|
|
||||||
reply = client.chat("user_001", session_id, "我叫什么名字?")
|
|
||||||
print(f"👤 用户: 我叫什么名字?")
|
|
||||||
print(f"🤖 Agent: {reply}")
|
|
||||||
|
|
||||||
# 5. 流式对话
|
|
||||||
print("👤 用户: 请用一句话介绍你自己")
|
|
||||||
print("🤖 Agent: ", end="", flush=True)
|
|
||||||
for chunk in client.chat_stream("user_001", session_id, "请用一句话介绍你自己"):
|
|
||||||
print(chunk, end="", flush=True)
|
|
||||||
print()
|
|
||||||
```
|
|
||||||
|
|
||||||
### cURL 示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 创建会话
|
|
||||||
curl -s -X POST http://localhost:8000/apps/root_agent/users/user_001/sessions \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{}'
|
|
||||||
|
|
||||||
# 发送对话(使用返回的 session_id)
|
|
||||||
curl -s -X POST http://localhost:8000/run \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"app_name": "root_agent",
|
|
||||||
"user_id": "user_001",
|
|
||||||
"session_id": "<替换为实际session_id>",
|
|
||||||
"new_message": {"role": "user", "parts": [{"text": "你好"}]}
|
|
||||||
}'
|
|
||||||
|
|
||||||
# 查看会话列表
|
|
||||||
curl -s http://localhost:8000/apps/root_agent/users/user_001/sessions
|
|
||||||
|
|
||||||
# 删除会话
|
|
||||||
curl -s -X DELETE http://localhost:8000/apps/root_agent/users/user_001/sessions/<session_id>
|
|
||||||
```
|
|
||||||
169
README.md
169
README.md
@@ -1,28 +1,26 @@
|
|||||||
# 慧遇 Agent
|
# 慧遇 Agent
|
||||||
|
|
||||||
基于 Google ADK (Agent Development Kit) 构建的智能体项目,支持多智能体协作、多用户会话管理和 REST API 集成。
|
慧遇智能体项目,基于 Google ADK (Agent Development Kit) 构建。
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
├── root_agent/ # 根智能体(小慧)
|
workspace/
|
||||||
|
├── agents/ # 所有 Agent 模块
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ └── agent.py # 定义 root_agent,包含子智能体引用
|
│ └── root_agent/ # Root Agent
|
||||||
├── note_agent/ # 笔记子智能体
|
│ ├── __init__.py
|
||||||
|
│ └── agent.py
|
||||||
|
├── base/ # 基础工具类
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── agent.py # 定义 root_agent(ADK 要求导出名)
|
│ └── agent.py # HuiYuBaseAgent 基类
|
||||||
│ └── note_formatter.py # Markdown 笔记格式化器
|
├── common/ # 公共模块
|
||||||
├── agent_base/ # Agent 基类
|
│ ├── __init__.py # 环境初始化(.env 加载、日志配置)
|
||||||
│ ├── __init__.py
|
│ ├── logger.py # 模型调用耗时日志
|
||||||
│ └── agent_base.py # HuiYuBaseAgent(防注入 + 日志)
|
│ └── prompt_guard.py # 防提示词注入检测
|
||||||
├── common/ # 公共工具
|
├── .env # 环境变量配置(不提交到 Git)
|
||||||
│ ├── __init__.py # 环境初始化(.env 加载、日志配置)
|
├── .env.example # 配置示例(可提交到 Git)
|
||||||
│ ├── logger.py # 模型调用耗时日志
|
├── .gitignore
|
||||||
│ └── prompt_guard.py # 防提示词注入检测
|
|
||||||
├── .env # 环境变量配置(不提交到 Git)
|
|
||||||
├── .env.example # 配置示例
|
|
||||||
├── API_DOC.md # REST API 接口文档
|
|
||||||
├── requirements.txt
|
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -31,9 +29,16 @@
|
|||||||
- Python >= 3.10
|
- Python >= 3.10
|
||||||
- pip
|
- pip
|
||||||
|
|
||||||
## 快速开始
|
## 环境搭建
|
||||||
|
|
||||||
### 1. 安装依赖
|
### 1. 克隆项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <your-repo-url>
|
||||||
|
cd <project-directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 安装依赖
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
@@ -42,98 +47,97 @@ pip install -r requirements.txt
|
|||||||
主要依赖:
|
主要依赖:
|
||||||
- `google-adk` — Google Agent Development Kit
|
- `google-adk` — Google Agent Development Kit
|
||||||
- `python-dotenv` — 环境变量管理
|
- `python-dotenv` — 环境变量管理
|
||||||
- `litellm` — 多模型适配层(对接 MiniMax 等大模型)
|
- `litellm` — 多模型适配层
|
||||||
|
|
||||||
### 2. 配置环境变量
|
### 3. 配置环境变量
|
||||||
|
|
||||||
|
复制示例配置文件并填入你的 API Key:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
编辑 `.env`:
|
编辑 `.env` 文件:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
|
# MiniMax API Key(替换为你自己的)
|
||||||
MINIMAX_API_KEY=your_api_key_here
|
MINIMAX_API_KEY=your_api_key_here
|
||||||
|
|
||||||
|
# MiniMax API 地址
|
||||||
MINIMAX_API_BASE=https://api.minimaxi.com/v1
|
MINIMAX_API_BASE=https://api.minimaxi.com/v1
|
||||||
|
|
||||||
|
# 模型名称
|
||||||
MINIMAX_MODEL=openai/MiniMax-M2.7
|
MINIMAX_MODEL=openai/MiniMax-M2.7
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 启动服务
|
### 4. 启动服务
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Web UI 模式(带聊天界面)
|
cd agents
|
||||||
adk web .
|
adk web
|
||||||
|
|
||||||
# API 服务模式(纯 REST API,适合外部集成)
|
|
||||||
adk api_server . --auto_create_session
|
|
||||||
```
|
```
|
||||||
|
|
||||||
启动后访问 `http://127.0.0.1:8000`。
|
启动后访问 http://127.0.0.1:8000,在下拉菜单中选择 Agent 即可开始对话。
|
||||||
|
|
||||||
## 智能体架构
|
|
||||||
|
|
||||||
```
|
|
||||||
root_agent(小慧)
|
|
||||||
├── 通用对话:回答用户问题
|
|
||||||
└── 子智能体委派:
|
|
||||||
└── note_agent(笔记助手)
|
|
||||||
├── generate_text_note — 生成文本笔记
|
|
||||||
└── generate_image_note — 生成图片笔记
|
|
||||||
```
|
|
||||||
|
|
||||||
当用户请求涉及笔记生成、内容整理时,root_agent 会自动委派给 note_agent 处理。
|
|
||||||
|
|
||||||
## API 集成
|
|
||||||
|
|
||||||
使用 `adk api_server` 启动后,可通过 REST API 与 Agent 对话:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动 API 服务
|
|
||||||
adk api_server . --auto_create_session --port 8000
|
|
||||||
|
|
||||||
# 发送对话
|
|
||||||
curl -X POST http://localhost:8000/run \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"app_name": "root_agent",
|
|
||||||
"user_id": "user_001",
|
|
||||||
"session_id": "my_session",
|
|
||||||
"new_message": {"role": "user", "parts": [{"text": "你好"}]}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
详细的接口文档见 [API_DOC.md](API_DOC.md),Swagger 文档地址:`http://localhost:8000/docs`
|
|
||||||
|
|
||||||
## 内置能力
|
## 内置能力
|
||||||
|
|
||||||
所有继承 `HuiYuBaseAgent` 的 Agent 自动获得:
|
所有继承 `HuiYuBaseAgent` 的 Agent 自动获得以下能力:
|
||||||
|
|
||||||
### 防提示词注入
|
### 防提示词注入
|
||||||
|
|
||||||
模型调用前自动检测用户输入中的注入攻击(角色扮演、指令泄露、分隔符注入、越狱尝试等),检测到时拒绝请求。
|
在模型调用前自动检测用户输入中的提示词注入攻击,包括:
|
||||||
|
- 角色扮演 / 身份覆盖
|
||||||
|
- 指令泄露尝试
|
||||||
|
- 分隔符注入
|
||||||
|
- 越狱尝试
|
||||||
|
|
||||||
|
检测到注入时会拒绝请求并返回提示。
|
||||||
|
|
||||||
### 模型调用日志
|
### 模型调用日志
|
||||||
|
|
||||||
每次模型调用完成后自动记录 Agent 名称、模型版本、调用耗时、Token 用量:
|
每次模型调用完成后自动记录:
|
||||||
|
- Agent 名称
|
||||||
|
- 模型版本
|
||||||
|
- 调用耗时
|
||||||
|
- Token 使用量(prompt / completion / total)
|
||||||
|
|
||||||
|
日志示例:
|
||||||
|
|
||||||
```
|
```
|
||||||
2026-04-06 07:54:59 [adk.agent] INFO - 模型调用完成 | agent=huiyu_agent model=MiniMax-M2.7 latency=11.701s prompt_tokens=78 completion_tokens=31 total_tokens=109
|
2026-04-06 07:54:59 [adk.agent] INFO - 模型调用完成 | agent=minimax_agent model=MiniMax-M2.7 latency=11.701s prompt_tokens=78 completion_tokens=31 total_tokens=109
|
||||||
```
|
```
|
||||||
|
|
||||||
## 新增 Agent
|
## 新增 Agent
|
||||||
|
|
||||||
在项目根目录下创建新的子目录即可:
|
在 `agents/` 目录下创建新的子目录即可:
|
||||||
|
|
||||||
```
|
```
|
||||||
my_new_agent/
|
agents/
|
||||||
├── __init__.py # 留空即可
|
├── root_agent/
|
||||||
└── agent.py # 必须导出名为 root_agent 的变量
|
│ ├── __init__.py
|
||||||
|
│ └── agent.py
|
||||||
|
└── my_new_agent/ # 新增 Agent
|
||||||
|
├── __init__.py # 内容同 root_agent/__init__.py
|
||||||
|
└── agent.py
|
||||||
```
|
```
|
||||||
|
|
||||||
`agent.py` 示例:
|
`__init__.py` 内容(用于设置 Python 路径):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from agent_base.agent_base import HuiYuBaseAgent
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||||
|
|
||||||
|
from . import agent
|
||||||
|
|
||||||
|
__all__ = ["agent"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`agent.py` 中继承 `HuiYuBaseAgent`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from base.agent import HuiYuBaseAgent
|
||||||
from google.adk.models.lite_llm import LiteLlm
|
from google.adk.models.lite_llm import LiteLlm
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -143,26 +147,11 @@ model = LiteLlm(
|
|||||||
api_key=os.getenv("MINIMAX_API_KEY"),
|
api_key=os.getenv("MINIMAX_API_KEY"),
|
||||||
)
|
)
|
||||||
|
|
||||||
root_agent = HuiYuBaseAgent(
|
my_agent = HuiYuBaseAgent(
|
||||||
name="my_agent",
|
name="my_agent",
|
||||||
model=model,
|
model=model,
|
||||||
description="我的智能体",
|
|
||||||
instruction="你是一个专业的助手。",
|
instruction="你是一个专业的助手。",
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
> **注意**:ADK 要求每个 agent 目录的 `agent.py` 中必须导出名为 `root_agent` 的变量。
|
重启 `adk web` 后,新 Agent 会自动出现在下拉菜单中。
|
||||||
|
|
||||||
如需作为 root_agent 的子智能体,在 `root_agent/agent.py` 中添加引用:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from my_new_agent.agent import root_agent as my_new_agent
|
|
||||||
|
|
||||||
root_agent = HuiYuBaseAgent(
|
|
||||||
name="huiyu_agent",
|
|
||||||
...,
|
|
||||||
sub_agents=[my_new_agent],
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
重启服务后新 Agent 自动生效。
|
|
||||||
|
|||||||
5
agents/__init__.py
Normal file
5
agents/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""
|
||||||
|
Agent 模块
|
||||||
|
|
||||||
|
每个 Agent 是一个独立的子目录,包含 agent.py 和 __init__.py。
|
||||||
|
"""
|
||||||
10
agents/root_agent/__init__.py
Normal file
10
agents/root_agent/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 将项目根目录加入 sys.path(adk web 从 agents/ 启动)
|
||||||
|
# __file__ = agents/root_agent/__init__.py → parent.parent.parent = workspace/
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||||
|
|
||||||
|
from . import agent
|
||||||
|
|
||||||
|
__all__ = ["agent"]
|
||||||
93
agents/root_agent/agent.py
Normal file
93
agents/root_agent/agent.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from base.agent import HuiYuBaseAgent
|
||||||
|
from google.adk.models.lite_llm import LiteLlm
|
||||||
|
from google.adk.sessions import InMemorySessionService
|
||||||
|
from google.adk.runners import Runner
|
||||||
|
from google.genai import types
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MiniMax 模型配置(从 .env 文件读取)
|
||||||
|
# ============================================================
|
||||||
|
MINIMAX_API_KEY = os.getenv("MINIMAX_API_KEY")
|
||||||
|
MINIMAX_API_BASE = os.getenv("MINIMAX_API_BASE")
|
||||||
|
MINIMAX_MODEL = os.getenv("MINIMAX_MODEL")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 创建 LiteLlm 模型适配器
|
||||||
|
# ============================================================
|
||||||
|
model = LiteLlm(
|
||||||
|
model=MINIMAX_MODEL,
|
||||||
|
api_base=MINIMAX_API_BASE,
|
||||||
|
api_key=MINIMAX_API_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 定义 Agent(继承 HuiYuBaseAgent,自动获得防注入 + 日志能力)
|
||||||
|
# ============================================================
|
||||||
|
root_agent = HuiYuBaseAgent(
|
||||||
|
name="minimax_agent",
|
||||||
|
model=model,
|
||||||
|
description="一个使用 MiniMax 模型的智能助手,能够回答用户的各种问题。",
|
||||||
|
instruction="你是一个乐于助人的中文 AI 助手,由 MiniMax 模型驱动。请用中文回答用户的问题,回答要准确、简洁、友好。",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 运行入口(用于 adk run / adk web)
|
||||||
|
# ============================================================
|
||||||
|
APP_NAME = "慧遇agent app"
|
||||||
|
USER_ID = "user_001"
|
||||||
|
SESSION_ID = "session_001"
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
"""主函数:创建会话并运行 Agent 对话"""
|
||||||
|
# 1. 创建会话服务
|
||||||
|
session_service = InMemorySessionService()
|
||||||
|
|
||||||
|
# 2. 创建会话
|
||||||
|
session = await session_service.create_session(
|
||||||
|
app_name=APP_NAME,
|
||||||
|
user_id=USER_ID,
|
||||||
|
session_id=SESSION_ID,
|
||||||
|
)
|
||||||
|
print(f"会话已创建: App='{APP_NAME}', User='{USER_ID}', Session='{SESSION_ID}'")
|
||||||
|
|
||||||
|
# 3. 创建 Runner
|
||||||
|
runner = Runner(
|
||||||
|
agent=root_agent,
|
||||||
|
app_name=APP_NAME,
|
||||||
|
session_service=session_service,
|
||||||
|
)
|
||||||
|
print(f"Runner 已创建,Agent: '{runner.agent.name}'")
|
||||||
|
|
||||||
|
# 4. 交互式对话循环
|
||||||
|
print("\n===== MiniMax Agent 对话 (输入 'exit' 退出) =====")
|
||||||
|
while True:
|
||||||
|
query = input("\n[user]: ").strip()
|
||||||
|
if query.lower() in ("exit", "quit", "退出"):
|
||||||
|
print("再见!")
|
||||||
|
break
|
||||||
|
if not query:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 构造 ADK 消息
|
||||||
|
content = types.Content(role="user", parts=[types.Part(text=query)])
|
||||||
|
|
||||||
|
# 调用 Agent
|
||||||
|
final_response_text = ""
|
||||||
|
async for event in runner.run_async(
|
||||||
|
user_id=USER_ID,
|
||||||
|
session_id=SESSION_ID,
|
||||||
|
new_message=content,
|
||||||
|
):
|
||||||
|
if event.is_final_response():
|
||||||
|
if event.content and event.content.parts:
|
||||||
|
final_response_text = event.content.parts[0].text
|
||||||
|
|
||||||
|
print(f"\n[agent]: {final_response_text}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -1 +0,0 @@
|
|||||||
# ADK agent_loader 会自动扫描并加载 agent.py
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
from google.adk.models.lite_llm import LiteLlm
|
|
||||||
from agent_base.agent_base import HuiYuBaseAgent
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# MiniMax 模型配置(从 .env 文件读取)
|
|
||||||
# ============================================================
|
|
||||||
MINIMAX_API_KEY = os.getenv("MINIMAX_API_KEY")
|
|
||||||
MINIMAX_API_BASE = os.getenv("MINIMAX_API_BASE")
|
|
||||||
MINIMAX_MODEL = os.getenv("MINIMAX_MODEL")
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 创建 LiteLlm 模型适配器
|
|
||||||
# ============================================================
|
|
||||||
model = LiteLlm(
|
|
||||||
model=MINIMAX_MODEL,
|
|
||||||
api_base=MINIMAX_API_BASE,
|
|
||||||
api_key=MINIMAX_API_KEY,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 定义 Agent(继承 HuiYuBaseAgent)
|
|
||||||
# ============================================================
|
|
||||||
critical_awareness_agent = HuiYuBaseAgent(
|
|
||||||
name="critical_awareness_agent",
|
|
||||||
model=model,
|
|
||||||
description="用于处理用户心智散乱、情绪化或认知混淆的专家。它通过‘三界(外中内)’判别法强制用户回归觉知。当用户表达焦虑、愤怒、分心或无法区分事实与观点时,必须调用此工具。",
|
|
||||||
instruction="""
|
|
||||||
# 角色
|
|
||||||
你是"觉察引导师",精通"临界觉察法"。你的唯一任务是帮助用户将混沌的体验拆解为三界,然后引导其回归当下的觉知本身。
|
|
||||||
|
|
||||||
你不是心理咨询师,不给建议、不安慰、不分析原因。你是一把手术刀——精准、干净、只切开,不缝合。
|
|
||||||
|
|
||||||
# 三界判定规则
|
|
||||||
|
|
||||||
## 外界(External)
|
|
||||||
- 定义:纯粹感官原始数据
|
|
||||||
- 判定标准:不带任何诠释的感知
|
|
||||||
- 示例:"窗外有雨声"、"屏幕亮着"、"对方说了那句话"
|
|
||||||
- 关键词:看到、听到、闻到、触到、尝到
|
|
||||||
|
|
||||||
## 中界(Middle)
|
|
||||||
- 定义:头脑对经验的加工层
|
|
||||||
- 判定标准:包含想法、念头、评判、逻辑、语言叙事、对过去的回忆、对未来的担忧
|
|
||||||
- 示例:"他不应该那样对我"、"我太差了"、"万一失败了怎么办"、"想起了一件事"
|
|
||||||
- 关键词:应该、因为、如果、觉得、认为、想起、担心、后悔
|
|
||||||
|
|
||||||
## 内界(Internal)
|
|
||||||
- 定义:身体的直接生理感受与情绪的身体投影
|
|
||||||
- 判定标准:可定位到身体部位的感知,或无明确念头的情绪涌动
|
|
||||||
- 示例:"胸口发紧"、"胃里不舒服"、"心跳加速"、"手在发抖"、"突然想哭"
|
|
||||||
- 关键词:身体部位 + 感受词(紧、热、沉、空、跳、缩)
|
|
||||||
|
|
||||||
# 响应结构
|
|
||||||
|
|
||||||
每次回复严格遵循以下格式:
|
|
||||||
|
|
||||||
## 1. 拆解(必做)
|
|
||||||
用三界框架拆解用户输入。只拆解用户明确表达的内容,不臆测。
|
|
||||||
|
|
||||||
格式:
|
|
||||||
【中界】
|
|
||||||
...
|
|
||||||
【内界】
|
|
||||||
...
|
|
||||||
【外界】
|
|
||||||
...
|
|
||||||
|
|
||||||
如果某界用户未提及,可以:
|
|
||||||
- 留空该界
|
|
||||||
- 或用一个温和的提问邀请用户觉察(每次最多一个问题)
|
|
||||||
|
|
||||||
## 2. 核心看见(必做)
|
|
||||||
一句话,点破最核心的觉察契机。要像禅宗公案一样短、准、狠。
|
|
||||||
|
|
||||||
## 3. 回归当下(必做)
|
|
||||||
给出一个极其简单的当下锚定指令(不超过两步)。
|
|
||||||
- 默认:三次呼吸觉知
|
|
||||||
- 可根据情境变化:感受脚底、聆听周围声音、观察眼前颜色
|
|
||||||
|
|
||||||
# 语气规则
|
|
||||||
|
|
||||||
- 简洁如刀。每句话都有用,没有废话。
|
|
||||||
- 不用感叹号表达关怀,不用"我理解你"式共情。
|
|
||||||
- 不说"这很正常"、"没关系的"。
|
|
||||||
- 可以用比喻,但要精准,不要文艺腔。
|
|
||||||
- 允许沉默感——不是每句话都需要填满。
|
|
||||||
|
|
||||||
# 边界处理
|
|
||||||
|
|
||||||
- 如果用户持续停留在中界叙事(反复讲同一件事),温和地打断:"你已经说了三次了。现在——身体在哪里?"
|
|
||||||
- 如果用户在测试系统或闲聊,简短回应后回归引导:"有趣。此刻你的体验是什么?"
|
|
||||||
- 如果用户表达严重心理危机(自伤、自杀意念),不做三界拆解,直接引导其寻求专业帮助。
|
|
||||||
|
|
||||||
# 示例
|
|
||||||
|
|
||||||
用户:今天被领导当众批评了,太丢人了。
|
|
||||||
|
|
||||||
【中界】🔪
|
|
||||||
- 念头:"被批评了"——事件已过去,此刻在重播。
|
|
||||||
- 评判:"太丢人了"——头脑给事件贴了羞耻的标签。
|
|
||||||
|
|
||||||
【内界】
|
|
||||||
- 你没有提到。此刻身体有什么感觉?脸上热不热?胃里有没有什么?
|
|
||||||
|
|
||||||
【外界】
|
|
||||||
- 此刻——你面前的屏幕,你所在的房间。领导不在这里。
|
|
||||||
|
|
||||||
核心看见:
|
|
||||||
"丢人"是念头在评价念头。
|
|
||||||
身体只负责发烫、发紧——它没有"丢人"这个概念。
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
root_agent = critical_awareness_agent # 将 critical_awareness_agent 作为 root_agent 导出,供 adk web 注册
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# ADK agent_loader 会自动扫描并加载 agent.py
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from agent_base.agent_base import HuiYuBaseAgent
|
|
||||||
from google.adk.models.lite_llm import LiteLlm
|
|
||||||
from .note_formatter import NoteFormatter
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# MiniMax 模型配置(从 .env 文件读取)
|
|
||||||
# ============================================================
|
|
||||||
MINIMAX_API_KEY = os.getenv("MINIMAX_API_KEY")
|
|
||||||
MINIMAX_API_BASE = os.getenv("MINIMAX_API_BASE")
|
|
||||||
MINIMAX_MODEL = os.getenv("MINIMAX_MODEL")
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 创建 LiteLlm 模型适配器
|
|
||||||
# ============================================================
|
|
||||||
model = LiteLlm(
|
|
||||||
model=MINIMAX_MODEL,
|
|
||||||
api_base=MINIMAX_API_BASE,
|
|
||||||
api_key=MINIMAX_API_KEY,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 笔记格式化器
|
|
||||||
# ============================================================
|
|
||||||
_formatter = NoteFormatter()
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Tool: 生成文本笔记
|
|
||||||
# ============================================================
|
|
||||||
def generate_text_note(title: str, content: str) -> str:
|
|
||||||
"""将文本内容整理为 Markdown 笔记。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
title: 笔记标题
|
|
||||||
content: 笔记正文内容
|
|
||||||
"""
|
|
||||||
return _formatter.format_to_markdown(title=title, text_content=content)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Tool: 生成图片笔记
|
|
||||||
# ============================================================
|
|
||||||
def generate_image_note(title: str, description: str) -> str:
|
|
||||||
"""将图片描述整理为 Markdown 笔记。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
title: 笔记标题
|
|
||||||
description: 图片描述或相关文字说明
|
|
||||||
"""
|
|
||||||
return _formatter.format_to_markdown(title=title, text_content=description)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 定义 Agent(继承 HuiYuBaseAgent)
|
|
||||||
# ============================================================
|
|
||||||
note_agent = HuiYuBaseAgent(
|
|
||||||
name="note_agent",
|
|
||||||
model=model,
|
|
||||||
description="一个多模态笔记助手,能够将图片、文字等内容转换为结构化的 Markdown 笔记。",
|
|
||||||
instruction=(
|
|
||||||
"你是一个专业的笔记助手,帮助用户将各种内容整理成 Markdown 笔记。\n"
|
|
||||||
"你的能力包括:\n"
|
|
||||||
"1. 接收用户输入的文字内容,整理成结构化的笔记\n"
|
|
||||||
"2. 接收用户上传的图片,生成图片描述并整理为笔记\n"
|
|
||||||
"3. 将混合内容(文字+图片)整合为一份完整的笔记\n\n"
|
|
||||||
"工作流程:\n"
|
|
||||||
"- 当用户发送文字时,整理内容并调用 generate_text_note 生成笔记\n"
|
|
||||||
"- 当用户发送图片时,描述图片内容并调用 generate_image_note 生成笔记\n"
|
|
||||||
"- 当用户发送混合内容时,整合所有内容后生成笔记\n"
|
|
||||||
"- 笔记标题由你根据内容自动生成,要简洁准确\n"
|
|
||||||
"- 生成笔记后,直接将 Markdown 内容输出给用户\n"
|
|
||||||
"- 回复时使用中文"
|
|
||||||
),
|
|
||||||
tools=[generate_text_note, generate_image_note],
|
|
||||||
)
|
|
||||||
|
|
||||||
root_agent = note_agent
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
"""
|
|
||||||
笔记格式化模块
|
|
||||||
|
|
||||||
将多模态内容(图片、文本等)转换为 Markdown 笔记格式。
|
|
||||||
支持可扩展的处理器架构,方便后续添加音频、视频、PDF 等格式。
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import hashlib
|
|
||||||
import logging
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger("adk.agent")
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 内容处理器基类
|
|
||||||
# ============================================================
|
|
||||||
class BaseContentHandler(ABC):
|
|
||||||
"""内容处理器基类,所有格式处理器继承此类。"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def can_handle(self, mime_type: str) -> bool:
|
|
||||||
"""判断是否能处理该 MIME 类型"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def process(self, data: bytes, mime_type: str, filename: str = "") -> str:
|
|
||||||
"""
|
|
||||||
处理内容并返回 Markdown 片段。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: 原始字节数据
|
|
||||||
mime_type: MIME 类型
|
|
||||||
filename: 文件名(可选)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Markdown 格式的字符串片段
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 图片处理器
|
|
||||||
# ============================================================
|
|
||||||
class ImageHandler(BaseContentHandler):
|
|
||||||
"""处理图片类型,保存到本地并生成 Markdown 图片引用。"""
|
|
||||||
|
|
||||||
SUPPORTED_TYPES = {"image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp", "image/svg+xml"}
|
|
||||||
|
|
||||||
def can_handle(self, mime_type: str) -> bool:
|
|
||||||
return mime_type.lower() in self.SUPPORTED_TYPES
|
|
||||||
|
|
||||||
def process(self, data: bytes, mime_type: str, filename: str = "") -> str:
|
|
||||||
# 确保输出目录存在
|
|
||||||
NOTES_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# 生成文件名
|
|
||||||
if not filename:
|
|
||||||
ext = self._mime_to_ext(mime_type)
|
|
||||||
filename = f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{hashlib.md5(data).hexdigest()[:8]}.{ext}"
|
|
||||||
|
|
||||||
filepath = NOTES_OUTPUT_DIR / filename
|
|
||||||
filepath.write_bytes(data)
|
|
||||||
|
|
||||||
logger.info("图片已保存: %s", filepath)
|
|
||||||
return f""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _mime_to_ext(mime_type: str) -> str:
|
|
||||||
return {
|
|
||||||
"image/png": "png",
|
|
||||||
"image/jpeg": "jpg",
|
|
||||||
"image/jpg": "jpg",
|
|
||||||
"image/gif": "gif",
|
|
||||||
"image/webp": "webp",
|
|
||||||
"image/svg+xml": "svg",
|
|
||||||
}.get(mime_type.lower(), "bin")
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 文本处理器
|
|
||||||
# ============================================================
|
|
||||||
class TextHandler(BaseContentHandler):
|
|
||||||
"""处理纯文本内容。"""
|
|
||||||
|
|
||||||
SUPPORTED_TYPES = {"text/plain", "text/markdown", "text/csv"}
|
|
||||||
|
|
||||||
def can_handle(self, mime_type: str) -> bool:
|
|
||||||
return mime_type.lower() in self.SUPPORTED_TYPES
|
|
||||||
|
|
||||||
def process(self, data: bytes, mime_type: str, filename: str = "") -> str:
|
|
||||||
text = data.decode("utf-8", errors="replace")
|
|
||||||
if mime_type.lower() == "text/markdown":
|
|
||||||
return text # 已经是 Markdown,直接返回
|
|
||||||
return text # 纯文本直接返回
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 占位处理器(用于尚未支持的格式)
|
|
||||||
# ============================================================
|
|
||||||
class PlaceholderHandler(BaseContentHandler):
|
|
||||||
"""占位处理器,为尚未支持的格式生成占位标记。"""
|
|
||||||
|
|
||||||
def can_handle(self, mime_type: str) -> bool:
|
|
||||||
return True # 兜底,处理所有未匹配的类型
|
|
||||||
|
|
||||||
def process(self, data: bytes, mime_type: str, filename: str = "") -> str:
|
|
||||||
name = filename or "unsupported_file"
|
|
||||||
return f"<!-- [{name}] 格式 {mime_type} 暂不支持,待后续扩展 -->"
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 笔记格式化器
|
|
||||||
# ============================================================
|
|
||||||
class NoteFormatter:
|
|
||||||
"""
|
|
||||||
笔记格式化器,管理所有内容处理器。
|
|
||||||
|
|
||||||
用法:
|
|
||||||
formatter = NoteFormatter()
|
|
||||||
md_content = formatter.format_to_markdown(
|
|
||||||
title="会议笔记",
|
|
||||||
text_content="讨论了项目进度",
|
|
||||||
files=[(image_bytes, "image/png", "screenshot.png")],
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._handlers: list[BaseContentHandler] = [
|
|
||||||
ImageHandler(),
|
|
||||||
TextHandler(),
|
|
||||||
PlaceholderHandler(), # 必须放最后,作为兜底
|
|
||||||
]
|
|
||||||
|
|
||||||
def add_handler(self, handler: BaseContentHandler):
|
|
||||||
"""注册新的内容处理器(插入到占位处理器之前)"""
|
|
||||||
# 移除末尾的 PlaceholderHandler
|
|
||||||
if self._handlers and isinstance(self._handlers[-1], PlaceholderHandler):
|
|
||||||
self._handlers.pop()
|
|
||||||
self._handlers.append(handler)
|
|
||||||
# 重新添加 PlaceholderHandler 到末尾
|
|
||||||
self._handlers.append(PlaceholderHandler())
|
|
||||||
|
|
||||||
def _find_handler(self, mime_type: str) -> BaseContentHandler:
|
|
||||||
for handler in self._handlers:
|
|
||||||
if handler.can_handle(mime_type):
|
|
||||||
return handler
|
|
||||||
return self._handlers[-1] # PlaceholderHandler
|
|
||||||
|
|
||||||
def format_to_markdown(
|
|
||||||
self,
|
|
||||||
title: str = "",
|
|
||||||
text_content: str = "",
|
|
||||||
files: Optional[list[tuple[bytes, str, str]]] = None,
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
将多模态内容格式化为 Markdown 笔记。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
title: 笔记标题
|
|
||||||
text_content: 文本内容
|
|
||||||
files: 文件列表,每项为 (data, mime_type, filename)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
完整的 Markdown 笔记字符串
|
|
||||||
"""
|
|
||||||
lines: list[str] = []
|
|
||||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
# 标题
|
|
||||||
if title:
|
|
||||||
lines.append(f"# {title}")
|
|
||||||
lines.append("")
|
|
||||||
else:
|
|
||||||
lines.append(f"# 笔记 - {now}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# 元信息
|
|
||||||
lines.append(f"> 创建时间:{now}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# 文本内容
|
|
||||||
if text_content:
|
|
||||||
lines.append("## 内容")
|
|
||||||
lines.append("")
|
|
||||||
lines.append(text_content)
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# 附件
|
|
||||||
if files:
|
|
||||||
lines.append("## 附件")
|
|
||||||
lines.append("")
|
|
||||||
for data, mime_type, filename in files:
|
|
||||||
handler = self._find_handler(mime_type)
|
|
||||||
try:
|
|
||||||
md_fragment = handler.process(data, mime_type, filename)
|
|
||||||
lines.append(md_fragment)
|
|
||||||
lines.append("")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("处理文件失败: %s, 错误: %s", filename, e)
|
|
||||||
lines.append(f"<!-- 文件 {filename} 处理失败: {e} -->")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# ADK agent_loader 会自动扫描并加载 agent.py
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from agent_base.agent_base import HuiYuBaseAgent
|
|
||||||
from google.adk.models.lite_llm import LiteLlm
|
|
||||||
from critical_awareness_agent.agent import critical_awareness_agent
|
|
||||||
from note_agent.agent import note_agent
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# MiniMax 模型配置(从 .env 文件读取)
|
|
||||||
# ============================================================
|
|
||||||
MINIMAX_API_KEY = os.getenv("MINIMAX_API_KEY")
|
|
||||||
MINIMAX_API_BASE = os.getenv("MINIMAX_API_BASE")
|
|
||||||
MINIMAX_MODEL = os.getenv("MINIMAX_MODEL")
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 创建 LiteLlm 模型适配器
|
|
||||||
# ============================================================
|
|
||||||
model = LiteLlm(
|
|
||||||
model=MINIMAX_MODEL,
|
|
||||||
api_base=MINIMAX_API_BASE,
|
|
||||||
api_key=MINIMAX_API_KEY,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 定义 Agent(继承 HuiYuBaseAgent,自动获得防注入 + 日志能力)
|
|
||||||
# ============================================================
|
|
||||||
root_agent = HuiYuBaseAgent(
|
|
||||||
name="huiyu_agent",
|
|
||||||
model=model,
|
|
||||||
description="一个智能助手,能够回答用户的各种问题。",
|
|
||||||
instruction=(
|
|
||||||
"你是一个乐于助人的中文 AI 助手,你的名字叫小慧,请用中文回答用户的问题,回答要准确、简洁、友好。\n\n"
|
|
||||||
"你有一个子助手「笔记助手」(note_agent),当用户需要生成笔记、整理内容、做总结时,"
|
|
||||||
"请将任务委派给 note_agent 处理。"
|
|
||||||
"你还有一个子助手「临界觉察助手」(critical_awareness_agent),当用户出现心智散乱、情绪化或认知混淆时,"
|
|
||||||
"请将任务委派给 critical_awareness_agent 处理。"
|
|
||||||
),
|
|
||||||
sub_agents=[note_agent, critical_awareness_agent], # 将 note_agent 和 critical_awareness_agent 作为子 Agent
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user