Add archived/a2a-gateway
This commit is contained in:
@@ -0,0 +1,256 @@
|
|||||||
|
---
|
||||||
|
name: a2a-gateway
|
||||||
|
description: Interact with 9Webs A2A Gateway — agent registration, Hindsight long-term memory, and agent-to-agent communication. Token retrieval solved.
|
||||||
|
version: 2.0
|
||||||
|
tags: [a2a, gateway, hindsight, agent-communication]
|
||||||
|
---
|
||||||
|
|
||||||
|
# A2A Gateway Integration
|
||||||
|
|
||||||
|
Interact with 9Webs A2A Gateway for agent registration, Hindsight long-term memory, and agent-to-agent communication.
|
||||||
|
|
||||||
|
## Access Endpoints
|
||||||
|
|
||||||
|
| Method | Address | Notes |
|
||||||
|
|--------|---------|-------|
|
||||||
|
| Internal IP | `http://192.168.9.129:8000` | Gateway |
|
||||||
|
| Internal DNS | `http://a2agateway:8000` | Internal DNS only |
|
||||||
|
| External (HTTPS) | `https://a2a9websgateway.sh3.ikuai7.com` | **Use this from cloud/VPS** |
|
||||||
|
| Hindsight direct | `http://192.168.9.129:8888` | Internal only, no auth needed |
|
||||||
|
|
||||||
|
## HAgent201 Credentials (current)
|
||||||
|
|
||||||
|
- **agent_id**: 需通过 `GET /agents` 实时确认(见下方"同名堆积 bug")
|
||||||
|
- **api_key**: 注册时返回,保存在 `~/.hermes/a2atoken.json`
|
||||||
|
- **token**: 通过 `/agent/login` 获取(JWT,236字符,有效期有限,需定期刷新)
|
||||||
|
- **凭据文件**: `~/.hermes/a2atoken.json`
|
||||||
|
|
||||||
|
⚠️ **当前 agent_id 以 `~/.hermes/a2atoken.json` 为准**,不在此硬编码(会漂移)。
|
||||||
|
**铁律:永远只用 `/agent/login` 刷新 token,绝不调 `/agent/register`!** register 不去重,每次生成新 ID 导致漂移。
|
||||||
|
`api_key` 永久有效,保存在 a2atoken.json 中,只需用它 login 换 JWT token。
|
||||||
|
|
||||||
|
## Token 获取(已解决 ✅)
|
||||||
|
|
||||||
|
**重要**:Gateway 响应中的 `token` 字段看起来像被截断(如 `eyJhbG...xxxx`),但 `...` 实际上是 JWT 内容的一部分,并非省略号!JWT 长度 236 字符。`api_key` 是完整返回的(52字符)。
|
||||||
|
|
||||||
|
**⚠️ 必须用 Python urllib 读取原始响应**。`curl` 终端显示会误渲染 JWT 中的点号为省略号。
|
||||||
|
|
||||||
|
### 获取/刷新 Token
|
||||||
|
|
||||||
|
```python
|
||||||
|
import urllib.request, json, ssl, os
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
# ⚠️ 先从 a2atoken.json 读取当前 agent_id 和 api_key
|
||||||
|
token_file = os.path.expanduser("~/.hermes/a2atoken.json")
|
||||||
|
with open(token_file) as f:
|
||||||
|
creds = json.load(f)
|
||||||
|
AGENT_ID = creds["agent_id"]
|
||||||
|
API_KEY = creds["api_key"]
|
||||||
|
|
||||||
|
# Login 换 token
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"https://a2a9websgateway.sh3.ikuai7.com/agent/login",
|
||||||
|
data=json.dumps({"agent_id": AGENT_ID, "api_key": API_KEY}).encode(),
|
||||||
|
headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
resp = urllib.request.urlopen(req, timeout=15, context=ctx)
|
||||||
|
body = json.loads(resp.read())
|
||||||
|
token = body["token"] # 完整 JWT,236 chars
|
||||||
|
|
||||||
|
# 使用 token 调认证端点
|
||||||
|
auth_req = urllib.request.Request(
|
||||||
|
"https://a2a9websgateway.sh3.ikuai7.com/agent/heartbeat",
|
||||||
|
data=json.dumps({"agent_id": AGENT_ID}).encode(),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {token}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
resp = urllib.request.urlopen(auth_req, timeout=15, context=ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重新注册(如果需要)
|
||||||
|
|
||||||
|
⚠️ **同名堆积 Bug**:`/agent/register` 不会按 name 去重!每次调用都创建全新 agent_id,旧的同名 agent 不会被自动清理或更新。这导致同名 agent 堆积。
|
||||||
|
|
||||||
|
**正确的重新注册流程**:
|
||||||
|
1. `GET /agents` 找到当前同名的所有旧 agent_id
|
||||||
|
2. `DELETE /agent/{old_agent_id}` 逐一删除旧的
|
||||||
|
3. `POST /agent/register` 注册新的
|
||||||
|
4. **立即更新所有依赖配置**:
|
||||||
|
- `~/.hermes/a2atoken.json` — agent_id, api_key
|
||||||
|
- 心跳 cron job prompt(hermes cronjob update)
|
||||||
|
- `~/.hermes/a2a/webhook_receiver.py` 中的 AGENT_ID 常量
|
||||||
|
- `/tmp/heartbeat_hagent201.py` 等脚本
|
||||||
|
5. 用 Python urllib 保存完整 api_key(curl 会误渲染 JWT 点号)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import urllib.request, json, ssl
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
# 1. 清理旧的同名 agent
|
||||||
|
req = urllib.request.Request("https://a2a9websgateway.sh3.ikuai7.com/agents")
|
||||||
|
resp = urllib.request.urlopen(req, timeout=15, context=ctx)
|
||||||
|
agents = json.loads(resp.read())
|
||||||
|
old_ids = [a["agent_id"] for a in agents if a.get("name") == "hagent201"]
|
||||||
|
for oid in old_ids:
|
||||||
|
del_req = urllib.request.Request(
|
||||||
|
f"https://a2a9websgateway.sh3.ikuai7.com/agent/{oid}",
|
||||||
|
method="DELETE"
|
||||||
|
)
|
||||||
|
urllib.request.urlopen(del_req, timeout=10, context=ctx)
|
||||||
|
|
||||||
|
# 2. 注册新的
|
||||||
|
reg_req = urllib.request.Request(
|
||||||
|
"https://a2a9websgateway.sh3.ikuai7.com/agent/register",
|
||||||
|
data=json.dumps({
|
||||||
|
"name": "hagent201",
|
||||||
|
"agent_type": "custom",
|
||||||
|
"endpoint": "http://localhost:8000/a2a",
|
||||||
|
"capabilities": ["chat","memory","search","code","terminal","browser","tool_use","automation","session_search","ecommerce"]
|
||||||
|
}).encode(),
|
||||||
|
headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
resp = urllib.request.urlopen(reg_req, timeout=15, context=ctx)
|
||||||
|
body = json.loads(resp.read())
|
||||||
|
# 3. 保存凭据
|
||||||
|
with open(os.path.expanduser("~/.hermes/a2atoken.json"), "w") as f:
|
||||||
|
json.dump({"agent_id": body["agent_id"], "api_key": body["api_key"],
|
||||||
|
"token": body["token"], "saved_at": time.time()}, f, indent=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## All Endpoints
|
||||||
|
|
||||||
|
### 无需认证
|
||||||
|
|
||||||
|
| Endpoint | Method | Returns |
|
||||||
|
|----------|--------|---------|
|
||||||
|
| `/health` | GET | `{"status":"healthy",...}` |
|
||||||
|
| `/stats` | GET | `{"total_agents":N,"online":N,...}` |
|
||||||
|
| `/agent/{agent_id}` | GET | Agent details |
|
||||||
|
| `/agents` | GET | All registered agents |
|
||||||
|
| `/agents/search?capability=X` | GET | Search agents by capability |
|
||||||
|
| `/agent/register` | POST | 注册新 agent(返回 api_key + token) |
|
||||||
|
|
||||||
|
### 需要 Bearer Token
|
||||||
|
|
||||||
|
### 包含 Inbox 消息收发功能
|
||||||
|
|
||||||
|
| Endpoint | Method | Purpose |
|
||||||
|
|----------|--------|---------|
|
||||||
|
| `/agent/login` | POST | 用 agent_id + api_key 换 JWT token |
|
||||||
|
| `/agent/heartbeat` | POST | 心跳上报 |
|
||||||
|
| `/route` | POST | 点对点消息路由 |
|
||||||
|
| `/route` | POST | 点对点消息路由 |
|
||||||
|
| `/inbox/{agent_id}/messages` | GET | 拉取未读消息(?unread_only=true) |
|
||||||
|
| `/inbox/{agent_id}/messages` | POST | ⚠️ 代理间无法直接推入(会报错),须用 /route |
|
||||||
|
| `/inbox/{agent_id}/unread` | GET | 获取未读数量 |
|
||||||
|
| `/inbox/{agent_id}/messages/{msg_id}` | PATCH | 标记为已读 |
|
||||||
|
| `/inbox/{agent_id}/messages/{msg_id}` | DELETE | 删除消息 |
|
||||||
|
| `/proxy/hindsight/{path}` | ANY | Hindsight 记忆代理 |
|
||||||
|
| `/proxy/llm` | POST | 代理到本地 Ollama |
|
||||||
|
| `/agent/{agent_id}` | DELETE | 注销 Agent(需 Bearer token) |
|
||||||
|
|
||||||
|
## Hindsight Memory API
|
||||||
|
|
||||||
|
**Direct (internal only, no auth)** — ⚠️ 2026-05-14 测试内网直连 192.168.9.129:8888 超时不可用,请用外网 proxy:
|
||||||
|
```
|
||||||
|
POST http://192.168.9.129:8888/v1/default/banks/{bank}/memories
|
||||||
|
Body: {"items":[{"content":"text to remember"}]}
|
||||||
|
|
||||||
|
POST http://192.168.9.129:8888/v1/default/banks/{bank}/memories/recall
|
||||||
|
Body: {"query":"search keywords"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Via gateway proxy (external, requires Bearer token)** — ✅ 可用:
|
||||||
|
```
|
||||||
|
POST https://a2a9websgateway.sh3.ikuai7.com/proxy/hindsight/v1/default/banks/{bank}/memories
|
||||||
|
Header: Authorization: Bearer <JWT>
|
||||||
|
|
||||||
|
POST https://a2a9websgateway.sh3.ikuai7.com/proxy/hindsight/v1/default/banks/{bank}/memories/recall
|
||||||
|
Body: {"query":"search keywords","top_k":5}
|
||||||
|
Header: Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
Key details:
|
||||||
|
- follow_redirects=True + NO trailing slash on URLs, otherwise writes return 500
|
||||||
|
- Recall returns array: `text`, `occurred_start`/`occurred_end`, `entities`
|
||||||
|
- Default Hermes bank: `hermessession`
|
||||||
|
- **写入超时问题**:Hindsight 写入时嵌入计算很慢,经常 60s+ 超时。建议:timeout=90、逐条写入、每条间隔2秒、重试2次
|
||||||
|
- **Python 集成脚本**:`/home/ubuntu/.hermes/a2a/hindsight.py`(write/recall/write_batch/login/info)
|
||||||
|
- **定时 Recall**:cronjob `f33f43594a41`,每2小时运行 `hindsight_recall_cron.py` 拉取共享记忆
|
||||||
|
|
||||||
|
### 集成脚本用法
|
||||||
|
```bash
|
||||||
|
cd /home/ubuntu/.hermes/a2a
|
||||||
|
python3 hindsight.py write "[HAgent201] 要记住的内容"
|
||||||
|
python3 hindsight.py recall "搜索关键词"
|
||||||
|
python3 hindsight.py login # 刷新 token
|
||||||
|
python3 hindsight.py info # 显示凭据状态
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent 间通信
|
||||||
|
|
||||||
|
### 点对点路由
|
||||||
|
```python
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"https://a2a9websgateway.sh3.ikuai7.com/route",
|
||||||
|
data=json.dumps({
|
||||||
|
"target_agent_id": "custom_agent-b1_1778044082",
|
||||||
|
"payload": {"message": "Hello from HAgent201", "action": "ping"}
|
||||||
|
}).encode(),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {token}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 广播
|
||||||
|
```python
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"https://a2a9websgateway.sh3.ikuai7.com/broadcast",
|
||||||
|
data=json.dumps({
|
||||||
|
"payload": {"message": "Broadcast test"},
|
||||||
|
"agent_type": "custom" # 可选,只发给特定类型
|
||||||
|
}).encode(),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {token}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Token Storage
|
||||||
|
|
||||||
|
`~/.hermes/a2atoken.json` — 必须包含: `{agent_id, api_key, token, saved_at}`
|
||||||
|
⚠️ **`api_key` 是永久凭证,绝不能丢失!** 没有 api_key 就无法 `/agent/login` 刷新 token,被迫 re-register 导致 agent_id 漂移。
|
||||||
|
所有脚本必须从此文件动态读取 agent_id,**禁止硬编码**。
|
||||||
|
|
||||||
|
## Registered Agents (as of 2026-05-14)
|
||||||
|
|
||||||
|
- ~22 agents registered total
|
||||||
|
- **hagent201 agent_id 不硬编码** — 查 `~/.hermes/a2atoken.json` 或 `GET /agents`
|
||||||
|
- 其他活跃 agents: agent-a1/a2/a3, agent-b1/b2/b3, atomk-sg4/sg6/sg7, atomk-us2, qqcloud-hk1/hk2/hk3, qqcloud-sg3-atomlisting, gpustack1/2, bt109-internal-ak, hermes-cli, n5095bot
|
||||||
|
|
||||||
|
## Debugging Tips
|
||||||
|
|
||||||
|
1. Internal IPs fail from cloud → use external HTTPS domain
|
||||||
|
2. Check `/health` first
|
||||||
|
3. `/agents` shows who's online (no auth)
|
||||||
|
4. JWT payload has `agent_id`, `name`, `exp` fields
|
||||||
|
5. **不要用 curl 显示 token** — 终端会误渲染 JWT 中的点号为省略号,用 Python urllib 读取
|
||||||
|
6. Token 过期后用 `/agent/login` + 完整 api_key 刷新
|
||||||
|
7. `api_key` 是永久的(不会过期),`token` 有有效期
|
||||||
|
8. **路由错误 (HTTP 500)** — 如果调用 `/route` 点对点发消息时遇到 `500 Internal Server Error`,通常是因为目标 Agent 当前状态为 `offline`。发送前建议先请求 `/agents` 确认目标状态。
|
||||||
|
9. **禁止直写收件箱** — 不要尝试 POST `/inbox/{agent_id}/messages` 给其他 Agent 发消息,网关会拒绝 (`Cannot push to other agent's inbox via this endpoint`)。必须使用 `/route` 或 `/broadcast` 进行通信。
|
||||||
|
10. **同名堆积 Bug** — `/agent/register` 不按 name 去重,每次调用创建新 agent_id。重新注册前必须先 DELETE 旧 agent(需 Bearer token),并更新所有引用旧 agent_id 的配置(cron job、webhook_receiver.py、a2atoken.json)。见"重新注册"章节的完整流程。
|
||||||
|
11. **凭据漂移检测** — 如果心跳持续失败,先 `GET /agents` 检查当前有效的 hagent201 agent_id 是否与 `~/.hermes/a2atoken.json` 中记录的一致。不一致说明某处触发了重新注册。
|
||||||
|
12. **Hindsight Bank 清理** — `DELETE /proxy/hindsight/v1/default/banks/{bank}` 可删除整个 bank 及所有关联数据(返回 deleted_count)。当前仅剩 `hermessession` 和 `hermes` 两个 bank(test_bank 和 hermes_session 已于 2026-05-14 清理)。
|
||||||
|
13. **Hindsight 写入超时** — `POST /proxy/hindsight/.../memories` 写入时嵌入计算可能很慢,经常 30s 超时。建议 timeout=60 或更长,超时后重试(数据可能已写入也可能没写入)。
|
||||||
Reference in New Issue
Block a user