一、背景介绍
在企业级自动化与智能体(Agent)快速发展的今天,流程自动化( RPA ) 与 智能服务编排(Agent) 正在逐步融合。 讯飞星辰体系通过 星辰 RPA 与 星辰 Agent 两个开源项目,提供了一套从「流程自动化」到「智能服务执行」的完整解决方案。
本文将围绕以下三个方面展开:
- 介绍 讯飞星辰 RPA 与 星辰 Agent 的核心能力与开源实现
- 说明 Agent 与 RPA 服务之间如何相互调用与协作
- 讲解 如何通过创建 RPA 机器人,实现 Agent 服务的全自动化部署
二、星辰 Agent 与 RPA 的服务协作模式
1. 技术融合趋势
在讯飞星辰体系中,RPA 与 Agent 并不是割裂的两个系统,而是天然互补的关系:
- RPA:负责“怎么做”(流程、步骤、自动化执行)
- Agent:负责“做什么”(具体能力与任务执行)
分层架构:
- 执行层( RPA 客户端) :负责具体的“干活”逻辑,无论是部署开源 Agent 还是进行自动化测试,这里产生的是具体的原子能力。
- 调度层(星辰 Agent ) :负责“大脑”决策。它不直接操作底层页面,而是通过工作流(Workflow)将 RPA 能力串联起来。
大模型前置解析:
- 这是流程中实现“灵活调用”的关键。用户不需要点击特定按钮,而是通过 自然语言(如:“帮我测试一下新发布的登录接口”)触发。
- LLM 将非结构化的语言转为结构化的指令,自动匹配对应的 RPA 机器人。
闭环反馈:
- 流程不仅包含下达指令,还强调了“返回机器人执行结果”。这使得整个系统具备了可观测性,用户能实时感知任务进度和最终产出。
用户可灵活配置 RPA 机器人, 通过 Agent 大模型决策调用或者配置MCP SERVER调用
2. 调用关系说明
官方文档中已经对两者的调用方式进行了详细说明: 👉 www.iflyrpa.com/docs/agent/…
整体模式可以理解为:
- Agent 可以作为一个服务,被 RPA 流程触发
- RPA 也可以被 Agent 反向调用,用于执行复杂自动化流程
三、通过 RPA 机器人实现 Agent 服务的全自动化部署
在实际生产环境中,一个非常有价值的实践是:
通过星辰 Agent 工作流 + 星辰 RPA 自定义机器人,实现开源服务的一键 / 全自动部署和测试。
1. 整体思路
2. 自动化部署流程说明
用户可以在文章末尾加入社区, 申请已经上架的机器人案例,用户只需提供必要参数,即可一键完成 Agent 的部署
(1)环境与前置条件
RPA 机器人执行前,需要满足以下注意事项:
-
运行环境
- Windows 系统
- 下载 RPA 客户端
- 安装 RPA 提供的浏览器插件等
- 已正确安装 Docker,且
docker命令可正常使用
-
密钥配置
- 在 RPA 客户端生成密钥,在 Agent 平台配置密钥
-
RPA 外部调用配置
- 创建机器人并发布版本、配置外部调用
-
网络要求
- 为保证资源拉取成功(代码、镜像等),需保持网络通畅
-
代码获取策略
-
如果系统中存在
git命令:- 自动通过 Git 拉取源码
-
如果
git不存在:- 自动下载源码 zip 并解压
- 或由用户手动上传代码 zip 包至:
- ~${work_dir}/source_code
-
-
Docker 镜像准备(离线场景)
-
若无法从远程仓库拉取镜像:
- 用户可提前下载
.tar镜像文件 - 放入
${docker_images}文件夹
- 用户可提前下载
-
镜像命名规则示例:
minio/minio:RELEASE.2025-07-23T15-54-02Z ➔ minio_minio_RELEASE.2025-07-23T15-54-02Z.tar
-
-
远程执行方式
- 通过 OpenSSH 执行命令
- 当前支持 密钥免密登录方式(Beta)
3. 机器人参数说明
(1)必填参数
work_dir
- 自动化部署流程的工作目录,用于存放源码、配置、临时文件等
(2)可选参数
docker_images
- 本地 Docker 镜像文件夹路径(用于离线部署)
ssh
- 是否启用远程命令执行
ssh_host
- 远程命令执行的目标主机地址
env_params
- Agent 启动所需的环境参数(如端口、模式、配置项等)
4. 使用方式
(1)星辰 Agent 平台配置工作流调用
(2)手动执行
(3)通过 RPA 执行器-计划任务定期执行
(4)通过 API 调用
👉 同步调用
👉 异步调用
5. 代码示例
from typing import Any
import subprocess
import urllib.request
import zipfile
from pathlib import Path
import shutil
import os
import glob
from rpahelper.helper import Helper, print, logger
IS_SSH = False
SSH_HOST = ''
def main(*args, **kwargs) -> Any:
h = Helper(**kwargs)
params = h.params()
# 获取工作目录
word_dir = params.get('work_dir')
if not word_dir:
raise ValueError("参数 'work_dir' 未提供")
# 获取 ssh 信息
global IS_SSH, SSH_HOST
is_ssh = params.get('ssh', False)
if is_ssh:
IS_SSH = True
SSH_HOST = params.get('ssh_host')
if not SSH_HOST:
raise ValueError("参数 'ssh_host' 未提供")
target_path = os.path.join(word_dir, 'source_code')
code_repo_name = "astron-agent"
# 判断 git 是否安装
git_installed = check_git_installed()
# 获取资源代码
if git_installed:
git_pull_code(target_path, code_repo_name)
else:
get_code_by_zip(target_path, code_repo_name)
print(f"✅ 代码已同步至: {target_path}")
def check_git_installed():
"""检查 git 是否安装"""
try:
print("⚙️ GIT 环境校验中...")
run_cmd("git --version", check=True)
print("✅ GIT 已安装")
return True
except:
print("❌ GIT 未安装")
return False
def get_code_by_zip(target_path, code_repo_name):
url_gitee = 'https://gitee.com/iflytek/astron-agent/repository/archive/main.zip'
url_github = 'https://github.com/iflytek/astron-agent/archive/refs/heads/main.zip'
zip_path = os.path.join(target_path, f"{code_repo_name}.zip")
if not os.path.exists(target_path):
# 确保父目录存在
os.makedirs(target_path, exist_ok=True)
else:
print("📂 文件夹存在资源, 正在处理 ...")
zip_path = dir_operate(target_path, zip_path)
# 判断是否存在有效的资源包文件
if not os.path.exists(zip_path):
try:
download_zip(url_github, target_path, code_repo_name)
extract_zip(zip_path, target_path, code_repo_name)
except Exception as e:
print(f"❌ 下载资源包失败, 请从 {url_gitee} 手动下载资源包或安装 git 命令获取")
raise RuntimeError(f"❌ 下载资源包失败: {e}")
extract_zip(zip_path, target_path, code_repo_name)
def dir_operate(target_path, zip_path):
# 查找所有匹配 astron-agent*.zip 的文件(不区分大小写可加 glob.IGNORECASE)
zip_files = glob.glob(os.path.join(target_path, "astron-agent*.zip"))
if not zip_files:
clear_folder(target_path)
elif len(zip_files) > 1:
raise RuntimeError(f"文件夹下存在多个资源包文件,请处理后重试: {target_path}")
else:
zip_path = os.path.abspath(zip_files[0])
# 遍历当前目录下的所有条目(文件和文件夹)
for item in os.listdir(target_path):
item_path = os.path.join(target_path, item)
if os.path.abspath(item_path) == zip_path:
# 跳过要保留的 ZIP
continue
# 删除文件或文件夹
try:
if os.path.isfile(item_path) or os.path.islink(item_path):
os.remove(item_path)
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
except Exception as e:
logger.error(f"无法删除 {item_path}: {e}")
return zip_path
def git_pull_code(target_path, code_repo_name):
"""
从指定 URL 通过 git 命令拉去代码到目标文件夹。
:param target_path: git 命令工作目录
:param code_repo_name: git 拉去代码的文件夹名称
"""
try:
# 确保父目录存在
os.makedirs(target_path, exist_ok=True)
# 目录存在,检查是否为 Git 仓库
code_repo_path = os.path.join(target_path, code_repo_name)
git_dir = os.path.join(code_repo_path, ".git")
env_example_path = os.path.join(code_repo_path, "docker", "astronAgent", ".env.example")
if not os.path.exists(git_dir):
# 目录不存在,执行 clone
print(f"📁 目录不存在,正在克隆仓库到: {target_path}")
# 执行 git clone
try:
run_cmd(["git", "clone", "--branch", "main", "--single-branch",
"https://gitee.com/iflytek/astron-agent.git",
code_repo_name], cwd=target_path)
except Exception as e:
logger.info(f"⚠️ 从 github 拉取代码失败: {e}")
logger.info(f"📥 尝试从 gitee 拉取代码")
run_cmd(["git", "clone", "--branch", "main", "--single-branch",
"https://github.com/iflytek/astron-agent.git",
code_repo_name], cwd=target_path)
else:
# if not os.path.exists(git_dir):
# raise ValueError(f"目录 {code_repo_path} 已存在,但不是有效的 Git 仓库!")
print(f"🔄 目录已存在,正在更新代码: {code_repo_path}")
# 先 fetch 再 pull,避免冲突(可选:加 --rebase)
commands = [
["git", "restore", env_example_path],
["git", "fetch", "--all"],
["git", "checkout", "main"],
["git", "pull", "origin", "main"]
]
for cmd in commands:
run_cmd(cmd, cwd=code_repo_path)
except Exception as e:
print("❌ Git 操作失败:")
raise e
def extract_zip(zip_path, extract_to, code_repo_name):
"""
解压 ZIP 文件并解压到目标文件夹。
:param zip_path: ZIP 文件的下载链接
:param extract_to: 解压目标目录(会自动创建)
:param code_repo_name: 解压目标目录名称
"""
try:
# 解压 ZIP 文件
print(f"正在解压文件 ...")
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
members = zip_ref.namelist()
common_prefix = os.path.commonprefix(members)
zip_ref.extractall(extract_to)
# 重命名文件夹
extract_dir = os.path.join(extract_to, common_prefix)
new_dir = os.path.join(extract_to, code_repo_name)
if os.path.exists(extract_dir):
os.rename(extract_dir, new_dir)
print("解压完成。")
except Exception as e:
raise e
finally:
# 删除临时 ZIP 文件(可选)
if os.path.exists(zip_path):
os.remove(zip_path)
logger.info("ZIP 文件已删除。")
def download_zip(url, extract_to, code_repo_name):
"""
从指定 URL 下载 ZIP 文件并解压到目标文件夹。
:param url: ZIP 文件的下载链接
:param extract_to: 解压目标目录(会自动创建)
:param code_repo_name: 解压目标目录名称
"""
# 确保目标目录存在
os.makedirs(extract_to, exist_ok=True)
# 临时 ZIP 文件路径(可选:也可直接用内存)
zip_path = os.path.join(extract_to, f"{code_repo_name}.zip")
# 下载 ZIP 文件
print(f"正在下载 {url} ...")
urllib.request.urlretrieve(url, zip_path)
print("下载完成。")
def clear_folder(folder_path):
"""
清空指定文件夹中的所有内容(包括子文件夹和文件),但保留文件夹本身。
:param folder_path: 要清空的文件夹路径(字符串或 Path 对象)
"""
folder = Path(folder_path)
if not folder.exists():
folder.mkdir(parents=True, exist_ok=True) # 如果不存在,可选择创建
return
for item in folder.iterdir():
if item.is_dir():
shutil.rmtree(item) # 删除子文件夹及其内容
else:
item.unlink() # 删除文件
def run_cmd(cmd, check=True, capture_output=True, cwd=None):
"""运行命令并返回结果"""
try:
if IS_SSH:
cmd = ['ssh', SSH_HOST, cmd]
result = subprocess.run(cmd, shell=True, text=True, capture_output=capture_output, cwd=cwd)
if check and result.returncode != 0:
raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr)
return result
except Exception as e:
logger.error(f"执行命令失败: {cmd}\n错误: {e}")
raise RuntimeError(f"执行命令失败: {cmd}\n错误: {e}")
from typing import Any
import os
import json
from rpahelper.helper import Helper, print, logger
def main(*args, **kwargs) -> Any:
h = Helper(**kwargs)
params = h.params()
word_dir = params.get('work_dir')
if not word_dir:
raise ValueError("参数 'work_dir' 未提供")
env_params = params.get('env_params')
if not word_dir:
env_params = '{}'
# env 参数转换成对象
env_dict = json.loads(env_params)
# 使用 os.path.join 构建路径
docker_dir = os.path.join(word_dir, 'source_code', 'astron-agent', 'docker', 'astronAgent')
source_file = os.path.join(docker_dir, '.env.example')
target_file = os.path.join(docker_dir, '.env')
delete_source = True
# 确保源文件存在
if not os.path.exists(source_file):
source_file = target_file
delete_source = False
if not os.path.exists(target_file):
raise FileNotFoundError(f"配置文件不存在: {source_file}")
# 读取并替换
with open(source_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
new_lines = []
for line in lines:
stripped = line.strip()
if not stripped or stripped.startswith('#'):
new_lines.append(line)
continue
if '=' in line:
key_part, rest = line.split('=', 1)
key = key_part.strip()
if key in env_dict:
parts = rest.split('#', 1)
new_value = str(env_dict[key])
if len(parts) > 1:
new_line = f"{key}={new_value} #{parts[1]}"
else:
new_line = f"{key}={new_value}\n"
new_lines.append(new_line)
else:
new_lines.append(line)
else:
new_lines.append(line)
# 写入目标文件(覆盖)
with open(target_file, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
print(f"✅ 已生成配置文件: {target_file}")
# 删除源模板文件(可选:根据需求决定是否保留)
if delete_source:
try:
os.remove(source_file)
print(f"🗑️ 已删除模板文件: {source_file}")
except OSError as e:
logger.warning(f"无法删除模板文件 {source_file}: {e}")
return True
from typing import Any
import subprocess
import os
import yaml
import re
from rpahelper.helper import Helper, print, logger
IS_SSH = False
SSH_HOST = ''
def main(*args, **kwargs) -> Any:
h = Helper(**kwargs)
params = h.params()
# 获取工作目录
word_dir = params.get('work_dir')
if not word_dir:
raise ValueError("参数 'work_dir' 未提供")
# 获取 ssh 信息
global IS_SSH, SSH_HOST
is_ssh = params.get('ssh', False)
if is_ssh:
IS_SSH = True
SSH_HOST = params.get('ssh_host')
if not SSH_HOST:
raise ValueError("参数 'ssh_host' 未提供")
# docker 环境校验
check_docker_installed()
images_dir = params.get('images_dir')
if not images_dir:
return True
print(f"⚙️ Docker 镜像校验中...")
compose_file = os.path.join(word_dir, 'source_code', 'astron-agent', 'docker', 'astronAgent', 'docker-compose.yaml')
auth_file = os.path.join(word_dir, 'source_code', 'astron-agent', 'docker', 'astronAgent',
'docker-compose-auth.yml')
if not os.path.isfile(compose_file):
print(f"❌ 找不到配置文件: {compose_file}")
raise RuntimeError(f"❌ 找不到配置文件: {compose_file}")
if not os.path.isfile(auth_file):
print(f"❌ 找不到配置文件: {auth_file}")
raise RuntimeError(f"❌ 找不到配置文件: {auth_file}")
images1 = parse_compose_images(compose_file)
images2 = parse_compose_images(auth_file)
all_images = sorted(set(images1 + images2))
logger.info(f"📦 需要安装以下镜像 ({len(all_images)} 个):")
for img in all_images:
logger.info(f" - {img}")
images_path = os.path.join(images_dir)
if os.path.exists(images_path):
print(f"🔍 存在本地镜像, 比对中...")
load_missing_images(all_images, images_path)
return True
def parse_compose_images(file: any):
"""从 docker-compose.yaml 中提取所有启用的服务的 image 字段"""
with open(file, 'r', encoding='utf-8') as f:
content = f.read()
# 移除 YAML 中被注释掉的行(以 # 开头,且在行首或前面只有空格)
lines = []
for line in content.splitlines():
stripped = line.lstrip()
if not stripped.startswith('#'):
lines.append(line)
else:
# 注释行保留但替换为无害内容,避免 yaml 解析错误
lines.append(re.sub(r'^(\s*)#.*', r'\1', line))
try:
data = yaml.safe_load('\n'.join(lines))
except yaml.YAMLError as e:
raise RuntimeError(f"❌ 解析 docker-compose.yaml 失败: {e}")
images = set()
services = data.get('services', {})
for svc_name, svc_config in services.items():
if isinstance(svc_config, dict) and 'image' in svc_config:
image = svc_config['image']
# 处理变量,如 ${ASTRON_AGENT_VERSION:-latest}
image = re.sub(r'\$\{[^}]*:-([^}]*)\}', r'\1', image)
image = re.sub(r'\$\{[^}]*\}', '', image) # 移除无法解析的变量(留空)
if image.strip():
images.add(image.strip())
return sorted(images)
def check_docker_installed():
"""检查 Docker 是否安装"""
try:
print("⚙️ Docker 环境校验中...")
run_cmd("docker --version", check=True)
print("✅ Docker 已安装")
except:
print("❌ Docker 未安装或不可用,请先安装 Docker 并启动。")
raise RuntimeError("❌ Docker 未安装或不可用,请先安装 Docker 并启动。")
def load_missing_images(images, images_dir):
"""检查镜像是否存在,若不存在则尝试从 ./images 加载"""
# 获取本地已有镜像列表
result = run_cmd("docker images --format \"{{.Repository}}:{{.Tag}}\"", capture_output=True)
local_images = set(line.strip() for line in result.stdout.splitlines() if line.strip())
# logger.debug("✅ 本地存在以下镜像: ")
# for local_image in local_images:
# logger.debug(f" - {local_image}")
print(f"📥 正在加载本地镜像...")
for img in images:
if img in local_images:
logger.info(f"✅ 镜像已存在: {img}")
continue
logger.info(f"ℹ️ 镜像缺失: {img}")
tar_name = normalize_image_name(img)
tar_file = os.path.join(images_dir, tar_name)
if os.path.isfile(tar_file):
logger.info(f"📥 正在加载本地镜像: {tar_file}")
run_cmd(f'docker load -i "{tar_file}"')
else:
logger.info(f"⚠️ 本地未找到镜像包: {tar_file},跳过加载")
def normalize_image_name(image):
"""将镜像名转换为合法的文件名(用于匹配 .tar 文件)"""
# 替换非法字符,只保留字母、数字、点、连字符、下划线
name = re.sub(r'[/:@]', '_', image)
name = re.sub(r'[^\w.-]', '_', name)
return name + ".tar"
def run_cmd(cmd, check=True, capture_output=True):
"""运行命令并返回结果"""
try:
if IS_SSH:
cmd = ['ssh', SSH_HOST, cmd]
result = subprocess.run(cmd, shell=True, text=True, capture_output=capture_output)
if check and result.returncode != 0:
raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr)
return result
except Exception as e:
logger.error(f"执行命令失败: {cmd}\n错误: {e}")
raise RuntimeError(f"执行命令失败: {cmd}\n错误: {e}")
from typing import Any
import subprocess
import os
from rpahelper.helper import Helper, print, logger
IS_SSH = False
SSH_HOST = ''
def main(*args, **kwargs) -> Any:
h = Helper(**kwargs)
params = h.params()
# 获取工作目录
word_dir = params.get('work_dir')
if not word_dir:
raise ValueError("参数 'work_dir' 未提供")
# 获取 ssh 信息
global IS_SSH, SSH_HOST
is_ssh = params.get('ssh', False)
if is_ssh:
IS_SSH = True
SSH_HOST = params.get('ssh_host')
if not SSH_HOST:
raise ValueError("参数 'ssh_host' 未提供")
# docker 环境校验
check_docker_installed()
docker_word_path = os.path.join(word_dir, 'source_code', 'astron-agent', 'docker', 'astronAgent')
docker_file_path = os.path.join(docker_word_path, 'docker-compose-with-auth.yaml')
if not os.path.exists(docker_file_path):
raise RuntimeError(f"Docker Compose 命令执行失败: {docker_file_path}")
print("🚀 Astron Agent 启动中...")
# 执行 docker compose
try:
run_cmd("docker compose -f docker-compose-with-auth.yaml up -d", check=True, cwd=docker_word_path)
except Exception as e:
print("❌ Astron Agent启动失败,请查看日志")
raise e
print("✅ Astron Agent启动成功")
def check_docker_installed():
"""检查 Docker 是否安装"""
try:
print("⚙️ Docker 环境校验中...")
run_cmd("docker --version", check=True)
print("✅ Docker 已安装")
except:
print("❌ Docker 未安装或不可用,请先安装 Docker 并启动。")
raise RuntimeError("❌ Docker 未安装或不可用,请先安装 Docker 并启动。")
def run_cmd(cmd, check=True, capture_output=True, cwd=None):
"""运行命令并返回结果"""
try:
if IS_SSH:
cmd = ['ssh', SSH_HOST, cmd]
result = subprocess.run(cmd, shell=True, text=True, capture_output=capture_output, cwd=cwd)
if check and result.returncode != 0:
raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr)
return result
except Exception as e:
logger.error(f"执行命令失败: {cmd}\n错误: {e}")
raise RuntimeError(f"执行命令失败: {cmd}\n错误: {e}")
6. 自动化部署的价值
通过这种方式,可以实现:
-
Agent 服务的 标准化部署
-
大幅降低人工部署成本
-
支持多环境(开发 / 测试 / 生产)快速复制
-
为后续 Agent + RPA + AI 的组合奠定基础
四、应用案例:Agent 智能调用 RPA 机器人
Agent 平台 现已内置 RPA 工具节点, 用户只需在同一手机号下注册登录即可看到配置的 RPA
- RPA(机器人流程自动化)工具节点是一个强大的自动化执行器,它通过获取RPA平台的机器人资源,直接连接并触发指定的RPA机器人流程,打通不同系统间的数据壁垒。
AstronRPA 内置 300+ 原子能力组件,并通过模块化方式进行组织,例如:
- UI 自动化(鼠标、键盘、窗口)
- 浏览器与网页操作
- Excel / Word / PDF 文档处理
- 网络请求、API 调用
- 系统命令、进程管理
- 图像识别与视觉定位
- AI 能力与模型调用
同时,平台支持 自定义组件扩展,非常适合企业进行能力沉淀与复用。
1. Agent + Rpa 部署开源 Astron Agent
(1)Agent 工作流配置
(2)流程演示
2. Agent + Rpa 自动化创建提示词工程
(1)RPA 流程配置
(2)流程演示
3. 自动创建工作流工程
(1)RPA 流程配置
(2)流程演示
五、总结
通过 讯飞星辰 RPA 与 星辰 Agent 的组合,可以构建一套:
从流程自动化 → 服务执行 → 智能调度 → 自动部署 的完整体系。
-
RPA 负责流程与自动化
-
Agent 负责服务与智能执行
-
两者结合,非常适合:
- AI 服务部署
- 智能体系统落地
- 企业级自动化与智能化升级
如果你正在探索 Agent 服务规模化部署 或 RPA + AI 的融合实践,这套方案非常值得深入研究。
参考链接
👉 www.iflyrpa.com/docs/agent/…
官方支持
- 📧 Technical Support: cbg_rpa_ml@iflytek.com
- 💬 Rpa Community Discussion: GitHub Discussions
- 💬 Agent Community Discussion: GitHub Discussions
- 🐛 Rpa Bug Reports: Issues
- 🐛 Agent Bug Reports: Issues
- 👥 WeChat Work Group: