computenest-agent-integrations 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1 @@
1
+ """ComputeNest Agent Integrations – reusable helpers for building agents."""
@@ -0,0 +1,7 @@
1
+ """ADK (Agent Development Kit) integration helpers."""
2
+
3
+ from .memory import BailianLongTermMemoryService
4
+ from .tools.bailian_mcp import BailianMcpTool
5
+ from .tools.bailian_rag import BailianRagTool
6
+
7
+ __all__ = ["BailianLongTermMemoryService", "BailianRagTool", "BailianMcpTool"]
@@ -0,0 +1,8 @@
1
+ """MCP (Model Context Protocol) helper utilities for ADK agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def hello() -> None:
7
+ """Simple hello helper – placeholder for MCP integration logic."""
8
+ print("Hello from computenest.agents.adk.mcp_helpers!")
@@ -0,0 +1,5 @@
1
+ """ADK memory service integrations."""
2
+
3
+ from .bailian_long_term_memory import BailianLongTermMemoryService
4
+
5
+ __all__ = ["BailianLongTermMemoryService"]
@@ -0,0 +1,194 @@
1
+ """Alibaba Cloud Model Studio long-term memory service for ADK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from datetime import timezone
7
+ import logging
8
+ from typing import Any
9
+
10
+ import aiohttp
11
+ from google.adk.memory.base_memory_service import BaseMemoryService
12
+ from google.adk.memory.base_memory_service import SearchMemoryResponse
13
+ from google.adk.memory.memory_entry import MemoryEntry
14
+ from google.adk.sessions.session import Session
15
+ from google.genai import types
16
+ from typing_extensions import override
17
+
18
+ DEFAULT_ENDPOINT = "https://dashscope.aliyuncs.com/api/v2/apps/memory"
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class BailianLongTermMemoryService(BaseMemoryService):
24
+ """Store and retrieve ADK conversation memory through Bailian."""
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ api_key: str,
30
+ memory_library_id: str,
31
+ add_project_id: str | None = None,
32
+ search_project_ids: list[str] | None = None,
33
+ top_k: int = 5,
34
+ min_score: float = 0.3,
35
+ endpoint: str = DEFAULT_ENDPOINT,
36
+ timeout: float = 30.0,
37
+ ) -> None:
38
+ if not api_key:
39
+ raise ValueError("api_key must not be empty")
40
+ if not memory_library_id:
41
+ raise ValueError("memory_library_id must not be empty")
42
+
43
+ self._api_key = api_key
44
+ self._memory_library_id = memory_library_id
45
+ self._add_project_id = add_project_id
46
+ self._search_project_ids = search_project_ids
47
+ self._top_k = top_k
48
+ self._min_score = min_score
49
+ self._endpoint = endpoint.rstrip("/")
50
+ self._timeout = timeout
51
+
52
+ @override
53
+ async def add_session_to_memory(self, session: Session) -> None:
54
+ """Extract memories from the user and model messages in a session."""
55
+ try:
56
+ messages = _events_to_messages(session.events)
57
+ if not messages:
58
+ logger.debug(
59
+ "No messages to store for session %s",
60
+ session.id,
61
+ )
62
+ return
63
+
64
+ payload: dict[str, Any] = {
65
+ "user_id": session.user_id,
66
+ "messages": messages,
67
+ "memory_library_id": self._memory_library_id,
68
+ "meta_data": {
69
+ "app_name": session.app_name,
70
+ "session_id": session.id,
71
+ },
72
+ }
73
+ if self._add_project_id:
74
+ payload["project_id"] = self._add_project_id
75
+
76
+ result = await self._request("/add", payload)
77
+ logger.info(
78
+ "Stored %d messages for session %s "
79
+ "(created %d memory nodes, request_id=%s)",
80
+ len(messages),
81
+ session.id,
82
+ len(result.get("memory_nodes", [])),
83
+ result.get("request_id", ""),
84
+ )
85
+ except Exception:
86
+ logger.exception(
87
+ "Failed to add session %s to Bailian memory",
88
+ session.id,
89
+ )
90
+ raise
91
+
92
+ @override
93
+ async def search_memory(
94
+ self,
95
+ *,
96
+ app_name: str,
97
+ user_id: str,
98
+ query: str,
99
+ ) -> SearchMemoryResponse:
100
+ """Search the configured Bailian memory library for one ADK user."""
101
+ try:
102
+ payload: dict[str, Any] = {
103
+ "user_id": user_id,
104
+ "messages": [{"role": "user", "content": query}],
105
+ "memory_library_id": self._memory_library_id,
106
+ "top_k": self._top_k,
107
+ "min_score": self._min_score,
108
+ }
109
+ if self._search_project_ids:
110
+ payload["project_ids"] = self._search_project_ids
111
+
112
+ result = await self._request(
113
+ "/memory_nodes/search",
114
+ payload,
115
+ )
116
+ memories = [
117
+ MemoryEntry(
118
+ id=node.get("memory_node_id"),
119
+ content=types.Content(
120
+ role="user",
121
+ parts=[
122
+ types.Part.from_text(
123
+ text=str(node.get("content", ""))
124
+ )
125
+ ],
126
+ ),
127
+ author="memory",
128
+ timestamp=_format_timestamp(node.get("created_at")),
129
+ custom_metadata=node.get("meta_data") or {},
130
+ )
131
+ for node in result.get("memory_nodes", [])
132
+ if node.get("content")
133
+ ]
134
+
135
+ logger.info(
136
+ "Found %d memories for query '%s' (app=%s, user=%s)",
137
+ len(memories),
138
+ query[:50],
139
+ app_name,
140
+ user_id,
141
+ )
142
+ return SearchMemoryResponse(memories=memories)
143
+ except Exception:
144
+ logger.exception(
145
+ "Failed to search Bailian memories "
146
+ "(app=%s, user=%s)",
147
+ app_name,
148
+ user_id,
149
+ )
150
+ return SearchMemoryResponse(memories=[])
151
+
152
+ async def _request(
153
+ self,
154
+ path: str,
155
+ payload: dict[str, Any],
156
+ ) -> dict[str, Any]:
157
+ headers = {
158
+ "Authorization": f"Bearer {self._api_key}",
159
+ "Content-Type": "application/json",
160
+ }
161
+ timeout = aiohttp.ClientTimeout(total=self._timeout)
162
+ async with aiohttp.ClientSession(timeout=timeout) as session:
163
+ async with session.post(
164
+ f"{self._endpoint}{path}",
165
+ headers=headers,
166
+ json=payload,
167
+ ) as response:
168
+ response.raise_for_status()
169
+ return await response.json()
170
+
171
+
172
+ def _events_to_messages(events: list[Any]) -> list[dict[str, str]]:
173
+ messages: list[dict[str, str]] = []
174
+ for event in events:
175
+ content = getattr(event, "content", None)
176
+ if not content or not content.parts:
177
+ continue
178
+
179
+ role = "assistant" if content.role == "model" else content.role
180
+ if role not in ("user", "assistant"):
181
+ continue
182
+
183
+ text = "\n".join(
184
+ part.text for part in content.parts if part.text
185
+ ).strip()
186
+ if text:
187
+ messages.append({"role": role, "content": text})
188
+ return messages
189
+
190
+
191
+ def _format_timestamp(timestamp: int | None) -> str | None:
192
+ if timestamp is None:
193
+ return None
194
+ return datetime.fromtimestamp(timestamp, tz=timezone.utc).isoformat()
File without changes
@@ -0,0 +1,50 @@
1
+ """Build ADK McpToolsets from Bailian MCP configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ from typing import Any
7
+
8
+ from google.adk.tools.mcp_tool import McpToolset
9
+ from google.adk.tools.mcp_tool import StreamableHTTPConnectionParams
10
+
11
+ _DASHSCOPE_API_KEY = "${DASHSCOPE_API_KEY}"
12
+
13
+
14
+ class BailianMcpTool:
15
+ """Build ADK McpToolsets from Bailian MCP JSON configuration."""
16
+
17
+ def __init__(
18
+ self,
19
+ mcp_config: dict[str, Any],
20
+ *,
21
+ dashscope_api_key: str,
22
+ **mcp_toolset_kwargs: Any,
23
+ ) -> None:
24
+ """Create toolsets from Bailian MCP JSON configuration.
25
+
26
+ ``mcp_config`` may be the full JSON (with ``mcpServers``) or just the
27
+ ``mcpServers`` object.
28
+ """
29
+ api_key = dashscope_api_key.strip()
30
+ servers = mcp_config.get("mcpServers", mcp_config)
31
+
32
+ self._toolsets = [
33
+ McpToolset(
34
+ connection_params=StreamableHTTPConnectionParams(
35
+ url=cfg["baseUrl"],
36
+ headers={
37
+ key: value.replace(_DASHSCOPE_API_KEY, api_key)
38
+ for key, value in (cfg.get("headers") or {}).items()
39
+ } or None,
40
+ ),
41
+ **mcp_toolset_kwargs,
42
+ )
43
+ for cfg in servers.values()
44
+ ]
45
+
46
+ def __iter__(self) -> Iterator[McpToolset]:
47
+ return iter(self._toolsets)
48
+
49
+ def __len__(self) -> int:
50
+ return len(self._toolsets)
@@ -0,0 +1,128 @@
1
+ """Alibaba Cloud Model Studio knowledge-base retrieval tool for ADK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from alibabacloud_bailian20231229 import models as bailian_models
9
+ from alibabacloud_bailian20231229.client import Client as BailianClient
10
+ from alibabacloud_tea_openapi.utils_models import Config
11
+ from google.adk.tools.base_tool import BaseTool
12
+ from google.adk.tools.tool_context import ToolContext
13
+ from google.genai import types
14
+ from typing_extensions import override
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class BailianRagTool(BaseTool):
20
+ """Retrieve relevant document chunks from a Bailian knowledge base."""
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ access_key_id: str,
26
+ access_key_secret: str,
27
+ workspace_id: str,
28
+ index_id: str,
29
+ region_id: str = "cn-beijing",
30
+ ) -> None:
31
+ super().__init__(
32
+ name="retrieve_from_knowledge_base",
33
+ description=(
34
+ "Retrieve document chunks from the configured Bailian "
35
+ "knowledge base that are relevant to the user's query."
36
+ ),
37
+ )
38
+ self._workspace_id = workspace_id
39
+ self._index_id = index_id
40
+ self._client = BailianClient(
41
+ Config(
42
+ access_key_id=access_key_id,
43
+ access_key_secret=access_key_secret,
44
+ endpoint=f"bailian.{region_id}.aliyuncs.com",
45
+ )
46
+ )
47
+
48
+ @override
49
+ def _get_declaration(self) -> types.FunctionDeclaration:
50
+ return types.FunctionDeclaration(
51
+ name=self.name,
52
+ description=self.description,
53
+ parameters=types.Schema(
54
+ type=types.Type.OBJECT,
55
+ properties={
56
+ "query": types.Schema(
57
+ type=types.Type.STRING,
58
+ description="The knowledge-base search query.",
59
+ ),
60
+ },
61
+ required=["query"],
62
+ ),
63
+ )
64
+
65
+ @override
66
+ async def run_async(
67
+ self,
68
+ *,
69
+ args: dict[str, Any],
70
+ tool_context: ToolContext,
71
+ ) -> dict[str, Any]:
72
+ del tool_context
73
+
74
+ query = args.get("query")
75
+ if not isinstance(query, str) or not query.strip():
76
+ return {
77
+ "success": False,
78
+ "error_code": "INVALID_QUERY",
79
+ "message": "The query must be a non-empty string.",
80
+ "query": query if isinstance(query, str) else None,
81
+ "chunks": [],
82
+ }
83
+
84
+ normalized_query = query.strip()
85
+ try:
86
+ response = await self._client.retrieve_async(
87
+ self._workspace_id,
88
+ bailian_models.RetrieveRequest(
89
+ index_id=self._index_id,
90
+ query=normalized_query,
91
+ ),
92
+ )
93
+ except Exception:
94
+ logger.exception("Bailian knowledge-base request failed.")
95
+ return {
96
+ "success": False,
97
+ "error_code": "BAILIAN_REQUEST_ERROR",
98
+ "message": "Unable to access the Bailian knowledge base.",
99
+ "query": normalized_query,
100
+ "chunks": [],
101
+ }
102
+
103
+ if not response.body or not response.body.success:
104
+ return {
105
+ "success": False,
106
+ "error_code": "BAILIAN_API_ERROR",
107
+ "message": "Bailian knowledge-base retrieval failed.",
108
+ "query": normalized_query,
109
+ "chunks": [],
110
+ }
111
+
112
+ nodes = response.body.data.nodes if response.body.data else []
113
+ return {
114
+ "success": True,
115
+ "message": (
116
+ f"Retrieved {len(nodes)} knowledge-base chunk(s)."
117
+ if nodes
118
+ else "No relevant knowledge-base content was found."
119
+ ),
120
+ "query": normalized_query,
121
+ "chunks": [
122
+ {
123
+ "text": node.text,
124
+ "score": node.score,
125
+ }
126
+ for node in nodes
127
+ ],
128
+ }
@@ -0,0 +1 @@
1
+ """Common utilities shared across agent integrations."""
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: computenest-agent-integrations
3
+ Version: 0.1.0
4
+ Summary: ComputeNest agent integrations for Google ADK with Alibaba Cloud Bailian
5
+ Author-email: Bali <zhaoyu.zhaoyu@alibaba-inc.com>, Yunyao <ziqiao.lzq@alibaba-inc.com>, Zhongsi <zhongsi.lz@alibaba-inc.com>
6
+ Project-URL: Homepage, https://computenest.console.aliyun.com/welcome
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: aiohttp>=3.9.0
10
+ Requires-Dist: alibabacloud-bailian20231229>=1.0.0
11
+ Requires-Dist: google-adk>=2.0.0
12
+ Requires-Dist: mcp<2.0.0,>=1.24.0
13
+ Requires-Dist: typing-extensions>=4.12.0
14
+ Provides-Extra: app
15
+ Requires-Dist: circus==0.19.0; extra == "app"
16
+ Requires-Dist: uvicorn[standard]==0.34.3; extra == "app"
17
+ Requires-Dist: python-dotenv==1.0.1; extra == "app"
18
+ Requires-Dist: fastapi==0.115.13; extra == "app"
19
+ Requires-Dist: sse-starlette==2.1.3; extra == "app"
20
+ Requires-Dist: pydantic-settings<3.0.0,>=2.2.1; extra == "app"
21
+
22
+ # 详细指南
23
+ Python部署请参考ATA文档:https://ata.atatech.org/articles/11020487205
24
+ 如有疑问,可加入“Aone Python应用部署答疑群”群 钉钉群号: 109775038267
25
+ # 项目结构
26
+ ```tree
27
+ ├── APP-META (构建用到的文件)
28
+ │ └── docker-config (构建镜像时的工作目录)
29
+ │ ├── Dockerfile
30
+ │ └── environment
31
+ │ └── common
32
+ │ ├── base (要使用aone启动应用,必须包含的脚本/配置)
33
+ │ │ ├── app
34
+ │ │ │ └── bin
35
+ │ │ │ ├── appctl.sh
36
+ │ │ │ ├── health.sh
37
+ │ │ │ ├── setenv.sh
38
+ │ │ │ ├── start.sh
39
+ │ │ │ └── stop.sh
40
+ │ │ └── cai
41
+ │ │ ├── bin
42
+ │ │ │ └── nginxctl
43
+ │ │ └── conf
44
+ │ │ ├── mime.types
45
+ │ │ └── nginx-proxy.conf
46
+ │ └── custom (项目自定义的脚本/配置)
47
+ │ └── app
48
+ │ └── bin
49
+ │ ├── health.sh
50
+ │ ├── setenv.sh
51
+ │ ├── start.sh
52
+ │ └── stop.sh
53
+ ├── README.md
54
+ ├── app
55
+ │ ├── __init__.py
56
+ │ ├── api (对外暴露的后端API)
57
+ │ │ ├── __init__.py
58
+ │ │ ├── main.py
59
+ │ │ └── routes
60
+ │ │ ├── __init__.py
61
+ │ │ ├── health.py
62
+ │ │ └── hello.py
63
+ │ ├── config (应用配置相关)
64
+ │ │ ├── __init__.py
65
+ │ │ ├── env.py
66
+ │ │ ├── diamond.py
67
+ │ ├── lifecycle (应用生命周期相关)
68
+ │ │ ├── __init__.py
69
+ │ │ └── manager.py
70
+ │ └──main.py (主文件,初始化FastAPI,设置路由并启动)
71
+
72
+ ├── conf (配置文件)
73
+ │ └── circus.ini (circus用于进程管理,Python应用的启动在此定义)
74
+
75
+ └── computenest-agent-integrations.release (构建文件)
76
+ ```
77
+ # 自定义启动脚本
78
+
79
+ ## 定制启动脚本请写到APP-META/.../common/custom目录下
80
+
81
+ APP-META/.../common/custom目录中存储了健康检查(health.sh)、环境变量(setenv.sh)、启动(start.sh)、停止(stop.sh)等脚本,这些脚本会在容器启动时被执行。如果项目有特殊需求,可以在custom目录下修改对应的脚本,最好不要直接修改base目录下的脚本,否则可能导致项目无法正常启动,如遇项目启动问题,请联系源码模板负责人。
82
+
83
+
84
+ # 本地启动
85
+
86
+ ### 1. 安装依赖
87
+ - 首先安装uv,最简单的是`pip install uv`,其它方法:https://docs.astral.sh/uv/getting-started/installation/
88
+ - uv安装依赖:uv sync
89
+ - 如果不使用uv,可以手动pip安装pyproject.toml中的依赖
90
+
91
+ ### 2. 启动命令
92
+ `uv run python -m app.main`
93
+
94
+ 注意uv run自动激活了.venv虚拟环境,如果要直接运行python -m app.main,首先需要激活虚拟环境: `source .venv/bin/activate`
95
+
96
+ 注意由于main.py被放置在app目录下,所以需要使用`python -m`模块启动,这是Python工程推荐做法,如果你希望直接使用`python main.py`启动,可以将main.py移出app目录,在根目录启动。
97
+
98
+ ### 3. FastAPI 交互式API文档页
99
+ [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)
100
+
101
+ ### 4. VSCode调试
102
+ 创建launch.json时,选择Python调试程序->模块->输入app.main即可
103
+
104
+ # Aone部署
105
+
106
+ ### 1. 部署
107
+
108
+ ### 2. FastAPI 交互式API文档页
109
+ http://{host}/docs
110
+
111
+ # 脚手架介绍
112
+
113
+ 该脚手架用于快速搭建并在Aone部署基于FastAPI的Python后端应用,也可以用于自定义部署参考,如果你希望部署其它框架或开源产品,只需删除app目录,并修改conf/circus.ini中的启动命令,但必须参考app/api/routes/health.py提供/status.taobao健康检查接口,如果你不需要VIPServer的优雅上下线,直接在/status.taobao中返回"success"即可。
@@ -0,0 +1,13 @@
1
+ computenest/agents/__init__.py,sha256=2VdnwimZsqhNzzkpDh8TZvOLdHhzmceZDubAE1eZ5JA,79
2
+ computenest/agents/adk/__init__.py,sha256=m47DjvuNGpBbwUdUW-gr1Cv5tC8XXXyuFhRRA5Ul7Kg,277
3
+ computenest/agents/adk/mcp_helpers.py,sha256=Vesddn1-290bLOJFUDPPB0o8htEoEeFT6JuUp2ImAJc,260
4
+ computenest/agents/adk/memory/__init__.py,sha256=TDFHm7FVCK7T7L3Cjq4ILKAgwuaKpQw4KDX_tMv-2S8,151
5
+ computenest/agents/adk/memory/bailian_long_term_memory.py,sha256=RzhzAj0YiNQ0f-EqQTe8eZB8rflhGyuEf3Os5VuIr58,6495
6
+ computenest/agents/adk/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ computenest/agents/adk/tools/bailian_mcp.py,sha256=wxVYaHeO7eWPLdN-VIQNZYIQjle62SgxWxDmPlYbby4,1505
8
+ computenest/agents/adk/tools/bailian_rag.py,sha256=bbtomRRYXdDCdQcC_JGrSO1pDDF7ZQ_cbEYY8gsTvf4,4144
9
+ computenest/agents/common/__init__.py,sha256=OCQY0h32BorokX2fIwa7FyVlmllo_-LSmQZKh1uxDoQ,57
10
+ computenest_agent_integrations-0.1.0.dist-info/METADATA,sha256=j-dYfBQAxh07s_2qHA0z5R0UeO9JjeK6mjrqAkk3fqI,5196
11
+ computenest_agent_integrations-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ computenest_agent_integrations-0.1.0.dist-info/top_level.txt,sha256=YC9gq6Rtqnrt-MbnbaqEUTXYFGEC2__8Ro3t3uKrrUw,12
13
+ computenest_agent_integrations-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ computenest