已合并
support to upload runtime to pypi #103
support to upload runtime to pypi #103
已合并
陈卉创建于 4月20日
19 个文件变更+400-338
@@ -0,0 +1 @@
1+Subproject commit 95637327f3bfcc3e9aca5b46a72192a1ee363d9c
Mapplications/ir_execution_service/pyproject.toml+3-5文件内容审核中,请稍后刷新重试
@@ -1,41 +1,42 @@
1-[build-system]1+[build-system]
2-requires = ["setuptools>=61.0", "wheel"]2+requires = ["setuptools>=61.0", "wheel"]
3-build-backend = "setuptools.build_meta"3+build-backend = "setuptools.build_meta"
4- 4+ 
5-[project]5+[project]
6-name = "lowcode-agent-runner"6+name = "lowcode-agent-runner"
7-version = "0.1.0"7+version = "0.1.0"
8-description = "Lowcode Agent Runner - 从导出的 JSON 配置加载并运行 Agent"8+description = "Lowcode Agent Runner - 从导出的 JSON 配置加载并运行 Agent"
9-requires-python = ">=3.11.4"9+requires-python = ">=3.11.4"
10-dependencies = [10+dependencies = [
11- "openjiuwen[chromadb,obs]==0.1.10",11+ "openjiuwen[chromadb,obs]==0.1.10",
12- "openjiuwen_studio>=0.1.7",12+ "fastapi==0.115.11",
13- "fastapi==0.115.11",13+ "uvicorn[standard]==0.42.0",
14- "uvicorn[standard]==0.42.0",14+ "pydantic==2.11.7",
15- "pydantic==2.11.7",15+ "pydantic[email]==2.11.7",
16- "pydantic[email]==2.11.7",16+ "pydantic-settings==2.5.2",
17- "pydantic-settings==2.5.2",17+ "openjiuwen-studio==0.1.7",
18-]18+ "openjiuwen-runtime-service==0.1.0"
19- 19+]
20-[project.scripts]20+ 
21-lowcode-agent-runner = "openjiuwen_runtime.examples.lowcode_agent.__main__:main"21+[project.scripts]
22- 22+lowcode-agent-runner = "openjiuwen_runtime.examples.lowcode_agent.__main__:main"
23-[tool.uv]23+ 
24-default-groups = ['dev']24+[tool.uv]
25- 25+default-groups = ['dev']
26-[dependency-groups]26+ 
27-dev = [27+[dependency-groups]
28- "coverage==7.8.0",28+dev = [
29- "pytest==8.3.5",29+ "coverage==7.8.0",
30- "pytest-asyncio==1.2.0",30+ "pytest==8.3.5",
31- "pytest-mock==3.14.0",31+ "pytest-asyncio==1.2.0",
32- "ruff==0.9.10",32+ "pytest-mock==3.14.0",
33-]33+ "ruff==0.9.10",
34- 34+]
35-[[tool.uv.index]]35+ 
36-url = "https://mirrors.aliyun.com/pypi/simple/"36+[[tool.uv.index]]
37- 37+url = "https://mirrors.aliyun.com/pypi/simple/"
38-[tool.setuptools.packages.find]38+ 
39-where = ["."]39+[tool.setuptools.packages.find]
40-exclude = ["*.egg-info", "tests"]40+where = ["."]
41-include = ["openjiuwen_runtime*"]41+exclude = ["*.egg-info", "tests"]
42+include = ["openjiuwen_runtime*"]
@@ -12,9 +12,8 @@ dependencies = [
12 "python-dotenv>=1.0.0",12 "python-dotenv>=1.0.0",
13 "fastapi>=0.100.0",13 "fastapi>=0.100.0",
14 "uvicorn>=0.23.0",14 "uvicorn>=0.23.0",
15- # local dependencies15+ "openjiuwen-runtime-foundation==0.1.0",
16- # "openjiuwen-runtime-management @ file:///${PROJECT_ROOT}/../management",16+ "openjiuwen-runtime-management==0.1.0"
17- "openjiuwen-runtime-management==0.0.1",
18]17]
19 18 
20[tool.uv]19[tool.uv]
@@ -7,45 +7,42 @@ We recommend using Runtime from **source**: clone the repository and use the sta
7- Python >= 3.11.47- Python >= 3.11.4
8- `uv` >= 0.25.x8- `uv` >= 0.25.x
9- `git` (clone the repository)9- `git` (clone the repository)
10-- `bash` (Linux / macOS) or **PowerShell** (Windows)10+- `bash` (Linux / macOS) or **Git Bash** (Windows)
11 11 
12## Start Runtime in one step12## Start Runtime in one step
13 13 
14-### 1) Clone the repository14+### 1) Prepare environment configuration
15 15 
16-```shell16+Refer to [Configuration](<2. Configuration.md>) to create and edit the `.env` file, and adjust runtime parameters as needed. Key configuration items are as follows:
17-git clone https://gitcode.com/openJiuwen/agent-runtime.git
18-cd agent-runtime
19-```
20- 
21-### 2) Prepare environment configuration
22- 
23-```shell
24-cd server
25-cp .env.example .env
26-```
27- 
28-Edit `server/.env` as needed. Important fields include:
29 17 
30- `DB_TYPE`: `mysql` or `sqlite`18- `DB_TYPE`: `mysql` or `sqlite`
19+- Set `MODE` to `product`
31- Other runtime settings (e.g. IP address, port)20- Other runtime settings (e.g. IP address, port)
32 21 
33-### 3) Run the startup script22+Set environment variables:
34- 
35-From the **repository root** (`agent-runtime/`):
36- 
37-**Linux / macOS:**
38 23 
39```shell24```shell
40-cd ..25+set -a; source .env; set +a
41-bash scripts/run-server.sh
42```26```
43 27 
44-**Windows (PowerShell):**28+### 2) Install the Runtime package
45 29 
46-```powershell30+**Linux/MacOS/Windows (Git Bash):**
47-cd ..31+ 
48-.\scripts\run-server.ps132+```shell
33+# Create and activate an isolated virtual environment:
34+uv venv
35+source .venv/Scripts/activate
36+# Install the runtime server package:
37+uv pip install agent-runtime-server
38+```
39+ 
40+### 3) Start the Runtime service
41+ 
42+**Linux/MacOS/Windows (Git Bash):**
43+ 
44+```shell
45+python -m openjiuwen_runtime.server.main
49```46```
50 47 
51By default, Runtime is available on port **8186**.48By default, Runtime is available on port **8186**.
@@ -113,6 +113,21 @@ These control how **runtime-server** starts low-code Agents: **`subprocess`**, *
113- **`docker`**: use containerized low-code Agents regardless of request details — for containerized deployments;113- **`docker`**: use containerized low-code Agents regardless of request details — for containerized deployments;
114- **`k8s`**: use Kubernetes-orchestrated low-code Agents — for K8s deployments.114- **`k8s`**: use Kubernetes-orchestrated low-code Agents — for K8s deployments.
115 115 
116+## MODE (Runtime Mode)
117+ 
118+**Purpose:** Only takes effect when DEPLOY_TYPE=subprocess. It determines the loading source of service dependencies.
119+**Available Values:** dev | product
120+ 
121+- **`dev`**: Applied to local source code deployment scenarios.
122+- **`product`**: Default value, applied to production environments running after installation from the official PyPI repository.
123+ 
124+**Modification Suggestions**:
125+ 
126+- Use dev for local development and source code debugging.
127+- Keep the default product in formal production environments.
128+- This configuration is invalid and will not affect service operation when the deployment type is docker or k8s.
129+- Restart the runtime-server after modification for changes to take effect.
130+ 
116**Guidance:** Match the value to your actual environment:131**Guidance:** Match the value to your actual environment:
117 132 
118- Process-based runtime-server → `DEPLOY_TYPE=subprocess`133- Process-based runtime-server → `DEPLOY_TYPE=subprocess`
@@ -3,64 +3,67 @@ Agent Runtime 首版本主要支持低码方式的 Agent 发布:与 openJiuwen
3当前版本推荐通过源码方式使用 Runtime,拉取代码后可通过启动脚本一键完成安装与启动。3当前版本推荐通过源码方式使用 Runtime,拉取代码后可通过启动脚本一键完成安装与启动。
4 4 
5# 环境要求5# 环境要求
6-* python>=3.11.4
7-* uv>=0.25.x
8-* git(用于拉取代码)
9-* bash(Linux/macOS 系统)或 PowerShell(Windows 系统)
10 6 
7+- python>=3.11.4
8+- uv>=0.25.x
9+- git(用于拉取代码)
10+- bash(Linux/macOS 系统)或 Git Bash(Windows 系统)
11 11 
12# 一键启动 Runtime 服务12# 一键启动 Runtime 服务
13 13 
14-### 1) 获取代码14+### 1) 准备环境配置
15+ 
16+请参考 [配置说明](<2. 配置说明.md>) 创建并编辑 `.env` 配置文件,按需调整运行参数,核心配置项如下,重点包括:
17+ 
18+- `DB_TYPE`:支持 `mysql``sqlite`
19+-`MODE`的值改成`product`
20+- 其他运行时相关参数(如ip地址、端口号等)
21+ 
22+设置环境变量
15 23 
16```shell24```shell
17-git clone https://gitcode.com/openJiuwen/agent-runtime.git25+set -a; source .env; set +a
18-cd agent-runtime
19```26```
20 27 
21-### 2) 准备环境配置28+### 2) 安装Runtime包
29+ 
30+- Linux/MacOS/Windows(Git Bash):
22 31 
23```shell32```shell
24-cd agent-runtime/server33+# 首先初始化并启用独立隔离的虚拟运行环境,实现依赖隔离与版本管控:
25-cp .env.example .env34+uv venv
35+source .venv/Scripts/activate
36+# 安装Runtime包
37+uv pip install agent-runtime-server
26```38```
27 39 
28-按需修改 `.env` 中的配置项,重点包括40+### 3) 运行Runtime服务
29-* `DB_TYPE`:支持 `mysql``sqlite`
30-* 其他运行时相关参数(如ip地址、端口号等)
31 41 
32-### 3) 执行启动脚本42+- Linux/MacOS/Windows(Git Bash)
33 43 
34-* Linux/MacOS:44+```shell
35- ```shell45+python -m openjiuwen_runtime.server.main
36- bash scripts/run-server.sh46+```
37- ```
38- 
39-* Windows(PowerShell):
40- ```powershell
41- .\scripts\run-server.ps1
42- ```
43 47 
44默认启动后可通过 `8186` 端口访问 Runtime 服务。48默认启动后可通过 `8186` 端口访问 Runtime 服务。
45 49 
46- 
47# 发布智能体50# 发布智能体
48-Runtime 支持两种智能体发布方式:低码发布(通过 Studio 一键发布)和高码发布(待开放)。
49 51 
52+Runtime 支持两种智能体发布方式:低码发布(通过 Studio 一键发布)和高码发布(待开放)。
50 53 
51## 通过studio发布低码智能体54## 通过studio发布低码智能体
52 55 
53在 Runtime 服务启动后,需要在 openJiuwen Studio 后端的 `.env` 中配置好 Runtime 的地址和端口,完成对接后即可在 Studio 中一键发布 Agent。56在 Runtime 服务启动后,需要在 openJiuwen Studio 后端的 `.env` 中配置好 Runtime 的地址和端口,完成对接后即可在 Studio 中一键发布 Agent。
54 57 
55### 在studio中配置runtime服务连接58### 在studio中配置runtime服务连接
59+ 
56在agent-studio根目录,从`.env.example`复制`.env`文件,打开`.env`文件,修改runtime服务的配置:60在agent-studio根目录,从`.env.example`复制`.env`文件,打开`.env`文件,修改runtime服务的配置:
57 61 
58- 62+```shell
59- ```shell63+ # Runtime 服务配置(样例)
60- # Runtime 服务配置(样例)64+ RUNTIME_HOST=localhost
61- RUNTIME_HOST=localhost65+ RUNTIME_PORT=8186
62- RUNTIME_PORT=818666+```
63- ```
64 67 
65### 发布智能体68### 发布智能体
66 69 
@@ -85,19 +88,16 @@ Runtime 支持两种智能体发布方式:低码发布(通过 Studio 一键
85 88 
86![alt text](images/runtime-management-chat.png)89![alt text](images/runtime-management-chat.png)
87 90 
88- 
89### 查看API调用示例91### 查看API调用示例
90 92 
91api调用页签展示调用运行态智能体的API参数(包括请求方式、url、body、返回结构等)。在请求配置里输入参数,会把你输入的参数自动放入右侧curl/python/javascript示例代码中,便于你直接复制样例代码运行。93api调用页签展示调用运行态智能体的API参数(包括请求方式、url、body、返回结构等)。在请求配置里输入参数,会把你输入的参数自动放入右侧curl/python/javascript示例代码中,便于你直接复制样例代码运行。
92 94 
93![alt text](images/runtime-management-api.png)95![alt text](images/runtime-management-api.png)
94 96 
95- 
96### 下架智能体97### 下架智能体
97 98 
98当你不需要该运行态的智能体时,可以在发布管理页标题栏右侧点击 `下架` 按钮, 页面会弹出确认框,点击确认后,系统将移除该智能体的运行态部署。下架成功后,会提示成功并跳转回智能体列表页。99当你不需要该运行态的智能体时,可以在发布管理页标题栏右侧点击 `下架` 按钮, 页面会弹出确认框,点击确认后,系统将移除该智能体的运行态部署。下架成功后,会提示成功并跳转回智能体列表页。
99 100 
100- 
101![alt text](images/runtime-management-offline.png)101![alt text](images/runtime-management-offline.png)
102 102 
103-## 通过高码发布智能体(待开放)103+## 通过高码发布智能体(待开放)
@@ -126,6 +126,19 @@
126 126 
127- K8s 集群部署的runtime-server: 配置 DEPLOY_TYPE=k8s,同时确保集群环境已配置完成,LOWCODE_IMAGE 配置正确,服务具备 K8s 编排权限。127- K8s 集群部署的runtime-server: 配置 DEPLOY_TYPE=k8s,同时确保集群环境已配置完成,LOWCODE_IMAGE 配置正确,服务具备 K8s 编排权限。
128 128 
129+## MODE(运行模式)
130+ 
131+**作用**:仅在 DEPLOY_TYPE=subprocess 进程部署模式下生效,用于控制低码 Agent 及 Runtime 服务的依赖加载来源,区分本地源码运行与正式包安装运行两种场景。
132+可选值:dev、product
133+ 
134+- dev:本地源码部署模式,优先加载项目本地源码与本地依赖,适用于本地开发、调试、二次开发场景;
135+- product:默认值,生产运行模式,优先从标准依赖环境、PyPI 仓库加载依赖,适用于正式安装部署后的生产环境。
136+ **修改建议**
137+ - 本地开发调试、源码直接运行场景,设置 MODE=dev;
138+ - 线上生产环境、通过依赖包安装部署的场景,保持默认 MODE=product;
139+ - 当部署类型为 docker / k8s 时,该配置不生效、无需修改,不影响服务正常运行;
140+ - 修改配置后需重启 runtime-server 服务方可生效。
141+ 
129# 通用修改注意事项142# 通用修改注意事项
130 143 
131- 所有配置项均为 键=值 格式,等号前后不可有空格,否则会导致参数解析失败;144- 所有配置项均为 键=值 格式,等号前后不可有空格,否则会导致参数解析失败;
@@ -18,7 +18,6 @@ class Settings(BaseSettings):
18 # --------------------------18 # --------------------------
19 DB_TYPE: Literal["mysql", "sqlite"] = Field(default="sqlite", env="DB_TYPE")19 DB_TYPE: Literal["mysql", "sqlite"] = Field(default="sqlite", env="DB_TYPE")
20 20 
21- 
22 # --------------------------21 # --------------------------
23 # 【MySQL 配置】(静态可选,DB_TYPE=mysql 时必选)22 # 【MySQL 配置】(静态可选,DB_TYPE=mysql 时必选)
24 # --------------------------23 # --------------------------
@@ -42,6 +41,7 @@ class Settings(BaseSettings):
42 PORT: int = Field(default=8186, env="PORT")41 PORT: int = Field(default=8186, env="PORT")
43 UV_EXTRA_ARGS: str = Field(default="", env="UV_EXTRA_ARGS")42 UV_EXTRA_ARGS: str = Field(default="", env="UV_EXTRA_ARGS")
44 DEPLOY_TYPE: Literal["subprocess", "docker", "k8s"] = Field(default="subprocess", env="DEPLOY_TYPE")43 DEPLOY_TYPE: Literal["subprocess", "docker", "k8s"] = Field(default="subprocess", env="DEPLOY_TYPE")
44+ MODE: Literal["dev", "product"] = Field(default="product", env="MODE")
45 45 
46 # ========================46 # ========================
47 # 内置 对象47 # 内置 对象
@@ -8,6 +8,7 @@ import subprocess
8import sys8import sys
9import os9import os
10import shlex10import shlex
11+import time
11from pathlib import Path12from pathlib import Path
12from typing import Optional13from typing import Optional
13from .log import get_logger14from .log import get_logger
@@ -155,82 +156,100 @@ class VirtualEnvironmentManager:
155 156 
156 return python_path157 return python_path
157 158 
158- 159+ def pip_install(self, deployment_id: str, package: str) -> bool:
159- def install_whl(self, deployment_id: str, whl_path: str) -> bool:
160 """160 """
161- 在虚拟环境中安装WHL包161+ Install a specified PyPI package in the isolated virtual environment.
162+ Auto retry on network timeout error, with uv cache resume support.
162 163 
163 Args:164 Args:
164- deployment_id: 部署ID165+ deployment_id: Unique identifier for the deployment
165- whl_path: WHL包文件路径166+ package: Package name with optional version specifier,
167+ e.g. requests, fastapi==0.115.11, local wheel path
166 168 
167 Returns:169 Returns:
168- 是否安装成功170+ True if installation succeeded, False otherwise
169- 
170- Raises:
171- RuntimeError: 安装失败
172 """171 """
173 python_executable = self.get_python_executable(deployment_id)172 python_executable = self.get_python_executable(deployment_id)
174- 
175- logger.info("Installing WHL package: %s into %s", whl_path, deployment_id)
176- 
177 uv_extra_args_list = shlex.split(settings.UV_EXTRA_ARGS.strip())173 uv_extra_args_list = shlex.split(settings.UV_EXTRA_ARGS.strip())
178 174 
179- # 使用 uv pip 安装包,通过虚拟环境路径指定目标环境175+ logger.info("Installing package: %s into venv [%s]", package, deployment_id)
176+ 
180 uv_exe = _resolve_uv_executable()177 uv_exe = _resolve_uv_executable()
181 cmd = [178 cmd = [
182 uv_exe, "pip", "install",179 uv_exe, "pip", "install",
183- whl_path,180+ package,
184 "--python", str(python_executable),181 "--python", str(python_executable),
185 *uv_extra_args_list182 *uv_extra_args_list
186 ]183 ]
187- logger.debug("Command: %s", " ".join(cmd))184+ logger.debug("Execute install command: %s", " ".join(cmd))
188 185 
189- try:186+ max_retry = 3
190- env = os.environ.copy()187+ retry_delay = 5
191- env["VIRTUAL_ENV"] = str(self.get_venv_path(deployment_id))188+ retry_count = 0
192- result = subprocess.run(
193- cmd,
194- env=env,
195- capture_output=True,
196- text=True,
197- encoding='utf-8',
198- )
199 189 
200- if result.returncode != 0:190+ timeout_keywords = {
201- logger.warning(191+ "operation timed out",
202- "pip install returned code %s: %s",192+ "network timeout",
203- result.returncode,193+ "request or response body error",
204- result.stderr,194+ "failed to fetch",
195+ "timeout"
196+ }
197+ 
198+ while retry_count < max_retry:
199+ try:
200+ env = os.environ.copy()
201+ env["VIRTUAL_ENV"] = str(self.get_venv_path(deployment_id))
202+ # Extended timeout for slow private pypi
203+ env["UV_HTTP_TIMEOUT"] = "600"
204+ env["UV_REQUEST_TIMEOUT"] = "600"
205+ # Disable concurrent download for unstable internal repo
206+ env["UV_CONCURRENT_DOWNLOADS"] = "1"
207+ 
208+ result = subprocess.run(
209+ cmd,
210+ env=env,
211+ capture_output=True,
212+ text=True,
213+ encoding="utf-8",
205 )214 )
206 215 
207- result = subprocess.run(216+ if result.returncode == 0:
208- [str(python_executable), "-m", "pip", "list", "--format=json"],217+ logger.info("Package installed successfully: %s", package)
209- capture_output=True,218+ return True
210- text=True,
211- encoding='utf-8',
212- )
213 219 
214- if result.returncode == 0:220+ # Check whether error is network timeout related
215- import json221+ stderr_lower = result.stderr.lower()
216- installed_packages = json.loads(result.stdout)222+ is_timeout_error = any(kw in stderr_lower for kw in timeout_keywords)
217- whl_name = Path(whl_path).stem.split("-")[0].lower().replace("_", "-")
218- for pkg in installed_packages:
219- if pkg["name"].lower().replace("_", "-") == whl_name:
220- logger.info("WHL package installed successfully: %s", whl_path)
221- return True
222 223 
223- logger.error("WHL package not found in installed list: %s", whl_path)224+ logger.error(
224- raise RuntimeError(225+ "Package install failed | exit_code=%s | retry=%s/%s | stderr=%s",
225- "Failed to install WHL package: package not in pip list"226+ result.returncode, retry_count + 1, max_retry, result.stderr
226- ) from None227+ )
227- else:228+ 
228- logger.error("Failed to verify installation: %s", result.stderr)229+ if not is_timeout_error:
229- raise RuntimeError("Failed to verify WHL installation") from None230+ # Non-timeout error, no retry
231+ return False
232+ 
233+ retry_count += 1
234+ if retry_count < max_retry:
235+ logger.warning(
236+ "Network timeout detected, retry after %s seconds",
237+ retry_delay
238+ )
239+ time.sleep(retry_delay)
240+ 
241+ except Exception as e:
242+ logger.error(
243+ "Exception occurred while installing package %s: %s",
244+ package, str(e)
245+ )
246+ retry_count += 1
247+ if retry_count < max_retry:
248+ time.sleep(retry_delay)
249+ 
250+ logger.error("Reached maximum retry limit, install failed: %s", package)
251+ return False
230 252 
231- except Exception as e:
232- logger.error("Failed to install WHL: %s", e)
233- raise RuntimeError(f"Failed to install WHL package: {e}") from e
234 253 
235 def delete_venv(self, deployment_id: str) -> bool:254 def delete_venv(self, deployment_id: str) -> bool:
236 """255 """
@@ -141,18 +141,22 @@ class LocalSubprocessDeployer(Deployer[SubprocessParams]):
141 venv_path = self.venv_manager.create_venv(deployment_id)141 venv_path = self.venv_manager.create_venv(deployment_id)
142 logger.info("Virtual environment created: %s", venv_path)142 logger.info("Virtual environment created: %s", venv_path)
143 143 
144- # 获取所有 .whl 文件144+ if settings.MODE == "dev":
145- logger.info("dist_path: %s", settings.dist_path)145+ # 开发模式,本地源码部署: 获取所有 .whl 文件
146- whl_files = list(settings.dist_path.glob("*.whl"))146+ logger.info("dist_path: %s", settings.dist_path)
147- if not whl_files:147+ whl_files = list(settings.dist_path.glob("*.whl"))
148- raise RuntimeError(f"No .whl files found in dist directory: {settings.dist_path}")148+ if not whl_files:
149+ raise RuntimeError(f"No .whl files found in dist directory: {settings.dist_path}")
149 150 
150- logger.info("whl_files: %s", whl_files)151+ logger.info("whl_files: %s", whl_files)
151 152 
152- # 循环安装所有 whl 包153+ # 循环安装所有 whl 包
153- for whl_file in whl_files:154+ for whl_file in whl_files:
154- self.venv_manager.install_whl(deployment_id, str(whl_file))155+ self.venv_manager.pip_install(deployment_id, str(whl_file))
155- logger.info("Installed WHL package: %s", whl_file)156+ logger.info("Installed WHL package: %s", whl_file)
157+ else:
158+ # 从 PyPI仓库安装 lowcode-agent-runner
159+ self.venv_manager.pip_install(deployment_id, "lowcode-agent-runner")
156 160 
157 # 获取虚拟环境Python解释器161 # 获取虚拟环境Python解释器
158 python_executable = self.venv_manager.get_python_executable(deployment_id)162 python_executable = self.venv_manager.get_python_executable(deployment_id)
@@ -1,55 +1,55 @@
1-[build-system]1+[build-system]
2-requires = ["setuptools>=61.0", "wheel"]2+requires = ["setuptools>=61.0", "wheel"]
3-build-backend = "setuptools.build_meta"3+build-backend = "setuptools.build_meta"
4- 4+ 
5-[project]5+[project]
6-name = "openjiuwen-runtime-management"6+name = "openjiuwen-runtime-management"
7-version = "0.1.0"7+version = "0.1.0"
8-description = "OpenJiuwen Runtime"8+description = "OpenJiuwen Runtime"
9-requires-python = ">=3.11.4"9+requires-python = ">=3.11.4"
10-dependencies = [10+dependencies = [
11- # service11+ # service
12- "build>=1.0.0",12+ "build>=1.0.0",
13- "aiohttp>=3.9.0",13+ "aiohttp>=3.9.0",
14- # config14+ # config
15- "pyyaml>=6.0",15+ "pyyaml>=6.0",
16- # db16+ # db
17- "sqlalchemy==2.0.48",17+ "sqlalchemy==2.0.41",
18- "pymysql==1.1.1",18+ "pymysql==1.1.1",
19- "cryptography>=42.0.0",19+ "cryptography>=42.0.0",
20- "pydantic==2.11.7",20+ "pydantic==2.11.7",
21- "pydantic-settings==2.5.2",21+ "pydantic-settings==2.5.2",
22- "aiomysql==0.2.0",22+ "aiomysql==0.2.0",
23- "aiosqlite==0.21.0",23+ "aiosqlite==0.21.0",
24- "redis==7.1.0",24+ "redis==7.1.0",
25- # lint25+ # lint
26- "ruff==0.9.10",26+ "ruff==0.9.10",
27- # local dependency27+ "openjiuwen-runtime-foundation==0.1.0"
28-]28+]
29- 29+ 
30-[tool.uv.sources]30+[tool.uv.sources]
31- 31+ 
32-[tool.uv]32+[tool.uv]
33-default-groups = ['dev']33+default-groups = ['dev']
34- 34+ 
35-[dependency-groups]35+[dependency-groups]
36-dev = [36+dev = [
37- # test37+ # test
38- "coverage==7.8.0",38+ "coverage==7.8.0",
39- "pytest==8.3.5",39+ "pytest==8.3.5",
40- "pytest-asyncio==1.2.0",40+ "pytest-asyncio==1.2.0",
41- "pytest-mock==3.14.0",41+ "pytest-mock==3.14.0",
42-]42+]
43- 43+ 
44-[[tool.uv.index]]44+[[tool.uv.index]]
45-url = "https://mirrors.aliyun.com/pypi/simple/"45+url = "https://mirrors.aliyun.com/pypi/simple/"
46-# url = "https://cn-north-4_a8fea7d0bff7453bab6394a0842d727c_047e5eaae5e442c4b9e79e3032d2d36e:=j1AK^h4uU@devrepo.devcloud.cn-north-4.huaweicloud.com/artgalaxy/api/pypi/cn-north-4_a8fea7d0bff7453bab6394a0842d727c_pypi_5_2/simple"46+# url = "https://cn-north-4_a8fea7d0bff7453bab6394a0842d727c_047e5eaae5e442c4b9e79e3032d2d36e:=j1AK^h4uU@devrepo.devcloud.cn-north-4.huaweicloud.com/artgalaxy/api/pypi/cn-north-4_a8fea7d0bff7453bab6394a0842d727c_pypi_5_2/simple"
47- 47+ 
48-[tool.setuptools.packages.find]48+[tool.setuptools.packages.find]
49-where = ["."]49+where = ["."]
50-exclude = ["*.egg-info", "tests"]50+exclude = ["*.egg-info", "tests"]
51-include = ["openjiuwen_runtime*"]51+include = ["openjiuwen_runtime*"]
52- 52+ 
53-[tool.pytest.ini_options]53+[tool.pytest.ini_options]
54-testpaths = ["tests"]54+testpaths = ["tests"]
55-pythonpath = ["."]55+pythonpath = ["."]
Rscripts/run-server.shscripts/deploy_dev_server.sh+13-13
@@ -43,10 +43,14 @@ else
43 SED_I_FLAG="-i"43 SED_I_FLAG="-i"
44fi44fi
45 45 
46-sed ${SED_I_FLAG} '/openjiuwen-runtime-service/d' ${PROJECT_DIR}/applications/lowcode_agent/pyproject.toml46+sed ${SED_I_FLAG} '/"openjiuwen-studio==/d' ${PROJECT_DIR}/applications/lowcode_agent/pyproject.toml
47-sed ${SED_I_FLAG} '/openjiuwen-runtime-foundation/d' ${PROJECT_DIR}/management/pyproject.toml47+sed ${SED_I_FLAG} '/"openjiuwen-runtime-service==/d' ${PROJECT_DIR}/applications/lowcode_agent/pyproject.toml
48-sed ${SED_I_FLAG} '/openjiuwen-runtime-management/d' ${PROJECT_DIR}/server/pyproject.toml48+sed ${SED_I_FLAG} '/"openjiuwen-runtime-foundation==/d' ${PROJECT_DIR}/cli/pyproject.toml
49- 49+sed ${SED_I_FLAG} '/"openjiuwen-runtime-management==/d' ${PROJECT_DIR}/cli/pyproject.toml
50+sed ${SED_I_FLAG} '/"openjiuwen-runtime-foundation==/d' ${PROJECT_DIR}/management/pyproject.toml
51+sed ${SED_I_FLAG} '/"openjiuwen-runtime-foundation==/d' ${PROJECT_DIR}/server/pyproject.toml
52+sed ${SED_I_FLAG} '/"openjiuwen-runtime-management==/d' ${PROJECT_DIR}/server/pyproject.toml
53+sed ${SED_I_FLAG} '/"openjiuwen-runtime-foundation==/d' ${PROJECT_DIR}/service/pyproject.toml
50 54 
51# Auto-resolve DIST_DIR:55# Auto-resolve DIST_DIR:
52# Relative path = based on PROJECT_DIR; Absolute path = keep unchanged56# Relative path = based on PROJECT_DIR; Absolute path = keep unchanged
@@ -60,15 +64,11 @@ echo "✅ Final build output directory (absolute path resolved): ${FINAL_DIST_DI
60rm -rf ${FINAL_DIST_DIR}64rm -rf ${FINAL_DIST_DIR}
61mkdir ${FINAL_DIST_DIR}65mkdir ${FINAL_DIST_DIR}
62 66 
63-# compile studio package from local workspace when available67+# complie dist/openjiuwen_studio-0.1.5-py3-none-any.whl
64-if [ -d "${PROJECT_DIR}/agent-studio/backend" ]; then68+cd ${PROJECT_DIR}/agent-studio/backend
65- cd ${PROJECT_DIR}/agent-studio/backend69+uv sync ${UV_EXTRA_ARGS}
66- uv sync ${UV_EXTRA_ARGS}70+rm -rf dist
67- rm -rf dist71+uv build --out-dir ${FINAL_DIST_DIR} ${UV_EXTRA_ARGS}
68- uv build --out-dir ${FINAL_DIST_DIR} ${UV_EXTRA_ARGS}
69-else
70- echo "⚠️ ${PROJECT_DIR}/agent-studio/backend not found, use openjiuwen_studio from index."
71-fi
72 72 
73# complie dist/openjiuwen-0.1.9-py3-none-any.whl (core library)73# complie dist/openjiuwen-0.1.9-py3-none-any.whl (core library)
74if [ -d "${PROJECT_DIR}/../agent-core" ]; then74if [ -d "${PROJECT_DIR}/../agent-core" ]; then
@@ -0,0 +1,63 @@
1+pip config set global.index-url https://repo.huaweicloud.com/repository/pypi/simple
2+pip config set global.trusted-host repo.huaweicloud.com
3+pip install uv==0.8.13 -i https://repo.huaweicloud.com/repository/pypi/simple
4+ 
5+# 强制卸载旧版本
6+pip uninstall -y setuptools twine -y
7+ 
8+# 安装依赖
9+pip install setuptools==82.0.1 wheel==0.46.3
10+pip install twine==6.2.0
11+ 
12+PROJECT_DIR=${PWD}
13+ 
14+# 按依赖顺序构建
15+for pkg_dir in foundation management service server applications/lowcode_agent
16+do
17+ cd "${PROJECT_DIR}/${pkg_dir}"
18+ 
19+ # 修改自身版本号
20+ sed -i 's/^version = .*/version = "'"${VERSION}"'"/' pyproject.toml
21+ 
22+ # 修改内部依赖版本号
23+ case "${pkg_dir}" in
24+ management|service)
25+ sed -i 's/"openjiuwen-runtime-foundation==.*"/"openjiuwen-runtime-foundation=='"${VERSION}"'"/' pyproject.toml
26+ ;;
27+ cli|server)
28+ sed -i 's/"openjiuwen-runtime-foundation==.*"/"openjiuwen-runtime-foundation=='"${VERSION}"'"/' pyproject.toml
29+ sed -i 's/"openjiuwen-runtime-management==.*"/"openjiuwen-runtime-management=='"${VERSION}"'"/' pyproject.toml
30+ ;;
31+ applications/lowcode_agent)
32+ sed -i 's/"openjiuwen-runtime-service==.*"/"openjiuwen-runtime-service=='"${VERSION}"'"/' pyproject.toml
33+ ;;
34+ *)
35+ ;;
36+ esac
37+ 
38+ 
39+ # 构建(去私有源拉包)
40+ uv sync \
41+ --trusted-host devrepo.devcloud.cn-north-4.huaweicloud.com \
42+ -i "https://${USERNAME}:${PASSWORD}@${HUAWEI_PRIVATE_PYPI}/simple/" \
43+ --index-strategy unsafe-best-match
44+ 
45+ uv build --out-dir dist
46+ 
47+ # 上传私有仓库
48+ twine upload \
49+ --repository-url "https://${HUAWEI_PRIVATE_PYPI}" \
50+ -u "${USERNAME}" \
51+ -p "${PASSWORD}" \
52+ dist/*
53+ 
54+ # 官方 PyPI 上传
55+ echo "UPLOAD_PYPI=${UPLOAD_PYPI}"
56+ if [ "${UPLOAD_PYPI}" == "true" ]; then
57+ twine upload --verbose \
58+ --repository-url "${PYPI_REPOSITORY}" \
59+ --username __token__ \
60+ --password "${PYPI_TOKEN}" \
61+ dist/*
62+ fi
63+done
@@ -50,3 +50,9 @@ UV_EXTRA_ARGS="--native-tls --allow-insecure-host mirrors.aliyun.com -i https:/
50# runtime-server 运行在 Kubernetes 集群中,无论部署请求内容如何,仅启动 K8s 编排版 Agent50# runtime-server 运行在 Kubernetes 集群中,无论部署请求内容如何,仅启动 K8s 编排版 Agent
51DEPLOY_TYPE=subprocess51DEPLOY_TYPE=subprocess
52 52 
53+ 
54+# 运行模式(仅在 DEPLOY_TYPE=subprocess 时生效,决定依赖加载来源)
55+# 可选值:dev | product
56+# - dev: 适用于本地源码部署
57+# - product:默认值,适用于从 PyPI 官方仓库安装后运行的生产环境
58+MODE=dev
@@ -82,14 +82,14 @@ async def health():
82 82 
83 83 
84def prepare_subprocess_deployment(84def prepare_subprocess_deployment(
85- mode: str,85+ deploy_type: str,
86 port: int | None86 port: int | None
87) -> tuple[int, Path]:87) -> tuple[int, Path]:
88 """88 """
89 预处理 subprocess 部署:分配端口、检查端口、获取 WHL 包路径89 预处理 subprocess 部署:分配端口、检查端口、获取 WHL 包路径
90 返回:(最终使用的端口, whl文件路径)90 返回:(最终使用的端口, whl文件路径)
91 """91 """
92- if mode != "subprocess":92+ if deploy_type != "subprocess":
93 return 0, Path("")93 return 0, Path("")
94 94 
95 # 端口分配与验证95 # 端口分配与验证
@@ -105,7 +105,11 @@ def prepare_subprocess_deployment(
105 )105 )
106 logger.info(f"Port: {port}")106 logger.info(f"Port: {port}")
107 107 
108- # 获取 WHL 包路径108+ # PyPI仓库安装模式
109+ if settings.MODE == "product":
110+ return port, Path("")
111+ 
112+ # 开发模式,本地源码部署: 获取 WHL 包路径
109 logger.info(f"dist_path: {settings.dist_path}")113 logger.info(f"dist_path: {settings.dist_path}")
110 whl_path = settings.dist_path / "lowcode_agent_runner-0.1.0-py3-none-any.whl"114 whl_path = settings.dist_path / "lowcode_agent_runner-0.1.0-py3-none-any.whl"
111 if not whl_path.exists():115 if not whl_path.exists():
@@ -1,36 +1,33 @@
1-[build-system]1+[build-system]
2-requires = ["setuptools>=61.0", "wheel"]2+requires = ["setuptools>=61.0", "wheel"]
3-build-backend = "setuptools.build_meta"3+build-backend = "setuptools.build_meta"
4- 4+ 
5-[project]5+[project]
6-name = "agent-runtime-server"6+name = "agent-runtime-server"
7-version = "1.0.0"7+version = "1.0.0"
8-description = "Agent Runtime Manager Server"8+description = "Agent Runtime Manager Server"
9-requires-python = ">=3.11.4"9+requires-python = ">=3.11.4"
10-dependencies = [10+dependencies = [
11- "openjiuwen-runtime-foundation",11+ "fastapi==0.115.11",
12- "fastapi==0.115.11",12+ "uvicorn==0.42.0",
13- "uvicorn==0.42.0",13+ "python-dotenv>=1.0.0",
14- "python-dotenv>=1.0.0",14+ "python-multipart>=0.0.6",
15- "python-multipart>=0.0.6",15+ "openjiuwen-runtime-foundation==0.1.0",
16- # local dependencies16+ "openjiuwen-runtime-management==0.1.0"
17-]17+]
18- 18+ 
19-[tool.uv.sources]19+[tool.uv]
20-openjiuwen-runtime-foundation = { path = "../foundation", editable = true }20+default-groups = ['dev']
21- 21+ 
22-[tool.uv]22+[dependency-groups]
23-default-groups = ['dev']23+dev = [
24- 24+ "ruff==0.9.10",
25-[dependency-groups]25+]
26-dev = [26+ 
27- "ruff==0.9.10",27+[[tool.uv.index]]
28-]28+url = "https://mirrors.aliyun.com/pypi/simple/"
29- 29+ 
30-[[tool.uv.index]]30+[tool.setuptools.packages.find]
31-url = "https://mirrors.aliyun.com/pypi/simple/"31+where = ["."]
32- 32+exclude = ["*.egg-info", "tests"]
33-[tool.setuptools.packages.find]33+include = ["openjiuwen_runtime*"]
34-where = ["."]
35-exclude = ["*.egg-info", "tests"]
36-include = ["openjiuwen_runtime*"]
@@ -1,58 +1,6 @@
1# coding: utf-81# coding: utf-8
2# Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved2# Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved
3 3 
4-"""
5-openjiuwen-runtime-sdk
6- 
7-agent-studio 的 Agent 和 Plugin 运行时 SDK。
8- 
9-导出:
10- AgentApp: 用于部署 Agent 的应用类
11- PluginApp: 用于部署工具的应用类
12- BaseApp: 所有应用的基类
13- AppGroup: 多应用容器,共享端口
14- QueryRequest: Agent 查询的请求模型
15- ResetConversationRequest: 重置对话的请求模型
16- 
17-示例 - AgentApp:
18- from openjiuwen_runtime.sdk import AgentApp
19- 
20- app = AgentApp("MyAgent")
21- 
22- @app.init
23- async def init():
24- app.agent = MyAgent()
25- 
26- @app.query
27- async def query(msgs, request):
28- async for msg, last in app.agent.stream(...):
29- yield msg, last
30- 
31- app.run(port=8090)
32- 
33-示例 - PluginApp:
34- from openjiuwen_runtime.sdk import PluginApp
35- 
36- app = PluginApp("CalculatorTools")
37- 
38- @app.restful.tool(name="add", description="加法运算")
39- async def add(a: float, b: float) -> dict:
40- return {"result": a + b}
41- 
42- app.run(port=8091)
43- 
44-示例 - AppGroup:
45- from openjiuwen_runtime.sdk import AgentApp, AppGroup
46- 
47- customer_app = AgentApp("CustomerAgent")
48- assistant_app = AgentApp("AssistantAgent")
49- 
50- group = AppGroup("MultiAgentGroup")
51- group.mount("/customer", customer_app)
52- group.mount("/assistant", assistant_app)
53- group.run(port=8090)
54-"""
55- 
56from .app import BaseApp, AgentApp, PluginApp, AppGroup4from .app import BaseApp, AgentApp, PluginApp, AppGroup
57from .app import Middleware, MiddlewareContext, LoggingMiddleware5from .app import Middleware, MiddlewareContext, LoggingMiddleware
58from .models import QueryRequest, ResetConversationRequest6from .models import QueryRequest, ResetConversationRequest
@@ -8,18 +8,15 @@ version = "0.1.0"
8description = "OpenJiuwen Runtime"8description = "OpenJiuwen Runtime"
9requires-python = ">=3.11.4"9requires-python = ">=3.11.4"
10dependencies = [10dependencies = [
11- "openjiuwen-runtime-foundation",
12 "fastapi==0.115.11",11 "fastapi==0.115.11",
13 "uvicorn[standard]==0.42.0",12 "uvicorn[standard]==0.42.0",
14 "pydantic==2.11.7",13 "pydantic==2.11.7",
15 "pydantic[email]==2.11.7",14 "pydantic[email]==2.11.7",
16 "pydantic-settings==2.5.2",15 "pydantic-settings==2.5.2",
17 "greenlet>=3.3.1",16 "greenlet>=3.3.1",
17+ "openjiuwen-runtime-foundation==0.1.0"
18]18]
19 19 
20-[tool.uv.sources]
21-openjiuwen-runtime-foundation = { path = "../foundation", editable = true }
22- 
23[tool.uv]20[tool.uv]
24default-groups = ['dev']21default-groups = ['dev']
25 22