Skip to content

Commit b843abe

Browse files
authored
feat(alias): add AgentScope-Runtime support (#95)
1 parent e7f1fdf commit b843abe

File tree

13 files changed

+991
-3
lines changed

13 files changed

+991
-3
lines changed

alias/README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,85 @@ After the first startup, you can log in with the superuser credentials configure
371371
- **Password**: As specified in `FIRST_SUPERUSER_PASSWORD`
372372

373373

374+
### 🌐 Basic Usage -- AgentScope Runtime Deployment
375+
376+
Alias is now fully compatible with [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime/), enabling you to quickly deploy Alias as a standardized backend service. Once launched, you can easily invoke Alias capabilities via the accompanying AgentScope Runtime API.
377+
378+
#### 1. Prerequisites
379+
380+
* **Sandbox & API Keys**: Please refer to the previous sections [🐳 Sandbox Setup (Optional)](#-sandbox-setup-optional) and [🔑 API Keys Configuration](#-api-keys-configuration) to complete the basic environment setup.
381+
* **Environment Variables**: Copy the example environment file from the project root:
382+
```bash
383+
cp .env.example .env
384+
```
385+
* **Start Redis**: Required for caching and session management:
386+
```bash
387+
docker run -d -p 6379:6379 --name alias-redis redis:7-alpine
388+
```
389+
390+
#### 2. Installation & Sandbox Launch
391+
392+
Install the package in editable mode from the project root. This will automatically install the `alias_agent_runtime` CLI tool:
393+
```bash
394+
pip install -e .
395+
```
396+
397+
To ensure proper code execution and file operations, start the sandbox server in a separate terminal:
398+
```bash
399+
runtime-sandbox-server --extension src/alias/runtime/alias_sandbox/alias_sandbox.py
400+
```
401+
402+
#### 3. Launching AgentScope Runtime Service
403+
404+
You can choose to start the service via the CLI or Python code, depending on your use case.
405+
406+
##### Option A: Using CLI (Recommended)
407+
Use the `alias_agent_runtime` command to launch the backend service with one click:
408+
409+
```bash
410+
alias_agent_runtime --host 127.0.0.1 --port 8090 --chat-mode general
411+
```
412+
413+
**Parameter Descriptions**:
414+
* `--host` / `--port`: Specify the service address and port (default port is 8090).
415+
* `--chat-mode`: Set the running mode. Options: `general`, `dr`, `browser`, `ds`, `finance` (default: `general`).
416+
* `--web-ui`: (Optional) Enable AgentScope Runtime WebUI for a visual interaction interface. Skip this if you only need the API.
417+
418+
> **Note**: When enabling `--web-ui` for the first time, the system will automatically install necessary frontend dependencies. This may take a few minutes.
419+
420+
##### Option B: Using Python Code (Recommended for Developers)
421+
If you wish to integrate or customize the launch logic within Python, you can use `AliasRunner` and `AgentApp` as shown below:
422+
423+
```python
424+
from agentscope_runtime.engine.app import AgentApp
425+
from alias.server.runtime.runner.alias_runner import AliasRunner
426+
427+
# 1. Initialize AliasRunner
428+
# default_chat_mode options: "general", "dr", "browser", "ds", "finance"
429+
runner = AliasRunner(
430+
default_chat_mode="general",
431+
)
432+
433+
# 2. Create AgentApp instance
434+
agent_app = AgentApp(
435+
runner=runner,
436+
app_name="Alias",
437+
app_description="An LLM-empowered agent built on AgentScope and AgentScope-Runtime",
438+
)
439+
440+
# 3. Run the service
441+
# Set web_ui=True to enable the visual debugging interface
442+
agent_app.run(host="127.0.0.1", port=8090)
443+
```
444+
445+
#### 4. Accessing the Application
446+
447+
Once the service is running, you can access Alias via:
448+
449+
* **Runtime API Access**: Send standard HTTP POST requests to `http://localhost:8090/process`. This is the primary method for integrating Alias into third-party frontends or backend workflows.
450+
* **Visual Monitoring (Optional)**: If started with the `--web-ui` flag, visit `http://localhost:5173`. This interface allows developers to observe the agent's reasoning process, tool execution traces, and other debugging information.
451+
452+
374453
## ⚖️ License
375454
376455
Alias-Agent is released under the **Apache 2.0 License** – see the [LICENSE](https://github.com/agentscope-ai/agentscope-samples/blob/main/LICENSE) file for details.

alias/README_ZH.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,83 @@ bash script/start_memory_service.sh
371371
- **用户名**:如 `FIRST_SUPERUSER_USERNAME` 所指定 (默认: `alias`)
372372
- **密码**:如 `FIRST_SUPERUSER_PASSWORD` 所指定
373373

374+
### 🌐 基础用法 -- AgentScope Runtime 部署
375+
376+
Alias 现已适配 [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime/),您可以利用 AgentScope Runtime 将 Alias 快速部署为标准后端服务。启动后,通过配套的 AgentScope Runtime API 即可轻松调用 Alias 所提供的服务。
377+
378+
#### 1. 前期准备
379+
380+
* **沙盒设置与 API 密钥**:请参考前文的 [🐳 沙盒设置](#-沙盒设置可选)[🔑 API 密钥配置](#-api-密钥配置) 完成基础环境配置。
381+
* **配置环境变量**:从项目根目录复制示例环境文件:
382+
```bash
383+
cp .env.example .env
384+
```
385+
* **启动 Redis**:缓存和会话管理所需:
386+
```bash
387+
docker run -d -p 6379:6379 --name alias-redis redis:7-alpine
388+
```
389+
390+
#### 2. 安装与沙盒启动
391+
392+
在项目根目录下,以开发模式安装包,这将自动安装 `alias_agent_runtime` 命令行工具:
393+
```bash
394+
pip install -e .
395+
```
396+
397+
为了确保代码执行和文件操作等功能正常,请在另一个终端启动沙盒服务器:
398+
```bash
399+
runtime-sandbox-server --extension src/alias/runtime/alias_sandbox/alias_sandbox.py
400+
```
401+
402+
#### 3. 启动 AgentScope Runtime 服务
403+
404+
您可以根据使用场景,选择通过命令行或 Python 代码启动服务。
405+
406+
##### 选项 A:使用命令行工具(推荐)
407+
使用 `alias_agent_runtime` 命令一键启动后端服务:
408+
409+
```bash
410+
alias_agent_runtime --host 127.0.0.1 --port 8090 --chat-mode general
411+
```
412+
413+
**参数说明**
414+
* `--host` / `--port`: 指定服务的运行地址和端口(默认端口为 8090)。
415+
* `--chat-mode`: 设置运行模式,可选 `general`, `dr`, `browser`, `ds`, `finance`(默认为 `general`)。
416+
* `--web-ui` : (可选) 启用 AgentScope Runtime WebUI 以开启可视化交互界面。若仅需调用 API,请忽略此参数。
417+
418+
> **注意**:首次启动并开启 `--web-ui` 时,系统会自动安装必要的前端依赖包,可能需要花费几分钟时间,请耐心等待。
419+
420+
##### 选项 B:使用代码启动(开发者推荐)
421+
如果您希望在 Python 代码中集成或自定义启动逻辑,可以参考以下示例,结合 `AliasRunner``AgentApp`
422+
423+
```python
424+
from agentscope_runtime.engine.app import AgentApp
425+
from alias.server.runtime.runner.alias_runner import AliasRunner
426+
427+
# 1. 初始化 AliasRunner
428+
# default_chat_mode 可选: "general", "dr", "browser", "ds", "finance"
429+
runner = AliasRunner(
430+
default_chat_mode="general",
431+
)
432+
433+
# 2. 创建 AgentApp 实例
434+
agent_app = AgentApp(
435+
runner=runner,
436+
app_name="Alias",
437+
app_description="An LLM-empowered agent built on AgentScope and AgentScope-Runtime",
438+
)
439+
440+
# 3. 运行服务
441+
# 如需启用可视化调试界面,可设置 web_ui=True
442+
agent_app.run(host="127.0.0.1", port=8090)
443+
```
444+
445+
#### 4. 访问应用程序
446+
447+
服务启动后,您可以通过以下方式访问 Alias:
448+
449+
* **Runtime API 调用**:通过标准 HTTP POST 请求访问 `http://localhost:8090/process`。这是将 Alias 集成至第三方前端或后端工作流的主要方式。
450+
* **可视化监控 (可选)**:若启动时开启了 `--web-ui` 参数,可通过 `http://localhost:5173` 访问 WebUI。该界面主要用于开发者观察智能体的思考过程以及工具调用轨迹等调试信息。
374451

375452
## ⚖️ 许可证
376453

alias/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,5 @@ dev = [
6464
]
6565

6666
[project.scripts]
67-
alias_agent = "alias.cli:main"
67+
alias_agent = "alias.cli:main"
68+
alias_agent_runtime = "alias.server.alias_agent_app:main"
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# -*- coding: utf-8 -*-
22
"""Runtime module for Alias"""
33

4-
__all__ = ["alias_sandbox"]
4+
__all__ = ["alias_sandbox", "runtime_compat"]
55

66
# Import submodule to make it accessible via alias.runtime.alias_sandbox
77
from . import alias_sandbox # noqa: E402, F401
8+
from . import runtime_compat # noqa: E402, F401

alias/src/alias/runtime/runtime_compat/__init__.py

Whitespace-only changes.

alias/src/alias/runtime/runtime_compat/adapter/__init__.py

Whitespace-only changes.
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# -*- coding: utf-8 -*-
2+
import json
3+
from typing import Any, AsyncIterator, Dict, Optional, Union
4+
5+
from agentscope_runtime.engine.helpers.agent_api_builder import ResponseBuilder
6+
from agentscope_runtime.engine.schemas.agent_schemas import (
7+
Content,
8+
ContentType,
9+
FunctionCall,
10+
FunctionCallOutput,
11+
Message,
12+
MessageType,
13+
Role,
14+
)
15+
16+
17+
def _try_deep_parse(val: Any) -> Any:
18+
"""
19+
Recursively parse JSON-like strings into native Python objects.
20+
"""
21+
if isinstance(val, str):
22+
content = val.strip()
23+
if (content.startswith("{") and content.endswith("}")) or (
24+
content.startswith("[") and content.endswith("]")
25+
):
26+
try:
27+
parsed = json.loads(content)
28+
return _try_deep_parse(parsed)
29+
except Exception:
30+
# If nested JSON parsing fails, treat it as a normal string.
31+
return val
32+
return val
33+
if isinstance(val, list):
34+
return [_try_deep_parse(i) for i in val]
35+
if isinstance(val, dict):
36+
return {k: _try_deep_parse(v) for k, v in val.items()}
37+
return val
38+
39+
40+
def _ensure_safe_json_string(val: Any) -> str:
41+
"""
42+
Serialize content into a valid JSON string suitable for WebUI parsing.
43+
"""
44+
parsed_val = _try_deep_parse(val)
45+
if parsed_val is None:
46+
return "{}"
47+
return json.dumps(parsed_val, ensure_ascii=False)
48+
49+
50+
def _extract_alias_output_obj(content_str: str) -> Any:
51+
"""
52+
Extract the `output` object from Alias nested tool-result content.
53+
"""
54+
try:
55+
data = json.loads(content_str)
56+
if isinstance(data, list) and data:
57+
return data[0].get("output")
58+
except Exception:
59+
# Best-effort parse: if the string is not a valid
60+
# JSON or doesn't follow the expected structure,
61+
# fall back to returning the original string.
62+
pass
63+
return content_str
64+
65+
66+
class AliasAdapterState:
67+
def __init__(
68+
self,
69+
message_builder: Any,
70+
content_builder: Any,
71+
runtime_type: str,
72+
):
73+
self.mb = message_builder
74+
self.cb = content_builder
75+
self.runtime_type = runtime_type
76+
self.last_content = ""
77+
self.is_completed = False
78+
79+
80+
async def adapt_alias_message_stream(
81+
source_stream: AsyncIterator[Dict[str, Any]],
82+
) -> AsyncIterator[Union[Message, Content]]:
83+
# pylint: disable=too-many-branches, too-many-statements
84+
rb = ResponseBuilder()
85+
state_map: Dict[str, AliasAdapterState] = {}
86+
last_active_key: Optional[str] = None
87+
88+
yield rb.created()
89+
yield rb.in_progress()
90+
91+
async for chunk in source_stream:
92+
if not isinstance(chunk, dict) or "data" not in chunk:
93+
continue
94+
95+
messages = chunk["data"].get("messages") or []
96+
for item in messages:
97+
alias_id = item.get("id")
98+
inner_msg = item.get("message") or {}
99+
100+
alias_type = inner_msg.get("type")
101+
alias_status = inner_msg.get("status")
102+
tool_call_id = inner_msg.get("tool_call_id") or alias_id
103+
104+
if alias_type in ["thought", "sub_thought"]:
105+
runtime_type = MessageType.REASONING
106+
target_role = Role.ASSISTANT
107+
elif alias_type in ["tool_call", "tool_use"]:
108+
runtime_type = MessageType.PLUGIN_CALL
109+
target_role = Role.ASSISTANT
110+
elif alias_type == "tool_result":
111+
runtime_type = MessageType.PLUGIN_CALL_OUTPUT
112+
target_role = Role.TOOL
113+
else:
114+
runtime_type = MessageType.MESSAGE
115+
target_role = Role.ASSISTANT
116+
117+
state_key = f"{tool_call_id}_{runtime_type}"
118+
119+
if last_active_key and last_active_key != state_key:
120+
old_state = state_map.get(last_active_key)
121+
if old_state and not old_state.is_completed:
122+
yield old_state.cb.complete()
123+
yield old_state.mb.complete()
124+
old_state.is_completed = True
125+
126+
last_active_key = state_key
127+
128+
if state_key not in state_map:
129+
mb = rb.create_message_builder(role=target_role)
130+
mb.message.type = runtime_type
131+
yield mb.get_message_data()
132+
133+
if runtime_type in [
134+
MessageType.PLUGIN_CALL,
135+
MessageType.PLUGIN_CALL_OUTPUT,
136+
]:
137+
c_type = ContentType.DATA
138+
else:
139+
c_type = ContentType.TEXT
140+
141+
cb = mb.create_content_builder(content_type=c_type)
142+
state_map[state_key] = AliasAdapterState(mb, cb, runtime_type)
143+
144+
state = state_map[state_key]
145+
146+
if runtime_type in [MessageType.MESSAGE, MessageType.REASONING]:
147+
raw_text = str(inner_msg.get("content") or "")
148+
149+
if alias_type == "files" and "files" in inner_msg:
150+
raw_text = "\n".join(
151+
[
152+
f"📁 [{f['filename']}]({f['url']})"
153+
for f in inner_msg["files"]
154+
],
155+
)
156+
157+
if raw_text.startswith(state.last_content):
158+
delta = raw_text[len(state.last_content) :]
159+
if delta:
160+
yield state.cb.add_text_delta(delta)
161+
state.last_content = raw_text
162+
else:
163+
yield state.cb.set_text(raw_text)
164+
state.last_content = raw_text
165+
166+
elif runtime_type == MessageType.PLUGIN_CALL:
167+
args = inner_msg.get("arguments") or {}
168+
fc = FunctionCall(
169+
call_id=tool_call_id,
170+
name=inner_msg.get("tool_name") or "tool",
171+
arguments=_ensure_safe_json_string(args),
172+
)
173+
yield state.cb.set_data(fc.model_dump())
174+
175+
elif runtime_type == MessageType.PLUGIN_CALL_OUTPUT:
176+
output_obj = _extract_alias_output_obj(
177+
inner_msg.get("content", ""),
178+
)
179+
fco = FunctionCallOutput(
180+
call_id=tool_call_id,
181+
name=inner_msg.get("tool_name") or "tool",
182+
output=_ensure_safe_json_string(output_obj),
183+
)
184+
yield state.cb.set_data(fco.model_dump())
185+
186+
if alias_status == "finished" and not state.is_completed:
187+
yield state.cb.complete()
188+
yield state.mb.complete()
189+
state.is_completed = True
190+
191+
for state in state_map.values():
192+
if not state.is_completed:
193+
try:
194+
yield state.cb.complete()
195+
yield state.mb.complete()
196+
state.is_completed = True
197+
except Exception:
198+
# Graceful cleanup: ignore errors during the
199+
# finalization phase to ensure the main response
200+
# stream can finish without crashing.
201+
pass
202+
203+
yield rb.completed()

alias/src/alias/runtime/runtime_compat/runner/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)