agent-api-server 2.1.13__tar.gz → 2.1.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/PKG-INFO +81 -8
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/README.md +79 -3
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/call_a2a_client.py +28 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/langgraph_executor.py +8 -3
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/acp_bridge/go/agent.go +20 -11
- agent_api_server-2.1.15/agent_api_server/acp_bridge/go/agent_test.go +46 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/acp_bridge/go/main.go +9 -6
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/v1/thread.py +10 -8
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/app.py +5 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/cache/redis_cache.py +9 -4
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/config_center/config_center.py +32 -4
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/configs/config.py +6 -1
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/dynamic_llm/dynamic_llm.py +59 -51
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/listener/base.py +62 -58
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/listener/nats_listener.py +88 -99
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/listener/redis_listener.py +2 -1
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/mcp_convert/mcp_convert.py +24 -8
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/agent_registry.py +6 -2
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/modes/chain.py +4 -1
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/modes/discussion.py +4 -1
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/orchestrator.py +1 -1
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/register/register.py +2 -2
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/services/config_builder.py +6 -1
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/services/stream_service.py +13 -9
- agent_api_server-2.1.15/agent_api_server/shared/context_manager.py +452 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/decode_token.py +4 -2
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/get_model_info.py +17 -6
- agent_api_server-2.1.15/agent_api_server/shared/loop_agent.py +1162 -0
- agent_api_server-2.1.15/agent_api_server/shared/markdown_fixer.py +469 -0
- agent_api_server-2.1.15/agent_api_server/shared/mcp_auth.py +458 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/message.py +9 -2
- agent_api_server-2.1.15/agent_api_server/shared/model_config.py +262 -0
- agent_api_server-2.1.15/agent_api_server/shared/model_manager_client.py +47 -0
- agent_api_server-2.1.15/agent_api_server/shared/redis_url.py +22 -0
- agent_api_server-2.1.15/agent_api_server/shared/request_context.py +80 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/sso_service/sdk/client.py +5 -4
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/sso_service/sso_service.py +8 -7
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/startup.py +3 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/pyproject.toml +3 -12
- agent_api_server-2.1.13/agent_api_server/.idea/.gitignore +0 -3
- agent_api_server-2.1.13/agent_api_server/.idea/agent_api_server.iml +0 -12
- agent_api_server-2.1.13/agent_api_server/.idea/inspectionProfiles/Project_Default.xml +0 -488
- agent_api_server-2.1.13/agent_api_server/.idea/inspectionProfiles/profiles_settings.xml +0 -6
- agent_api_server-2.1.13/agent_api_server/.idea/misc.xml +0 -4
- agent_api_server-2.1.13/agent_api_server/.idea/modules.xml +0 -8
- agent_api_server-2.1.13/agent_api_server/.idea/vcs.xml +0 -6
- agent_api_server-2.1.13/agent_api_server/.idea/workspace.xml +0 -50
- agent_api_server-2.1.13/agent_api_server/acp_bridge/go/agenthub-acp +0 -0
- agent_api_server-2.1.13/agent_api_server/shared/model_config.py +0 -203
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/agent_card_builder.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/redis_task_store.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/server.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/acp_bridge/go/Makefile +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/acp_bridge/go/go.mod +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/acp_bridge/go/go.sum +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/agent_registration.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/v1/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/v1/api.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/v1/config.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/v1/graph.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/api/v1/schema.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/cache/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/callback_handler.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/client/css/styles.css +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/client/favicon.ico +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/client/index.html +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/client/js/app.js +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/client/js/index.umd.js +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/configs/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/dynamic_llm/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/listener/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/log/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/log/formatters.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/log/logging.json +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/mcp_convert/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/mcp_server.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/memory/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/memory/postgres.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/modes/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/orchestrator/types.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/register/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/service.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/service_hub/service_hub.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/service_hub/service_hub_test.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/services/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/ase.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/base_model.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/common.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/detect_message.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/exceptions.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/graph_loader.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/shared/util_func.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/sso_service/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/sso_service/sdk/__init__.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/sso_service/sdk/credential.py +0 -0
- {agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/sso_service/sdk/encoding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: agent-api-server
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.15
|
|
4
4
|
Summary: A Langgraph agent API server that implements Langgraph agent's web capabilities and can interact with chatbot
|
|
5
5
|
Author: zijie.zhang@advantech.com.cn
|
|
6
6
|
Requires-Python: >=3.10,<3.13
|
|
@@ -8,18 +8,14 @@ Classifier: Programming Language :: Python :: 3
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.10
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
Provides-Extra: a2a
|
|
12
11
|
Requires-Dist: a2a-sdk[http-server] (==1.1.2)
|
|
13
|
-
Requires-Dist: a2a-sdk[http-server] (==1.1.2) ; extra == "a2a"
|
|
14
12
|
Requires-Dist: agent-client-protocol (>=0.11.0,<1.0.0)
|
|
15
|
-
Requires-Dist: agent-client-protocol (>=0.11.0,<1.0.0) ; extra == "a2a"
|
|
16
13
|
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
|
17
14
|
Requires-Dist: authlib (>=1.6.5)
|
|
18
15
|
Requires-Dist: cryptography (>=45.0.4,<46.0.0)
|
|
19
16
|
Requires-Dist: fastapi (>=0.117.0,<0.121.2)
|
|
20
17
|
Requires-Dist: fastmcp (>=2.13.0,<3.0.0)
|
|
21
18
|
Requires-Dist: httpx (>=0.27.0,<1.0.0)
|
|
22
|
-
Requires-Dist: httpx (>=0.27.0,<1.0.0) ; extra == "a2a"
|
|
23
19
|
Requires-Dist: langchain-community (==0.3.29)
|
|
24
20
|
Requires-Dist: langchain-core (==0.3.76)
|
|
25
21
|
Requires-Dist: langchain-openai (==0.3.33)
|
|
@@ -36,6 +32,7 @@ Requires-Dist: nats-py (>=2.11.0,<3.0.0)
|
|
|
36
32
|
Requires-Dist: psycopg-binary (>=3.2.9,<4.0.0)
|
|
37
33
|
Requires-Dist: psycopg-pool (>=3.2.6,<4.0.0)
|
|
38
34
|
Requires-Dist: pydantic-settings (>=2.9.1,<3.0.0)
|
|
35
|
+
Requires-Dist: pytest (>=9.1.1,<10.0.0)
|
|
39
36
|
Requires-Dist: redis (>=6.2.0,<7.0.0)
|
|
40
37
|
Requires-Dist: starlette (>=0.49.3,<0.50.0)
|
|
41
38
|
Requires-Dist: tenacity (>=9.1.2,<10.0.0)
|
|
@@ -616,13 +613,22 @@ ACP 采用**代理模式(Proxy Mode)**,CLI 进程作为编辑器与 FastAP
|
|
|
616
613
|
|
|
617
614
|
```bash
|
|
618
615
|
# 从 PyPI 安装
|
|
619
|
-
pip install agent-api-server
|
|
616
|
+
pip install agent-api-server==2.1.15
|
|
620
617
|
|
|
621
618
|
# 或从本地 wheel 安装
|
|
622
|
-
pip install dist/agent_api_server-2.1.
|
|
619
|
+
pip install dist/agent_api_server-2.1.15-py3-none-any.whl
|
|
623
620
|
```
|
|
624
621
|
|
|
625
|
-
|
|
622
|
+
`2.1.15` 使用本仓库的 LangGraph 0.6 依赖,并包含 WisePortal 组织适配及 Redis `mode=standalone` 地址兼容。PyPI 上另有使用 LangGraph 1.x 的 `2.2.x` 版本,安装本版本时请指定版本号。
|
|
623
|
+
|
|
624
|
+
ACP 命令使用仓库中的 Go 实现,需要在源码目录单独构建,并将生成的程序放入 `PATH`:
|
|
625
|
+
|
|
626
|
+
```bash
|
|
627
|
+
make -C agent_api_server/acp_bridge/go build
|
|
628
|
+
mkdir -p ~/.local/bin
|
|
629
|
+
install -m 755 agent_api_server/acp_bridge/go/agenthub-acp ~/.local/bin/agenthub-acp
|
|
630
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
631
|
+
```
|
|
626
632
|
|
|
627
633
|
**2. 启动 AgentHub 服务**
|
|
628
634
|
|
|
@@ -965,6 +971,73 @@ AGENT_AUTO_REGISTRATION=false
|
|
|
965
971
|
ENABLE_A2A=false
|
|
966
972
|
```
|
|
967
973
|
|
|
974
|
+
### WisePortal 配置
|
|
975
|
+
|
|
976
|
+
服务启动时会从 `${CONFIG_CENTER_ADDRESS}/v1/public/configs`
|
|
977
|
+
读取 `enableSrpPortal`,保存为 `global_config.isWisePortal`。布尔值 `true` 和
|
|
978
|
+
字符串 `"true"`(忽略大小写和首尾空格)均开启该模式,其余值关闭。
|
|
979
|
+
`CONFIG_CENTER_ADDRESS` 默认是 `http://172.21.92.244:10086`,可通过环境变量覆盖。
|
|
980
|
+
配置读取失败时记录警告并保留当前开关值,初始默认值为 `false`。
|
|
981
|
+
REST 与独立 MCP 服务都会在启动时读取配置;修改配置后需重启服务。
|
|
982
|
+
|
|
983
|
+
- 关闭时,REST 继续读取 Cookie `TSTenant`,MCP 继续读取 Header `TSTenant`。
|
|
984
|
+
- 开启时,优先读取 Header `Org-Str-Id`,其次 Header `orgStrId`、Cookie `orgStrId`,
|
|
985
|
+
将该值作为 `TSTenant`。缺少组织字段时不会回退到旧租户字段。
|
|
986
|
+
- A2A 接收同样的 HTTP 组织字段,也支持 `metadata.orgStrId`。
|
|
987
|
+
- SDK 的 MCP 认证头、A2A、Model Manager、SSO 和配置中心客户端会在当前组织上下文中
|
|
988
|
+
添加 `Org-Str-Id`。组织值不存入共享客户端默认 Header。
|
|
989
|
+
- Go ACP 可通过 `--org-str-id` 或 `ORG_STR_ID` 显式传入组织,代理到后端时携带组织 Header/Cookie。
|
|
990
|
+
|
|
991
|
+
业务 Agent 自行创建 MCP/HTTP 客户端时,使用共享函数构建每次调用的 Header:
|
|
992
|
+
|
|
993
|
+
```python
|
|
994
|
+
from agent_api_server.shared.request_context import outbound_headers
|
|
995
|
+
|
|
996
|
+
headers = outbound_headers({"Authorization": "Bearer ..."}, config=config)
|
|
997
|
+
# 开启 WisePortal 时:{"Authorization": "Bearer ...", "Org-Str-Id": "组织ID"}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
`MCPAuthManager.get_headers(config)` 和 `get_headers_safe(config)` 也支持显式传入
|
|
1001
|
+
LangGraph 配置;在 SDK 执行的请求中,省略参数会自动使用当前请求的组织。
|
|
1002
|
+
业务代码应按请求构建 Header,避免跨组织复用已生成的 Header 字典。
|
|
1003
|
+
|
|
1004
|
+
#### WisePortal 模型通知和缓存
|
|
1005
|
+
|
|
1006
|
+
配置 `MODEL_MANAGER_NATS_URL` 并开启 WisePortal 后,每个 Agent 接收两类通知:
|
|
1007
|
+
|
|
1008
|
+
| Subject | 处理方式 |
|
|
1009
|
+
| --- | --- |
|
|
1010
|
+
| `model-management-service.model.agent.{agent_name}.{org_str_id}` | 更新或删除当前 Agent 对应工具的模型配置 |
|
|
1011
|
+
| `model-management-service.model.tenant.{org_str_id}` | 重新查询当前 Agent 的模型绑定,替换本地快照,并清空已删除的绑定 |
|
|
1012
|
+
|
|
1013
|
+
`org_str_id` 支持放在消息顶层或 `message` 内;存在时必须与 subject 的末尾一致。
|
|
1014
|
+
没有 `org_str_id` 的旧消息使用 subject 末尾作为标识。`org_id` 仅作为消息元数据保留,
|
|
1015
|
+
不会替代 `org_str_id`。模型回调仍使用 `ts_id` 参数,在 WisePortal 模式下该值为 `org_str_id`。
|
|
1016
|
+
租户级通知中的通用凭证不会直接写入 Agent 缓存:SDK 会携带 `Org-Str-Id` 查询该 Agent
|
|
1017
|
+
自己的模型配置,保存对应 Agent 注册 ID,用于解密模型凭证。
|
|
1018
|
+
|
|
1019
|
+
WisePortal 缓存路径为 `data/wiseportal_models/{org_str_id}/{agent_name}.env`,
|
|
1020
|
+
其中配置键仍沿用 `CHAT_MODEL_{org_str_id}`、`CHAT_CREDENTIALS_{org_str_id}` 等格式。
|
|
1021
|
+
缓存按组织和 Agent 隔离,写入采用文件锁和原子替换;不会将组织模型凭证写入共享进程环境。
|
|
1022
|
+
`DynamicLLM` 和模型信息查询会读取最新缓存,因此同一个执行配置也能在下次调用时看到更新或删除。
|
|
1023
|
+
模型实例缓存同时区分组织和 Agent 注册 ID。
|
|
1024
|
+
|
|
1025
|
+
首次切换时保留旧的 `data/.llm_cfg_env_{tenant_id}` 文件,新的缓存由通知或首次模型查询填充,
|
|
1026
|
+
无需手动把旧租户 ID 改成组织 ID。关闭 WisePortal 后继续使用原有缓存路径和 Agent subject。
|
|
1027
|
+
自行调用缓存接口的业务代码,应在 WisePortal 模式传入图名称:
|
|
1028
|
+
|
|
1029
|
+
```python
|
|
1030
|
+
get_env(ts_tenant=org_str_id, agent_name=graph_name)
|
|
1031
|
+
set_model_config(..., ts_id=org_str_id, agent_name=graph_name)
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
通知成功持久化后才 ACK;查询或写入失败会 NAK 请求重投,身份不一致或格式错误的消息会记录错误并终止重投。
|
|
1035
|
+
两个 subject 对同一组织的处理会串行执行,防止较慢的租户级刷新覆盖后续 Agent 更新。
|
|
1036
|
+
消费者配置更新保留已确认的位置;`ack_wait` 为 30 秒,最多投递 5 次。
|
|
1037
|
+
|
|
1038
|
+
回归测试:`python -m pytest tests/test_model_notifications.py tests/test_wiseportal.py tests/test_markdown_fixer.py`。
|
|
1039
|
+
可设置 `NATS_TEST_URL` 指向独立的 JetStream 测试实例,启用真实订阅、重投和重连测试。
|
|
1040
|
+
|
|
968
1041
|
### 定义 Agent
|
|
969
1042
|
|
|
970
1043
|
创建 `langgraph.json` 配置文件:
|
|
@@ -572,13 +572,22 @@ ACP 采用**代理模式(Proxy Mode)**,CLI 进程作为编辑器与 FastAP
|
|
|
572
572
|
|
|
573
573
|
```bash
|
|
574
574
|
# 从 PyPI 安装
|
|
575
|
-
pip install agent-api-server
|
|
575
|
+
pip install agent-api-server==2.1.15
|
|
576
576
|
|
|
577
577
|
# 或从本地 wheel 安装
|
|
578
|
-
pip install dist/agent_api_server-2.1.
|
|
578
|
+
pip install dist/agent_api_server-2.1.15-py3-none-any.whl
|
|
579
579
|
```
|
|
580
580
|
|
|
581
|
-
|
|
581
|
+
`2.1.15` 使用本仓库的 LangGraph 0.6 依赖,并包含 WisePortal 组织适配及 Redis `mode=standalone` 地址兼容。PyPI 上另有使用 LangGraph 1.x 的 `2.2.x` 版本,安装本版本时请指定版本号。
|
|
582
|
+
|
|
583
|
+
ACP 命令使用仓库中的 Go 实现,需要在源码目录单独构建,并将生成的程序放入 `PATH`:
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
make -C agent_api_server/acp_bridge/go build
|
|
587
|
+
mkdir -p ~/.local/bin
|
|
588
|
+
install -m 755 agent_api_server/acp_bridge/go/agenthub-acp ~/.local/bin/agenthub-acp
|
|
589
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
590
|
+
```
|
|
582
591
|
|
|
583
592
|
**2. 启动 AgentHub 服务**
|
|
584
593
|
|
|
@@ -921,6 +930,73 @@ AGENT_AUTO_REGISTRATION=false
|
|
|
921
930
|
ENABLE_A2A=false
|
|
922
931
|
```
|
|
923
932
|
|
|
933
|
+
### WisePortal 配置
|
|
934
|
+
|
|
935
|
+
服务启动时会从 `${CONFIG_CENTER_ADDRESS}/v1/public/configs`
|
|
936
|
+
读取 `enableSrpPortal`,保存为 `global_config.isWisePortal`。布尔值 `true` 和
|
|
937
|
+
字符串 `"true"`(忽略大小写和首尾空格)均开启该模式,其余值关闭。
|
|
938
|
+
`CONFIG_CENTER_ADDRESS` 默认是 `http://172.21.92.244:10086`,可通过环境变量覆盖。
|
|
939
|
+
配置读取失败时记录警告并保留当前开关值,初始默认值为 `false`。
|
|
940
|
+
REST 与独立 MCP 服务都会在启动时读取配置;修改配置后需重启服务。
|
|
941
|
+
|
|
942
|
+
- 关闭时,REST 继续读取 Cookie `TSTenant`,MCP 继续读取 Header `TSTenant`。
|
|
943
|
+
- 开启时,优先读取 Header `Org-Str-Id`,其次 Header `orgStrId`、Cookie `orgStrId`,
|
|
944
|
+
将该值作为 `TSTenant`。缺少组织字段时不会回退到旧租户字段。
|
|
945
|
+
- A2A 接收同样的 HTTP 组织字段,也支持 `metadata.orgStrId`。
|
|
946
|
+
- SDK 的 MCP 认证头、A2A、Model Manager、SSO 和配置中心客户端会在当前组织上下文中
|
|
947
|
+
添加 `Org-Str-Id`。组织值不存入共享客户端默认 Header。
|
|
948
|
+
- Go ACP 可通过 `--org-str-id` 或 `ORG_STR_ID` 显式传入组织,代理到后端时携带组织 Header/Cookie。
|
|
949
|
+
|
|
950
|
+
业务 Agent 自行创建 MCP/HTTP 客户端时,使用共享函数构建每次调用的 Header:
|
|
951
|
+
|
|
952
|
+
```python
|
|
953
|
+
from agent_api_server.shared.request_context import outbound_headers
|
|
954
|
+
|
|
955
|
+
headers = outbound_headers({"Authorization": "Bearer ..."}, config=config)
|
|
956
|
+
# 开启 WisePortal 时:{"Authorization": "Bearer ...", "Org-Str-Id": "组织ID"}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
`MCPAuthManager.get_headers(config)` 和 `get_headers_safe(config)` 也支持显式传入
|
|
960
|
+
LangGraph 配置;在 SDK 执行的请求中,省略参数会自动使用当前请求的组织。
|
|
961
|
+
业务代码应按请求构建 Header,避免跨组织复用已生成的 Header 字典。
|
|
962
|
+
|
|
963
|
+
#### WisePortal 模型通知和缓存
|
|
964
|
+
|
|
965
|
+
配置 `MODEL_MANAGER_NATS_URL` 并开启 WisePortal 后,每个 Agent 接收两类通知:
|
|
966
|
+
|
|
967
|
+
| Subject | 处理方式 |
|
|
968
|
+
| --- | --- |
|
|
969
|
+
| `model-management-service.model.agent.{agent_name}.{org_str_id}` | 更新或删除当前 Agent 对应工具的模型配置 |
|
|
970
|
+
| `model-management-service.model.tenant.{org_str_id}` | 重新查询当前 Agent 的模型绑定,替换本地快照,并清空已删除的绑定 |
|
|
971
|
+
|
|
972
|
+
`org_str_id` 支持放在消息顶层或 `message` 内;存在时必须与 subject 的末尾一致。
|
|
973
|
+
没有 `org_str_id` 的旧消息使用 subject 末尾作为标识。`org_id` 仅作为消息元数据保留,
|
|
974
|
+
不会替代 `org_str_id`。模型回调仍使用 `ts_id` 参数,在 WisePortal 模式下该值为 `org_str_id`。
|
|
975
|
+
租户级通知中的通用凭证不会直接写入 Agent 缓存:SDK 会携带 `Org-Str-Id` 查询该 Agent
|
|
976
|
+
自己的模型配置,保存对应 Agent 注册 ID,用于解密模型凭证。
|
|
977
|
+
|
|
978
|
+
WisePortal 缓存路径为 `data/wiseportal_models/{org_str_id}/{agent_name}.env`,
|
|
979
|
+
其中配置键仍沿用 `CHAT_MODEL_{org_str_id}`、`CHAT_CREDENTIALS_{org_str_id}` 等格式。
|
|
980
|
+
缓存按组织和 Agent 隔离,写入采用文件锁和原子替换;不会将组织模型凭证写入共享进程环境。
|
|
981
|
+
`DynamicLLM` 和模型信息查询会读取最新缓存,因此同一个执行配置也能在下次调用时看到更新或删除。
|
|
982
|
+
模型实例缓存同时区分组织和 Agent 注册 ID。
|
|
983
|
+
|
|
984
|
+
首次切换时保留旧的 `data/.llm_cfg_env_{tenant_id}` 文件,新的缓存由通知或首次模型查询填充,
|
|
985
|
+
无需手动把旧租户 ID 改成组织 ID。关闭 WisePortal 后继续使用原有缓存路径和 Agent subject。
|
|
986
|
+
自行调用缓存接口的业务代码,应在 WisePortal 模式传入图名称:
|
|
987
|
+
|
|
988
|
+
```python
|
|
989
|
+
get_env(ts_tenant=org_str_id, agent_name=graph_name)
|
|
990
|
+
set_model_config(..., ts_id=org_str_id, agent_name=graph_name)
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
通知成功持久化后才 ACK;查询或写入失败会 NAK 请求重投,身份不一致或格式错误的消息会记录错误并终止重投。
|
|
994
|
+
两个 subject 对同一组织的处理会串行执行,防止较慢的租户级刷新覆盖后续 Agent 更新。
|
|
995
|
+
消费者配置更新保留已确认的位置;`ack_wait` 为 30 秒,最多投递 5 次。
|
|
996
|
+
|
|
997
|
+
回归测试:`python -m pytest tests/test_model_notifications.py tests/test_wiseportal.py tests/test_markdown_fixer.py`。
|
|
998
|
+
可设置 `NATS_TEST_URL` 指向独立的 JetStream 测试实例,启用真实订阅、重投和重连测试。
|
|
999
|
+
|
|
924
1000
|
### 定义 Agent
|
|
925
1001
|
|
|
926
1002
|
创建 `langgraph.json` 配置文件:
|
{agent_api_server-2.1.13 → agent_api_server-2.1.15}/agent_api_server/a2a_bridge/call_a2a_client.py
RENAMED
|
@@ -6,6 +6,10 @@ from uuid import uuid4
|
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
from agent_api_server.shared.decode_token import decode_jwt
|
|
9
|
+
from agent_api_server.configs import global_config
|
|
10
|
+
from agent_api_server.shared.request_context import (
|
|
11
|
+
add_organization_header, organization_context, organization_id,
|
|
12
|
+
)
|
|
9
13
|
from a2a.client.client import ClientConfig
|
|
10
14
|
from a2a.client.client_factory import create_client
|
|
11
15
|
from a2a.types import (
|
|
@@ -124,6 +128,7 @@ class AgentHubClient:
|
|
|
124
128
|
if self._client is None:
|
|
125
129
|
httpx_client = httpx.AsyncClient(
|
|
126
130
|
timeout=httpx.Timeout(self._timeout, connect=10.0),
|
|
131
|
+
event_hooks={"request": [add_organization_header]},
|
|
127
132
|
)
|
|
128
133
|
client_config = ClientConfig(httpx_client=httpx_client)
|
|
129
134
|
|
|
@@ -162,6 +167,29 @@ class AgentHubClient:
|
|
|
162
167
|
context_id: str | None = None,
|
|
163
168
|
on_stream_chunk: Callable[[StreamResponse], None] | None = None,
|
|
164
169
|
**extra_metadata,
|
|
170
|
+
) -> A2AResult:
|
|
171
|
+
"""Send a request with an organization scoped to this call, not the client."""
|
|
172
|
+
if global_config.isWisePortal:
|
|
173
|
+
tenant_id = extra_metadata.get("orgStrId") or organization_id() or tenant_id or ""
|
|
174
|
+
extra_metadata["orgStrId"] = tenant_id
|
|
175
|
+
with organization_context(tenant_id):
|
|
176
|
+
return await self._call(
|
|
177
|
+
raw_question=raw_question, tenant_id=tenant_id, ei_token=ei_token,
|
|
178
|
+
files=files, task_id=task_id, context_id=context_id,
|
|
179
|
+
on_stream_chunk=on_stream_chunk, **extra_metadata,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
async def _call(
|
|
183
|
+
self,
|
|
184
|
+
*,
|
|
185
|
+
raw_question: str,
|
|
186
|
+
tenant_id: str = "",
|
|
187
|
+
ei_token: str = "",
|
|
188
|
+
files: list | None = None,
|
|
189
|
+
task_id: str | None = None,
|
|
190
|
+
context_id: str | None = None,
|
|
191
|
+
on_stream_chunk: Callable[[StreamResponse], None] | None = None,
|
|
192
|
+
**extra_metadata,
|
|
165
193
|
) -> A2AResult:
|
|
166
194
|
"""Send a message to the remote agent and consume the full event stream.
|
|
167
195
|
|
|
@@ -10,6 +10,8 @@ from a2a.server.events import EventQueue
|
|
|
10
10
|
from a2a.server.tasks import TaskUpdater
|
|
11
11
|
from a2a.types import Part, Message, Role, Task, TaskState, TaskStatus
|
|
12
12
|
from agent_api_server.services.config_builder import build_configurable, build_langfuse_callbacks
|
|
13
|
+
from agent_api_server.configs import global_config
|
|
14
|
+
from agent_api_server.shared.request_context import organization_context, organization_id
|
|
13
15
|
|
|
14
16
|
try:
|
|
15
17
|
from langfuse import propagate_attributes
|
|
@@ -194,9 +196,10 @@ class LangGraphAgentExecutor(AgentExecutor):
|
|
|
194
196
|
|
|
195
197
|
await updater.start_work()
|
|
196
198
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
with organization_context(config["configurable"].get("TSTenant")):
|
|
200
|
+
graph_task = asyncio.create_task(
|
|
201
|
+
self._run_graph(graph, user_message, metadata, config, updater, is_continuation)
|
|
202
|
+
)
|
|
200
203
|
self._running_tasks[task_id] = graph_task
|
|
201
204
|
|
|
202
205
|
try:
|
|
@@ -341,6 +344,8 @@ class LangGraphAgentExecutor(AgentExecutor):
|
|
|
341
344
|
) -> Dict[str, Any]:
|
|
342
345
|
metadata = metadata or {}
|
|
343
346
|
ts_tenant = metadata.get("tenant_id", "")
|
|
347
|
+
if global_config.isWisePortal:
|
|
348
|
+
ts_tenant = organization_id() or metadata.get("orgStrId", "")
|
|
344
349
|
|
|
345
350
|
# Namespace thread_id by graph_name to isolate checkpoints
|
|
346
351
|
# across multiple agents sharing the same PostgreSQL checkpointer.
|
|
@@ -28,7 +28,6 @@ func (e *HTTPError) Error() string {
|
|
|
28
28
|
return fmt.Sprintf("Service error: %d", e.StatusCode)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
31
|
// SessionState tracks an active ACP session.
|
|
33
32
|
type SessionState struct {
|
|
34
33
|
SessionID string `json:"session_id"`
|
|
@@ -50,6 +49,7 @@ type AgentHubACPAgent struct {
|
|
|
50
49
|
serviceURL string
|
|
51
50
|
agentName string
|
|
52
51
|
tsTenant string
|
|
52
|
+
orgStrID string
|
|
53
53
|
eiToken string
|
|
54
54
|
agentVersion string
|
|
55
55
|
agentDesc string
|
|
@@ -61,7 +61,7 @@ type AgentHubACPAgent struct {
|
|
|
61
61
|
httpClient *http.Client
|
|
62
62
|
sessions map[string]*SessionState
|
|
63
63
|
pendingToolCalls map[string]map[string]*pendingToolCall // sessionID -> tcID -> info
|
|
64
|
-
streamedNodes map[string]map[string]bool
|
|
64
|
+
streamedNodes map[string]map[string]bool // sessionID -> set of nodes
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// NewAgentHubACPAgent creates a new agent instance.
|
|
@@ -81,7 +81,7 @@ func NewAgentHubACPAgent(
|
|
|
81
81
|
agentDesc: agentDesc,
|
|
82
82
|
userInputField: userInputField,
|
|
83
83
|
extraInputs: extraInputs,
|
|
84
|
-
streamMode:
|
|
84
|
+
streamMode: streamMode,
|
|
85
85
|
sessions: make(map[string]*SessionState),
|
|
86
86
|
pendingToolCalls: make(map[string]map[string]*pendingToolCall),
|
|
87
87
|
streamedNodes: make(map[string]map[string]bool),
|
|
@@ -95,6 +95,15 @@ func (a *AgentHubACPAgent) getHTTPClient() *http.Client {
|
|
|
95
95
|
return a.httpClient
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// addOrganizationHeaders forwards an explicitly configured WisePortal identity.
|
|
99
|
+
// The backend's isWisePortal setting determines whether it replaces TSTenant.
|
|
100
|
+
func (a *AgentHubACPAgent) addOrganizationHeaders(req *http.Request) {
|
|
101
|
+
if a.orgStrID != "" {
|
|
102
|
+
req.Header.Set("Org-Str-Id", a.orgStrID)
|
|
103
|
+
req.AddCookie(&http.Cookie{Name: "orgStrId", Value: a.orgStrID})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
98
107
|
func (a *AgentHubACPAgent) doRequest(method, path string, body io.Reader, params url.Values) (*http.Response, error) {
|
|
99
108
|
u := a.serviceURL + path
|
|
100
109
|
if params != nil {
|
|
@@ -112,6 +121,7 @@ func (a *AgentHubACPAgent) doRequest(method, path string, body io.Reader, params
|
|
|
112
121
|
if a.eiToken != "" {
|
|
113
122
|
req.AddCookie(&http.Cookie{Name: "EIToken", Value: a.eiToken})
|
|
114
123
|
}
|
|
124
|
+
a.addOrganizationHeaders(req)
|
|
115
125
|
return a.httpClient.Do(req)
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -181,6 +191,7 @@ func (a *AgentHubACPAgent) streamPrompt(sessionID, userText string) error {
|
|
|
181
191
|
req.AddCookie(&http.Cookie{Name: "EIToken", Value: a.eiToken})
|
|
182
192
|
}
|
|
183
193
|
|
|
194
|
+
a.addOrganizationHeaders(req)
|
|
184
195
|
resp, err := a.httpClient.Do(req)
|
|
185
196
|
if err != nil {
|
|
186
197
|
return err
|
|
@@ -217,7 +228,6 @@ func (a *AgentHubACPAgent) streamPrompt(sessionID, userText string) error {
|
|
|
217
228
|
return scanner.Err()
|
|
218
229
|
}
|
|
219
230
|
|
|
220
|
-
|
|
221
231
|
// --- Non-streaming (blocking run) ---
|
|
222
232
|
|
|
223
233
|
func (a *AgentHubACPAgent) nonStreamPrompt(sessionID, userText string) error {
|
|
@@ -256,6 +266,7 @@ func (a *AgentHubACPAgent) nonStreamPrompt(sessionID, userText string) error {
|
|
|
256
266
|
req.AddCookie(&http.Cookie{Name: "EIToken", Value: a.eiToken})
|
|
257
267
|
}
|
|
258
268
|
|
|
269
|
+
a.addOrganizationHeaders(req)
|
|
259
270
|
resp, err := a.httpClient.Do(req)
|
|
260
271
|
if err != nil {
|
|
261
272
|
return err
|
|
@@ -268,9 +279,9 @@ func (a *AgentHubACPAgent) nonStreamPrompt(sessionID, userText string) error {
|
|
|
268
279
|
}
|
|
269
280
|
|
|
270
281
|
var result struct {
|
|
271
|
-
Status
|
|
282
|
+
Status string `json:"status"`
|
|
272
283
|
ThreadID string `json:"thread_id"`
|
|
273
|
-
Result
|
|
284
|
+
Result struct {
|
|
274
285
|
Type string `json:"type"`
|
|
275
286
|
ContentType string `json:"content_type"`
|
|
276
287
|
Content string `json:"content"`
|
|
@@ -457,14 +468,14 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
|
|
|
457
468
|
}
|
|
458
469
|
// Send as tool_call (for tool list)
|
|
459
470
|
a.sendStartToolCall(sessionID, tcID, title, nil, text, "completed")
|
|
460
|
-
|
|
471
|
+
|
|
461
472
|
// Only send as agent_message_chunk if this node hasn't streamed tokens yet
|
|
462
473
|
// (to avoid duplicate content in chat area)
|
|
463
474
|
a.mu.Lock()
|
|
464
475
|
streamed := a.streamedNodes[sessionID]
|
|
465
476
|
hasStreamed := streamed != nil && streamed[node]
|
|
466
477
|
a.mu.Unlock()
|
|
467
|
-
|
|
478
|
+
|
|
468
479
|
if !hasStreamed {
|
|
469
480
|
a.sendAgentMessage(sessionID, text)
|
|
470
481
|
}
|
|
@@ -538,7 +549,7 @@ func truncate(s string, maxLen int) string {
|
|
|
538
549
|
return s
|
|
539
550
|
}
|
|
540
551
|
return s[:maxLen]
|
|
541
|
-
}// sendStartToolCallWithContent sends a tool call with content field (for tools_message).
|
|
552
|
+
} // sendStartToolCallWithContent sends a tool call with content field (for tools_message).
|
|
542
553
|
func (a *AgentHubACPAgent) sendStartToolCallWithContent(sessionID, tcID, title, contentText string) {
|
|
543
554
|
a.sendNotification(sessionID, "session/update", map[string]interface{}{
|
|
544
555
|
"sessionId": sessionID,
|
|
@@ -558,5 +569,3 @@ func (a *AgentHubACPAgent) sendStartToolCallWithContent(sessionID, tcID, title,
|
|
|
558
569
|
},
|
|
559
570
|
})
|
|
560
571
|
}
|
|
561
|
-
|
|
562
|
-
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"net/http"
|
|
5
|
+
"net/http/httptest"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestOrganizationForwarding(t *testing.T) {
|
|
10
|
+
for _, org := range []string{"", "org-a"} {
|
|
11
|
+
t.Run("organization="+org, func(t *testing.T) {
|
|
12
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
13
|
+
if got := r.Header.Get("Org-Str-Id"); got != org {
|
|
14
|
+
t.Errorf("Org-Str-Id = %q, want %q", got, org)
|
|
15
|
+
}
|
|
16
|
+
cookie, err := r.Cookie("TSTenant")
|
|
17
|
+
if err != nil || cookie.Value != "legacy" {
|
|
18
|
+
t.Error("legacy tenant cookie was not preserved")
|
|
19
|
+
}
|
|
20
|
+
cookie, err = r.Cookie("orgStrId")
|
|
21
|
+
if org == "" {
|
|
22
|
+
if err != http.ErrNoCookie {
|
|
23
|
+
t.Error("unexpected orgStrId cookie in legacy request")
|
|
24
|
+
}
|
|
25
|
+
} else if err != nil || cookie.Value != org {
|
|
26
|
+
t.Error("missing organization cookie")
|
|
27
|
+
}
|
|
28
|
+
w.WriteHeader(http.StatusOK)
|
|
29
|
+
}))
|
|
30
|
+
defer server.Close()
|
|
31
|
+
agent := NewAgentHubACPAgent(server.URL, "agent", "legacy", "", "1", "", "user_input", nil, true)
|
|
32
|
+
agent.orgStrID = org
|
|
33
|
+
agent.sessions["session"] = &SessionState{ThreadID: "thread"}
|
|
34
|
+
response, err := agent.doRequest(http.MethodGet, "/probe", nil, nil)
|
|
35
|
+
if err != nil {
|
|
36
|
+
t.Fatal(err)
|
|
37
|
+
}
|
|
38
|
+
if err := response.Body.Close(); err != nil {
|
|
39
|
+
t.Fatal(err)
|
|
40
|
+
}
|
|
41
|
+
if err := agent.streamPrompt("session", "hello"); err != nil {
|
|
42
|
+
t.Fatal(err)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -27,6 +27,7 @@ func main() {
|
|
|
27
27
|
serviceURL := flag.String("service-url", envOr("AGENT_SERVICE_URL", "http://localhost:8080"), "Base URL of the AgentHub service")
|
|
28
28
|
agentName := flag.String("agent-name", envOr("AGENT_NAME", ""), "Agent (graph_name) to proxy")
|
|
29
29
|
tsTenant := flag.String("ts-tenant", envOr("TS_TENANT", ""), "TSTenant auth cookie")
|
|
30
|
+
orgStrID := flag.String("org-str-id", envOr("ORG_STR_ID", ""), "WisePortal organization ID")
|
|
30
31
|
eiToken := flag.String("ei-token", envOr("EI_TOKEN", ""), "EIToken auth cookie")
|
|
31
32
|
agentVersion := flag.String("agent-version", envOr("AGENT_VERSION", "0.1.0"), "Agent version string")
|
|
32
33
|
userInputField := flag.String("user-input-field", envOr("USER_INPUT_FIELD", "user_input"), "Field name for user input")
|
|
@@ -88,6 +89,7 @@ func main() {
|
|
|
88
89
|
*serviceURL, *agentName, *tsTenant, *eiToken,
|
|
89
90
|
*agentVersion, "", *userInputField, extraInputs, *streamMode,
|
|
90
91
|
)
|
|
92
|
+
agent.orgStrID = *orgStrID
|
|
91
93
|
|
|
92
94
|
if err := runStdioServer(agent); err != nil {
|
|
93
95
|
log.Fatalf("Server error: %v", err)
|
|
@@ -112,6 +114,8 @@ Flags:
|
|
|
112
114
|
(env: AGENT_NAME)
|
|
113
115
|
-ts-tenant string
|
|
114
116
|
TSTenant auth cookie (env: TS_TENANT)
|
|
117
|
+
-org-str-id string
|
|
118
|
+
WisePortal organization ID (env: ORG_STR_ID)
|
|
115
119
|
-ei-token string
|
|
116
120
|
EIToken auth cookie (env: EI_TOKEN)
|
|
117
121
|
-agent-version string
|
|
@@ -300,7 +304,7 @@ func (a *AgentHubACPAgent) handleLoadSession(req jsonrpcRequest) *jsonrpcRespons
|
|
|
300
304
|
|
|
301
305
|
func (a *AgentHubACPAgent) loadSessionHistory(sessionID, cwd string) {
|
|
302
306
|
params := url.Values{
|
|
303
|
-
"user_input_field":
|
|
307
|
+
"user_input_field": {a.userInputField},
|
|
304
308
|
}
|
|
305
309
|
resp, err := a.doRequest("GET", "/api/v1/thread/"+sessionID+"/history", nil, params)
|
|
306
310
|
if err != nil {
|
|
@@ -315,7 +319,7 @@ func (a *AgentHubACPAgent) loadSessionHistory(sessionID, cwd string) {
|
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
var data struct {
|
|
318
|
-
Messages
|
|
322
|
+
Messages []map[string]interface{} `json:"messages"`
|
|
319
323
|
}
|
|
320
324
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
321
325
|
log.Printf("Failed to decode session history: %v", err)
|
|
@@ -327,7 +331,6 @@ func (a *AgentHubACPAgent) loadSessionHistory(sessionID, cwd string) {
|
|
|
327
331
|
log.Printf("Loaded session %s with %d messages", sessionID, len(data.Messages))
|
|
328
332
|
}
|
|
329
333
|
|
|
330
|
-
|
|
331
334
|
// replayMessages replays session history using simple messages list.
|
|
332
335
|
func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[string]interface{}) {
|
|
333
336
|
// Pre-scan: collect tool results by name for including in tool_call events
|
|
@@ -714,9 +717,9 @@ func cmdLoadSession(serviceURL, agentName, sessionID string, limit int, jsonOutp
|
|
|
714
717
|
}
|
|
715
718
|
|
|
716
719
|
var data struct {
|
|
717
|
-
Messages
|
|
718
|
-
GraphName string
|
|
719
|
-
Total
|
|
720
|
+
Messages []map[string]interface{} `json:"messages"`
|
|
721
|
+
GraphName string `json:"graph_name"`
|
|
722
|
+
Total int `json:"total"`
|
|
720
723
|
}
|
|
721
724
|
json.Unmarshal(body, &data)
|
|
722
725
|
|
|
@@ -24,6 +24,7 @@ from agent_api_server.shared.message import (
|
|
|
24
24
|
from langgraph.pregel import Pregel
|
|
25
25
|
from agent_api_server.services.config_builder import build_configurable
|
|
26
26
|
from agent_api_server.services.stream_service import extract_user_id, execute_graph_stream
|
|
27
|
+
from agent_api_server.shared.request_context import request_tenant, organization_context
|
|
27
28
|
|
|
28
29
|
logger = logging.getLogger(__name__)
|
|
29
30
|
api_router = APIRouter()
|
|
@@ -239,7 +240,7 @@ async def stream(
|
|
|
239
240
|
cookie_header = request.headers.get("cookie", "")
|
|
240
241
|
cookie = SimpleCookie()
|
|
241
242
|
cookie.load(cookie_header)
|
|
242
|
-
ts_tenant =
|
|
243
|
+
ts_tenant = request_tenant(request)
|
|
243
244
|
ei_token = cookie.get("EIToken").value if "EIToken" in cookie else None
|
|
244
245
|
|
|
245
246
|
state = await _get_thread_or_404(thread_id, get_graph_name)
|
|
@@ -316,7 +317,7 @@ async def run_thread(
|
|
|
316
317
|
cookie_header = request.headers.get("cookie", "")
|
|
317
318
|
cookie = SimpleCookie()
|
|
318
319
|
cookie.load(cookie_header)
|
|
319
|
-
ts_tenant =
|
|
320
|
+
ts_tenant = request_tenant(request)
|
|
320
321
|
ei_token = cookie.get("EIToken").value if "EIToken" in cookie else None
|
|
321
322
|
|
|
322
323
|
storage = AsyncRedisThreadStorage.get_worker_instance()
|
|
@@ -434,13 +435,14 @@ async def _execute_graph_with_redis_check(
|
|
|
434
435
|
else inputs or {}
|
|
435
436
|
)
|
|
436
437
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
438
|
+
with organization_context(run_cfg.get("configurable", {}).get("TSTenant")):
|
|
439
|
+
execute_task = asyncio.create_task(
|
|
440
|
+
graph_instance.ainvoke(
|
|
441
|
+
invoke_input,
|
|
442
|
+
config=run_cfg,
|
|
443
|
+
stream_mode=["values"]
|
|
444
|
+
)
|
|
442
445
|
)
|
|
443
|
-
)
|
|
444
446
|
|
|
445
447
|
while not execute_task.done():
|
|
446
448
|
if await storage.should_stop(thread_id):
|
|
@@ -15,6 +15,8 @@ from agent_api_server.cache.redis_cache import AsyncRedisThreadStorage
|
|
|
15
15
|
from agent_api_server.configs import global_config
|
|
16
16
|
from agent_api_server.memory.postgres import AsyncPostgresCheckpointer
|
|
17
17
|
from agent_api_server.shared.exceptions import AgentHubException
|
|
18
|
+
from agent_api_server.shared.request_context import OrganizationContextMiddleware
|
|
19
|
+
from agent_api_server.config_center.config_center import initialize_wiseportal_config
|
|
18
20
|
|
|
19
21
|
import logging
|
|
20
22
|
|
|
@@ -72,6 +74,8 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
|
72
74
|
cache = AsyncRedisThreadStorage.get_worker_instance()
|
|
73
75
|
|
|
74
76
|
try:
|
|
77
|
+
await initialize_wiseportal_config()
|
|
78
|
+
|
|
75
79
|
# 1. Infrastructure (always run, these are per-worker)
|
|
76
80
|
await checkpointer.initialize()
|
|
77
81
|
await cache.initialize()
|
|
@@ -134,6 +138,7 @@ async def _cleanup_resources():
|
|
|
134
138
|
def create_fastapi_app() -> FastAPI:
|
|
135
139
|
"""Create and configure FastAPI application."""
|
|
136
140
|
app = FastAPI(lifespan=lifespan)
|
|
141
|
+
app.add_middleware(OrganizationContextMiddleware)
|
|
137
142
|
|
|
138
143
|
# Mount static files
|
|
139
144
|
client_dir = Path(__file__).parent / "client"
|
|
@@ -9,6 +9,7 @@ from uuid import uuid4
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
import redis.asyncio as redis
|
|
11
11
|
from agent_api_server.configs import global_config
|
|
12
|
+
from agent_api_server.shared.redis_url import normalize_redis_url
|
|
12
13
|
from redis.exceptions import RedisError, WatchError
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -65,9 +66,13 @@ class AsyncRedisThreadStorage:
|
|
|
65
66
|
|
|
66
67
|
@staticmethod
|
|
67
68
|
def _get_redis_url() -> str:
|
|
68
|
-
"""
|
|
69
|
-
redis_url = global_config.REDIS_URL
|
|
70
|
-
|
|
69
|
+
"""Normalize the configured Redis URL and redact credentials in logs."""
|
|
70
|
+
redis_url = normalize_redis_url(global_config.REDIS_URL)
|
|
71
|
+
scheme = redis_url.partition("://")[0]
|
|
72
|
+
safe_redis_url = (
|
|
73
|
+
f"{scheme}://[REDACTED]@{redis_url.rsplit('@', 1)[1]}"
|
|
74
|
+
if '@' in redis_url else redis_url
|
|
75
|
+
)
|
|
71
76
|
logger.debug("Using Redis URL: %s", safe_redis_url)
|
|
72
77
|
return redis_url
|
|
73
78
|
|
|
@@ -382,4 +387,4 @@ class AsyncRedisThreadStorage:
|
|
|
382
387
|
return bool(exists) and status != b"stopped"
|
|
383
388
|
except RedisError as e:
|
|
384
389
|
logger.error(f"Failed to check stop flag: {str(e)}")
|
|
385
|
-
return False
|
|
390
|
+
return False
|