chore: initial import of standalone agentscope project
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled

This commit is contained in:
2026-03-02 18:21:40 +08:00
commit a842f1861f
561 changed files with 91892 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
# Clean old build files
rm -rf build/ doctrees/
# Build the html
sphinx-build -M html ./ build
# Remove temporary files (double insurance)
rm -rf build/html/.doctrees
rm -f build/html/.buildinfo
find build/html -name "*.pickle" -delete
find build/html -name "__pycache__" -delete
find build/html -name "*.pyc" -delete
echo "✅ Chinese docs built successfully, temporary files cleaned"

140
docs/tutorial/zh_CN/conf.py Normal file
View File

@@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "AgentScope"
copyright = "2025, Alibaba"
author = "Alibaba Tongyi Lab"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"myst_parser",
"sphinx_gallery.gen_gallery",
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
]
myst_enable_extensions = [
"colon_fence",
]
sphinx_gallery_conf = {
"download_all_examples": False,
"examples_dirs": [
"src",
],
"gallery_dirs": [
"tutorial",
],
"filename_pattern": "src/.*\.py",
"example_extensions": [".py"],
}
templates_path = ["../_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
languages = ["en", "zh_CN"]
language = "zh_CN"
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "furo"
html_title = (
"<span style='font-weight: 700; color: #2196f3;'>AgentScope</span>"
)
html_logo = "../_static/images/logo.svg"
html_favicon = "../_static/images/logo.svg"
html_static_path = ["../_static"]
html_css_files = [
"css/gallery.css",
]
html_js_files = [
"language_switch.js",
]
html_theme_options = {
"footer_icons": [
{
"name": "GitHub",
"url": "https://github.com/agentscope-ai/agentscope",
"html": """
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>
</svg>
""",
"class": "",
},
{
"name": "Discord",
"url": "https://discord.gg/eYMpfnkG8h",
"html": """
<svg stroke="currentColor" fill="currentColor" stroke-width="0" t="1753331148815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5721" width="200" height="200">
<path d="M723.903423 359.138018c-69.65045-52.952793-136.256577-51.476757-136.256576-51.476757l-6.088649 7.564685c83.027027 25.738378 121.127207 62.085766 121.127207 62.085766a387.459459 387.459459 0 0 0-145.297297-46.956397 418.179459 418.179459 0 0 0-98.340901 1.752793 73.801802 73.801802 0 0 1-7.564684 1.476036 357.385225 357.385225 0 0 0-110.702703 30.258739 278.786306 278.786306 0 0 0-28.782703 13.653333S353.049369 339.488288 440.873514 313.657658l-4.612613-6.088649s-66.513874-1.476036-136.164324 51.476757A654.252973 654.252973 0 0 0 230.630631 642.167928s40.867748 71.126486 148.341621 73.801802c0 0 16.697658-22.694054 31.827027-40.867748-62.085766-18.45045-84.77982-57.565405-84.77982-57.565405a130.998198 130.998198 0 0 0 13.653334 7.564684s0 1.568288 1.476036 1.568289c1.476036 1.476036 3.044324 1.476036 4.52036 3.044324a238.748829 238.748829 0 0 0 34.779099 16.605405 513.199279 513.199279 0 0 0 71.218739 21.218018 350.558559 350.558559 0 0 0 125.555315 0 329.894054 329.894054 0 0 0 69.650451-21.218018A247.328288 247.328288 0 0 0 702.685405 618.09009s-24.262342 39.391712-87.824144 57.565405c13.653333 18.45045 31.827027 39.299459 31.827027 39.29946 107.473874-2.952072 148.341622-73.801802 146.773334-72.602523a654.990991 654.990991 0 0 0-69.558199-283.214414zM421.131532 596.77982a54.705586 54.705586 0 0 1 0-109.042162 54.705586 54.705586 0 0 1 0 109.042162z m177.124324 0a54.705586 54.705586 0 1 1 49.908468-54.521081 52.491532 52.491532 0 0 1-49.908468 54.521081z" p-id="5722"></path><path d="M512 1024A512 512 0 1 1 1024 512 512.645766 512.645766 0 0 1 512 1024z m0-972.892252a461.261261 461.261261 0 1 0 461.261261 461.261261 461.261261 461.261261 0 0 0-461.261261-461.261261z" p-id="5723"></path>
</svg>
""",
"class": "",
},
{
"name": "DingTalk",
"url": "https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11",
"html": """
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M512 0C229.205333 0 0 229.205333 0 512s229.205333 512 512 512 512-229.205333 512-512S794.794667 0 512 0z m237.312 480.810667c-1.109333 4.48-3.712 11.093333-7.424 18.986666h0.128l-0.426667 0.682667c-21.504 46.037333-77.610667 136.106667-77.610666 136.106667l-0.298667-0.597334-16.384 28.501334h79.018667l-150.912 200.917333 34.304-136.533333h-62.208l21.589333-90.282667c-17.493333 4.224-38.101333 10.026667-62.592 17.92 0 0-33.109333 19.370667-95.317333-37.333333 0 0-41.984-36.992-17.578667-46.165334 10.410667-3.925333 50.304-8.917333 81.706667-13.226666 42.410667-5.674667 68.48-8.789333 68.48-8.789334s-130.773333 2.005333-161.792-2.901333c-30.976-4.906667-70.4-56.704-78.805334-102.186667 0 0-12.970667-25.002667 27.904-13.226666 40.917333 11.818667 210.005333 46.08 210.005334 46.08S321.109333 411.434667 306.517333 394.922667c-14.634667-16.469333-43.093333-89.770667-39.424-134.869334 0 0 1.621333-11.221333 13.098667-8.192 0 0 162.602667 74.282667 273.792 114.986667 111.104 40.704 207.786667 61.397333 195.328 114.005333z" opacity=".65" p-id="6077"></path>
</svg>
""",
"class": "",
},
],
"light_css_variables": {
"color-brand-primary": "#2196f3",
"color-brand-content": "#2196f3",
"color-admonition-background": "#f8f9fa",
},
"dark_css_variables": {
"color-link": "#2196f3",
"color-link--hover": "#2196f3",
"color-brand-primary": "#64b5f6",
"color-brand-content": "#64b5f6",
},
}
source_suffix = [".md", ".rst"]
# -- Options for API documentation -------------------------------------------
autodoc_member_order = "bysource"
autodoc_typehints = "description"
autodoc_class_signature = "separated"
autodoc_default_options = {
"special-members": "__call__",
}
add_module_names = False
python_display_short_literal_types = True
def skip_member(app, what, name, obj, skip, options):
if name in [
"__call__",
"_format",
"_format_agent_message",
"_format_tool_sequence",
]:
return False
return skip
def setup(app):
app.connect("autodoc-skip-member", skip_member)

View File

@@ -0,0 +1,77 @@
.. AgentScope Doc documentation master file, created by
sphinx-quickstart on Thu Aug 8 15:07:21 2024.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to AgentScope's documentation!
==========================================
.. toctree::
:maxdepth: 1
:caption: Tutorial
tutorial/quickstart_installation
tutorial/quickstart_key_concept
tutorial/quickstart_message
tutorial/quickstart_agent
.. toctree::
:maxdepth: 1
:caption: Workflow
tutorial/workflow_conversation
tutorial/workflow_multiagent_debate
tutorial/workflow_concurrent_agents
tutorial/workflow_routing
tutorial/workflow_handoffs
.. toctree::
:maxdepth: 1
:caption: FAQ
tutorial/faq
.. toctree::
:maxdepth: 1
:caption: Model and Context
tutorial/task_model
tutorial/task_prompt
tutorial/task_token
tutorial/task_memory
tutorial/task_long_term_memory
.. toctree::
:maxdepth: 1
:caption: Tool
tutorial/task_tool
tutorial/task_mcp
tutorial/task_agent_skill
.. toctree::
:maxdepth: 1
:caption: Agent
tutorial/task_agent
tutorial/task_state
tutorial/task_hook
tutorial/task_middleware
tutorial/task_a2a
tutorial/task_realtime
.. toctree::
:maxdepth: 1
:caption: Features
tutorial/task_pipeline
tutorial/task_plan
tutorial/task_rag
tutorial/task_studio
tutorial/task_tracing
tutorial/task_eval
tutorial/task_eval_openjudge
tutorial/task_embedding
tutorial/task_tts
tutorial/task_tuner

View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""
.. _faq:
常见问题
========================================
关于 AgentScope
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*什么是AgentScope*
AgentScope 是一个多智能体框架,旨在提供一种简单高效的方式来构建基于大语言模型的智能体应用程序。
*AgentScope v1.0 与 v0.x 版本有什么区别?*
AgentScope v1.0是对框架的完全重写,配备了新功能和改进。详细变更请参考相关文档。
关于模型
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*如何在 AgentScope 中集成我自己的模型?*
通过继承 ``agentscope.model.ChatModelBase`` 并实现 ``__call__`` 方法来创建您自己的模型。
*AgentScope 支持哪些模型?*
目前AgentScope 内置支持 DashScope、Gemini、OpenAI、Anthropic 和 Ollama API以及与 DeepSeek 和 vLLMs 模型兼容的 ``OpenAIChatModel``。
*如何在 AgentScope 中监控token 使用情况?*
在 AgentScope Studio中我们提供了 token 使用情况的可视化和追踪功能。详情请参考:ref:`studio` 部分。
关于智能体
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*如何创建我自己的智能体?*
您可以选择直接使用 ``ReActAgent`` 类,或者通过继承 ``AgentBase`` 或 ``ReActAgentBase`` 类来创建您自己的智能体。详情请参考 :ref:`agent` 部分。
*如何将智能体的(流式)输出转发到我自己的前端或应用程序?*
使用 ``print`` 函数的前置钩子来转发打印消息。请参考 :ref:`hook` 部分。
关于工具
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*AgentScope 提供了多少工具?*
AgentScope 提供了一套内置工具,包括 ``execute_python_code``、``execute_shell_command``、``write_text_file`` 等。您可以在 ``agentscope.tool`` 模块下找到它们。
关于错误报告
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*如何报告 AgentScope中的错误*
如果您在使用 AgentScope 时遇到错误,请通过在我们的 GitHub仓库中开启一个issue来报告。
*如何报告AgentScope 中的安全漏洞?*
如果您在AgentScope 中发现安全问题,请通过 `阿里巴巴安全响应中心(ASRC) <https://security.alibaba.com/>`_ 向我们报告。
"""

View File

@@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
"""
.. _react-agent:
创建 ReAct 智能体
====================
AgentScope 在 ``agentscope.agent`` 模块下提供了开箱即用的 ReAct 智能体 ``ReActAgent`` 供开发者使用。
它同时支持以下功能:
- ✨ 基础功能
- 支持围绕 ``reply``、``observe``、``print``、``_reasoning`` 和 ``_acting`` 的 **钩子函数hooks**
- 支持结构化输出
- ✋ 实时介入Realtime Steering
- 支持用户 **中断**
- 支持自定义 **中断处理**
- 🛠️ 工具
- 支持 **同步/异步** 工具函数
- 支持 **流式** 工具响应
- 支持 **状态化** 的工具管理
- 支持 **并行** 工具调用
- 支持 **MCP** 服务器
- 💾 记忆
- 支持智能体 **自主管理** 长期记忆
- 支持“静态”的长期记忆管理
.. tip:: 有关这些功能的更多详细信息,请参考 :ref:`agent` 部分。本章节中,我们重点介绍如何创建 ReAct 智能体并运行。
"""
from agentscope.agent import ReActAgent, AgentBase
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
import asyncio
import os
from agentscope.tool import Toolkit, execute_python_code
# %%
# 创建 ReAct 智能体
# ------------------------------
# 为了提高灵活性,``ReActAgent`` 类在其构造函数中暴露了以下参数:
#
# .. list-table:: ``ReActAgent`` 类的初始化参数
# :header-rows: 1
#
# * - 参数
# - 进一步阅读
# - 描述
# * - ``name`` (必需)
# -
# - 智能体的名称
# * - ``sys_prompt`` (必需)
# -
# - 智能体的系统提示
# * - ``model`` (必需)
# - :ref:`model`
# - 智能体用于生成响应的模型
# * - ``formatter`` (必需)
# - :ref:`prompt`
# - 提示构建策略,应与使用的模型保持一致
# * - ``toolkit``
# - :ref:`tool`
# - 用于注册/调用工具函数的工具模块
# * - ``memory``
# - :ref:`memory`
# - 用于存储对话历史的短期记忆
# * - ``long_term_memory``
# - :ref:`long-term-memory`
# - 长期记忆
# * - ``long_term_memory_mode``
# - :ref:`long-term-memory`
# - 长期记忆的管理模式:
#
# - ``agent_control``: 允许智能体通过工具函数自己控制长期记忆
# - ``static_control``: 在每次回复reply的开始/结束时,会自动从长期记忆中检索/记录信息
# - ``both``: 同时激活上述两种模式
# * - ``enable_meta_tool``
# - :ref:`tool`
# - 是否启用元工具Meta tool即允许智能体自主管理工具函数
# * - ``parallel_tool_calls``
# - :ref:`agent`
# - 是否允许并行工具调用
# * - ``max_iters``
# -
# - 智能体生成响应的最大迭代次数
# * - ``plan_notebook``
# - :ref:`plan`
# - 计划模块,允许智能体制定和管理计划与子任务
# * - ``print_hint_msg``
# -
# - 是否在终端打印 ``plan_notebook`` 生成的提示消息
#
# 以 DashScope API 为例,我们创建一个智能体对象如下:
async def creating_react_agent() -> None:
"""创建一个 ReAct 智能体并运行一个简单任务。"""
# 准备工具
toolkit = Toolkit()
toolkit.register_tool_function(execute_python_code)
jarvis = ReActAgent(
name="Jarvis",
sys_prompt="你是一个名为 Jarvis 的助手",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=True,
enable_thinking=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
msg = Msg(
name="user",
content="你好Jarvis用 Python 运行 Hello World。",
role="user",
)
await jarvis(msg)
asyncio.run(creating_react_agent())
# %%
# 从零开始创建
# --------------------------------
# 为了支持开发者从零开始创建智能体AgentScope 提供了两个基类:
#
# .. list-table::
# :header-rows: 1
#
# * - 类
# - 抽象方法
# - 描述
# * - ``AgentBase``
# - | ``reply``
# | ``observe``
# | ``handle_interrupt``
# - - 所有智能体的基类,支持 ``reply``、``observe`` 和 ``print`` 函数的前置和后置钩子函数。
# - 在 ``__call__`` 函数内实现了基础的实时介入Realtime Steering逻辑。
# * - ``ReActAgentBase``
# - | ``reply``
# | ``observe``
# | ``handle_interrupt``
# | ``_reasoning``
# | ``_acting``
# - 在 ``AgentBase`` 的基础上添加了两个抽象函数 ``_reasoning`` 和 ``_acting``,以及它们的钩子函数。
#
# 有关智能体类的更多详细信息,请参考 :ref:`agent` 部分。
#
# 以 ``AgentBase`` 类为例,我们可以通过继承它并实现 ``reply`` 方法来创建自定义智能体类。
class MyAgent(AgentBase):
"""自定义智能体类"""
def __init__(self) -> None:
"""初始化智能体"""
super().__init__()
self.name = "Friday"
self.sys_prompt = "你是一个名为 Friday 的助手。"
self.model = DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
)
self.formatter = DashScopeChatFormatter()
self.memory = InMemoryMemory()
async def reply(self, msg: Msg | list[Msg] | None) -> Msg:
"""直接调用大模型,产生回复消息。"""
await self.memory.add(msg)
# 准备提示
prompt = await self.formatter.format(
[
Msg("system", self.sys_prompt, "system"),
*await self.memory.get_memory(),
],
)
# 调用模型
response = await self.model(prompt)
msg = Msg(
name=self.name,
content=response.content,
role="assistant",
)
# 在记忆中记录响应
await self.memory.add(msg)
# 打印消息
await self.print(msg)
return msg
async def observe(self, msg: Msg | list[Msg] | None) -> None:
"""观察消息。"""
# 将消息存储在记忆中
await self.memory.add(msg)
async def handle_interrupt(self) -> Msg:
"""处理中断。"""
# 以固定响应为例
return Msg(
name=self.name,
content="我注意到您打断了我的回复,我能为你做些什么?",
role="assistant",
)
async def run_custom_agent() -> None:
"""运行自定义智能体。"""
agent = MyAgent()
msg = Msg(
name="user",
content="你是谁?",
role="user",
)
await agent(msg)
asyncio.run(run_custom_agent())
# %%
#
# 进一步阅读
# ---------------------
# - :ref:`agent`
# - :ref:`model`
# - :ref:`prompt`
# - :ref:`tool`
#

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
"""
.. _installation:
安装
============================
AgentScope 需要 Python 3.10 或更高版本。您可以从源代码或 PyPI 安装。
从 PyPI 安装
----------------
.. code-block:: bash
pip install agentscope
从源代码安装
----------------
从源代码安装 AgentScope需要从 GitHub 克隆仓库,并通过以下命令安装
.. code-block:: bash
git clone -b main https://github.com/agentscope-ai/agentscope
cd agentscope
pip install -e .
执行以下代码确保 AgentScope 正常安装:
"""
import agentscope
print(agentscope.__version__)
# %%
# 额外依赖
# ----------------------------
#
# 为了满足不同功能的需求AgentScope 提供了额外依赖项。
#
# - full: 包含模型 API 和工具函数的额外依赖项
# - dev: 开发依赖项,包括测试和文档工具
#
# 以 full 模式为例,安装命令根据您的操作系统而有所不同。
#
# 对于 Windows 用户:
#
# .. code-block:: bash
#
# pip install agentscope[full]
#
# 对于 Mac 和 Linux 用户:
#
# .. code-block:: bash
#
# pip install agentscope\[full\]

View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
"""
.. key-concepts:
核心概念
====================================
本章从工程实践的角度介绍 AgentScope 中的核心概念,从而阐明 AgentScope 的设计理念。
.. note:: 介绍核心概念的目标是为了更好的阐明 AgentScope 在工程实践中解决的问题,以及为开发者提供的帮助,而非给出严谨的定义。
状态
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
状态State管理是 AgentScope 框架构建的基础。其中,状态表示对象运行时某一时刻数据的快照。
AgentScope 将对象的“初始化”与“状态管理”分离,对象在初始化后通过 ``load_state_dict`` 和 ``state_dict`` 方法恢复到不同的状态,或导出当前的状态。
在 AgentScope 中智能体Agent、记忆memory、长期记忆Long-term memory和工具模块toolkit都是有状态的对象。
AgentScope 通过支持嵌套式的状态管理,将这些对象的状态管理联系起来。
消息
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
消息message是 AgentScope 最核心的数据结构,用于
- 在智能体之间交换信息,
- 在用户交互界面显示信息,
- 在记忆中存储信息,
- 作为 AgentScope 与不同 LLM API 之间的统一媒介。
工具
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AgentScope 中的“工具”指的是可调用的 Python 对象,包括
- 函数,
- 偏函数Partial function
- 实例方法,
- 类方法,
- 静态方法,以及
- 带有 ``__call__`` 方法的可调用实例。
此外,可调用对象可以是
- 异步或同步调用的,
- 流式或非流式返回结果的。
因此,请放心在 AgentScope 中使用任何调用对象作为智能体的工具。
智能体
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在 AgentScope 中智能体Agent行为被抽象为 ``AgentBase`` 类中的三个核心函数:
- ``reply``:处理传入的消息并生成响应消息。
- ``observe``:接收来自环境或其它智能体的消息,但不返回响应。
- ``print``将消息显示到目标输出例如终端、Web 界面)。
为了支持用户实时介入Realtime SteeringAgentScope 提供了额外
的 ``handle_interrupt`` 函数来处理智能体回复过程中的用户中断。
此外ReAct 智能体是 AgentScope 中最重要的智能体,该智能体的回复过程分为两个阶段:
- 推理Reasoning通过调用 LLM 进行推理并生成工具调用
- 行动Acting执行工具函数。
因此,我们在 ``ReActAgentBase`` 类中提供了两个额外的核心函数,``_reasoning`` 和 ``_acting``。
提示词格式化
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
提示词格式化器Prompt Formatter是 AgentScope 中保证 LLM 兼容性的核心组件,负责将消息对象转换为 LLM API 所需的格式。
此外,诸如提示工程、截断和消息验证等附加功能也可以在格式化器中实现。
在格式化器中,"多智能体"(或"多实体")概念与常见的多智能体编排概念不同。
它专注于给定消息中包含多个身份实体的场景,因此 LLM API 中常用的 ``role`` 字段(通常取值为 "user""assistant""system")无法区分它们。
因此AgentScope 提供 MultiAgentFormatter 来处理这种场景,通常用于游戏、多人聊天和社交仿真。
.. note:: 多智能体工作流 **!=** 格式化器中的多智能体。例如,即使以下代码片段可能涉及多个
智能体(``tool_agent`` 和 ``tool_function`` 的调用者),但是输入的 ``query`` 参数
被包装成了 ``role`` 为 **“user”** 消息,因此 ``role`` 字段仍然可以区分它们。
.. code-block:: python
async def tool_function(query: str) -> str:
\"\"\"调用另一个智能体的工具函数\"\"\"
msg = Msg("user", query, role="user")
tool_agent = Agent(name="Programmer")
return await tool_agent(msg)
理解这种区别有助于开发者了解格式化器部分中单智能体和多智能体的区别。
长期记忆
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
虽然 AgentScope 为短期记忆和长期记忆提供了不同的基类,但是 AgentScope 中并没有严格区分它们的作用。
在我们看来,一切都应该是 **需求驱动的**。只要开发者的需求得到了很好的满足,完全可以只使用一个强大的记忆系统。
这里为了确保 AgentScope 的灵活性,我们为长期记忆提供了两种运行和管理方式,开发者可以根据自己的需要进行选择。
其中“agent_control”模式允许智能体自己主动管理长期记忆而“static_control”则是传统的由开发者管理的长期记忆
模式。
"""

View File

@@ -0,0 +1,267 @@
# -*- coding: utf-8 -*-
"""
.. _message:
创建消息
====================
消息是 AgentScope 中的核心概念,用于支持多模态数据、工具 API、信息存储/交换和提示构建。
一个消息由四个字段组成:
- ``name``
- ``role``
- ``content``,以及
- ``metadata``
这些字段的类型和含义如下:
.. list-table:: 消息对象中的字段
:header-rows: 1
* - 字段
- 类型
- 描述
* - name
- ``str``
- 消息发送者的名称/身份
* - role
- | ``Literal[``
| ``"system",``
| ``"assistant",``
| ``"user"``
| ``]``
- 消息发送者的角色,必须是 "system""assistant""user" 之一。
* - content
- ``str | list[ContentBlock]``
- 消息包含的数据,可以是字符串或 block 的列表。
* - metadata
- ``dict[str, JSONSerializableObject] | None``
- 包含额外元数据的字典,通常用于结构化输出。
.. tip:: - 在具有多个身份实体的应用程序中,``name`` 字段用于区分不同的身份。
- 建议将 ``metadata`` 字段用于结构化输出,在 AgentScope 内置的模块中,``metadata`` 不会参与 LLM 的提示构建。
接下来,我们根据不同的场景分别介绍 ``content`` 字段中支持的不同数据结构block
"""
from agentscope.message import (
Msg,
Base64Source,
TextBlock,
ThinkingBlock,
ImageBlock,
AudioBlock,
VideoBlock,
ToolUseBlock,
ToolResultBlock,
)
import json
# %%
# 创建文本消息
# -----------------------------
# 通过提供 ``name``、``role`` 和 ``content`` 字段来创建消息对象。
#
msg = Msg(
name="Jarvis",
role="assistant",
content="你好!我能怎么帮助你?",
)
print(f"发送者的名称: {msg.name}")
print(f"发送者的角色: {msg.role}")
print(f"消息的内容: {msg.content}")
# %%
# 创建多模态消息
# --------------------------------------
# Message 类通过提供不同的 block 结构来支持多模态内容:
#
# .. list-table:: AgentScope 中的多模态 block
# :header-rows: 1
#
# * - 类
# - 描述
# - 示例
# * - TextBlock
# - 纯文本数据
# - .. code-block:: python
#
# TextBlock(
# type="text",
# text="Hello, world!"
# )
# * - ImageBlock
# - 图像数据
# - .. code-block:: python
#
# ImageBlock(
# type="image",
# source=URLSource(
# type="url",
# url="https://example.com/image.jpg"
# )
# )
# * - AudioBlock
# - 音频数据
# - .. code-block:: python
#
# AudioBlock(
# type="audio",
# source=URLSource(
# type="url",
# url="https://example.com/audio.mp3"
# )
# )
# * - VideoBlock
# - 视频数据
# - .. code-block:: python
#
# VideoBlock(
# type="video",
# source=URLSource(
# type="url",
# url="https://example.com/video.mp4"
# )
# )
#
# 对于 ``ImageBlock``、``AudioBlock`` 和 ``VideoBlock``,还可以使用 base64 编码的字符串作为数据源Source
#
msg = Msg(
name="Jarvis",
role="assistant",
content=[
TextBlock(
type="text",
text="这是一个包含 base64 编码数据的多模态消息。",
),
ImageBlock(
type="image",
source=Base64Source(
type="base64",
media_type="image/jpeg",
data="/9j/4AAQSkZ...",
),
),
AudioBlock(
type="audio",
source=Base64Source(
type="base64",
media_type="audio/mpeg",
data="SUQzBAAAAA...",
),
),
VideoBlock(
type="video",
source=Base64Source(
type="base64",
media_type="video/mp4",
data="AAAAIGZ0eX...",
),
),
],
)
# %%
# 创建推理消息
# --------------------------------------
# ``ThinkingBlock`` 用于支持推理模型,包含模型的思考过程。
#
msg_thinking = Msg(
name="Jarvis",
role="assistant",
content=[
ThinkingBlock(
type="thinking",
thinking="我正在为 AgentScope 构建一个思考块的示例。",
),
TextBlock(
type="text",
text="这是一个思考块的示例。",
),
],
)
# %%
# .. _tool-block:
#
# 创建工具使用/结果消息
# --------------------------------------
# ``ToolUseBlock`` 和 ``ToolResultBlock`` 用于支持工具 API
#
msg_tool_call = Msg(
name="Jarvis",
role="assistant",
content=[
ToolUseBlock(
type="tool_use",
id="343",
name="get_weather",
input={
"location": "Beijing",
},
),
],
)
msg_tool_res = Msg(
name="system",
role="system",
content=[
ToolResultBlock(
type="tool_result",
id="343",
name="get_weather",
output="北京的天气是晴天,温度为 25°C。",
),
],
)
# %%
# .. tip:: AgentScope 中,通常使用 ``role`` 为“system”的消息来记录工具函数的执行结果。有关 AgentScope 中工具的更多信息,请参考 :ref:`tool` 部分。
#
# 序列化和反序列化
# ------------------------------------------------
# 消息对象可以分别通过 ``to_dict`` 和 ``from_dict`` 方法进行序列化和反序列化。
serialized_msg = msg.to_dict()
print(type(serialized_msg))
print(json.dumps(serialized_msg, indent=4, ensure_ascii=False))
# %%
# 从 JSON 格式的数据反序列化消息。
new_msg = Msg.from_dict(serialized_msg)
print(type(new_msg))
print(f'消息的发送者: "{new_msg.name}"')
print(f'发送者的角色: "{new_msg.role}"')
print(f'消息的内容: "{json.dumps(new_msg.content, indent=4, ensure_ascii=False)}"')
# %%
# 属性函数
# ------------------------------------------------
# 为了便于使用 Msg 对象AgentScope 提供了以下函数:
#
# .. list-table:: Msg 对象的函数
# :header-rows: 1
#
# * - 函数
# - 参数
# - 描述
# * - ``get_text_content``
# -
# - 将所有 ``TextBlock`` 中的内容收集到单个字符串中(用 "\\n" 分隔)。
# * - ``get_content_blocks``
# - ``block_type``
# - 返回指定类型的内容块列表。如果未提供 ``block_type``,则以块格式返回全部内容。
# * - ``has_content_blocks``
# - ``block_type``
# - 检查消息是否具有指定类型的内容块。``str`` 内容会被视为 ``TextBlock`` 类型。

View File

@@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
"""
.. _a2a:
A2A 智能体
============================
A2AAgent-to-Agent是一种开放标准协议用于实现不同 AI 智能体之间的互操作通信。
AgentScope 从获取 Agent Card 信息和连接远程智能体两个层面提供对 A2A 协议的支持,涉及到的相关 API 如下:
.. list-table:: A2A 相关类
:header-rows: 1
* - 类
- 描述
* - ``A2AAgent``
- 用于与远程 A2A 智能体通信的智能体类
* - ``A2AChatFormatter``
- 用于在 AgentScope 消息和 A2A 消息/任务格式之间进行转换的格式化器
* - ``AgentCardResolverBase``
- Agent Card 解析器基类
* - ``FileAgentCardResolver``
- 从本地 JSON 文件加载 Agent Card 的解析器
* - ``WellKnownAgentCardResolver``
- 从 URL 的 well-known 路径获取 Agent Card 的解析器
* - ``NacosAgentCardResolver``
- 从 Nacos Agent 注册中心获取 Agent Card 的解析器
本节将演示如何创建 ``A2aAgent`` 并与远程 A2A 智能体进行通信。
.. note:: 注意 A2A 的支持为**实验性功能**,可能会在未来版本中发生变化。同时由于 A2A 协议自身
的局限性,因此功能上 ``A2AAgent`` 无法完全对齐 ``ReActAgent`` 等本地智能体,包括:
- 仅支持 chatbot 场景,即仅支持一个用户与一个智能体之间的对话(不影响 handsoff/router 等使用方式)
- 不支持在对话过程中实时打断
- 不支持 agentic 结构化输出
- 目前实现中,``observe`` 方法收到的消息会被存储在本地,并在调用 ``reply`` 方法时一并发送给远程智能体,因此如果最后若干 ``observe`` 调用后未发生 ``reply`` 调用,则这些消息不会被远程智能体看到
"""
# %%
# 获取 Agent Card
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 首先,我们需要获得一个 Agent Card 来连接对应的智能体。Agent Card 中包含了智能体的名称,描述,能力以及连接方式等信息。
#
# 手动创建 Agent Card 对象
# --------------------------------
#
# 在已知 Agent Card 各项信息的情况下,可以直接从 `a2a.types.AgentCard` 手动创建 Agent Card 对象。
#
from a2a.types import AgentCard, AgentCapabilities
from v2.nacos import ClientConfig
from agentscope.a2a import WellKnownAgentCardResolver, NacosAgentCardResolver
from agentscope.agent import A2AAgent, UserAgent
from agentscope.message import Msg, TextBlock
from agentscope.tool import ToolResponse
# 创建 Agent Card 对象
agent_card = AgentCard(
name="Friday", # 智能体名称
description="一个有趣的聊天伙伴", # 智能体描述
url="http://localhost:8000", # 智能体的 RPC 服务地址
version="1.0.0", # 智能体版本
capabilities=AgentCapabilities( # 智能体能力配置
push_notifications=False,
state_transition_history=True,
streaming=True,
),
default_input_modes=["text/plain"], # 支持的输入格式
default_output_modes=["text/plain"], # 支持的输出格式
skills=[], # 智能体技能列表
)
# %%
#
# 从远程服务获取 Agent Card
# --------------------------------
# 同时AgentScope 也支持通过多种方式动态获取 Agent Card包括从本地文件加载、从远程服务 (well-known server) 的标准路径获取以及从 Nacos 注册中心获取等。
# 这里以 ``WellKnownAgentCardResolver`` 为例,从远程服务的标准路径获取 Agent Card
#
async def agent_card_from_well_known_website() -> AgentCard:
"""从远程服务的 well-known 路径获取 Agent Card 的示例。"""
# 创建 Agent Card 解析器
resolver = WellKnownAgentCardResolver(
base_url="http://localhost:8000",
)
# 获取并返回 Agent Card
return await resolver.get_agent_card()
# %%
# 从本地文件加载 Agent Card
# --------------------------------
#
# ``FileAgentCardResolver`` 类支持从本地 JSON 文件加载 Agent Card适用于配置文件管理的场景。
# 一个 JSON 格式的 Agent Card 样例如下所示:
#
# .. code-block:: json
# :caption: 示例 Agent Card JSON 文件内容
#
# {
# "name": "RemoteAgent",
# "url": "http://localhost:8000",
# "description": "远程 A2A 智能体",
# "version": "1.0.0",
# "capabilities": {},
# "default_input_modes": ["text/plain"],
# "default_output_modes": ["text/plain"],
# "skills": []
# }
#
# 通过 ``FileAgentCardResolver`` 可以方便地加载该文件:
#
async def agent_card_from_file() -> AgentCard:
"""从本地 JSON 文件加载 Agent Card 的示例。"""
from agentscope.a2a import FileAgentCardResolver
# 从 JSON 文件加载 Agent Card
resolver = FileAgentCardResolver(
file_path="./agent_card.json", # JSON 文件路径
)
# 获取并返回 Agent Card
return await resolver.get_agent_card()
# %%
# 从 Nacos 注册中心获取 Agent Card
# --------------------------------
#
# Nacos 是一款开源的动态服务发现、配置管理和服务管理平台,在 3.1.0 版本中引入了 Agent 注册中心功能,支持 A2A 智能体的分布式注册、发现和版本管理。
#
# .. important:: 使用 ``NacosAgentCardResolver`` 的前提是用户已经部署了 3.1.0 版本以上的 Nacos 服务端,部署与注册流程请参考`官方文档 <https://nacos.io/docs/latest/quickstart/quick-start>`_。
#
async def agent_card_from_nacos() -> AgentCard:
"""从 Nacos 注册中心获取 Agent Card 的示例。"""
# 创建 Nacos Agent Card 解析器
resolver = NacosAgentCardResolver(
remote_agent_name="my-remote-agent", # Nacos 中注册的智能体名称
nacos_client_config=ClientConfig(
server_addresses="http://localhost:8848", # Nacos 服务器地址
# 其他可选配置项
),
)
# 获取并返回 Agent Card
return await resolver.get_agent_card()
# %%
# 构建 A2A 智能体
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# AgentScope 提供的 ``A2AAgent`` 类用于与远程 A2A 智能体进行通信,其使用方式与普通智能体类似。
agent = A2AAgent(agent_card=agent_card)
# %%
# 利用 ``A2AAgent``,开发者可以构建 chatbot 场景的聊天,或是封装成工具函数,从而构建 handsoff/router 等更复杂的应用场景。
# 目前 ``A2AAgent`` 支持的格式协议转换由 ``agentscope.formatter.A2AChatFormatter`` 负责,支持
#
# - 将 AgentScope 的 ``Msg`` 消息转换为 A2A 协议的 ``Message`` 格式
# - 将 A2A 协议的响应转换回 AgentScope 的 ``Msg`` 格式
# - 将 A2A 协议的 ``Task`` 相应转换成 AgentScope 的 ``Msg`` 格式
# - 支持文本、图像、音频、视频等多种内容类型
#
async def a2a_in_chatbot() -> None:
"""使用 A2AAgent 进行聊天的示例。"""
user = UserAgent("user")
msg = None
while True:
msg = await user(msg)
if msg.get_text_content() == "exit":
break
msg = await agent(msg)
# %%
# 或是如下封装成工具函数用于调用:
async def create_worker(query: str) -> ToolResponse:
"""通过子智能体完成给定的任务
Args:
query (`str`):
需要子智能体完成的任务描述
"""
res = await agent(
Msg("user", query, "user"),
)
return ToolResponse(
content=[
TextBlock(
type="text",
text=res.get_text_content(),
),
],
)

View File

@@ -0,0 +1,400 @@
# -*- coding: utf-8 -*-
"""
.. _agent:
智能体
=========================
在章我们首先重点介绍 AgentScope 中的 ReAct 智能体,然后简要介绍如何从零开始自定义智能体。
ReAct 智能体
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在 AgentScope 中,``ReActAgent`` 类将各种功能集成到最终实现中,具体包括
.. list-table:: ``ReActAgent`` 的功能特性
:header-rows: 1
* - 功能特性
- 参考文档
* - 支持实时介入Realtime Steering
-
* - 支持记忆压缩
-
* - 支持并行工具调用
-
* - 支持结构化输出
-
* - 支持智能体自主管理工具Meta tool
- :ref:`tool`
* - 支持函数粒度的 MCP 控制
- :ref:`mcp`
* - 支持智能体自主控制长期记忆
- :ref:`long-term-memory`
* - 支持自动状态管理
- :ref:`state`
由于篇幅限制,本章我们仅演示 ``ReActAgent`` 类的前三个功能特性,其它功能我们在对应的章节进行介绍。
"""
import asyncio
import json
import os
from datetime import datetime
import time
from pydantic import BaseModel, Field
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import TextBlock, Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit, ToolResponse
# %%
# 实时控制
# ---------------------------------------
#
# 实时控制指 **允许用户随时中断智能体的回复,介入智能体的执行过程**AgentScope 基于 asyncio 取消机制实现了该功能。
#
# 具体来说AgentScope 中智能体提供了 ``interrupt`` 方法,当该函数被调用时,它将取消当前正在执行的 `reply` 函数,并执行 ``handle_interrupt`` 方法进行后处理。
#
# .. hint:: 结合 :ref:`tool` 中提到的 AgentScope 支持工具函数流式返回结果的功能,工具执行过程中如果执行时间过长或偏离用户期望,用户可以通过在终端中按 Ctrl+C 或在代码中调用智能体的
# ``interrupt`` 方法来中断工具执行。
#
# .. hint:: ``ReActAgent`` 中提供了完善的中断逻辑,智能体的记忆和状态会在中断发生时被正确的保存。
#
# 中断逻辑已在 ``AgentBase`` 类中作为基本功能实现,并提供 ``handle_interrupt`` 抽象方法供用户自定义
# 中断的后处理,如下所示:
#
# .. code-block:: python
#
# # AgentBase 的代码片段
# class AgentBase:
# ...
# async def __call__(self, *args: Any, **kwargs: Any) -> Msg:
# ...
# reply_msg: Msg | None = None
# try:
# self._reply_task = asyncio.current_task()
# reply_msg = await self.reply(*args, **kwargs)
#
# except asyncio.CancelledError:
# # 捕获中断并通过 handle_interrupt 方法处理
# reply_msg = await self.handle_interrupt(*args, **kwargs)
#
# ...
#
# @abstractmethod
# async def handle_interrupt(self, *args: Any, **kwargs: Any) -> Msg:
# pass
#
#
# 在 ``ReActAgent`` 类的实现中,我们返回一个固定消息"I noticed that you have interrupted me. What can I do for you?",如下所示:
#
# .. figure:: ../../_static/images/interruption_zh.gif
# :width: 100%
# :align: center
# :class: bordered-image
# :alt: 中断示例
#
# 中断智能体 ``reply`` 的执行过程
#
# 开发者可以通过覆盖 ``handle_interrupt`` 函数实现自定义的中断后处理逻辑,例如,调用 LLM 生成对中断的简单响应。
#
#
# 记忆压缩
# ----------------------------------------
# 随着对话的不断增长,记忆中的 token 数量可能会超过模型的上下文限制或导致推理速度变慢。
# ``ReActAgent`` 提供了自动记忆压缩功能来解决这个问题。
#
# **基础用法**
#
# 要启用记忆压缩,在初始化 ``ReActAgent`` 时提供一个 ``CompressionConfig`` 实例:
#
# .. code-block:: python
#
# from agentscope.agent import ReActAgent
# from agentscope.token import CharTokenCounter
#
# agent = ReActAgent(
# name="助手",
# sys_prompt="你是一个有用的助手。",
# model=model,
# formatter=formatter,
# compression_config=ReActAgent.CompressionConfig(
# enable=True,
# agent_token_counter=CharTokenCounter(), # 智能体的 token 计数器
# trigger_threshold=10000, # 超过 10000 个 token 时触发压缩
# keep_recent=3, # 保持最近 3 条消息不被压缩
# ),
# )
#
# 启用记忆压缩后,智能体会监控其记忆中的 token 数量。
# 一旦超过 ``trigger_threshold``,智能体会自动:
#
# 1. 识别尚未被压缩的消息(通过 ``exclude_mark``
# 2. 保持最近 ``keep_recent`` 条消息不被压缩(以保留最近的上下文)
# 3. 将较早的消息发送给 LLM 生成结构化摘要
# 4. 使用 ``MemoryMark.COMPRESSED`` 标记已压缩的消息(通过 ``update_messages_mark``
# 5. 将摘要存储在记忆中(通过 ``update_compressed_summary``
#
# .. important:: 压缩采用**标记机制**而非替换消息。旧消息被标记为已压缩,并通过 ``exclude_mark=MemoryMark.COMPRESSED`` 在后续检索中被排除,而生成的摘要则单独存储,在需要时检索。这种方式保留了原始消息,允许灵活的记忆管理。关于标记功能的更多详情,请参考 :ref:`memory`。
#
# 默认情况下,压缩摘要被结构化为五个关键字段:
#
# - **task_overview**:用户的核心请求和成功标准
# - **current_state**:到目前为止已完成的工作,包括文件和输出
# - **important_discoveries**:技术约束、决策、错误和失败的尝试
# - **next_steps**:完成任务所需的具体操作
# - **context_to_preserve**:用户偏好、领域细节和做出的承诺
#
# **自定义压缩**
#
# 可以通过指定 ``summary_schema``、``summary_template`` 和 ``compression_prompt`` 参数来自定义压缩的工作方式。
#
# - **summary_schema**:使用 Pydantic 模型定义压缩摘要的结构
# - **compression_prompt**:指导 LLM 如何生成摘要
# - **summary_template**:格式化压缩摘要如何呈现给智能体
#
# 下面是一个自定义压缩的示例:
#
# .. code-block:: python
#
# from pydantic import BaseModel, Field
#
# # 定义自定义摘要结构
# class CustomSummary(BaseModel):
# main_topic: str = Field(
# max_length=200,
# description="对话的主题"
# )
# key_points: str = Field(
# max_length=400,
# description="讨论的重要观点"
# )
# pending_tasks: str = Field(
# max_length=200,
# description="待完成的任务"
# )
#
# # 使用自定义压缩配置创建智能体
# agent = ReActAgent(
# name="助手",
# sys_prompt="你是一个有用的助手。",
# model=model,
# formatter=formatter,
# compression_config=ReActAgent.CompressionConfig(
# enable=True,
# agent_token_counter=CharTokenCounter(),
# trigger_threshold=10000,
# keep_recent=3,
# # 结构化摘要的自定义 schema
# summary_schema=CustomSummary,
# # 指导压缩的自定义提示
# compression_prompt=(
# "<system-hint>请总结上述对话,"
# "重点关注主题、关键讨论点和待完成任务。</system-hint>"
# ),
# # 格式化摘要的自定义模板
# summary_template=(
# "<system-info>对话摘要:\n"
# "主题:{main_topic}\n\n"
# "关键观点:\n{key_points}\n\n"
# "待完成任务:\n{pending_tasks}"
# "</system-info>"
# ),
# ),
# )
#
# ``summary_template`` 使用 ``summary_schema`` 中定义的字段作为占位符
# (例如 ``{main_topic}``、``{key_points}``)。在 LLM 生成结构化摘要后,
# 这些占位符将被实际值替换。
#
# .. note:: 智能体确保工具使用和工具结果对在压缩过程中保持在一起,以维护对话流程的完整性。
#
# .. tip:: 可以通过指定不同的 ``compression_model`` 和 ``compression_formatter`` 来使用更小、更快的模型进行压缩,以降低成本和延迟。
#
#
#
# 并行工具调用
# ----------------------------------------
# ``ReActAgent`` 通过在其构造函数中提供 ``parallel_tool_calls`` 参数来支持并行工具调用。
# 当 LLM 生成多个工具调用且 ``parallel_tool_calls`` 设置为 ``True`` 时,
# 它们将通过 ``asyncio.gather`` 函数并行执行。
#
# .. note:: ``ReActAgent`` 中的工具并行调用是基于异步 ``asyncio.gather`` 实现的,因此,只有当工具函数是异步函数,同时工具函数内也为异步逻辑时,才能最大程度发挥工具并行执行的效果
#
# .. note:: 运行时请确保模型层面支持工具并行调用,并且相应参数设置正确(可以通过 ``generate_kwargs`` 传入例如对于DashScope API需要设置 ``parallel_tool_calls`` 为 ``True``,否则将无法进行并行工具调用。
# 准备一个工具函数
async def example_tool_function(tag: str) -> ToolResponse:
"""一个示例工具函数"""
start_time = datetime.now().strftime("%H:%M:%S.%f")
# 休眠 3 秒以模拟长时间运行的任务
await asyncio.sleep(3)
end_time = datetime.now().strftime("%H:%M:%S.%f")
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"标签 {tag} 开始于 {start_time},结束于 {end_time}",
),
],
)
toolkit = Toolkit()
toolkit.register_tool_function(example_tool_function)
# 创建一个 ReAct 智能体
agent = ReActAgent(
name="Jarvis",
sys_prompt="你是一个名为 Jarvis 的有用助手。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
# 启用并行工具调用
generate_kwargs={
"parallel_tool_calls": True,
},
),
memory=InMemoryMemory(),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
parallel_tool_calls=True,
)
async def example_parallel_tool_calls() -> None:
"""并行工具调用示例"""
# 提示智能体同时生成两个工具调用
await agent(
Msg(
"user",
"同时生成两个 'example_tool_function' 函数的工具调用,标签分别为 'tag1''tag2',以便它们可以并行执行。",
"user",
),
)
asyncio.run(example_parallel_tool_calls())
# %%
# 结构化输出
# ----------------------------------------
# AgentScope 中的结构化输出是与工具调用紧密结合的。具体来说,``ReActAgent`` 类在其 ``__call__`` 函数中接收 ``pydantic.BaseModel`` 的子类作为 ``structured_model`` 参数。
# 从而提供复杂的结构化输出限制。
# 然后我们可以从 返回消息的 ``metadata`` 字段获取结构化输出。
#
# 以介绍爱因斯坦为例:
#
# 创建一个 ReAct 智能体
agent = ReActAgent(
name="Jarvis",
sys_prompt="你是一个名为 Jarvis 的有用助手。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
)
# 结构化模型
class Model(BaseModel):
name: str = Field(description="人物的姓名")
description: str = Field(description="人物的一句话描述")
age: int = Field(description="年龄")
honor: list[str] = Field(description="人物荣誉列表")
async def example_structured_output() -> None:
"""结构化输出示例"""
res = await agent(
Msg(
"user",
"介绍爱因斯坦",
"user",
),
structured_model=Model,
)
print("\n结构化输出:")
print(json.dumps(res.metadata, indent=4, ensure_ascii=False))
asyncio.run(example_structured_output())
# %%
# 自定义智能体
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 提供了两个基类:``AgentBase`` 和 ``ReActAgentBase``,它们在抽象方法和支持的钩子函数方面有所不同。
# 具体来说,``ReActAgentBase`` 扩展了 ``AgentBase``,增加了额外的 ``_reasoning`` 和 ``_acting`` 抽象方法,以及它们的前置和后置钩子函数。
#
# 开发者可以根据需要选择继承这两个基类中的任一个。
# 我们总结了 ``agentscope.agent`` 模块下的智能体如下:
#
# .. list-table:: AgentScope 中的智能体类
# :header-rows: 1
#
# * - 类
# - 抽象方法
# - 支持的钩子函数
# - 描述
# * - ``AgentBase``
# - | ``reply``
# | ``observe``
# | ``print``
# | ``handle_interrupt``
# - | pre\_/post_reply
# | pre\_/post_observe
# | pre\_/post_print
# - 所有智能体的基类,提供基本接口和钩子。
# * - ``ReActAgentBase``
# - | ``reply``
# | ``observe``
# | ``print``
# | ``handle_interrupt``
# | ``_reasoning``
# | ``_acting``
# - | pre\_/post_reply
# | pre\_/post_observe
# | pre\_/post_print
# | pre\_/post_reasoning
# | pre\_/post_acting
# - ReAct 类智能体的抽象类,扩展了 ``AgentBase``,增加了 ``_reasoning`` 和 ``_acting`` 抽象方法及其钩子。
# * - ``ReActAgent``
# - \-
# - | pre\_/post_reply
# | pre\_/post_observe
# | pre\_/post_print
# | pre\_/post_reasoning
# | pre\_/post_acting
# - ``ReActAgentBase`` 的实现
# * - ``UserAgent``
# -
# -
# - 代表用户的特殊智能体,用于与智能体交互
# * - ``A2aAgent``
# - \-
# - | pre\_/post_reply
# | pre\_/post_observe
# | pre\_/post_print
# - 用于与远程 A2A 代理通信的智能体,详见 :ref:`a2a`
#
#
#
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# - :ref:`tool`
# - :ref:`hook`
# - :ref:`a2a`
#

View File

@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
"""
.. _agent_skill:
智能体技能
============================
`智能体技能Agent skill <https://claude.com/blog/skills>`_ 是 Anthropic 提出的一种提升智能体在特定任务上能力的方法。
AgentScope 通过 ``Toolkit`` 类提供了对智能体技能的内置支持,让开发者可以注册和管理智能体技能。
相关 API 如下:
.. list-table:: ``Toolkit`` 类中的智能体技能 API
:header-rows: 1
* - API
- 描述
* - ``register_agent_skill``
- 从指定目录注册智能体技能
* - ``remove_agent_skill``
- 根据名称移除已注册的智能体技能
* - ``get_agent_skill_prompt``
- 获取所有已注册智能体技能的提示词,可以附加到智能体的系统提示词中
本节将演示如何注册智能体技能并在 ReActAgent 类中使用它们。
"""
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
# %%
# 注册智能体技能
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 首先,我们需要准备一个智能体技能目录,该目录需要遵循 `Anthropic blog <https://claude.com/blog/skills>`_ 中指定的要求。
#
# .. note:: 技能目录必须包含一个 ``SKILL.md`` 文件,其中包含 YAML 前置元数据和指令说明。
#
# 这里我们创建一个示例技能目录 ``sample_skill``,包含以下文件:
#
# .. code-block:: markdown
#
# ---
# name: sample_skill
# description: 用于演示的示例智能体技能
# ---
#
# # 示例技能
# ...
#
os.makedirs("sample_skill", exist_ok=True)
with open("sample_skill/SKILL.md", "w", encoding="utf-8") as f:
f.write(
"""---
name: sample_skill
description: 用于演示的示例智能体技能
---
# 示例技能
...
""",
)
# %%
# 然后,我们可以使用 ``Toolkit`` 类的 ``register_agent_skill`` API 注册技能。
#
toolkit = Toolkit()
toolkit.register_agent_skill("sample_skill")
# %%
# 之后,我们可以使用 ``get_agent_skill_prompt`` API 获取所有已注册智能体技能的提示词
agent_skill_prompt = toolkit.get_agent_skill_prompt()
print("智能体技能提示词:")
print(agent_skill_prompt)
# %%
# 当然,我们也可以在创建 ``Toolkit`` 实例时自定义提示词模板。
custom_toolkit = Toolkit(
# 向智能体/大语言模型介绍如何使用技能的指令
agent_skill_instruction="<system-info>为你提供了一组技能,每个技能都在一个目录中,并由 SKILL.md 文件进行描述。</system-info>",
# 用于格式化每个技能提示词的模板,必须包含 {name}、{description} 和 {dir} 字段
agent_skill_template="- {name}(in directory '{dir}'): {description}",
)
custom_toolkit.register_agent_skill("sample_skill")
agent_skill_prompt = custom_toolkit.get_agent_skill_prompt()
print("自定义智能体技能提示词:")
print(agent_skill_prompt)
# %%
# 在 ReActAgent 中集成智能体技能
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# AgentScope 中的 `ReActAgent` 类会自动将智能体技能提示词附加到系统提示词中。
#
# 我们可以按如下方式创建一个带有已注册智能体技能的 ReAct 智能体:
#
# .. important:: 使用智能体技能时,智能体必须配备文本文件读取或 shell 命令工具,以便访问 `SKILL.md` 文件中的技能指令。
#
agent = ReActAgent(
name="Friday",
sys_prompt="你是一个名为 Friday 的智能助手。",
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
memory=InMemoryMemory(),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
)
print("带有智能体技能的系统提示词:")
print(agent.sys_prompt)

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""
.. _embedding:
嵌入(Embedding)
=========================
AgentScope 中,嵌入模块提供了用于向量生成的统一接口,具有以下特性:
- 支持 **缓存 embedding** 以避免冗余的 API 调用
- 支持 **不同 embedding API 提供商** 并提供一致的接口
AgentScope 内置支持以下 API
.. list-table::
:header-rows: 1
* - API 提供商
- 类
* - OpenAI
- ``OpenAITextEmbedding``
* - Gemini
- ``GeminiTextEmbedding``
* - DashScope
- ``DashScopeTextEmbedding``, ``DashScopeMultiModalEmbedding``
* - Ollama
- ``OllamaTextEmbedding``
所有类都继承自 ``EmbeddingModelBase``,实现了 ``__call__`` 方法并生成包含嵌入和使用信息的 ``EmbeddingResponse`` 对象。
其中 ``DashScopeMultiModalEmbedding`` 支持文本,图像和视频的多模态嵌入。
以 DashScope 嵌入类为例,可以按如下方式使用:
"""
import asyncio
import os
import tempfile
from agentscope.embedding import DashScopeTextEmbedding, FileEmbeddingCache
async def example_dashscope_embedding() -> None:
"""DashScope 文本嵌入的使用示例。"""
texts = [
"法国的首都是什么?",
"巴黎是法国的首都城市。",
]
# 初始化 DashScope 文本嵌入实例
embedding_model = DashScopeTextEmbedding(
model_name="text-embedding-v2",
api_key=os.getenv("DASHSCOPE_API_KEY"),
)
# 从模型获取嵌入
response = await embedding_model(texts)
print("嵌入 ID: ", response.id)
print("嵌入创建时间: ", response.created_at)
print("嵌入使用情况: ", response.usage)
print("嵌入向量:")
print(response.embeddings)
asyncio.run(example_dashscope_embedding())
# %%
# 可以通过继承 ``EmbeddingModelBase`` 并实现 ``__call__`` 方法来自定义 embedding 模型。
#
# Embedding 缓存
# ---------------------
# AgentScope 提供了用于缓存 embedding 的基类 ``EmbeddingCacheBase``,以及基于文件的实现 ``FileEmbeddingCache``。
# 它在 embedding 模块中的工作方式如下:
#
# .. image:: ../../_static/images/embedding_cache.png
# :align: center
# :width: 90%
#
# 要使用缓存,只需将 ``FileEmbeddingCache`` 实例(或自定义缓存)传给模型的构造函数,如下所示:
#
async def example_embedding_cache() -> None:
"""演示带有缓存功能的 embedding。"""
# 示例文本
texts = [
"法国的首都是什么?",
"巴黎是法国的首都城市。",
]
# 为缓存演示创建临时目录
# 在实际应用中,建议使用持久目录以最大发挥缓存效果
cache_dir = tempfile.mkdtemp(prefix="embedding_cache_")
print(f"使用缓存目录: {cache_dir}")
# 使用缓存初始化嵌入模型
# 为演示目的,我们将缓存限制为 100 个文件和 10MB
embedder = DashScopeTextEmbedding(
model_name="text-embedding-v3",
api_key=os.getenv("DASHSCOPE_API_KEY"),
embedding_cache=FileEmbeddingCache(
cache_dir=cache_dir,
max_file_number=100,
max_cache_size=10, # 最大缓存大小MB
),
)
# 第一次调用 - 将从 API 获取并存储在缓存中
print("\n=== 第一次 API 调用(无缓存命中)===")
start_time = asyncio.get_event_loop().time()
response1 = await embedder(texts)
elapsed_time1 = asyncio.get_event_loop().time() - start_time
print(f"来源: {response1.source}") # 应该是 'api'
print(f"耗时: {elapsed_time1:.4f}")
print(f"使用的 token: {response1.usage.tokens}")
# 使用相同文本的第二次调用 - 应该使用缓存
print("\n=== 第二次 API 调用(预期缓存命中)===")
start_time = asyncio.get_event_loop().time()
response2 = await embedder(texts)
elapsed_time2 = asyncio.get_event_loop().time() - start_time
print(f"来源: {response2.source}") # 应该是 'cache'
print(f"耗时: {elapsed_time2:.4f}")
print(
f"使用的 token: {response2.usage.tokens}",
) # 缓存结果应该为 0
print(
f"速度提升: 使用缓存快 {elapsed_time1 / elapsed_time2:.1f}",
)
asyncio.run(example_embedding_cache())

View File

@@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
"""
.. _eval:
智能体评测
=========================
AgentScope 提供了一个内置的评测框架,用于评测智能体在不同任务和基准测试中的性能,主要特性包括:
- 基于 `Ray <https://github.com/ray-project/ray>`_ 的并行和分布式评估
- 支持中断后继续评估
- [开发中] 评估结果可视化
.. note:: 我们正在持续集成新的基准测试到 AgentScope 中:
- ✅ `ACEBench <https://github.com/ACEBench/ACEBench>`_
- 🚧 `GAIA <https://huggingface.co/datasets/gaia-benchmark/GAIA/tree/main>`_ 基准测试
概述
---------------------------
AgentScope 评估框架由几个关键组件组成:
- **基准测试 (Benchmark)**: 用于系统性评估的任务集合
- **任务 (Task)**: 包含输入、标准答案和指标的独立评估单元
- **指标 (Metric)**: 评估解决方案质量的测量函数
- **评估器 (Evaluator)**: 运行评估的引擎,聚合结果并分析性能
- **评估器存储 (Evaluator Storage)**: 用于记录和检索评估结果的持久化存储
- **解决方案 (Solution)**: 用户定义的解决方案
.. figure:: ../../_static/images/evaluation.png
:width: 90%
:alt: AgentScope 评估框架
*AgentScope 评估框架*
AgentScope 当前的实现包括:
- 评估器:
- ``RayEvaluator``: 基于 ray 的评估器,支持并行和分布式评估。
- ``GeneralEvaluator``: 通用评估器,按顺序运行任务,便于调试。
- 基准测试:
- ``ACEBench``: 用于评估智能体能力的基准测试。
我们在 `GitHub 仓库 <https://github.com/agentscope-ai/agentscope/tree/main/examples/evaluation/ace_bench>`_ 中提供了一个使用 ``RayEvaluator`` 和 ACEBench 中智能体多步骤任务的玩具示例。
核心组件
---------------
我们将构建一个简单的玩学问题基准测试来演示如何使用 AgentScope 评估模块。
"""
TOY_BENCHMARK = [
{
"id": "math_problem_1",
"question": "What is 2 + 2?",
"ground_truth": 4.0,
"tags": {
"difficulty": "easy",
"category": "math",
},
},
{
"id": "math_problem_2",
"question": "What is 12345 + 54321 + 6789 + 9876?",
"ground_truth": 83331,
"tags": {
"difficulty": "medium",
"category": "math",
},
},
]
# %%
# 从任务、解决方案和指标到基准测试
# ~~~~~~~~~~~~~~~~~~~
#
# - 一个 ``SolutionOutput`` (Agent解决方案输出) 包含智能体生成的所有信息,包括轨迹和最终输出。
# - 一个 ``Metric`` (评测指标) 代表一个单一的评估可调用实例,它将生成的解决方案(例如,轨迹或最终输出)与标准答案进行比较。
# 在这个示例中,我们定义了一个指标,简单地检查解决方案中的 ``output`` 字段是否与标准答案匹配。
from agentscope.evaluate import (
SolutionOutput,
MetricBase,
MetricResult,
MetricType,
)
class CheckEqual(MetricBase):
def __init__(
self,
ground_truth: float,
):
super().__init__(
name="math check number equal",
metric_type=MetricType.NUMERICAL,
description="Toy metric checking if two numbers are equal",
categories=[],
)
self.ground_truth = ground_truth
async def __call__(
self,
solution: SolutionOutput,
) -> MetricResult:
if solution.output == self.ground_truth:
return MetricResult(
name=self.name,
result=1.0,
message="Correct",
)
else:
return MetricResult(
name=self.name,
result=0.0,
message="Incorrect",
)
# %%
# - 一个 ``Task`` (任务) 是基准测试中的一个单元,包含智能体执行和评估所需的所有信息(例如,输入/查询及其标准答案)。
# - 一个 ``Benchmark`` (基准测试) 组织多个任务进行系统性评估。
from typing import Generator
from agentscope.evaluate import (
Task,
BenchmarkBase,
)
class ToyBenchmark(BenchmarkBase):
def __init__(self):
super().__init__(
name="Toy bench",
description="A toy benchmark for demonstrating the evaluation module.",
)
self.dataset = self._load_data()
@staticmethod
def _load_data() -> list[Task]:
dataset = []
for item in TOY_BENCHMARK:
dataset.append(
Task(
id=item["id"],
input=item["question"],
ground_truth=item["ground_truth"],
tags=item.get("tags", {}),
metrics=[
CheckEqual(item["ground_truth"]),
],
metadata={},
),
)
return dataset
def __iter__(self) -> Generator[Task, None, None]:
"""遍历基准测试。"""
for task in self.dataset:
yield task
def __getitem__(self, index: int) -> Task:
"""根据索引获取任务。"""
return self.dataset[index]
def __len__(self) -> int:
"""获取基准测试的长度。"""
return len(self.dataset)
# %%
# 评估器
# ~~~~~~~~~~
#
# 评估器 (Evaluators) 管理评估过程。它们可以自动遍历
# 基准测试中的任务,并将每个任务输入到解决方案生成函数中,
# 开发者需要在其中定义运行智能体和检索
# 执行结果和轨迹的逻辑。下面是一个
# 使用我们的玩具基准测试运行 ``GeneralEvaluator`` (通用评估器) 的示例。如果有一个大型
# 基准测试,开发者希望通过并行化更高效地进行评估,
# ``RayEvaluator`` (Ray评估器) 也可作为内置解决方案使用。
import os
import asyncio
from typing import Callable
from pydantic import BaseModel
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.formatter import DashScopeChatFormatter
from agentscope.agent import ReActAgent
from agentscope.evaluate import (
GeneralEvaluator,
FileEvaluatorStorage,
)
class ToyBenchAnswerFormat(BaseModel):
answer_as_number: float
async def toy_solution_generation(
task: Task,
pre_hook: Callable,
) -> SolutionOutput:
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday. "
"Your target is to solve the given task with your tools. "
"Try to solve the task as best as you can.",
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen-max",
stream=False,
),
formatter=DashScopeChatFormatter(),
)
agent.register_instance_hook(
"pre_print",
"save_logging",
pre_hook,
)
msg_input = Msg("user", task.input, role="user")
res = await agent(
msg_input,
structured_model=ToyBenchAnswerFormat,
)
return SolutionOutput(
success=True,
output=res.metadata.get("answer_as_number", None),
trajectory=[],
)
async def main() -> None:
evaluator = GeneralEvaluator(
name="Toy benchmark evaluation",
benchmark=ToyBenchmark(),
# 重复多少次
n_repeat=1,
storage=FileEvaluatorStorage(
save_dir="./results",
),
# 使用多少个工作进程
n_workers=1,
)
# 运行评估
await evaluator.run(toy_solution_generation)
asyncio.run(main())

View File

@@ -0,0 +1,346 @@
# -*- coding: utf-8 -*-
"""
OpenJudge 评估器
=======================
[OpenJudge](https://github.com/agentscope-ai/OpenJudge) 是一个专为评估LLM/Agent应用质量而设计的评估框架。通过将 OpenJudge 集成到 AgentScope 中,您可以将 AgentScope 的原生评估能力从基础的执行检查扩展到深度的语义质量分析。
本指南中我们将介绍如何使用 OpenJudge 的评估器(Grader)作为 AgentScope 的评估指标(Metric)来评估您的智能体应用。
.. note::
在运行本教程之前,请安装必要的依赖:
.. code-block:: bash
pip install agentscope py-openjudge
为什么选择 OpenJudge
----------------------
虽然 AgentScope 提供了强大的评估框架用于定义评估逻辑,但实现复杂的语义级指标(如“幻觉检测”或“回复相关性”)通常需要大量的 Prompt 工程和流程构建工作。
集成 OpenJudge 可以为 AgentScope 带来了三个维度的能力提升:
1. **提升评估深度**:从简单的成功/失败检查升级为多维度的质量评估(如准确性、安全性、语气等)。
2. **开箱即用的 Grader**:直接使用 50+ 个预置的、专家级验证过的 Grader无需手动编写评估 Prompt详情请参阅 [OpenJudge官方文档](https://agentscope-ai.github.io/OpenJudge/built_in_graders/overview/)。
3. **闭环迭代**:将 OpenJudge 无缝嵌入 AgentScope 的 ``Benchmark`` 中,同时获取量化的分数和定性的理由分析。
如何使用 OpenJudge 进行评估
---------------------------
我们将构建一个简单的问答QA基准测试演示如何通过集成 OpenJudge 的 Grader 来使用 AgentScope 的评估模块。
"""
# %%
QA_BENCHMARK_DATASET = [
{
"id": "qa_task_1",
"question": "What are the health benefits of regular exercise?",
"reference_output": "Regular exercise improves cardiovascular health, strengthens muscles and bones, "
"helps maintain a healthy weight, and can improve mental health by reducing anxiety and depression.",
"ground_truth": "Answers should cover physical and mental health benefits",
"difficulty": "medium",
"category": "health",
},
{
"id": "qa_task_2",
"question": "Describe the main causes of climate change.",
"reference_output": "Climate change is primarily caused by increased concentrations of greenhouse gases "
"in the atmosphere due to human activities like burning fossil fuels, deforestation, and industrial processes.",
"ground_truth": "Answers should mention greenhouse gases and human activities",
"difficulty": "hard",
"category": "environment",
},
{
"id": "qa_task_3",
"question": "What is the significance of the Turing Test in AI?",
"reference_output": "The Turing Test, proposed by Alan Turing, is a measure of a machine's ability to exhibit"
" intelligent behavior equivalent to, or indistinguishable from, that of a human.",
"ground_truth": "Should mention Alan Turing, purpose of the test, and its implications for AI",
"difficulty": "hard",
"category": "technology",
},
]
# %% [markdown]
# AgentScope Metric vs. OpenJudge Grader
# ~~~~~~~~~~
# 为了使 AgentScope 兼容 OpenJudge我们需要一个适配器Adapter来完成两个框架间的转换。
# 这个适配器继承自 AgentScope 的 ``MetricBase``,并充当通往 OpenJudge ``BaseGrader`` 的桥梁。
#
# * **AgentScope Metric**: 一个通用的评估单元,接收 ``SolutionOutput`` 并返回 ``MetricResult``。
# * **OpenJudge Grader**: 一个特定的评估单元(例如 ``RelevanceGrader``),需要特定的语义输入(如 ``query``, ``response``, ``context``),返回``GraderResult``。
#
# 这个“适配器”允许您将 *任何* OpenJudge Grader 无缝插入到您的 AgentScope 基准测试中。
# %%
from openjudge.graders.base_grader import BaseGrader
from openjudge.graders.schema import GraderScore, GraderError
from openjudge.utils.mapping import parse_data_with_mapper
from agentscope.evaluate import (
MetricBase,
MetricType,
MetricResult,
SolutionOutput,
)
class OpenJudgeMetric(MetricBase):
def __init__(
self,
grader_cls: type[BaseGrader],
data: dict,
mapper: dict,
name: str | None = None,
description: str | None = None,
**grader_kwargs,
):
"""Initializes the OpenJudgeMetric.
Args:
grader_cls (`type[BaseGrader]`):
The OpenJudge grader class to be wrapped.
data (`dict`):
The static data for the task.
mapper (`dict`):
The mapper to extract grader inputs from combined data.
name (`str | None`, optional):
The name of the metric. Defaults to the grader's name.
description (`str | None`, optional):
The description of the metric. Defaults to the grader's
description.
**grader_kwargs:
Additional keyword arguments for the grader initialization.
"""
self.grader = grader_cls(**grader_kwargs)
super().__init__(
name=name or self.grader.name,
metric_type=MetricType.NUMERICAL,
description=description or self.grader.description,
)
self.data = data
self.mapper = mapper
async def __call__(self, solution: SolutionOutput) -> MetricResult:
"""针对 Agent 的输出执行封装好的 OpenJudge Grader。"""
if not solution.success:
return MetricResult(
name=self.name,
result=0.0,
message="Solution failed",
)
try:
# 1. 构建上下文
# 将静态的任务上下文 (data) 和动态的 Agent 输出 (solution) 组合
combined_data = {
"data": self.data,
"solution": {
"output": solution.output,
"meta": solution.meta,
"trajectory": getattr(solution, "trajectory", []),
},
}
# 2. 数据映射
# 使用 mapper 从组合数据中提取Grader需要的 'query', 'response', 'context' 等参数
grader_inputs = parse_data_with_mapper(
combined_data,
self.mapper,
)
## 3. 执行评估
result = await self.grader.aevaluate(**grader_inputs)
# 4. 格式化结果
if isinstance(result, GraderScore):
return MetricResult(
name=self.name,
result=result.score,
# 保留 OpenJudge 提供的详细理由
message=result.reason or "",
)
elif isinstance(result, GraderError):
return MetricResult(
name=self.name,
result=0.0,
message=f"Error: {result.error}",
)
else:
return MetricResult(
name=self.name,
result=0.0,
message="Unknown result type",
)
except Exception as e:
return MetricResult(
name=self.name,
result=0.0,
message=f"Exception: {str(e)}",
)
# %% [markdown]
# 从 OpenJudge 到 AgentScope
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# OpenJudge 提供了丰富的内置 Grader 集合。在当前实例中,我们选择两个适合问答任务的常用 Grader
#
# * **RelevanceGrader (相关性)**:评估 Agent 的回答是否直接回应了用户的查询(忽略事实准确性)。
# * **CorrectnessGrader (正确性)**根据提供的参考答案Ground Truth验证回答的事实准确性。
#
# .. tip::
# OpenJudge 提供了 50+ 种内置 Grader涵盖 **幻觉检测**、**安全性**、**代码质量** 和 **JSON 格式化** 等多个维度。
# 请查阅 `OpenJudge 官方文档 <https://agentscope-ai.github.io/OpenJudge/built_in_graders/overview/>`_ 获取完整列表。
#
# .. note::
# 在运行以下示例之前,请确保您已设置 ``DASHSCOPE_API_KEY`` 环境变量。
# %%
import os
from typing import Generator
from openjudge.graders.common.relevance import RelevanceGrader
from openjudge.graders.common.correctness import CorrectnessGrader
from agentscope.evaluate import (
Task,
BenchmarkBase,
)
class QABenchmark(BenchmarkBase):
def __init__(self):
super().__init__(
name="QA Quality Benchmark",
description="Benchmark to evaluate QA systems using OpenJudge grader classes",
)
self.dataset = self._load_data()
def _load_data(self):
tasks = []
# 配置 LLM Grader 的模型参数
# 注意:如果不使用环境变量,请在此处设置 "api_key"
model_config = {
"model": "qwen3-32b",
"api_key": os.environ.get("DASHSCOPE_API_KEY"),
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
}
for data in QA_BENCHMARK_DATASET:
# 定义映射关系:左侧是 OpenJudge 的键,右侧是 AgentScope 的数据路径
mapper = {
"query": "data.input",
"response": "solution.output",
"context": "data.ground_truth",
"reference_response": "data.reference_output",
}
# 通过 Adapter 实例化 Metrics
metrics = [
OpenJudgeMetric(
grader_cls=RelevanceGrader,
data=data,
mapper=mapper,
name="Relevance",
model=model_config,
),
OpenJudgeMetric(
grader_cls=CorrectnessGrader,
data=data,
mapper=mapper,
name="Correctness",
model=model_config,
),
]
# 创建 Task
task = Task(
id=data["id"],
input=data["question"],
ground_truth=data["ground_truth"],
metrics=metrics,
)
tasks.append(task)
return tasks
def __iter__(self) -> Generator[Task, None, None]:
yield from self.dataset
def __getitem__(self, index: int) -> Task:
return self.dataset[index]
def __len__(self) -> int:
return len(self.dataset)
# %% [markdown]
# 运行评估
# ~~~~~~~~~~
# 最后,使用 AgentScope 的 ``GeneralEvaluator`` 对一个简单的QA Agent进行评估测试。
# 我们将收集到来自 OpenJudge Grader 的 **量化分数 (Score)** 和 **定性理由 (Reasoning)**。
# %%
import asyncio
from typing import Callable
from agentscope.agent import ReActAgent
from agentscope.evaluate import GeneralEvaluator
from agentscope.evaluate import FileEvaluatorStorage
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import OpenAIChatModel
async def qa_agent(task: Task, pre_hook: Callable) -> SolutionOutput:
model = OpenAIChatModel(
model_name="qwen3-32b",
api_key=os.getenv("DASHSCOPE_API_KEY"),
)
agent = ReActAgent(
name="QAAgent",
sys_prompt="You are an expert at answering questions. Provide clear, accurate, and comprehensive answers.",
model=model,
formatter=DashScopeChatFormatter(),
)
# Generate response
msg_input = Msg(name="User", content=task.input, role="user")
response = await agent(msg_input)
response_text = response.content
return SolutionOutput(
success=True,
output=response_text,
trajectory=[
task.input,
response_text,
], # Store the interaction trajectory
)
async def main() -> None:
evaluator = GeneralEvaluator(
name="OpenJudge Integration Demo",
benchmark=QABenchmark(),
# Repeat how many times
n_repeat=1,
storage=FileEvaluatorStorage(
save_dir="./results",
),
# How many workers to use
n_workers=1,
)
await evaluator.run(qa_agent)
# %% [markdown]
#
# ~~~~~~~~~~
# 最后,使用 AgentScope 的 ``GeneralEvaluator`` 对一个简单的QA Agent进行评估测试。
# 我们将收集到来自 OpenJudge Grader 的 **量化分数 (Score)** 和 **定性理由 (Reasoning)**。

View File

@@ -0,0 +1,286 @@
# -*- coding: utf-8 -*-
"""
.. _hook:
智能体钩子函数
===========================
钩子Hook是 AgentScope 中的扩展点,允许开发者在特定位置自定义智能体行为,提供了一种灵活的方式来修改或扩展智能体的功能,而无需更改其核心实现。
在 AgentScope 中,钩子围绕智能体的核心函数实现:
.. list-table:: AgentScope 中支持的钩子类型
:header-rows: 1
* - 智能体类
- 核心函数
- 钩子类型
- 描述
* - | ``AgentBase`` 及其子类
- ``reply``
- | ``pre_reply``
| ``post_reply``
- 智能体回复消息前/后的钩子
* -
- ``print``
- | ``pre_print``
| ``post_print``
- 向目标输出如终端、Web 界面)打印消息前/后的钩子
* -
- ``observe``
- | ``pre_observe``
| ``post_observe``
- 从环境或其它智能体观察消息前/后的钩子
* - | ``ReActAgentBase`` 及其子类
- | ``reply``
| ``print``
| ``observe``
- | ``pre_reply``
| ``post_reply``
| ``pre_print``
| ``post_print``
| ``pre_observe``
| ``post_observe``
-
* -
- ``_reasoning``
- | ``pre_reasoning``
| ``post_reasoning``
- 智能体推理过程前/后的钩子
* -
- ``_acting``
- | ``pre_acting``
| ``post_acting``
- 智能体行动过程前/后的钩子
.. tip:: 由于 AgentScope 中的钩子函数是通过 meta class 实现的,因此支持继承。
为了简化使用AgentScope 为所有钩子提供了统一的签名。
"""
import asyncio
from typing import Any, Type
from agentscope.agent import ReActAgentBase, AgentBase
from agentscope.message import Msg
# %%
# 钩子签名
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# AgentScope 为所有前置pre_和后置post_钩子提供统一的钩子签名如下所示
#
# **前置钩子签名**
#
# .. list-table:: 所有前置钩子的签名
# :header-rows: 1
#
# * -
# - 名称
# - 描述
# * - 参数
# - ``self: AgentBase | ReActAgentBase``
# - 智能体实例
# * -
# - ``kwargs: dict[str, Any]``
# - | 目标函数的输入参数,或来自最近
# | 一个非 None 返回值的钩子修
# | 改后的参数
# * - 返回值
# - ``dict[str, Any] | None``
# - 修改后的参数或 None
#
# .. note:: 核心函数的所有位置参数(*args和关键字参数**kwargs被统一成单个 ``kwargs`` 字典传递给钩子函数
#
# 前置钩子模板定义如下:
#
def pre_hook_template(
self: AgentBase | ReActAgentBase,
kwargs: dict[str, Any],
) -> dict[str, Any] | None: # 修改后的输入
"""前置钩子模板。"""
pass
# %%
# **后置钩子签名**
#
# 对于后置钩子,在签名中增加了一个额外的 ``output`` 参数,表示目标函数的输出。
# 如果核心函数没有输出,``output`` 参数将为 ``None``。
#
# .. list-table:: 所有后置钩子的签名
# :header-rows: 1
#
# * -
# - 名称
# - 描述
# * - 参数
# - ``self: AgentBase | ReActAgentBase``
# - 智能体实例
# * -
# - ``kwargs: dict[str, Any]``
# - | 包含目标函数所有参数的字典
# * -
# - ``output: Any``
# - | 目标函数的输出或来自前序钩子
# | 最近一个非 None 返回值
# * - 返回值
# - ``dict[str, Any] | None``
# - 修改后的输出或 None
#
def post_hook_template(
self: AgentBase | ReActAgentBase,
kwargs: dict[str, Any],
output: Any, # 目标函数的输出
) -> Any: # 修改后的输出
"""后置钩子模板。"""
pass
# %%
# 钩子管理
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# AgentScope 提供实例级instance和类级class钩子其区别在于钩子函数的作用范围。
# 它们按以下顺序执行:
#
# .. image:: ../../_static/images/sequential_hook.png
# :width: 90%
# :align: center
# :alt: AgentScope 中的钩子
# :class: bordered-image
#
# AgentScope 提供内置方法来管理实例级和类级的钩子,如下所示:
#
# .. list-table:: AgentScope 中的钩子管理方法
# :header-rows: 1
#
# * - 级别
# - 方法
# - 描述
# * - 实例级
# - ``register_instance_hook``
# - | 为当前对象注册具有给定钩子类型
# | 和名称的钩子。
# * -
# - ``remove_instance_hook``
# - | 移除当前对象具有给定钩子类型
# | 和名称的钩子。
# * -
# - ``clear_instance_hooks``
# - | 清除当前对象具有给定钩子类型
# | 的所有钩子。
# * - 类级
# - ``register_class_hook``
# - | 为该类的所有对象注册具有给定
# | 钩子类型和名称的钩子。
# * -
# - ``remove_class_hook``
# - | 移除该类所有对象具有给定
# | 钩子类型和名称的钩子。
# * -
# - ``clear_class_hooks``
# - | 清除该类所有对象具有给定
# | 钩子类型的所有钩子。
#
# 使用钩子时,开发者需要注意以下规则:
#
# .. important:: **执行顺序**
#
# - 钩子按注册顺序执行
# - 多个钩子可以链式连接
# **返回值处理**
#
# - 对于前置钩子:非 None 返回值会传递给下一个钩子或核心函数
# - 当钩子返回 None 时,下一个钩子将使用前序钩子中最近的非 None 返回值
# - 如果所有前序钩子都返回 None那该钩子接收原始参数的副本作为输入
# - 最后一个非 None 返回值(或如果所有钩子都返回 None 则使用原始参数)传递给核心函数
# - 对于后置钩子:工作方式与前置钩子相似。
# **重要提示**不要在钩子内调用核心函数reply/speak/observe/_reasoning/_acting以避免循环调用
#
# 以下面的智能体为例,我们可以看到如何注册、移除和清除钩子:
#
# 创建一个简单的测试智能体类
class TestAgent(AgentBase):
"""用于演示钩子的测试智能体。"""
async def reply(self, msg: Msg) -> Msg:
"""回复消息。"""
return msg
# %%
# 我们创建一个实例级钩子和一个类级钩子来在回复前修改消息内容。
#
# 创建两个前置回复钩子
def instance_pre_reply_hook(
self: AgentBase,
kwargs: dict[str, Any],
) -> dict[str, Any]:
"""修改消息内容的前置回复钩子。"""
msg = kwargs["msg"]
msg.content += "[instance-pre-reply]"
# 返回修改后的 kwargs
return {
**kwargs,
"msg": msg,
}
def cls_pre_reply_hook(
self: AgentBase,
kwargs: dict[str, Any],
) -> dict[str, Any]:
"""修改消息内容的前置回复钩子。"""
msg = kwargs["msg"]
msg.content += "[cls-pre-reply]"
# 返回修改后的 kwargs
return {
**kwargs,
"msg": msg,
}
# 注册类钩子
TestAgent.register_class_hook(
hook_type="pre_reply",
hook_name="test_pre_reply",
hook=cls_pre_reply_hook,
)
# 注册实例钩子
agent = TestAgent()
agent.register_instance_hook(
hook_type="pre_reply",
hook_name="test_pre_reply",
hook=instance_pre_reply_hook,
)
async def example_test_hook() -> None:
"""测试钩子的示例函数。"""
msg = Msg(
name="user",
content="Hello, world!",
role="user",
)
res = await agent(msg)
print("响应内容:", res.content)
TestAgent.clear_class_hooks()
asyncio.run(example_test_hook())
# %%
# 我们可以看到 "[instance-pre-reply]" 和 "[cls-pre-reply]" 被添加到了消息内容中。
#

View File

@@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
"""
.. _long-term-memory:
长期记忆
========================
AgentScope 为长期记忆提供了一个基类 ``LongTermMemoryBase`` 和一个基于 `mem0 <https://github.com/mem0ai/mem0>`_ 的具体实现 ``Mem0LongTermMemory``。
结合 :ref:`agent` 章节中 ``ReActAgent`` 类的设计,我们提供了两种长期记忆模式:
- ``agent_control``:智能体通过工具调用自主管理长期记忆。
- ``static_control``:开发者通过编程显式控制长期记忆操作。
当然,开发者也可以使用 ``both`` 参数,将同时激活上述两种记忆管理模式。
.. hint:: 不同的记忆模式适用于不同的使用场景,开发者可以根据需要选择合适的模式。
使用 mem0 长期记忆
~~~~~~~~~~~~~~~~~~~~~~~~
.. note:: 在 GitHub 仓库的 ``examples/long_term_memory/mem0`` 目录下我们提供了 mem0 长期记忆的使用示例。
"""
import os
import asyncio
from agentscope.message import Msg
from agentscope.memory import InMemoryMemory, Mem0LongTermMemory
from agentscope.agent import ReActAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
# 创建 mem0 长期记忆实例
long_term_memory = Mem0LongTermMemory(
agent_name="Friday",
user_name="user_123",
model=DashScopeChatModel(
model_name="qwen-max-latest",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v2",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
),
on_disk=False,
)
# %%
# ``Mem0LongTermMemory`` 类提供了两个操作长期记忆的方法,``record`` 和 ``retrieve``。
# 它们接收消息对象的列表作为输入,分别记录和检索长期记忆中的信息。
#
# 例如下面的例子中,我们先存入用户的一条偏好,然后在长期记忆中检索相关信息。
#
# 基本使用示例
async def basic_usage():
"""基本使用示例"""
# 记录记忆
await long_term_memory.record([Msg("user", "我喜欢住民宿", "user")])
# 检索记忆
results = await long_term_memory.retrieve(
[Msg("user", "我的住宿偏好", "user")],
)
print(f"检索结果: {results}")
asyncio.run(basic_usage())
# %%
# 与 ReAct 智能体集成
# ----------------------------------------
# AgentScope 中的 ``ReActAgent`` 在构造函数中包含 ``long_term_memory`` 和 ``long_term_memory_mode`` 两个参数,
# 其中 ``long_term_memory`` 用于指定长期记忆实例,``long_term_memory_mode`` 的取值为 ``"agent_control"``, ``"static_control"`` 或 ``"both"``。
#
# 当 ``long_term_memory_mode`` 设置为 ``"agent_control"`` 或 ``both`` 时,在 ``ReActAgent`` 的构造函数中将
# 注册两个工具函数:``record_to_memory`` 和 ``retrieve_from_memory``。
# 从而使智能体能够自主的管理长期记忆。
#
# .. note:: 为了达到最好的效果,``"agent_control"`` 模式可能还需要在系统提示system prompt中添加相应的说明。
#
# 创建带有长期记忆的 ReAct 智能体
agent = ReActAgent(
name="Friday",
sys_prompt="你是一个具有长期记忆功能的助手。",
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen-max-latest",
),
formatter=DashScopeChatFormatter(),
toolkit=Toolkit(),
memory=InMemoryMemory(),
long_term_memory=long_term_memory,
long_term_memory_mode="static_control", # 使用 static_control 模式
)
async def record_preferences():
"""ReAct agent integration example"""
# 对话示例
msg = Msg("user", "我去杭州旅行时,喜欢住民宿", "user")
await agent(msg)
asyncio.run(record_preferences())
# %%
# 然后我们清空智能体的短期记忆,以避免造成干扰,并测试智能体是否会记住之前的对话。
#
async def retrieve_preferences():
"""Retrieve user preferences from long-term memory"""
# 我们清空智能体的短期记忆,以避免造成干扰
await agent.memory.clear()
# 测试智能体是否会记住之前的对话
msg2 = Msg("user", "我有什么偏好?简要的回答我", "user")
await agent(msg2)
asyncio.run(retrieve_preferences())
# %%
# 使用 ReMe 个人长期记忆
# ~~~~~~~~~~~~~~~~~~~~~~~~
#
# .. note:: 在 GitHub 仓库的 ``examples/long_term_memory/reme`` 目录下我们提供了 ReMe 长期记忆的使用示例。
#
# .. code-block:: python
# :caption: 安装 ReMe 依赖
#
# from agentscope.memory import ReMePersonalLongTermMemory
#
# # 创建 ReMe 个人长期记忆实例
# reme_long_term_memory = ReMePersonalLongTermMemory(
# agent_name="Friday",
# user_name="user_123",
# model=DashScopeChatModel(
# model_name="qwen3-max",
# api_key=os.environ.get("DASHSCOPE_API_KEY"),
# stream=False,
# ),
# embedding_model=DashScopeTextEmbedding(
# model_name="text-embedding-v4",
# api_key=os.environ.get("DASHSCOPE_API_KEY"),
# dimensions=1024,
# ),
# )
#
#
# ``ReMePersonalLongTermMemory`` 类提供了四个操作长期记忆的方法。
# 它们分别是用于工具调用的 ``record_to_memory`` 和 ``retrieve_from_memory``
# 以及用于直接调用的 ``record`` 和 ``retrieve``。
#
# 例如下面的例子中,我们使用 ``record_to_memory`` 记录用户偏好。
#
# .. code-block:: python
# :caption: 创建 ReMe 个人长期记忆实例
#
# async def test_record_to_memory():
# """测试 record_to_memory 工具函数接口"""
# async with reme_long_term_memory:
# result = await reme_long_term_memory.record_to_memory(
# thinking="用户正在分享他们的旅行偏好和习惯",
# content=[
# "我去杭州旅行时喜欢住民宿",
# "我喜欢早上去西湖游玩",
# "我喜欢喝龙井茶",
# ],
# )
# # 提取结果文本
# result_text = " ".join(
# block.get("text", "")
# for block in result.content
# if block.get("type") == "text"
# )
# print(f"记录结果: {result_text}")
#
#
# 然后我们使用 ``retrieve_from_memory`` 检索相关记忆。
#
# .. code-block:: python
# :caption: 使用 retrieve_from_memory 检索记忆
#
# async def test_retrieve_from_memory():
# """测试 retrieve_from_memory 工具函数接口"""
# async with reme_long_term_memory:
# # 先记录一些内容
# await reme_long_term_memory.record_to_memory(
# thinking="用户正在分享旅行偏好",
# content=["我去杭州旅行时喜欢住民宿"],
# )
#
# # 然后检索
# result = await reme_long_term_memory.retrieve_from_memory(
# keywords=["杭州旅行", "茶偏好"],
# )
# retrieved_text = " ".join(
# block.get("text", "")
# for block in result.content
# if block.get("type") == "text"
# )
# print(f"检索到的记忆: {retrieved_text}")
#
#
# 除了工具函数接口,我们也可以使用 ``record`` 方法直接记录消息对话。
#
# .. code-block:: python
# :caption: 使用 record 直接记录消息
#
# async def test_record_direct():
# """测试 record 直接记录方法"""
# async with reme_long_term_memory:
# await reme_long_term_memory.record(
# msgs=[
# Msg(
# role="user",
# content="我是一名软件工程师,喜欢远程工作",
# name="user",
# ),
# Msg(
# role="assistant",
# content="明白了!您是一名重视远程工作灵活性的软件工程师。",
# name="assistant",
# ),
# Msg(
# role="user",
# content="我通常早上9点开始工作会先喝一杯咖啡",
# name="user",
# ),
# ],
# )
# print("成功记录了对话消息")
#
#
# 类似地,我们使用 ``retrieve`` 方法检索相关记忆。
#
# .. code-block:: python
# :caption: 使用 retrieve 直接检索消息
#
# async def test_retrieve_direct():
# """测试 retrieve 直接检索方法"""
# async with reme_long_term_memory:
# # 先记录一些内容
# await reme_long_term_memory.record(
# msgs=[
# Msg(
# role="user",
# content="我是一名软件工程师,喜欢远程工作",
# name="user",
# ),
# ],
# )
#
# # 然后检索
# memories = await reme_long_term_memory.retrieve(
# msg=Msg(
# role="user",
# content="你知道我的工作偏好吗?",
# name="user",
# ),
# )
# print(f"检索到的记忆: {memories if memories else '未找到相关记忆'}")
#
#
# 与 ReAct 智能体集成
# ----------------------------------------
# AgentScope 中的 ``ReActAgent`` 在构造函数中包含 ``long_term_memory`` 和 ``long_term_memory_mode`` 两个参数。
#
# 当 ``long_term_memory_mode`` 设置为 ``"agent_control"`` 或 ``both`` 时,在 ``ReActAgent`` 的构造函数中将
# 注册 ``record_to_memory`` 和 ``retrieve_from_memory`` 工具函数,使智能体能够自主的管理长期记忆。
#
# .. note:: 为了达到最好的效果,``"agent_control"`` 模式可能还需要在系统提示system prompt中添加相应的说明。
#
# .. code-block:: python
# :caption: 创建带有长期记忆的 ReAct 智能体
#
# # 创建带有长期记忆的 ReAct 智能体agent_control 模式)
# async def test_react_agent_with_reme():
# """测试 ReActAgent 与 ReMe 个人记忆的集成"""
# async with reme_long_term_memory:
# agent_with_reme = ReActAgent(
# name="Friday",
# sys_prompt=(
# "你是一个名为 Friday 的助手,具有长期记忆能力。"
# "\n\n## 记忆管理指南:\n"
# "1. **记录记忆**:当用户分享个人信息、偏好、习惯或关于自己的事实时,"
# "始终使用 `record_to_memory` 记录这些信息以供将来参考。\n"
# "\n2. **检索记忆**:在回答关于用户偏好、过去信息或个人详细信息的问题之前,"
# "你必须首先调用 `retrieve_from_memory` 来检查是否有任何相关的存储信息。"
# "不要仅依赖当前对话上下文。\n"
# "\n3. **何时检索**:在以下情况下调用 `retrieve_from_memory`\n"
# " - 用户问类似'我喜欢什么?'、'我的偏好是什么?'、"
# "'你对我了解多少?'的问题\n"
# " - 用户询问他们过去的行为、习惯或偏好\n"
# " - 用户提到他们之前提到的信息\n"
# " - 你需要关于用户的上下文来提供个性化的响应\n"
# "\n在声称不了解用户的某些信息之前始终先检查你的记忆。"
# ),
# model=DashScopeChatModel(
# model_name="qwen3-max",
# api_key=os.environ.get("DASHSCOPE_API_KEY"),
# stream=False,
# ),
# formatter=DashScopeChatFormatter(),
# toolkit=Toolkit(),
# memory=InMemoryMemory(),
# long_term_memory=reme_long_term_memory,
# long_term_memory_mode="agent_control", # 使用 agent_control 模式
# )
#
# # 用户分享偏好
# msg = Msg(
# role="user",
# content="我去杭州旅行时,喜欢住民宿",
# name="user",
# )
# response = await agent_with_reme(msg)
# print(f"智能体响应: {response.get_text_content()}")
#
# # 清空短期记忆以测试长期记忆
# await agent_with_reme.memory.clear()
#
# # 查询偏好
# msg2 = Msg(role="user", content="我有什么偏好?", name="user")
# response2 = await agent_with_reme(msg2)
# print(f"智能体响应: {response2.get_text_content()}")
#
#
# 然后我们清空智能体的短期记忆,以避免造成干扰,并测试智能体是否会记住之前的对话。
#
# .. code-block:: python
# :caption: 测试 ReAct 智能体是否记住偏好
#
# async def retrieve_reme_preferences():
# """从长期记忆中检索用户偏好"""
# async with reme_long_term_memory:
# # 创建智能体(这里可以复用之前创建的智能体,为了示例完整性重新创建)
# agent_with_reme = ReActAgent(
# name="Friday",
# sys_prompt="你是一个具有长期记忆功能的助手。",
# model=DashScopeChatModel(
# api_key=os.environ.get("DASHSCOPE_API_KEY"),
# model_name="qwen3-max",
# stream=False,
# ),
# formatter=DashScopeChatFormatter(),
# toolkit=Toolkit(),
# memory=InMemoryMemory(),
# long_term_memory=reme_long_term_memory,
# long_term_memory_mode="agent_control",
# )
#
# # 我们清空智能体的短期记忆,以避免造成干扰
# await agent_with_reme.memory.clear()
#
# # 测试智能体是否会记住之前的对话
# msg2 = Msg("user", "我有什么偏好?简要的回答我", "user")
# await agent_with_reme(msg2)
#
#
# 自定义长期记忆
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 提供了 ``LongTermMemoryBase`` 基类,它定义了长期记忆的基本接口。
#
# 开发者可以继承 ``LongTermMemoryBase`` 并实现以下的抽象方法来定义自己的长期记忆类:
#
# .. list-table:: AgentScope 中的长期记忆类
# :header-rows: 1
#
# * - 类
# - 抽象方法
# - 描述
# * - ``LongTermMemoryBase``
# - | ``record``
# | ``retrieve``
# | ``record_to_memory``
# | ``retrieve_from_memory``
# - - 如果想支持 "static_control" 模式,必须实现 ``record`` 和 ``retrieve`` 方法。
# - 想要支持 "agent_control" 模式,必须实现 ``record_to_memory`` 和 ``retrieve_from_memory`` 方法。
# * - ``Mem0LongTermMemory``
# - | ``record``
# | ``retrieve``
# | ``record_to_memory``
# | ``retrieve_from_memory``
# - 基于 mem0 库的长期记忆实现,支持向量存储和检索。
# * - ``ReMePersonalLongTermMemory``
# - | ``record``
# | ``retrieve``
# | ``record_to_memory``
# | ``retrieve_from_memory``
# - 基于 ReMe 框架的个人记忆实现,提供强大的记忆管理和检索功能。
#
#
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# - :ref:`memory` - 基础记忆系统
# - :ref:`agent` - ReAct 智能体
# - :ref:`tool` - 工具系统

View File

@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""
.. _mcp:
MCP
=========================
本章将介绍 AgentScope 对 MCPModel Context Protocol的以下支持
- 支持 **HTTP** StreamableHTTP 和 SSE和 **StdIO** 类型的 MCP 服务器
- 提供 **有状态** 和 **无状态** 两种 MCP 客户端
- 提供 **MCP 级别** 和 **函数级别** 的 MCP 工具管理
这里的有状态/无状态是指客户端是否会维持与 MCP 服务器的会话session
无状态客户端只会在调用工具发生时建立会话,并在工具调用结束后立即销毁会话,是一种轻量化的使用方式。
下表总结了支持的 MCP 客户端类型和协议:
.. list-table:: 支持的 MCP 客户端类型和协议
:header-rows: 1
* - 客户端类型
- HTTPStreamableHTTP 和 SSE
- StdIO
* - 有状态客户端
- ``HttpStatefulClient``
- ``StdIOStatefulClient``
* - 无状态客户端
- ``HttpStatelessClient``
-
"""
import asyncio
import json
import os
from agentscope.mcp import HttpStatefulClient, HttpStatelessClient
from agentscope.tool import Toolkit
# %%
# MCP 客户端
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 在 AgentScope 中MCP 客户端负责
#
# - 连接到 MCP 服务器,
# - 从服务器获取工具函数,以及
# - 调用 MCP 服务器中的工具函数。
#
# AgentScope 中有两种类型的 MCP 客户端:**有状态** 和 **无状态**。
# 它们仅在 **如何管理与 MCP 服务器的会话** 方面有所不同。
#
# - 有状态客户端:有状态 MCP 客户端在其生命周期内 **维持与 MCP 服务器的持久会话**。开发者应显式调用 ``connect()`` 和 ``close()`` 方法来管理会话的生命周期。
# - 无状态客户端:无状态 MCP 客户端在调用工具函数时创建新会话,在工具函数调用完成后立即销毁会话,更加轻量化。
#
# .. note:: - StdIO MCP 服务器只有有状态客户端,当调用 ``connect()`` 时,它将在本地启动 MCP 服务器然后连接到它。
# - 对于有状态客户端,开发者必须确保在调用工具函数时客户端已连接。
# - 当有多个 `HttpStatefulClient` 或 `StdIOStatefulClient` 建立连接时,应按照后进先出 (LIFO) 的顺序关闭它们以避免引发错误。
#
# 以高德地图 MCP 服务器为例,有状态和无状态客户端的创建非常相似:
#
stateful_client = HttpStatefulClient(
# 用于标识 MCP 的名称
name="mcp_services_stateful",
transport="streamable_http",
url=f"https://mcp.amap.com/mcp?key={os.environ['GAODE_API_KEY']}",
)
stateless_client = HttpStatelessClient(
# 用于标识 MCP 的名称
name="mcp_services_stateless",
transport="streamable_http",
url=f"https://mcp.amap.com/mcp?key={os.environ['GAODE_API_KEY']}",
)
# %%
# 有状态和无状态客户端都提供以下方法:
#
# .. list-table:: MCP 客户端方法
# :header-rows: 1
#
# * - 方法
# - 描述
# * - ``list_tools``
# - 列出 MCP 服务器中所有可用的工具。
# * - ``get_callable_function``
# - 通过名称从 MCP 服务器获取可调用的函数对象。
#
# MCP 作为工具
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 提供了对 MCP 工具的细粒度管理,包括 MCP 级别和函数级别的管理。
#
# MCP 级别管理
# --------------------------------
# 您可以将 MCP 服务器的所有工具一次性注册到 ``Toolkit`` 中,如下所示。
#
# .. tip:: 可选地,开发者可以通过指定组名来管理工具。有关分组工具管理,请参考 :ref:`tool` 部分。
#
toolkit = Toolkit()
async def example_register_stateless_mcp() -> None:
"""注册无状态客户端 MCP 工具的示例。"""
# 从 MCP 服务器注册所有工具
await toolkit.register_mcp_client(
stateless_client,
# group_name="map_services", # 可选的组名
)
print("注册的 MCP 工具总数:", len(toolkit.get_json_schemas()))
maps_geo = next(
tool
for tool in toolkit.get_json_schemas()
if tool["function"]["name"] == "maps_geo"
)
print("\n示例 ``maps_geo`` 函数:")
print(
json.dumps(
maps_geo,
indent=4,
ensure_ascii=False,
),
)
asyncio.run(example_register_stateless_mcp())
# %%
# 要移除已注册的工具,可以使用 ``remove_tool_function`` 函数,或使用 ``remove_mcp_clients`` 移除特定 MCP 的所有工具。
#
async def example_remove_mcp_tools() -> None:
"""移除 MCP 工具的示例。"""
print("移除前的工具总数:", len(toolkit.get_json_schemas()))
# 通过名称移除特定的工具函数
toolkit.remove_tool_function("maps_geo")
print("工具数量:", len(toolkit.get_json_schemas()))
# 通过名称移除 MCP 客户端的所有工具
await toolkit.remove_mcp_clients(client_names=["mcp_services_stateless"])
print("工具数量:", len(toolkit.get_json_schemas()))
asyncio.run(example_remove_mcp_tools())
# %%
# 函数级别管理
# --------------------------------
# 注意到开发者有对 MCP 工具进行更细粒度控制的需求,例如对工具结果进行后处理,或使用它们创建更复杂的工具函数。
#
# 因此AgentScope 支持通过工具名从 MCP 客户端获取可调用的函数对象,这样开发者可以
#
# - 直接调用它,
# - 将其包装到自己的函数中,或以任何其它方式进行使用。
#
# 此外,开发者可以指定是否将工具函数执行结果包装成 ``ToolResponse`` 对象,以便与 ``Toolkit`` 无缝使用。
# 如果设置 ``wrap_tool_result=False``,将返回原始结果类型 ``mcp.types.CallToolResult``。
#
# 以 ``maps_geo`` 函数为例,可以将其获取为可调用的函数对象,如下所示:
#
async def example_function_level_usage() -> None:
"""使用函数级别 MCP 工具的示例。"""
func_obj = await stateless_client.get_callable_function(
func_name="maps_geo",
# 是否将工具结果包装到 AgentScope 的 ToolResponse 中
wrap_tool_result=True,
)
# 您可以获取其名称、描述和 JSON schema
print("函数名称:", func_obj.name)
print("函数描述:", func_obj.description)
print(
"函数 JSON schema",
json.dumps(func_obj.json_schema, indent=4, ensure_ascii=False),
)
# 直接调用函数对象
res = await func_obj(
address="天安门广场",
city="北京",
)
print("\n函数调用结果:")
print(res)
asyncio.run(example_function_level_usage())
# %%
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 有关更多详细信息,请参见:
#
# - :ref:`tool`
# - :ref:`agent`
#

View File

@@ -0,0 +1,433 @@
# -*- coding: utf-8 -*-
"""
.. _memory:
记忆
========================
AgentScope 中的记忆模块负责:
- 存储消息对象(``Msg``
- 利用标记mark管理消息
**标记** 是与记忆中每条消息关联的字符串标签,可用于根据消息的上下文或目的对消息进行分类、过滤和检索。
可用于实现进阶的记忆管理功能,例如在 `ReActAgent` 类中,使用``"hint"``标签标记一次性的提示消息,
以便在使用完成后将其从记忆中删除。
.. note:: AgentScope 中的记忆模块仅提供消息存储和管理的原子功能,记忆压缩等算法逻辑在 `智能体 <agent>`_ 中实现。
目前AgentScope 提供以下记忆存储实现:
.. list-table:: AgentScope 中的内置记忆类
:header-rows: 1
* - 类
- 描述
* - ``InMemoryMemory``
- 简单的内存记忆存储实现。
* - ``AsyncSQLAlchemyMemory``
- 基于异步 SQLAlchemy 的记忆存储实现,支持如 SQLite、PostgreSQL、MySQL 等多种关系数据库。
* - ``RedisMemory``
- 基于 Redis 的记忆存储实现。
.. tip:: 如果您有兴趣贡献新的记忆存储实现,请参考 `贡献指南 <https://github.com/agentscope-ai/agentscope/blob/main/CONTRIBUTING.md#types-of-contributions>`_。
以上所有记忆类均继承自基类 ``MemoryBase``,并提供以下方法来管理记忆中的消息:
.. list-table:: 记忆类提供的方法
:header-rows: 1
* - 方法
- 描述
* - ``add(
memories: Msg | list[Msg] | None,
marks: str | list[str] | None = None,
) -> None``
- 将 ``Msg`` 对象添加到记忆存储中,并使用给定的标记(如果提供)。
* - ``delete(msg_ids: list[str]) -> int``
- 通过ID从记忆存储中删除消息。
* - ``delete_by_mark(mark: str | list[str]) -> int``
- 通过标记从记忆中删除消息。
* - ``size() -> int``
- 获取记忆存储的大小。
* - ``clear() -> None``
- 清空记忆存储。
* - ``get_memory(
mark: str | None = None,
exclude_mark: str | None = None,
) -> list[Msg]``
- 通过标记从记忆中获取消息(如果提供)。否则,获取所有消息。如果使用 ``update_compressed_summary`` 方法存储压缩摘要,它将附加到返回消息的头部。
* - ``update_messages_mark(
new_mark: str | None,
old_mark: str | None = None,
msg_ids: list[str] | None = None,
) -> int``
- 统一的方法,用于更新存储中消息的标记(添加、删除或更改标记)。
* - ``update_compressed_summary(
summary: str,
) -> None``
- 更新存储在记忆中的摘要属性。
"""
import asyncio
import json
import fakeredis
from sqlalchemy.ext.asyncio import create_async_engine
from agentscope.memory import (
InMemoryMemory,
AsyncSQLAlchemyMemory,
RedisMemory,
)
from agentscope.message import Msg
# %%
# 内存记忆
# ~~~~~~~~~~~~~~~~~~~~~~~~
#
# 内存记忆提供了一种在内存中存储消息的简单方式。
# 结合 :ref:`state` 模块,它可以在不同用户和会话之间持久化记忆内容。
#
async def in_memory_example():
"""使用InMemoryMemory在内存中存储消息的示例。"""
memory = InMemoryMemory()
await memory.add(
Msg("Alice", "生成一份关于AgentScope的报告", "user"),
)
# 添加一条带有标记"hint"的提示消息
await memory.add(
[
Msg(
"system",
"<system-hint>首先创建一个计划来收集信息,然后逐步生成报告。</system-hint>",
"system",
),
],
marks="hint",
)
msgs = await memory.get_memory(mark="hint")
print("带有标记'hint'的消息:")
for msg in msgs:
print(f"- {msg}")
# 所有存储的消息都可以通过 ``state_dict`` 和 ``load_state_dict`` 方法导出和加载。
state = memory.state_dict()
print("记忆的状态字典:")
print(json.dumps(state, indent=2, ensure_ascii=False))
# 通过标记删除消息
deleted_count = await memory.delete_by_mark("hint")
print(f"删除了 {deleted_count} 条带有标记'hint'的消息。")
print("删除后的记忆状态字典:")
state = memory.state_dict()
print(json.dumps(state, indent=2, ensure_ascii=False))
asyncio.run(in_memory_example())
# %%
# 关系数据库记忆Relational Database Memory
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 通过 SQLAlchemy 提供统一的接口来使用关系数据库,支持:
#
# - 多种数据库,如 SQLite、PostgreSQL、MySQL 等
# - 用户和会话管理
# - 生产环境中的连接池
#
# 具体来说这里我们以SQLite支持的记忆为例。
async def sqlalchemy_example() -> None:
"""使用 AsyncSQLAlchemyMemory 在 SQLite 数据库中存储消息的示例。"""
# 首先创建一个异步 SQLAlchemy 引擎
engine = create_async_engine("sqlite+aiosqlite:///./test_memory.db")
# 然后使用该引擎创建记忆
memory = AsyncSQLAlchemyMemory(
engine_or_session=engine,
# 可选传入指定user_id和session_id
user_id="user_1",
session_id="session_1",
)
await memory.add(
Msg("Alice", "生成一份关于AgentScope的报告", "user"),
)
await memory.add(
[
Msg(
"system",
"<system-hint>首先创建一个计划来收集信息,然后逐步生成报告。</system-hint>",
"system",
),
],
marks="hint",
)
msgs = await memory.get_memory(mark="hint")
print("带有标记'hint'的消息:")
for msg in msgs:
print(f"- {msg}")
# 完成后关闭引擎
await memory.close()
asyncio.run(sqlalchemy_example())
# %%
# 可选地,您也可以将 ``AsyncSQLAlchemyMemory`` 用作异步上下文管理器,退出上下文时会话将自动关闭。
async def sqlalchemy_context_example() -> None:
"""使用 AsyncSQLAlchemyMemory 作为异步上下文管理器的示例。"""
engine = create_async_engine("sqlite+aiosqlite:///./test_memory.db")
async with AsyncSQLAlchemyMemory(
engine_or_session=engine,
user_id="user_1",
session_id="session_1",
) as memory:
await memory.add(
Msg("Alice", "生成一份关于 AgentScope 的报告", "user"),
)
msgs = await memory.get_memory()
print("记忆中的所有消息:")
for msg in msgs:
print(f"- {msg}")
asyncio.run(sqlalchemy_context_example())
# %%
# 在生产环境中例如使用FastAPI时可以按如下方式启用连接池
#
# .. code-block:: python
# :caption: FastAPI中使用连接池的SQLAlchemy记忆
#
# from typing import AsyncGenerator
#
# from fastapi import FastAPI, Depends
# from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
#
# from agentscope.agent import ReActAgent
# from agentscope.pipeline import stream_printing_messages
#
#
# app = FastAPI()
#
# # 创建带连接池的异步SQLAlchemy引擎
# engine = create_async_engine(
# "sqlite+aiosqlite:///./test_memory.db",
# pool_size=10,
# max_overflow=20,
# pool_timeout=30,
# # ... 其他连接池设置
# )
#
# # 创建会话制造器
# async_session_marker = async_sessionmaker(
# engine,
# expire_on_commit=False,
# autocommit=False,
# autoflush=False,
# )
#
# async def get_db() -> AsyncGenerator[AsyncSession, None]:
# async with async_session_marker() as session:
# try:
# yield session
# await session.commit()
# except Exception:
# await session.rollback()
# raise
# finally:
# await session.close()
#
# @app.post("/chat")
# async def chat_endpoint(
# user_id: str,
# session_id: str,
# input: str,
# db_session: AsyncSession = Depends(get_db),
# ):
# # 智能体的一些设置
# ...
#
# # 使用SQLAlchemy记忆创建智能体
# agent = ReActAgent(
# # ...
# memory=AsyncSQLAlchemyMemory(
# engine_or_session=db_session,
# user_id=user_id,
# session_id=session_id,
# ),
# )
#
# # 处理与智能体的对话
# async for msg, _ in stream_printing_messages(
# agents=[agent],
# coroutine_task=agent(Msg("user", input, "user")),
# ):
# # 将消息返回给客户端
# ...
#
#
# NoSQL数据库记忆NoSQL Database Memory
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope还提供基于NoSQL数据库如Redis的记忆实现。
# 它也支持用户和会话管理,以及生产环境中的连接池。
#
# 首先我们可以按如下方式初始化Redis记忆
async def redis_memory_example() -> None:
"""使用 RedisMemory 在 Redis 中存储消息的示例。"""
# 使用fakeredis进行内存测试无需真实的 Redis 服务器
fake_redis = fakeredis.aioredis.FakeRedis(decode_responses=True)
# 创建 Redis 记忆
memory = RedisMemory(
# 使用fake redis进行演示
connection_pool=fake_redis.connection_pool,
# 也可以通过指定主机和端口连接到真实的Redis服务器
# host="localhost",
# port=6379,
# 可选地指定 user_id 和 session_id
user_id="user_1",
session_id="session_1",
)
# 向记忆中添加消息
await memory.add(
Msg(
"Alice",
"生成一份关于AgentScope的报告",
"user",
),
)
# 添加一条带有标记"hint"的提示消息
await memory.add(
Msg(
"system",
"<system-hint>首先创建一个计划来收集信息,然后逐步生成报告。</system-hint>",
"system",
),
marks="hint",
)
# 检索带有标记"hint"的消息
msgs = await memory.get_memory(mark="hint")
print("带有标记'hint'的消息:")
for msg in msgs:
print(f"- {msg}")
asyncio.run(redis_memory_example())
# %%
# 同样,`RedisMemory` 也可以在生产环境中使用连接池例如与FastAPI一起使用。
#
# .. code-block:: python
# :caption: FastAPI中使用连接池的Redis记忆
#
# from fastapi import FastAPI, HTTPException
# from redis.asyncio import ConnectionPool
# from contextlib import asynccontextmanager
#
# # 全局Redis连接池
# redis_pool: ConnectionPool | None = None
#
#
# # 使用lifespan事件管理Redis连接池
# @asynccontextmanager
# async def lifespan(app: FastAPI):
# global redis_pool
# redis_pool = ConnectionPool(
# host="localhost",
# port=6379,
# db=0,
# password=None,
# decode_responses=True,
# max_connections=10,
# encoding="utf-8",
# )
# print("✅ Redis连接已建立")
#
# yield
#
# await redis_pool.disconnect()
# print("✅ Redis连接已关闭")
#
#
# app = FastAPI(lifespan=lifespan)
#
#
# @app.post("/chat_endpoint")
# async def chat_endpoint(
# user_id: str, session_id: str, input: str
# ):
# """聊天端点"""
# global redis_pool
# if redis_pool is None:
# raise HTTPException(
# status_code=500,
# detail="Redis连接池未初始化。",
# )
#
# # 创建Redis记忆
# memory = RedisMemory(
# connection_pool=redis_pool,
# user_id=user_id,
# session_id=session_id,
# )
#
# ...
#
# # 完成后关闭Redis客户端连接
# client = memory.get_client()
# await client.aclose()
#
#
#
# 自定义记忆Customizing Memory
# ~~~~~~~~~~~~~~~~~~~~~~~~
#
# 要自定义您自己的记忆实现,只需从 ``MemoryBase`` 继承并实现以下方法:
#
# .. list-table::
# :header-rows: 1
#
# * - 方法
# - 描述
# * - ``add``
# - 向记忆中添加 ``Msg`` 对象
# * - ``delete``
# - 从记忆中删除 ``Msg`` 对象
# * - ``delete_by_mark``
# - 通过标记从记忆中删除 ``Msg`` 对象
# * - ``size``
# - 记忆的大小
# * - ``clear``
# - 清空记忆内容
# * - ``get_memory``
# - 以 ``Msg`` 对象列表的形式获取记忆内容
# * - ``update_messages_mark``
# - 更新记忆中消息的标记
# * - ``state_dict``
# - 获取记忆的状态字典
# * - ``load_state_dict``
# - 加载记忆的状态字典
#
# 延伸阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~
# - :ref:`agent`
# - :ref:`long-term-memory`

View File

@@ -0,0 +1,403 @@
# -*- coding: utf-8 -*-
"""
.. _middleware:
中间件
===========================
AgentScope 提供了灵活的中间件系统,允许开发者拦截和修改各种操作的执行。
目前,中间件支持已在 ``Toolkit`` 类中实现,用于**工具执行**。
中间件系统遵循**洋葱模型**,每个中间件包裹在前一个中间件之外,形成层次结构。
这使得开发者可以:
- 在操作前进行**预处理**
- 在执行过程中**拦截和修改**响应
- 在操作完成后进行**后处理**
- 根据条件**跳过**操作执行
.. tip:: 未来版本的 AgentScope 将扩展中间件支持到其他组件,如智能体和模型。
"""
import asyncio
from typing import AsyncGenerator, Callable
from agentscope.message import TextBlock, ToolUseBlock
from agentscope.tool import ToolResponse, Toolkit
# %%
# 工具执行中间件
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ``Toolkit`` 类通过 ``register_middleware`` 方法支持工具执行的中间件。
# 每个中间件可以拦截工具调用并修改输入或输出。
#
# 中间件签名
# ------------------------------
#
# 中间件函数应具有以下签名:
#
# .. code-block:: python
#
# async def middleware(
# kwargs: dict,
# next_handler: Callable,
# ) -> AsyncGenerator[ToolResponse, None]:
# # 从 kwargs 访问参数
# tool_call = kwargs["tool_call"]
#
# # 预处理
# # ...
#
# # 调用下一个中间件或工具函数
# async for response in await next_handler(**kwargs):
# # 后处理
# yield response
#
# .. list-table:: 中间件参数
# :header-rows: 1
#
# * - 参数
# - 类型
# - 描述
# * - ``kwargs``
# - ``dict``
# - 上下文参数。当前包含 ``tool_call`` (ToolUseBlock)。未来版本可能包含更多参数。
# * - ``next_handler``
# - ``Callable``
# - 一个可调用对象,接受 kwargs dict 并返回产生 AsyncGenerator[ToolResponse] 的协程
# * - **返回值**
# - ``AsyncGenerator[ToolResponse, None]``
# - 产生 ToolResponse 对象的异步生成器
#
# 基本示例
# ------------------------------
#
# 以下是一个记录工具调用的简单中间件:
#
async def logging_middleware(
kwargs: dict,
next_handler: Callable,
) -> AsyncGenerator[ToolResponse, None]:
"""记录工具执行的中间件。"""
# 从 kwargs 访问工具调用
tool_call = kwargs["tool_call"]
# 预处理:在工具执行前记录日志
print(f"[中间件] 调用工具:{tool_call['name']}")
print(f"[中间件] 输入:{tool_call['input']}")
# 调用下一个处理器(另一个中间件或实际工具)
async for response in await next_handler(**kwargs):
# 后处理:记录响应
print(f"[中间件] 响应:{response.content[0]['text']}")
yield response
# 在所有响应产生后执行
print(f"[中间件] 工具 {tool_call['name']} 完成")
# %%
# 让我们将这个中间件注册到工具包并测试它:
#
async def search_tool(query: str) -> ToolResponse:
"""一个简单的搜索工具。
Args:
query (`str`):
搜索查询。
Returns:
`ToolResponse`:
搜索结果。
"""
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"'{query}' 的搜索结果",
),
],
)
async def example_logging_middleware() -> None:
"""使用日志中间件的示例。"""
# 创建工具包并注册工具
toolkit = Toolkit()
toolkit.register_tool_function(search_tool)
# 注册中间件
toolkit.register_middleware(logging_middleware)
# 调用工具
result = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="1",
name="search_tool",
input={"query": "AgentScope"},
),
)
async for response in result:
print(f"\n[最终] {response.content[0]['text']}\n")
print("=" * 60)
print("示例 1日志中间件")
print("=" * 60)
asyncio.run(example_logging_middleware())
# %%
# 修改输入和输出
# ------------------------------
#
# 中间件还可以修改工具调用的输入和响应内容:
#
async def transform_middleware(
kwargs: dict,
next_handler: Callable,
) -> AsyncGenerator[ToolResponse, None]:
"""转换输入和输出的中间件。"""
# 从 kwargs 访问工具调用
tool_call = kwargs["tool_call"]
# 预处理:修改输入
original_query = tool_call["input"]["query"]
tool_call["input"]["query"] = f"[已转换] {original_query}"
async for response in await next_handler(**kwargs):
# 后处理:修改响应
original_text = response.content[0]["text"]
response.content[0]["text"] = f"{original_text} [已修改]"
yield response
async def example_transform_middleware() -> None:
"""转换中间件的示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(search_tool)
toolkit.register_middleware(transform_middleware)
result = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="2",
name="search_tool",
input={"query": "中间件"},
),
)
async for response in result:
print(f"结果:{response.content[0]['text']}")
print("\n" + "=" * 60)
print("示例 2转换中间件")
print("=" * 60)
asyncio.run(example_transform_middleware())
# %%
# 授权中间件
# ------------------------------
#
# 可以使用中间件实现授权检查,如果未授权则跳过工具执行:
#
async def authorization_middleware(
kwargs: dict,
next_handler: Callable,
) -> AsyncGenerator[ToolResponse, None]:
"""检查授权的中间件。"""
# 从 kwargs 访问工具调用
tool_call = kwargs["tool_call"]
# 检查工具是否已授权(简单示例)
authorized_tools = {"search_tool"}
if tool_call["name"] not in authorized_tools:
# 跳过执行并直接返回错误
print(f"[授权] 工具 {tool_call['name']} 未授权")
yield ToolResponse(
content=[
TextBlock(
type="text",
text=f"错误:工具 '{tool_call['name']}' 未授权",
),
],
)
return
# 工具已授权,继续执行
print(f"[授权] 工具 {tool_call['name']} 已授权")
async for response in await next_handler(**kwargs):
yield response
async def unauthorized_tool(data: str) -> ToolResponse:
"""一个未授权的工具。
Args:
data (`str`):
一些数据。
Returns:
`ToolResponse`:
结果。
"""
return ToolResponse(
content=[TextBlock(type="text", text=f"处理 {data}")],
)
async def example_authorization_middleware() -> None:
"""授权中间件的示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(search_tool)
toolkit.register_tool_function(unauthorized_tool)
toolkit.register_middleware(authorization_middleware)
# 尝试授权的工具
print("\n调用已授权的工具:")
result = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="3",
name="search_tool",
input={"query": "测试"},
),
)
async for response in result:
print(f"结果:{response.content[0]['text']}")
# 尝试未授权的工具
print("\n调用未授权的工具:")
result = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="4",
name="unauthorized_tool",
input={"data": "测试"},
),
)
async for response in result:
print(f"结果:{response.content[0]['text']}")
print("\n" + "=" * 60)
print("示例 3授权中间件")
print("=" * 60)
asyncio.run(example_authorization_middleware())
# %%
# 多个中间件(洋葱模型)
# ------------------------------
#
# 当注册多个中间件时,它们形成类似洋葱的结构。
# 执行顺序遵循洋葱模型:
#
# - **预处理**:按照中间件注册的顺序执行
# - **后处理**:按相反顺序执行(从内到外)
#
# 这是因为实际的工具响应对象会通过中间件链传递,
# 每个中间件都会原地修改它。
#
async def middleware_1(
kwargs: dict,
next_handler: Callable,
) -> AsyncGenerator[ToolResponse, None]:
"""第一个中间件。"""
# 从 kwargs 访问工具调用
tool_call = kwargs["tool_call"]
# 预处理
print("[M1] 预处理")
tool_call["input"]["query"] += " [M1]"
async for response in await next_handler(**kwargs):
# 后处理
response.content[0]["text"] += " [M1]"
print("[M1] 后处理")
yield response
async def middleware_2(
kwargs: dict,
next_handler: Callable,
) -> AsyncGenerator[ToolResponse, None]:
"""第二个中间件。"""
# 从 kwargs 访问工具调用
tool_call = kwargs["tool_call"]
# 预处理
print("[M2] 预处理")
tool_call["input"]["query"] += " [M2]"
async for response in await next_handler(**kwargs):
# 后处理
response.content[0]["text"] += " [M2]"
print("[M2] 后处理")
yield response
async def example_multiple_middleware() -> None:
"""多个中间件的示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(search_tool)
# 按顺序注册中间件
toolkit.register_middleware(middleware_1)
toolkit.register_middleware(middleware_2)
result = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="5",
name="search_tool",
input={"query": "测试"},
),
)
async for response in result:
print(f"\n最终结果:{response.content[0]['text']}")
print("\n" + "=" * 60)
print("示例 4多个中间件洋葱模型")
print("=" * 60)
print("\n执行流程:")
print("M1 预处理 → M2 预处理 → 工具 → M2 后处理 → M1 后处理")
print()
asyncio.run(example_multiple_middleware())
# %%
# 使用场景
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 中间件系统适用于各种场景:
#
# - **日志和监控**:跟踪工具使用情况和性能
# - **授权**:控制对特定工具的访问
# - **速率限制**:限制工具调用的频率
# - **缓存**:缓存重复调用的工具响应
# - **错误处理**:添加重试逻辑或优雅降级
# - **输入验证**:验证和清理工具输入
# - **输出转换**:格式化或过滤工具输出
# - **指标收集**:收集有关工具使用情况的统计信息
#
# .. note::
# - 中间件按注册顺序应用
# - 同一个 ``ToolResponse`` 对象通过中间件链传递并原地修改
# - 中间件可以通过不调用 ``next_handler`` 来完全跳过工具执行
# - 所有中间件必须是产生 ``ToolResponse`` 对象的异步生成器函数

View File

@@ -0,0 +1,235 @@
# -*- coding: utf-8 -*-
"""
.. _model:
模型
====================
在本教程中,我们介绍 AgentScope 中集成的模型 API、如何使用它们以及如何集成新的模型 API。
AgentScope 目前支持的模型 API 和模型提供商包括:
.. list-table::
:header-rows: 1
* - API
- 类
- 兼容
- 流式
- 工具
- 视觉
- 推理
* - OpenAI
- ``OpenAIChatModel``
- vLLM, DeepSeek
- ✅
- ✅
- ✅
- ✅
* - DashScope
- ``DashScopeChatModel``
-
- ✅
- ✅
- ✅
- ✅
* - Anthropic
- ``AnthropicChatModel``
-
- ✅
- ✅
- ✅
- ✅
* - Gemini
- ``GeminiChatModel``
-
- ✅
- ✅
- ✅
- ✅
* - Ollama
- ``OllamaChatModel``
-
- ✅
- ✅
- ✅
- ✅
.. note:: 当使用 vLLM 时,需要在部署时为不同模型配置相应的工具调用参数,例如 ``--enable-auto-tool-choice``、``--tool-call-parser`` 等参数。更多详情请参考 `vLLM 官方文档 <https://docs.vllm.ai/en/latest/features/tool_calling.html>`_。
.. note:: 兼容 OpenAI API 的模型(例如 vLLM 部署的模型),推荐使用 ``OpenAIChatModel``,并通过 ``client_kwargs={"base_url": "http://your-api-endpoint"}`` 参数指定 API 端点。例如:
.. code-block:: python
OpenAIChatModel(client_kwargs={"base_url": "http://localhost:8000/v1"})
.. note:: 模型的行为参数(如温度、最大长度等)可以通过 ``generate_kwargs`` 参数在构造函数中提前设定。例如:
.. code-block:: python
OpenAIChatModel(generate_kwargs={"temperature": 0.3, "max_tokens": 1000})
为了提供统一的模型接口,上述所有类均被统一为:
- ``__call__`` 函数的前三个参数是 ``messages````tools`` 和 ``tool_choice``,分别是输入消息,工具函数的 JSON schema以及工具选择的模式。
- 非流式返回时,返回类型是 ``ChatResponse`` 实例;流式返回时,返回的是 ``ChatResponse`` 的异步生成器。
.. note:: 不同的模型 API 在输入消息格式上有所不同AgentScope 通过 formatter 模块处理消息的转换,请参考 :ref:`format`。
``ChatResponse`` 包含大模型生成的推理/文本/工具使用内容、身份、创建时间和使用信息。
"""
import asyncio
import json
import os
from agentscope.message import TextBlock, ToolUseBlock, ThinkingBlock, Msg
from agentscope.model import ChatResponse, DashScopeChatModel
response = ChatResponse(
content=[
ThinkingBlock(
type="thinking",
thinking="我应该在 Google 上搜索 AgentScope。",
),
TextBlock(type="text", text="我将在 Google 上搜索 AgentScope。"),
ToolUseBlock(
type="tool_use",
id="642n298gjna",
name="google_search",
input={"query": "AgentScope"},
),
],
)
print(response)
# %%
# 以 ``DashScopeChatModel`` 为例,调用和返回结果如下:
async def example_model_call() -> None:
"""使用 DashScopeChatModel 的示例。"""
model = DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
)
res = await model(
messages=[
{"role": "user", "content": "你好!"},
],
)
# 您可以直接使用响应内容创建 ``Msg`` 对象
msg_res = Msg("Friday", res.content, "assistant")
print("LLM 返回结果:", res)
print("作为 Msg 的响应:", msg_res)
asyncio.run(example_model_call())
# %%
# 流式返回
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 要启用流式返回,请在模型的构造函数中将 ``stream`` 参数设置为 ``True``。
# 流式返回中,``__call__`` 方法将返回一个 **异步生成器**,该生成器迭代返回 ``ChatResponse`` 实例。
#
# .. note:: AgentScope 中的流式返回结果为 **累加式**,这意味着每个 chunk 中的内容包含所有之前的内容加上新生成的内容。
#
async def example_streaming() -> None:
"""使用流式模型的示例。"""
model = DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=True,
)
generator = await model(
messages=[
{
"role": "user",
"content": "从 1 数到 20只报告数字不要任何其他信息。",
},
],
)
print("响应的类型:", type(generator))
i = 0
async for chunk in generator:
print(f"{i}")
print(f"\t类型: {type(chunk.content)}")
print(f"\t{chunk}\n")
i += 1
asyncio.run(example_streaming())
# %%
# 推理模型
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 通过提供 ``ThinkingBlock`` 来支持推理模型。
#
async def example_reasoning() -> None:
"""使用推理模型的示例。"""
model = DashScopeChatModel(
model_name="qwen-turbo",
api_key=os.environ["DASHSCOPE_API_KEY"],
enable_thinking=True,
)
res = await model(
messages=[
{"role": "user", "content": "我是谁?"},
],
)
last_chunk = None
async for chunk in res:
last_chunk = chunk
print("最终响应:")
print(last_chunk)
asyncio.run(example_reasoning())
# %%
# 工具 API
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 不同的模型提供商在工具 API 方面有所不同,例如工具 JSON schema、工具调用/响应格式。
# 为了提供统一的接口AgentScope 通过以下方式解决了这个问题:
#
# - 提供了统一的工具调用结构 block :ref:`ToolUseBlock <tool-block>` 和工具响应结构 :ref:`ToolResultBlock <tool-block>`。
# - 在模型类的 ``__call__`` 方法中提供统一的工具接口 ``tools``,接受工具 JSON schema 列表,如下所示:
#
json_schemas = [
{
"type": "function",
"function": {
"name": "google_search",
"description": "在 Google 上搜索查询。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索查询。",
},
},
"required": ["query"],
},
},
},
]
# %%
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# - :ref:`message`
# - :ref:`prompt`
#

View File

@@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
"""
.. _pipeline:
管道 (Pipeline)
========================
对于多智能体编排AgentScope 提供了 ``agentscope.pipeline`` 模块
作为将智能体链接在一起的语法糖,具体包括
- **MsgHub**: 用于多个智能体之间消息的广播
- **sequential_pipeline** 和 **SequentialPipeline**: 以顺序方式执行多个智能体的函数式和类式实现
- **fanout_pipeline** 和 **FanoutPipeline**: 将相同输入分发给多个智能体的函数式和类式实现
- **stream_printing_messages**: 将智能体在回复过程中,调用 ``self.print`` 打印的消息转换为一个异步生成器
"""
import os, asyncio
from agentscope.formatter import DashScopeMultiAgentFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.agent import ReActAgent
from agentscope.pipeline import MsgHub, stream_printing_messages
# %%
# 使用 MsgHub 进行广播
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ``MsgHub`` 类是一个 **异步上下文管理器**,它接收一个智能体列表作为其参与者。
# 当一个参与者生成回复消息时,将通过调用所有其他参与者的 ``observe`` 方法广播该消息。
# 这意味着在 ``MsgHub`` 上下文中,开发者无需手动将回复消息从一个智能体发送到另一个智能体。
#
# 这里我们创建四个智能体Alice、Bob、Charlie 和 David。
# 然后我们让 Alice、Bob 和 Charlie 通过自我介绍开始一个会议。需要注意的是 David 没有包含在这个会议中。
#
def create_agent(name: str, age: int, career: str) -> ReActAgent:
"""根据给定信息创建智能体对象。"""
return ReActAgent(
name=name,
sys_prompt=f"你是{name},一个{age}岁的{career}",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeMultiAgentFormatter(),
)
alice = create_agent("Alice", 50, "老师")
bob = create_agent("Bob", 35, "工程师")
charlie = create_agent("Charlie", 28, "设计师")
david = create_agent("David", 30, "开发者")
# %%
# 然后我们创建一个 ``MsgHub`` 上下文,并让他们自我介绍:
#
# .. hint:: ``announcement`` 中的消息将在进入 ``MsgHub`` 上下文时广播给所有参与者。
#
async def example_broadcast_message():
"""使用 MsgHub 广播消息的示例。"""
# 创建消息中心
async with MsgHub(
participants=[alice, bob, charlie],
announcement=Msg(
"user",
"现在请简要介绍一下自己,包括你的姓名、年龄和职业。",
"user",
),
) as hub:
# 无需手动消息传递的群聊
await alice()
await bob()
await charlie()
asyncio.run(example_broadcast_message())
# %%
# 现在让我们检查 Bob、Charlie 和 David 是否收到了 Alice 的消息。
#
async def check_broadcast_message():
"""检查消息是否正确广播。"""
user_msg = Msg(
"user",
"你知道 Alice 是谁吗,她是做什么的?",
"user",
)
await bob(user_msg)
await charlie(user_msg)
await david(user_msg)
asyncio.run(check_broadcast_message())
# %%
# 现在我们观察到 Bob 和 Charlie 知道 Alice 和她的职业,而 David 对
# Alice 一无所知,因为他没有包含在 ``MsgHub`` 上下文中。
#
#
# 动态管理
# ---------------------------
# 此外,``MsgHub`` 支持通过以下方法动态管理参与者:
#
# - ``add``: 添加一个或多个智能体作为新参与者
# - ``delete``: 从参与者中移除一个或多个智能体,他们将不再接收广播消息
# - ``broadcast``: 向所有当前参与者广播消息
#
# .. note:: 新添加的参与者不会接收到之前的消息。
#
# .. code-block:: python
#
# async with MsgHub(participants=[alice]) as hub:
# # 添加新参与者
# hub.add(david)
#
# # 移除参与者
# hub.delete(alice)
#
# # 向所有当前参与者广播
# await hub.broadcast(
# Msg("system", "现在我们开始...", "system"),
# )
#
#
# 管道
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 管道是 AgentScope 中多智能体编排的一种语法糖。
#
# 目前AgentScope 提供三种管道,用于减轻开发者的负担:
#
# 1. **顺序管道 (Sequential Pipeline)**: 按预定义顺序逐个执行智能体
# 2. **扇出管道 (Fanout Pipeline)**: 将相同输入分发给多个智能体并收集它们的响应
# 3. **流式获取打印消息 (stream printing messages)**: 将智能体在回复过程中,调用 ``self.print`` 打印的消息转换为一个异步生成器
#
# 顺序管道
# ------------------------
# 顺序管道逐个执行智能体,前一个智能体的输出成为下一个智能体的输入。
#
# 例如,以下两个代码片段是等价的:
#
# .. code-block:: python
# :caption: 代码片段 1: 手动消息传递
#
# msg = None
# msg = await alice(msg)
# msg = await bob(msg)
# msg = await charlie(msg)
# msg = await david(msg)
#
#
# .. code-block:: python
# :caption: 代码片段 2: 使用顺序管道
#
# from agentscope.pipeline import sequential_pipeline
#
# msg = await sequential_pipeline(
# # 按顺序执行的智能体列表
# agents=[alice, bob, charlie, david],
# # 第一个输入消息,可以是 None
# msg=None
# )
#
# %%
# 扇出管道
# ------------------------
# 扇出管道将相同的输入消息同时分发给多个智能体并收集所有响应。当你想要收集对同一话题的不同观点或专业意见时,这非常有用。
#
# 例如,以下两个代码片段是等价的:
#
# .. code-block:: python
# :caption: 代码片段 3: 手动逐个调用智能体
#
# from copy import deepcopy
#
# msgs = []
# msg = None
# for agent in [alice, bob, charlie, david]:
# msgs.append(await agent(deepcopy(msg)))
#
#
# .. code-block:: python
# :caption: 代码片段 4: 使用扇出管道
#
# from agentscope.pipeline import fanout_pipeline
#
# msgs = await fanout_pipeline(
# # 要执行的智能体列表
# agents=[alice, bob, charlie, david],
# # 输入消息,可以是 None
# msg=None,
# enable_gather=False,
# )
#
# .. note::
# ``enable_gather`` 参数控制扇出管道的执行模式:
#
# - ``enable_gather=True`` (默认): 使用 ``asyncio.gather()`` **并发** 执行所有智能体。这为 I/O 密集型操作(如 API 调用)提供更好的性能,因为智能体并行运行。
# - ``enable_gather=False``: 逐个 **顺序** 执行智能体。当你需要确定性的执行顺序或想要避免并发请求压垮外部服务时,这很有用。
#
# 选择并发执行以获得更好的性能,或选择顺序执行以获得可预测的顺序和资源控制。
#
# .. tip::
# 通过结合 ``MsgHub`` 和 ``sequential_pipeline`` 或 ``fanout_pipeline``,你可以非常容易地创建更复杂的工作流。
# %%
# 流式获取打印消息
# ------------------------
# ``stream_printing_messages`` 函数将智能体在回复过程中调用 ``self.print`` 打印的消息转换为一个异步生成器。
# 可以帮助开发者快速以流式方式获取智能体的中间消息。
#
# 该函数接受一个或多个智能体和一个协程任务作为输入,并返回一个异步生成器。
# 该异步生成器返回一个二元组,包含执行协程任务过程中通过 ``await self.print(...)`` 打印的消息,以及一个布尔值,表示该消息是否为一组流式消息中的最后一个。
#
# 需要注意的是,生成器返回的元组中,布尔值表示该消息是否为一组流式消息中的最后一个,而非此次智能体调用的最后一条消息。
async def run_example_pipeline() -> None:
"""运行流式打印消息的示例。"""
agent = create_agent("Alice", 20, "student")
# 我们关闭agent的终端打印以避免输出混乱
agent.set_console_output_enabled(False)
async for msg, last in stream_printing_messages(
agents=[agent],
coroutine_task=agent(
Msg("user", "你好,你是谁?", "user"),
),
):
print(msg, last)
if last:
print()
asyncio.run(run_example_pipeline())
# %%
# 高级管道特性
# ~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 此外,为了可重用性,我们还提供了基于类的实现:
#
# .. code-block:: python
# :caption: 使用 SequentialPipeline 类
#
# from agentscope.pipeline import SequentialPipeline
#
# # 创建管道对象
# pipeline = SequentialPipeline(agents=[alice, bob, charlie, david])
#
# # 调用管道
# msg = await pipeline(msg=None)
#
# # 使用不同输入复用管道
# msg = await pipeline(msg=Msg("user", "你好!", "user"))
#
#
# .. code-block:: python
# :caption: 使用 FanoutPipeline 类
#
# from agentscope.pipeline import FanoutPipeline
#
# # 创建管道对象
# pipeline = FanoutPipeline(agents=[alice, bob, charlie, david])
#
# # 调用管道
# msgs = await pipeline(msg=None)
#
# # 使用不同输入复用管道
# msgs = await pipeline(msg=Msg("user", "你好!", "user"))
#

View File

@@ -0,0 +1,273 @@
# -*- coding: utf-8 -*-
"""
.. _plan:
计划
=========================
AgentScope 中的计划Plan模块使智能体能够正式地将复杂任务分解为可管理的子任务并系统地执行它们。主要功能包括
- 支持 **手动计划规范**
- 全面的计划管理功能:
- **创建、修改、放弃和恢复** 计划
- 在多个计划之间 **切换**
- 通过临时暂停计划来处理用户查询或紧急任务,**优雅地处理中断**
- 计划执行的 **实时可视化和监控**
.. note:: 当前计划模块仅支持子任务按照顺序执行。
具体来说,计划模块的工作原理是
- 提供计划管理的工具函数
- 插入提示消息来指导ReAct智能体完成计划
下图说明了计划模块如何与ReAct智能体协作
.. figure:: ../../_static/images/plan.png
:width: 90%
:alt: 计划模块
:class: bordered-image
:align: center
计划模块如何与ReAct智能体协作
"""
import asyncio
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.model import DashScopeChatModel
from agentscope.plan import PlanNotebook, Plan, SubTask
# %%
# PlanNotebook
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ``PlanNotebook`` 类是计划模块的核心,负责提供
#
# - 管理计划,子任务的工具函数
# - 提供用于“引导智能体正确完成任务”的提示消息Hint message
#
# ``PlanNotebook`` 类使用以下参数实例化:
#
# .. list-table:: ``PlanNotebook`` 构造函数的参数
# :header-rows: 1
#
# * - 名称
# - 类型
# - 描述
# * - ``max_subtasks``
# - ``int | None``
# - 计划中允许的子任务最大数量,如果为 ``None`` 则无限制
# * - ``plan_to_hint``
# - ``Callable[[Plan | None], str | None] | None``
# - 基于当前计划的完成情况,生成对应提示消息的函数。如果未提供,将使用默认的 ``DefaultPlanToHint`` 对象。
# * - ``storage``
# - ``PlanStorageBase | None``
# - 计划的存储模块用于恢复保存历史计划。如果未提供将使用默认的内存In-memory存储。
#
# ``plan_to_hint`` 参数是 ``PlanNotebook`` 类的核心参数,也是开发者进行提示工程的接口。
# 作为可调用对象,接受当前计划作为输入,并返回一个字符串类型的提示消息。
# AgentScope 构建了一个默认的 ``DefaultPlanToHint`` 类,可以直接使用,同时我们鼓励开发者提供自己的 ``plan_to_hint`` 函数以获得更好的性能。
#
# ``storage`` 用于存储历史计划,允许智能体检索和恢复历史计划。
# 我们同样鼓励开发者通过继承 ``PlanStorageBase`` 类来实现自己的计划存储。如果未提供,将使用默认的内存存储。
#
# .. tip:: ``PlanStorageBase`` 类继承自 ``StateModule`` 类,因此 storage也会通过会话管理进行保存和加载。
#
# ``PlanNotebook`` 类的核心属性和方法总结如下:
#
# .. list-table:: ``PlanNotebook`` 类的核心属性和方法
# :header-rows: 1
#
# * - 类型
# - 名称
# - 描述
# * - 属性
# - ``current_plan``
# - 智能体正在执行的当前计划
# * -
# - ``storage``
# - 历史计划的存储,用于检索和恢复历史计划
# * -
# - ``plan_to_hint``
# - 一个可调用对象,以当前计划为输入并生成提示消息来指导智能体完成计划
# * - 函数
# - ``list_tools``
# - 列出 ``PlanNotebook`` 类提供的所有工具函数
# * -
# - ``get_current_hint``
# - 获取当前计划的提示消息,将调用 ``plan_to_hint`` 函数
# * -
# - | ``create_plan``,
# | ``view_subtasks``,
# | ``revise_current_plan``,
# | ``update_subtask_state``,
# | ``finish_subtask``,
# | ``finish_plan``,
# | ``view_historical_plans``,
# | ``recover_historical_plan``
# - 允许智能体管理计划和子任务的工具函数
# * -
# - ``register_plan_change_hook``
# - 注册一个钩子函数,当计划发生变化时将被调用,用于计划可视化和监控
# * -
# - ``remove_plan_change_hook``
# - 移除已注册的钩子函数
#
# ``list_tools`` 方法是获取所有工具函数的快速方法,这样您就可以将它们注册到智能体的工具包中。
plan_notebook = PlanNotebook()
async def list_tools() -> None:
"""列出PlanNotebook提供的工具函数。"""
print("PlanNotebook提供的工具")
for tool in plan_notebook.list_tools():
print(tool.__name__)
asyncio.run(list_tools())
# %%
# 与ReActAgent协作
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope中的 ``ReActAgent`` 已通过构造函数中的 ``plan_notebook`` 参数集成了计划模块。
# 一旦提供,智能体将
#
# - 配备计划管理工具函数,并且
# - 在每个推理步骤开始时插入提示消息
#
# 有两种方式在 ``ReActAgent`` 中使用计划模块:
#
# - 开发者指定计划:开发者可以通过调用 ``create_plan`` 工具函数手动创建计划,并使用该计划来初始化 ``ReActAgent`` 。
# - 智能体管理的计划执行:智能体将通过调用计划管理工具函数自己创建和管理计划。
#
# 手动计划规范
# ---------------------------------
# 通过调用 ``create_plan`` 工具函数手动创建计划非常简单。
# 以下是手动创建计划以对LLM赋能的智能体进行全面研究的示例。
#
async def manual_plan_specification() -> None:
"""手动计划规范示例。"""
await plan_notebook.create_plan(
name="智能体研究",
description="对基于LLM的智能体进行全面研究",
expected_outcome="一份Markdown格式的报告回答三个问题1. 什么是智能体2. 智能体的当前技术水平是什么3. 智能体的未来趋势是什么?",
subtasks=[
SubTask(
name="搜索智能体相关调研论文",
description=(
"在多个来源搜索调研论文,包括"
"Google Scholar、arXiv和Semantic Scholar。必须"
"在2021年后发表且引用数超过50。"
),
expected_outcome="Markdown格式的论文列表",
),
SubTask(
name="阅读和总结论文",
description="阅读前一步找到的论文,并总结关键点,包括定义、分类、挑战和关键方向。",
expected_outcome="Markdown格式的关键点总结",
),
SubTask(
name="研究大公司的最新进展",
description=(
"研究大公司的最新进展包括但不限于Google、Microsoft、OpenAI、"
"Anthropic、阿里巴巴和Meta。查找官方博客或新闻文章。"
),
expected_outcome="大公司的最新进展",
),
SubTask(
name="撰写报告",
description="基于前面的步骤撰写报告,并回答预期结果中的三个问题。",
expected_outcome=(
"一份Markdown格式的报告回答三个问题1. "
"什么是智能体2. 智能体的当前技术水平"
"是什么3. 智能体的未来趋势是什么?"
),
),
],
)
print("当前提示消息:\n")
msg = await plan_notebook.get_current_hint()
print(f"{msg.name}: {msg.content}")
asyncio.run(manual_plan_specification())
# %%
# 创建计划后,可以按如下方式使用计划笔记本初始化 ``ReActAgent``
agent = ReActAgent(
name="Friday",
sys_prompt="你是一个有用的助手。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
plan_notebook=plan_notebook,
)
# %%
# 智能体自主管理
# ---------------------------------
# 智能体也可以通过调用计划管理工具函数自己创建和管理计划。
# 我们只需要按如下方式使用计划笔记本初始化 ``ReActAgent``
#
agent = ReActAgent(
name="Friday",
sys_prompt="你是一个有用的助手。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
plan_notebook=PlanNotebook(),
)
# %%
# 之后,我们可以构建一个循环来与智能体交互,如下所示。
# 一旦用户的任务复杂比较复杂,智能体将自己创建计划并逐步执行计划。
#
# .. code-block:: python
# :caption: 与计划智能体建立对话
#
# async def interact_with_agent() -> None:
# """与计划智能体交互。"""
# user = UserAgent(name="user")
#
# msg = None
# while True:
# msg = await user(msg)
# if msg.get_text_content() == "exit":
# break
# msg = await agent(msg)
#
# asyncio.run(interact_with_agent())
#
# 可视化和监控
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 通过钩子函数支持计划执行的实时可视化和监控。
#
# 当前计划被工具函数改变时,钩子函数将被触发,开发者可以在这些钩子函数中将当前的计划转发到对应的前端进行可视化或其他处理。
# 计划变化钩子函数的模板如下:
#
def plan_change_hook_template(self: PlanNotebook, plan: Plan) -> None:
"""计划变化钩子函数的模板。
Args:
self (`PlanNotebook`):
PlanNotebook实例。
plan (`Plan`):
当前计划实例(变化后)。
"""
# 将计划转发到前端进行可视化或其他处理

View File

@@ -0,0 +1,329 @@
# -*- coding: utf-8 -*-
"""
.. _prompt:
提示词格式化
=========================
AgentScope 中的格式化器formatter模块负责
- 将 Msg 对象转换为不同 LLM API 要求的格式,
- (可选)截断消息以适应 max_token 的限制,
- (可选)执行提示工程,例如对长对话进行总结。
后两个功能是可选的开发者也可以选择在记忆memory或智能体agent层面进行处理和实现。
在 AgentScope 中,有两种类型的格式化器:"ChatFormatter""MultiAgentFormatter",它们根据输入消息中的“身份实体”进行区分。
- **ChatFormatter**:专为标准的用户-助手场景(聊天机器人)设计,使用 ``role`` 字段来识别用户和助手。
- **MultiAgentFormatter**:专为多智能体场景设计,使用 ``name`` 字段来识别不同的实体,在格式化的过程中会将多智能体的对话历史合并为单个消息。
AgentScope 内置的格式化器如下所列
.. list-table:: AgentScope 中的内置格式化器
:header-rows: 1
* - API 提供商
- 用户-助手场景
- 多智能体场景
* - OpenAI
- ``OpenAIChatFormatter``
- ``OpenAIMultiAgentFormatter``
* - Anthropic
- ``AnthropicChatFormatter``
- ``AnthropicMultiAgentFormatter``
* - DashScope
- ``DashScopeChatFormatter``
- ``DashScopeMultiAgentFormatter``
* - Gemini
- ``GeminiChatFormatter``
- ``GeminiChatFormatter``
* - Ollama
- ``OllamaChatFormatter``
- ``OllamaMultiAgentFormatter``
* - DeepSeek
- ``DeepSeekChatFormatter``
- ``DeepSeekMultiAgentFormatter``
* - vLLM
- ``OpenAIFormatter``
- ``OpenAIFormatter``
.. tip:: OpenAI API 支持 `name` 字段,因此 `OpenAIFormatter` 也可以用于多智能体场景。也可以使用 `OpenAIMultiAgentFormatter` 代替,它会将对话历史合并为单个用户消息。
此外内置格式化器对于不同的消息块message blocks的支持情况如下表所示
.. list-table:: 内置格式化器中支持的消息块
:header-rows: 1
* - 格式化器
- tool_use/result
- image
- audio
- video
- thinking
* - ``OpenAIChatFormatter``
- ✅
- ✅
- ✅
- ❌
-
* - ``DashScopeChatFormatter``
- ✅
- ✅
- ✅
- ❌
-
* - ``DashScopeMultiAgentFormatter``
- ✅
- ✅
- ✅
- ❌
-
* - ``AnthropicChatFormatter``
- ✅
- ✅
- ❌
- ❌
- ✅
* - ``AnthropicMultiAgentFormatter``
- ✅
- ✅
- ❌
- ❌
- ✅
* - ``GeminiChatFormatter``
- ✅
- ✅
- ✅
- ✅
-
* - ``GeminiMultiAgentFormatter``
- ✅
- ✅
- ✅
- ✅
-
* - ``OllamaChatFormatter``
- ✅
- ✅
- ❌
- ❌
-
* - ``OllamaMultiAgentFormatter``
- ✅
- ✅
- ❌
- ❌
-
* - ``DeepSeekChatFormatter``
- ✅
- ❌
- ❌
- ❌
-
* - ``DeepSeekMultiAgentFormatter``
- ✅
- ❌
- ❌
- ❌
-
.. note:: 如 `官方文档 <https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#preserving-thinking-blocks>`_ 所述,只有 Anthropic 建议在输入的提示词中保留推理的部分thinking blocks。对于其它格式化器我们忽略输入消息中包含的 ``ThinkingBlock``。
面向 ReAct 的格式化
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
内置的 formatter 均面向 ReAct 智能体进行设计,其中输入消息由交替的 **对话历史** 和 **工具调用序列** 组成。
在用户-助手场景中,对话历史是用户和助手的消息,我们直接将它们转换为所期望的格式。
然而,在多智能体场景中,对话历史是来自不同智能体的消息列表,如下所示:
.. figure:: ../../_static/images/multiagent_msgs.png
:alt: 多智能体消息示例
:width: 85%
:align: center
*多智能体消息示例*
因此,我们必须将对话历史合并为带有标签 "<history>""</history>" 的单个用户消息。
以 DashScope 为例,格式化后的消息将如下所示:
"""
from agentscope.token import HuggingFaceTokenCounter
from agentscope.formatter import DashScopeMultiAgentFormatter
from agentscope.message import Msg, ToolResultBlock, ToolUseBlock, TextBlock
import asyncio, json
input_msgs = [
# 系统提示
Msg("system", "你是一个名为 Friday 的有用助手", "system"),
# 对话历史
Msg("Bob", "你好Alice你知道最近的图书馆在哪里吗", "assistant"),
Msg(
"Alice",
"抱歉我不知道。Charlie你有什么想法吗",
"assistant",
),
Msg(
"Charlie",
"没有,我们问问 Friday 吧。Friday帮我找到最近的图书馆。",
"assistant",
),
# 工具序列
Msg(
"Friday",
[
ToolUseBlock(
type="tool_use",
name="get_current_location",
id="1",
input={},
),
],
"assistant",
),
Msg(
"system",
[
ToolResultBlock(
type="tool_result",
name="get_current_location",
id="1",
output=[TextBlock(type="text", text="104.48, 36.30")],
),
],
"system",
),
Msg(
"Friday",
[
ToolUseBlock(
type="tool_use",
name="search_around",
id="2",
input={"location": [104.48, 36.30], "keyword": "library"},
),
],
"assistant",
),
Msg(
"system",
[
ToolResultBlock(
type="tool_result",
name="search_around",
id="2",
output=[TextBlock(type="text", text="[...]")],
),
],
"system",
),
# 对话历史继续
Msg("Friday", "最近的图书馆是...", "assistant"),
Msg("Bob", "谢谢Friday", "assistant"),
Msg("Alice", "我们一起去吧。", "assistant"),
]
async def run_formatter_example() -> list[dict]:
"""多智能体消息格式化示例。"""
formatter = DashScopeMultiAgentFormatter()
formatted_message = await formatter.format(input_msgs)
print("格式化后的消息:")
print(json.dumps(formatted_message, indent=4, ensure_ascii=False))
return formatted_message
formatted_message = asyncio.run(run_formatter_example())
# %%
# 具体来说,对话历史被格式化为:
#
print("第一段对话历史:")
print(formatted_message[1]["content"])
print("\n第二段对话历史:")
print(formatted_message[-1]["content"])
# %%
# 基于截断的格式化
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 通过 AgentScope 中的 token 模块,内置格式化器支持通过 **删除最旧的消息**(除了系统提示消息)在 token 超过限制时截断输入消息。
#
# 以 OpenAIFormatter 为例,我们首先计算输入消息的总 token 数。
#
async def run_token_counter() -> int:
"""计算输入消息的 token 数量。"""
# 我们使用 huggingface token 计数器用于 dashscope 模型。
token_counter = HuggingFaceTokenCounter(
"Qwen/Qwen2.5-VL-3B-Instruct",
use_mirror=False,
)
return await token_counter.count(formatted_message)
# %%
# 然后我们将最大 token 限制设置为比总 token 数少 20 个,并运行格式化器。
#
async def run_truncated_formatter() -> None:
"""带截断的消息格式化示例。"""
token_counter = HuggingFaceTokenCounter(
pretrained_model_name_or_path="Qwen/Qwen2.5-VL-3B-Instruct",
use_mirror=False,
)
formatter = DashScopeMultiAgentFormatter(
token_counter=token_counter,
max_tokens=n_tokens - 20,
)
truncated_formatted_message = await formatter.format(input_msgs)
n_truncated_tokens = await token_counter.count(truncated_formatted_message)
print("截断后的 token 数量:", n_truncated_tokens)
print("\n截断后的对话历史:")
print(truncated_formatted_message[1]["content"])
# %%
# 我们可以看到来自 Bob 和 Alice 的前两条消息被删除以适应 ``max_tokens`` 的限制。
#
#
# 自定义格式化器
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 提供了两个基类 ``FormatterBase`` 和其子类 ``TruncatedFormatterBase``。
# 其中 ``TruncatedFormatterBase`` 类提供了 FIFOFirst In First Out截断策略所有内置格式化器都继承自它。
#
# .. list-table:: AgentScope 中格式化器的基类
# :header-rows: 1
#
# * - 类
# - 抽象方法
# - 描述
# * - ``FormatterBase``
# - ``format``
# - 将输入的 ``Msg`` 对象格式化为目标 API 所期望的格式
# * - ``TruncatedFormatterBase``
# - ``_format_agent_message``
# - 格式化智能体消息,在多智能体场景中可能包含多个身份
# * -
# - ``_format_tool_sequence``
# - 将工具使用和结果序列格式化为所期望的格式
# * -
# - ``_format`` (可选)
# - 将输入的 ``Msg`` 对象格式化为目标 API 所期望的格式
#
# .. tip:: ``TruncatedFormatterBase`` 中的 ``_format`` 将输入消息分组为智能体消息和工具序列,然后分别通过调用 ``_format_agent_message`` 和 ``_format_tool_sequence`` 来格式化它们。开发者可以重写两个函数来实现自己的格式化策略。
#
# .. tip:: 可选地,开发者可以重写 ``TruncatedFormatterBase`` 中的 ``_truncate`` 方法来实现自己的截断策略。
#
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# - :ref:`token`
# - :ref:`model`
#

View File

@@ -0,0 +1,404 @@
# -*- coding: utf-8 -*-
"""
.. _rag:
RAG
==========================
AgentScope 提供了内置的 RAGRetrieval-Augmented Generation) 实现。本节将详细介绍
- 如何使用 AgentScope 中的 RAG 模块,
- 如何实现 **多模态** RAG
- 如何在 ``ReActAgent`` 中以两种不同的方式集成 RAG 模块:
- 智能体自主控制Agentic manner
- 通用方式Generic manner
我们在下列表格中总结了两种模式的优缺点:
.. list-table:: RAG 集成方式比较
:header-rows: 1
* - 集成方式
- 描述
- 优点
- 缺点
* - 智能体自主控制
- 以工具调用方式让智能体自主决定何时进行查询,查询什么关键字
- - 与 ReAct 算法契合,灵活性高
- 智能体能够根据当前的上下文改写查询关键词
- 避免在不必要时发生查询
- 对 LLM 模型能力要求较高
* - 通用方式
- 每次在 ``reply`` 函数开始时固定进行查询并将检索结果整合到提示prompt
- - 实现简单
- 对 LLM 模型能力要求低
- - 每次都会运行查询,因此会引入过多不必要的查询检索
- 查询数据库较大时,回复延迟较高
.. note:: 作为开源框架AgentScope 的目标是让开发过程更简单也更有趣。因此AgentScope 的设计中并不强制要求使用内置的 RAG 实现,同时支持、鼓励开发者集成现有的 RAG 实现或第三方 RAG 框架。
"""
import asyncio
import json
import os
from matplotlib import pyplot as plt
import agentscope
from agentscope.agent import ReActAgent
from agentscope.embedding import (
DashScopeTextEmbedding,
DashScopeMultiModalEmbedding,
)
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.rag import (
TextReader,
SimpleKnowledge,
QdrantStore,
Document,
ImageReader,
)
from agentscope.tool import Toolkit
# %%
# 使用 RAG 模块
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 中的 RAG 模块由以下三个核心组件构成:
#
# - **Reader**负责从数据源读取数据并进行分块chunking
# - **Knowledge**:负责知识库查询检索和数据存储逻辑的算法实现
# - **Store**:负责与向量数据库交互的逻辑实现
#
# .. note:: 我们将持续在 AgentScope 中集成新的向量数据库和数据读取模块。详情请查看我们的 `开发路线图 <https://github.com/orgs/agentscope-ai/projects/2>`_同时也欢迎贡献代码
#
# 当前 AgentScope 中内置支持的 reader 包括:
#
for _ in agentscope.rag.__all__:
if _.endswith("Reader"):
print(f"- {_}")
# %%
# 这些 reader 的作用是读取数据,将数据分块并包装成 ``agentscope.rag.Document`` 对象。 ``Document`` 对象包含以下字段:
#
# - ``metadata``:数据块的元信息,包含数据内容(``content``)、数据 ID``doc_id``)、块 ID``chunk_id``)和总块数(``total_chunks``
# - ``embedding``: 数据块的向量表示,默认为 ``None``,在将数据块添加到知识库时会被填充
# - ``score``: 数据块的相关性分数,默认为 ``None``,在从知识库检索数据块时会被填充
#
# 以 ``TextReader`` 为例,通过如下代码读取文本字符串,并将文本分块为 ``Document`` 对象:
#
async def example_text_reader(print_docs: bool) -> list[Document]:
"""使用 TextReader 读取文本字符串,并将文本分块为 Document 对象。"""
# 创建 TextReader 对象
reader = TextReader(chunk_size=512, split_by="paragraph")
# 读取文本字符串
documents = await reader(
text=(
# 我们准备一些文本数据用于演示 RAG 功能。
"我的名字是李明今年28岁。\n"
"我居住在中国杭州,是一名算法工程师。我喜欢打篮球和玩游戏。\n"
"我父亲的名字是李强,是一名医生,我的母亲是陈芳芳,是一名教师,她总是指导我学习。\n"
"我现在在北京大学攻读博士学位,研究方向是人工智能。\n"
"我最好的朋友是王伟,我们从小一起长大,现在他是一名律师。"
),
)
if print_docs:
print(f"文本被分块为 {len(documents)} 个 Document 对象:")
for idx, doc in enumerate(documents):
print(f"Document {idx}:")
print("\tScore: ", doc.score)
print(
"\tMetadata: ",
json.dumps(doc.metadata, indent=2, ensure_ascii=False),
"\n",
)
return documents
docs = asyncio.run(example_text_reader(print_docs=True))
# %%
# 由于并不存在一个 “one-for-all” 的数据读取和分块方法,特别像是 PDF 和 Word 这类复杂格式的文档。
# 因此AgentScope 鼓励开发者根据自己的数据格式实现自定义的 reader。
# 只需要继承 ``BaseReader`` 类,并实现 ``__call__`` 方法即可。
#
# 在数据分块后,接下来需要将数据块添加到知识库中。
# 在 AgentScope 中,知识库的初始化需要提供 **嵌入模型** 和 **向量存储** (即向量数据库) 的对象。
# AgentScope 目前内置支持基于 `Qdrant <https://qdrant.tech/>`_ 实现的向量存储,以及一个知识库的基础实现 ``SimpleKnowledge``。
# 具体使用方式如下:
#
# .. note::
#
# - 我们正在 AgentScope 中集成新的向量数据库,详情请查看我们的 `开发路线图 <https://github.com/orgs/agentscope-ai/projects/2>`_。欢迎贡献代码
# - Qdrant 的实现通过 ``location`` 参数支持多种不同的部署方式,包括内存模式,本地模式和云端模式。详情请参考 `Qdrant 文档 <https://qdrant.tech/>`_。
#
async def build_knowledge_base() -> SimpleKnowledge:
"""构建知识库。"""
# 读取 documents 数据
documents = await example_text_reader(print_docs=False)
# 创建一个内存中的 Qdrant 向量存储,以及使用 DashScopeTextEmbedding 作为嵌入模型,初始化知识库
knowledge = SimpleKnowledge(
# 提供一个 embedding 模型用于将文本转换为向量
embedding_model=DashScopeTextEmbedding(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="text-embedding-v4",
dimensions=1024,
),
# 选择 Qdrant 作为向量存储
embedding_store=QdrantStore(
location=":memory:", # 使用内存模式
collection_name="test_collection",
dimensions=1024, # 嵌入向量的维度必须与嵌入模型输出的维度一致
),
)
# 将 documents 添加到知识库中
await knowledge.add_documents(documents)
# 从知识库中检索数据
docs = await knowledge.retrieve(
query="李明的父亲是谁?",
limit=3,
score_threshold=0.5,
)
print("检索到的 Document 对象:")
for doc in docs:
print(doc, "\n")
return knowledge
knowledge = asyncio.run(build_knowledge_base())
# %%
# AgentScope 中的知识库类提供两个核心方法:``add_documents`` 和 ``retrieve``,分别用于添加数据块和搜索检索数据块。
#
# 此外AgentScope 提供了 ``retrieve_knowledge`` 方法,它将 ``retrieve`` 方法封装成一个智能体能够直接调用的工具函数。开发者可以直接使用该工具函数装备智能体。
#
# 自定义 RAG 组件
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 支持并鼓励开发者自定义 RAG 组件,包括 reader、知识库和向量数据库。
# 具体来说,我们提供了以下基类用于自定义:
#
# .. list-table:: RAG 基类
# :header-rows: 1
#
# * - 基类
# - 描述
# - 抽象方法
# * - ``ReaderBase``
# - 所有 reader 的基类
# - ``__call__``
# * - ``VDBStoreBase``
# - 向量数据库的基类
# - | ``add``
# | ``search``
# | ``get_client`` (可选)
# | ``delete`` (可选)
# * - ``KnowledgeBase``
# - 知识库的基类
# - | ``retrieve``
# | ``add_documents``
#
# ``VDBStoreBase`` 中的 ``get_client`` 方法允许开发者访问底层向量数据库的完整功能。
# 这样,他们就可以基于向量数据库实现更高级的功能,例如建立索引、高级搜索等。
#
# 与 ReActAgent 集成
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 接下来我们将演示如何以智能体自主控制agentic和通用generic两种方式将 RAG 模块与 AgentScope 中的 ``ReActAgent`` 集成。
#
# 智能体自主控制
# --------------------------------
# 在智能体自主控制的方式中ReAct 智能体可以自主决定何时检索知识以及检索的查询内容。
# 将 RAG 模块与 AgentScope 中的 ``ReActAgent`` 集成非常简单,只需将知识库的 ``retrieve_knowledge`` 方法注册为工具,并为该工具提供适当的描述即可。
async def example_agentic_manner() -> None:
"""以智能体自主控制方式将 RAG 模块与 ReActAgent 集成的示例。"""
# 创建一个 ReAct 智能体
toolkit = Toolkit()
# 使用 DashScope 作为模型创建 ReAct 智能体
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen-max",
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
)
print("第一次回复: ")
# 第一次我们进行一些交流,提供“李明”这个名字作为上下文内容
await agent(
Msg(
"user",
"李明是我最好的朋友。",
"user",
),
)
# 将 retrieve_knowledge 方法注册为工具箱中的工具函数
toolkit.register_tool_function(
knowledge.retrieve_knowledge,
func_description=( # 为工具提供清晰的描述
"用于检索与给定查询相关的文档的工具。" "当你需要查找有关李明的信息时使用此工具。"
),
)
print("\n\n第二次回复: ")
# 第二次回复中,我们希望智能体能够将查询中“他父亲”改写得更具体,例如
# “李明的父亲是谁?”或“李明的父亲”
await agent(
Msg(
"user",
"你知道他父亲是谁吗?",
"user",
),
)
asyncio.run(example_agentic_manner())
# %%
# 在上面的例子中,我们模拟了正常与智能体交流过程。第一次的交流我们提供了“李明”的名字作为上下文内容。
# 第二次提问时,我们的问题是“你知道他父亲是谁吗?”,
# 我们希望智能体能够利用上下文历史信息改写查询,使其更具体,更好的进行检索,例如改写为“李明的父亲是谁?”或“李明的父亲”。
#
# 更进一步,结合 :ref:`plan` 模块,我们可以让智能体实现更加复杂的查询改写和多轮检索。
#
# 通用方式
# --------------------------------
# ``ReActAgent`` 还以一种更加通用的方式集成了 RAG 模块,
# 它在每次 ``reply`` 函数开始执行时检索知识,并将检索到的知识附加到用户消息的提示中。
#
# 只需设置 ``ReActAgent`` 的 ``knowledge`` 参数,智能体就会在每次回复开始时自动检索知识。
#
async def example_generic_manner() -> None:
"""以通用方式将 RAG 模块与 ReActAgent 集成的示例。"""
# 创建一个 ReAct 智能体
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen-max",
),
formatter=DashScopeChatFormatter(),
# 将知识库传递给智能体
knowledge=knowledge,
)
await agent(
Msg(
"user",
"你知道李明的父亲是谁吗?",
"user",
),
)
print("\n查看智能体记忆中检索信息如何插入:")
content = (await agent.memory.get_memory())[1].content
print(json.dumps(content, indent=2, ensure_ascii=False))
asyncio.run(example_generic_manner())
# %%
# 多模态 RAG
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 中的 RAG 模块原生支持多模态,因为
#
# - AgentScope 支持多模态嵌入 API例如 ``DashScopeMultimodalEmbedding``。
# - ``Document`` 类的 ``metadata`` 中,``content`` 字段的类型是 ``TextBlock | ImageBlock | VideoBlock``,因此可以存储文本、图片和视频等多模态数据。
#
# 因此,我们可以直接使用多模态 reader 和嵌入模型来构建多模态知识库,如下所示。
#
# 首先我们准备一张本地的图片这张图片上包含了文本“My name is Ming Li”。
# 准备一张包含文本“My name is Ming Li”的图片。
path_image = "./example.png"
plt.figure(figsize=(8, 3))
plt.text(0.5, 0.5, "My name is Ming Li", ha="center", va="center", fontsize=30)
plt.axis("off")
plt.savefig(path_image, bbox_inches="tight", pad_inches=0.1)
plt.close()
# %%
# 然后我们可以构建一个多模态知识库,构建过程与纯文本知识库类似。只是将 reader 和嵌入模型替换为多模态版本即可。
# 在下面的例子中,我们使用了 ``ImageReader`` 和 ``DashScopeMultiModalEmbedding``。
# 同时,这里我们使用多模态模型 ``qwen3-vl-plus`` 作为智能体的语言模型。
#
async def example_multimodal_rag() -> None:
"""使用多模态 RAG 的示例。"""
# 使用 ImageReader 读取图片
reader = ImageReader()
docs = await reader(image_url=path_image)
# 创建一个知识库
knowledge = SimpleKnowledge(
embedding_model=DashScopeMultiModalEmbedding(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="multimodal-embedding-v1",
dimensions=1024,
),
embedding_store=QdrantStore(
location=":memory:",
collection_name="test_collection",
dimensions=1024,
),
)
await knowledge.add_documents(docs)
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen3-vl-plus",
),
formatter=DashScopeChatFormatter(),
knowledge=knowledge,
)
await agent(
Msg(
"user",
"你知道我的名字吗?",
"user",
),
)
# 让我们看看检索到的图片数据是否已经加入了智能体的记忆中
print("\n查看智能体记忆中检索信息如何插入:")
content = (await agent.memory.get_memory())[1].content
print(json.dumps(content, indent=2, ensure_ascii=False))
asyncio.run(example_multimodal_rag())
# %%
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# - :ref:`embedding`
# - :ref:`plan`
#

View File

@@ -0,0 +1,486 @@
# -*- coding: utf-8 -*-
"""
.. _realtime:
实时智能体
====================
**实时智能体Realtime Agent** 用于处理实时交互场景,例如语音对话或实时聊天会话。
AgentScope 中的实时智能体具有以下特性:
- 集成 OpenAI、DashScope、Gemini 等实时模型 API
- 统一的事件接口,简化与不同实时模型的交互
- 支持工具调用能力
- 支持多智能体交互
.. note:: 实时智能体目前处于活跃开发阶段,欢迎社区贡献、讨论和反馈!如果开发者对实时智能体感兴趣,欢迎加入讨论和开发。
"""
import asyncio
import os
from agentscope.agent import RealtimeAgent
from agentscope.realtime import (
DashScopeRealtimeModel,
OpenAIRealtimeModel,
GeminiRealtimeModel,
)
# %%
# 创建实时模型
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# AgentScope 目前支持以下实时模型 API
#
# .. list-table::
# :header-rows: 1
# :widths: 15 25 25 15 20
#
# * - 提供商
# - 类名
# - 支持的模型
# - 输入模态
# - 工具支持
# * - DashScope
# - ``DashScopeRealtimeModel``
# - ``qwen3-omni-flash-realtime``
# - 文本、音频、图像
# - 否
# * - OpenAI
# - ``OpenAIRealtimeModel``
# - ``gpt-4o-realtime-preview``
# - 文本、音频
# - 是
# * - Gemini
# - ``GeminiRealtimeModel``
# - ``gemini-2.5-flash-native-audio-preview-09-2025``
# - 文本、音频、图像
# - 是
#
#
# 以下是初始化不同实时模型的示例:
#
# .. code-block:: python
# :caption: 初始化不同实时模型的示例
#
# # DashScope 实时模型
# dashscope_model = DashScopeRealtimeModel(
# model_name="qwen3-omni-flash-realtime",
# api_key=os.getenv("DASHSCOPE_API_KEY"),
# voice="Cherry", # 可选项: "Cherry", "Serena", "Ethan", "Chelsie"
# enable_input_audio_transcription=True,
# )
#
# # OpenAI 实时模型
# openai_model = OpenAIRealtimeModel(
# model_name="gpt-4o-realtime-preview",
# api_key=os.getenv("OPENAI_API_KEY"),
# voice="alloy", # 可选项: "alloy", "echo", "marin", "cedar"
# enable_input_audio_transcription=True,
# )
#
# # Gemini 实时模型
# gemini_model = GeminiRealtimeModel(
# model_name="gemini-2.5-flash-native-audio-preview-09-2025",
# api_key=os.getenv("GEMINI_API_KEY"),
# voice="Puck", # 可选项: "Puck", "Charon", "Kore", "Fenrir"
# enable_input_audio_transcription=True,
# )
#
#
#
# 实时模型提供以下核心方法:
#
# .. list-table::
# :header-rows: 1
# :widths: 30 70
#
# * - 方法
# - 描述
# * - ``connect(outgoing_queue, instructions, tools)``
# - 建立与实时模型 API 的 WebSocket 连接
# * - ``disconnect()``
# - 关闭 WebSocket 连接
# * - ``send(data)``
# - 向实时模型发送音频/文本/图像数据进行处理
#
# ``connect()`` 方法中的 ``outgoing_queue`` 参数是一个 asyncio 队列,
# 用于将实时模型的事件转发到外部(例如智能体或前端)。
#
#
# 模型事件接口
# -----------------------
#
# AgentScope 提供统一的 ``agentscope.realtime.ModelEvents`` 接口,
# 简化与不同实时模型的交互。支持以下事件:
#
# .. note:: ModelEvents 中的 "session" 指的是实时模型与模型 API 之间的
# WebSocket 连接会话,而非前端与后端之间的会话。
#
# .. list-table::
# :header-rows: 1
# :widths: 40 60
#
# * - 事件
# - 描述
# * - ``ModelEvents.ModelSessionCreatedEvent``
# - 会话创建成功
# * - ``ModelEvents.ModelSessionEndedEvent``
# - 会话已结束
# * - ``ModelEvents.ModelResponseCreatedEvent``
# - 模型开始生成响应
# * - ``ModelEvents.ModelResponseDoneEvent``
# - 模型完成响应生成
# * - ``ModelEvents.ModelResponseAudioDeltaEvent``
# - 流式音频数据块
# * - ``ModelEvents.ModelResponseAudioDoneEvent``
# - 音频响应完成
# * - ``ModelEvents.ModelResponseAudioTranscriptDeltaEvent``
# - 流式音频转录文本块
# * - ``ModelEvents.ModelResponseAudioTranscriptDoneEvent``
# - 音频转录完成
# * - ``ModelEvents.ModelResponseToolUseDeltaEvent``
# - 流式工具调用参数
# * - ``ModelEvents.ModelResponseToolUseDoneEvent``
# - 工具调用参数完成
# * - ``ModelEvents.ModelInputTranscriptionDeltaEvent``
# - 流式用户输入转录文本块
# * - ``ModelEvents.ModelInputTranscriptionDoneEvent``
# - 用户输入转录完成
# * - ``ModelEvents.ModelInputStartedEvent``
# - 检测到用户音频输入开始VAD
# * - ``ModelEvents.ModelInputDoneEvent``
# - 检测到用户音频输入结束VAD
# * - ``ModelEvents.ModelErrorEvent``
# - 发生错误
#
#
#
# 创建实时智能体
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ``RealtimeAgent`` 作为桥接层,负责:
#
# - 将实时模型的 ``ModelEvents`` 转换为 ``ServerEvents``,发送给前端和其他智能体
# - 接收来自前端或其他智能体的 ``ClientEvents``,并转发给实时模型 API
# - 管理智能体的生命周期和事件队列
#
# 服务端和客户端事件
# -------------------------
#
# AgentScope 提供统一的 ``ServerEvents`` 和 ``ClientEvents``
# 用于后端与前端之间的通信:
#
# **ServerEvents**(后端 → 前端):
#
# .. list-table::
# :header-rows: 1
# :widths: 40 60
#
# * - 事件
# - 描述
# * - ``ServerEvents.ServerSessionCreatedEvent``
# - 后端创建会话
# * - ``ServerEvents.ServerSessionUpdatedEvent``
# - 后端更新会话
# * - ``ServerEvents.ServerSessionEndedEvent``
# - 后端结束会话
# * - ``ServerEvents.AgentReadyEvent``
# - 智能体准备接收输入
# * - ``ServerEvents.AgentEndedEvent``
# - 智能体已结束
# * - ``ServerEvents.AgentResponseCreatedEvent``
# - 智能体开始生成响应
# * - ``ServerEvents.AgentResponseDoneEvent``
# - 智能体完成响应生成
# * - ``ServerEvents.AgentResponseAudioDeltaEvent``
# - 智能体流式音频块
# * - ``ServerEvents.AgentResponseAudioDoneEvent``
# - 音频响应完成
# * - ``ServerEvents.AgentResponseAudioTranscriptDeltaEvent``
# - 智能体响应的流式转录
# * - ``ServerEvents.AgentResponseAudioTranscriptDoneEvent``
# - 转录完成
# * - ``ServerEvents.AgentResponseToolUseDeltaEvent``
# - 流式工具调用数据
# * - ``ServerEvents.AgentResponseToolUseDoneEvent``
# - 工具调用完成
# * - ``ServerEvents.AgentResponseToolResultEvent``
# - 工具执行结果
# * - ``ServerEvents.AgentInputTranscriptionDeltaEvent``
# - 用户输入的流式转录
# * - ``ServerEvents.AgentInputTranscriptionDoneEvent``
# - 输入转录完成
# * - ``ServerEvents.AgentInputStartedEvent``
# - 用户音频输入开始
# * - ``ServerEvents.AgentInputDoneEvent``
# - 用户音频输入结束
# * - ``ServerEvents.AgentErrorEvent``
# - 发生错误
#
# **ClientEvents**(前端 → 后端):
#
# .. list-table::
# :header-rows: 1
# :widths: 40 60
#
# * - 事件
# - 描述
# * - ``ClientEvents.ClientSessionCreateEvent``
# - 创建指定配置的新会话
# * - ``ClientEvents.ClientSessionEndEvent``
# - 结束当前会话
# * - ``ClientEvents.ClientResponseCreateEvent``
# - 请求智能体立即生成响应
# * - ``ClientEvents.ClientResponseCancelEvent``
# - 中断智能体的当前响应
# * - ``ClientEvents.ClientTextAppendEvent``
# - 追加文本输入
# * - ``ClientEvents.ClientAudioAppendEvent``
# - 追加音频输入
# * - ``ClientEvents.ClientAudioCommitEvent``
# - 提交音频输入(标志输入结束)
# * - ``ClientEvents.ClientImageAppendEvent``
# - 追加图像输入
# * - ``ClientEvents.ClientToolResultEvent``
# - 发送工具执行结果
#
# 初始化实时智能体
# ------------------------------
#
# 以下是创建和使用实时智能体的示例:
async def example_realtime_agent() -> None:
"""创建和使用实时智能体的示例。"""
agent = RealtimeAgent(
name="Friday",
sys_prompt="你是一个名为 Friday 的助手。",
model=DashScopeRealtimeModel(
model_name="qwen3-omni-flash-realtime",
api_key=os.getenv("DASHSCOPE_API_KEY"),
),
)
# 创建队列接收来自智能体的消息
outgoing_queue = asyncio.Queue()
# 智能体现在已准备好处理输入
# 在独立任务中处理输出消息
async def handle_agent_messages():
while True:
event = await outgoing_queue.get()
# 处理事件(例如通过 WebSocket 发送到前端)
print(f"智能体事件: {event.type}")
# 启动消息处理任务
asyncio.create_task(handle_agent_messages())
# 启动智能体(建立连接)
await agent.start(outgoing_queue)
# 完成后停止智能体
await agent.stop()
# %%
# 启动实时对话
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 下面演示如何在用户和实时智能体之间建立实时对话。
#
# 这里以 FastAPI 为例,展示如何搭建实时对话的后端框架。
#
# **后端设置(服务端):**
#
# 后端需要:
#
# 1. 创建 WebSocket 端点接受前端连接
# 2. 在会话开始时创建 ``RealtimeAgent``
# 3. 将前端的 ``ClientEvents`` 转发给智能体
# 4. 将智能体的 ``ServerEvents`` 转发给前端
#
# .. code-block:: python
#
# from fastapi import FastAPI, WebSocket
# from agentscope.agent import RealtimeAgent
# from agentscope.realtime import (
# DashScopeRealtimeModel,
# ClientEvents,
# ServerEvents,
# )
#
# app = FastAPI()
#
# @app.websocket("/ws/{user_id}/{session_id}")
# async def websocket_endpoint(
# websocket: WebSocket,
# user_id: str,
# session_id: str,
# ):
# await websocket.accept()
#
# # 创建智能体消息队列
# frontend_queue = asyncio.Queue()
#
# # 创建智能体
# agent = RealtimeAgent(
# name="Assistant",
# sys_prompt="你是一个有用的助手。",
# model=DashScopeRealtimeModel(
# model_name="qwen3-omni-flash-realtime",
# api_key=os.getenv("DASHSCOPE_API_KEY"),
# ),
# )
#
# # 启动智能体
# await agent.start(frontend_queue)
#
# # 将智能体消息转发到前端
# async def send_to_frontend():
# while True:
# msg = await frontend_queue.get()
# await websocket.send_json(msg.model_dump())
#
# asyncio.create_task(send_to_frontend())
#
# # 接收前端消息并转发给智能体
# while True:
# data = await websocket.receive_json()
# client_event = ClientEvents.from_json(data)
# await agent.handle_input(client_event)
#
# **前端设置(客户端):**
#
# 前端需要:
#
# 1. 建立与后端的 WebSocket 连接
# 2. 发送 ``CLIENT_SESSION_CREATE`` 事件初始化会话
# 3. 捕获麦克风音频,通过 ``CLIENT_AUDIO_APPEND`` 事件发送
# 4. 接收并处理 ``ServerEvents``(例如播放音频、显示转录文本)
#
# .. code-block:: javascript
#
# // 连接 WebSocket
# const ws = new WebSocket('ws://localhost:8000/ws/user1/session1');
#
# ws.onopen = () => {
# // 创建会话
# ws.send(JSON.stringify({
# type: 'client_session_create',
# config: {
# instructions: '你是一个有用的助手。',
# user_name: 'User1'
# }
# }));
# };
#
# // 处理来自后端的消息
# ws.onmessage = (event) => {
# const data = JSON.parse(event.data);
# if (data.type === 'response_audio_delta') {
# // 播放音频块
# playAudio(data.delta);
# }
# };
#
# // 发送音频数据
# function sendAudioChunk(audioData) {
# ws.send(JSON.stringify({
# type: 'client_audio_append',
# session_id: 'session1',
# audio: audioData, // base64 编码
# format: { encoding: 'pcm16', sample_rate: 16000 }
# }));
# }
#
# 完整的工作示例请参见 AgentScope 仓库中的
# ``examples/agent/realtime_voice_agent/``。
# %%
# 多智能体实时对话
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# AgentScope 通过 ``ChatRoom`` 类支持多智能体实时交互。
#
# 请注意目前大多数实时模型 API 仅支持单用户交互,但 AgentScope 的架构设计支持多智能体和多用户,
# 当 API 能力扩展时即可应用到多智能体场景。
#
# 实时聊天室
# ----------------------------
#
# AgentScope 引入 ``ChatRoom`` 类来管理共享对话空间中的多个实时智能体。
# ChatRoom 提供:
#
# - 集中管理多个 ``RealtimeAgent`` 实例
# - 智能体之间的自动消息广播
# - 统一的前端通信消息队列
# - 房间内所有智能体的生命周期管理
#
# 使用 ChatRoom
# --------------
#
# ``ChatRoom`` 的用法与 ``RealtimeAgent`` 类似:
#
async def example_chat_room() -> None:
"""使用 ChatRoom 和多个实时智能体的示例。"""
from agentscope.pipeline import ChatRoom
from agentscope.agent import RealtimeAgent
from agentscope.realtime import DashScopeRealtimeModel
# 创建多个智能体
agent1 = RealtimeAgent(
name="Agent1",
sys_prompt="你是 Agent1一个有用的助手。",
model=DashScopeRealtimeModel(
model_name="qwen3-omni-flash-realtime",
api_key=os.getenv("DASHSCOPE_API_KEY"),
),
)
agent2 = RealtimeAgent(
name="Agent2",
sys_prompt="你是 Agent2一个有用的助手。",
model=DashScopeRealtimeModel(
model_name="qwen3-omni-flash-realtime",
api_key=os.getenv("DASHSCOPE_API_KEY"),
),
)
# 创建包含多个智能体的聊天室
chat_room = ChatRoom(agents=[agent1, agent2])
# 创建队列接收来自所有智能体的消息
outgoing_queue = asyncio.Queue()
# 启动聊天室
await chat_room.start(outgoing_queue)
# 处理来自前端的输入
# 聊天室会广播给所有智能体
from agentscope.realtime import ClientEvents
client_event = ClientEvents.ClientTextAppendEvent(
session_id="session1",
text="大家好!",
)
await chat_room.handle_input(client_event)
# 完成后停止聊天室
await chat_room.stop()
# %%
# 发展规划
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 实时智能体功能目前为实验性质,正在积极开发中。未来计划包括:
#
# - 支持更多实时模型 API
# - 增强对话历史的记忆管理
# - 多用户语音交互支持
# - 改进 VAD语音活动检测配置
# - 更好的错误处理和恢复机制
#
#

View File

@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
"""
.. _state:
状态/会话管理
=================================
在 AgentScope 中,**"状态"** 是指智能体在运行中某一时刻的数据快照,包括其当前的系统提示、记忆、上下文、装备的工具以及其他 **随时间变化** 的信息。
为了管理应用程序的状态AgentScope 设计实现了 **自动状态注册** 和 **会话级状态管理**,具有以下特性:
- 支持所有继承自 ``StateModule`` 的变量的 **自动状态注册**
- 支持使用自定义序列化/反序列化方法的 **手动状态注册**
- 支持 **会话/应用程序级别管理**
"""
import asyncio
import json
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.module import StateModule
from agentscope.session import JSONSession
from agentscope.tool import Toolkit
# %%
# 状态模块
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ``StateModule`` 类是 AgentScope 中状态管理的基础,提供三个基本函数:
#
# .. list-table:: ``StateModule`` 的方法
# :header-rows: 1
#
# * - 方法
# - 参数
# - 描述
# * - ``register_state``
# - | ``attr_name``,
# | ``custom_to_json`` (可选),
# | ``custom_from_json`` (可选)
# - 将属性注册为其状态,带有可选的序列化/反序列化函数。
# * - ``state_dict``
# -
# - 获取当前对象的状态字典
# * - ``load_state_dict``
# - | ``state_dict``,
# | ``strict`` (可选)
# - 将状态字典加载到当前对象
#
# 在 ``StateModule`` 的对象中,以下所有属性都将被视为其状态的一部分:
#
# - 继承自 ``StateModule`` 的 **属性**
# - 通过 ``register_state`` 方法注册的 **属性**
#
# 注意 ``StateModule`` 支持 **嵌套** 序列化和反序列化,例如下面的示例中,``ClassB`` 中包含一个 ``ClassA`` 的实例:
#
class ClassA(StateModule):
def __init__(self) -> None:
super().__init__()
self.cnt = 123
# 将 cnt 属性注册为状态
self.register_state("cnt")
class ClassB(StateModule):
def __init__(self) -> None:
super().__init__()
# 属性 "a" 继承自 StateModule将自动视为状态的一部分
self.a = ClassA()
# 手动将属性 "c" 注册为状态
self.c = "Hello, world!"
self.register_state("c")
obj_b = ClassB()
print("obj_b.a 的状态:")
print(obj_b.a.state_dict())
print("\nobj_b 的状态:")
print(json.dumps(obj_b.state_dict(), indent=4))
# %%
# 我们可以观察到 ``obj_b`` 的状态自动包含了其属性 ``a`` 的状态。
#
# 在 AgentScope 中,``AgentBase``、``MemoryBase``、``LongTermMemoryBase`` 和 ``Toolkit`` 类都继承自 ``StateModule``,因此支持自动和嵌套状态管理。
#
# 创建一个智能体
agent = ReActAgent(
name="Friday",
sys_prompt="你是一个名为 Friday 的助手。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
memory=InMemoryMemory(),
toolkit=Toolkit(),
)
initial_state = agent.state_dict()
print("智能体的初始状态:")
print(json.dumps(initial_state, indent=4, ensure_ascii=False))
# %%
# 然后我们通过生成回复消息来改变其状态:
#
async def example_agent_state() -> None:
"""智能体状态管理示例。"""
await agent(Msg("user", "你好,智能体!", "user"))
print("生成回复后智能体的状态:")
print(json.dumps(agent.state_dict(), indent=4, ensure_ascii=False))
asyncio.run(example_agent_state())
# %%
# 现在我们将智能体的状态恢复到初始状态:
#
agent.load_state_dict(initial_state)
print("加载初始状态后:")
print(json.dumps(agent.state_dict(), indent=4, ensure_ascii=False))
# %%
# 会话管理
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# 会话Session是应用程序中状态的集合例如多个智能体。
#
# AgentScope 提供了 ``SessionBase`` 类,包含两个用于会话管理的抽象方法:``save_session_state`` 和 ``load_session_state``。
# 开发者可以继承该类来实现自己的状态保存方案,例如对接到自己的数据库或文件系统。
#
# 在 AgentScope 中,我们提供了基于 JSON 和文件系统的的会话类 ``JSONSession``
# 它会将状态保存到会化 ID 命名的 JSON 文件中,也可以从中加载状态。
#
# 保存会话状态
# -----------------------------------------
#
# 通过生成回复消息改变智能体状态
asyncio.run(example_agent_state())
print("\n智能体的状态:")
print(json.dumps(agent.state_dict(), indent=4, ensure_ascii=False))
# %%
# 然后我们将其保存到会话文件中:
session = JSONSession(
save_dir="./", # 保存所有session文件的目录
)
async def example_session() -> None:
"""会话管理示例。"""
# 可以保存多个状态,只需要输入的对象为 `StateModule` 的子类。
await session.save_session_state(
session_id="user_bob",
agent=agent,
)
print("保存的状态:")
with open("./user_bob.json", "r", encoding="utf-8") as f:
print(json.dumps(json.load(f), indent=4, ensure_ascii=False))
asyncio.run(example_session())
# %%
# 加载会话状态
# -----------------------------------------
# 然后我们可以从会话文件中加载状态:
#
async def example_load_session() -> None:
"""加载会话状态示例。"""
# 清空智能体状态
await agent.memory.clear()
print("当前智能体状态:")
print(json.dumps(agent.state_dict(), indent=4, ensure_ascii=False))
# 从会话文件中加载状态
await session.load_session_state(
session_id="user_bob",
# 这里使用的关键词参数必须与 `save_session_state` 中的参数一致
agent=agent,
)
print("加载会话状态后智能体的状态:")
print(json.dumps(agent.state_dict(), indent=4, ensure_ascii=False))
asyncio.run(example_load_session())
# %%
# 此时我们可以观察到智能体的状态已经恢复到之前保存的状态。
#

View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
"""
.. _studio:
AgentScope Studio
=========================
AgentScope Studio 是一个本地部署的 Web 应用程序,它
- 为智能体应用程序的开发提供 **项目管理**
- 为运行中的应用程序提供 **可视化** 追踪
- 内置一个为 "Friday" 的 **智能体**,支持用户二次开发
.. note:: Studio 正在快速开发中,更多功能即将推出!
.. figure:: ../../_static/images/studio_home.webp
:width: 100%
:alt: AgentScope Studio 主页
:class: bordered-image
:align: center
AgentScope Studio 主页
快速开始
~~~~~~~~~~~~~~~~~~~~~~~~
AgentScope Studio 通过 ``npm`` 安装:
.. code-block:: bash
npm install -g @agentscope/studio
使用以下命令启动 Studio
.. code-block:: bash
as_studio
要将应用程序连接到 Studio请在 ``agentscope.init`` 函数中使用 ``studio_url`` 参数:
.. code-block:: python
import agentscope
agentscope.init(studio_url="http://localhost:3000")
# 应用程序代码
...
然后可以在 Studio 中看到该应用程序,如下所示:
.. figure:: ../../_static/images/studio_project.webp
:width: 100%
:alt: 项目管理
:class: bordered-image
:align: center
AgentScope Studio 中的项目管理
关于应用程序的详细信息,例如 token 使用情况、模型调用和追踪信息,都可以在 Studio 中查看。
.. figure:: ../../_static/images/studio_run.webp
:width: 100%
:alt: AgentScope Studio 运行页面
:class: bordered-image
:align: center
AgentScope Studio 中的应用程序可视化
Friday 智能体
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Friday 是由 AgentScope 构建的实验性本地部署智能体,旨在
- 回答关于 AgentScope 开发的问题,
- 为开发者提供便捷的二次开发环境,
- 集成 AgentScope 中所有可用功能以构建更强大的智能体,以及
- 持续测试和集成 AgentScope 中的高级功能。
.. note:: 我们非常欢迎开源社区贡献并改进 Friday欢迎在我们的 `GitHub 仓库 <https://github.com/agentscope-ai/agentscope>`_ 上提出问题或拉取请求。
我们正在持续改进 Friday目前它集成了 AgentScope 中的以下功能:
.. list-table::
:header-rows: 1
* - 功能
- 状态
- 进一步阅读
- 描述
* - 元工具
- ✅
- :ref:`tool`
- 分组工具管理,允许智能体自己更改装备的工具。
* - 智能体钩子
- ✅
- :ref:`hook`
- 使用钩子将打印消息转发到前端。
* - 智能体中断
- ✅
- :ref:`agent`
- 允许用户通过后处理中断智能体的回复过程。
* - 截断提示
- ✅
- :ref:`prompt`
- 支持使用预设的最大 token 限制截断提示。
* - 状态和会话管理
- ✅
- :ref:`state`
- 智能体的自动状态管理和会话管理,在不同运行之间维护状态。
* - 长期记忆
- 🚧
- :ref:`memory`
- 支持长期记忆管理。
"""

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
"""
.. _token:
Token 计数
=========================
AgentScope 在 ``agentscope.token`` 模块下提供了 token 计数功能,用于计算给定消息中
的 token 数量,允许开发者在调用 LLM API 前预估 token 数量。
具体而言,可用的 token 计数器如下:
.. list-table::
:header-rows: 1
* - LLM API
- 类
- 实现方式
- 支持图像数据
- 支持工具
* - Anthropic
- ``AnthropicTokenCounter``
- 官方 API
- ✅
- ✅
* - OpenAI
- ``OpenAITokenCounter``
- 本地计算
- ✅
- ✅
* - Gemini
- ``GeminiTokenCounter``
- 官方 API
- ✅
- ✅
* - HuggingFace
- ``HuggingFaceTokenCounter``
- 基于Tokenizer计算
- 取决于模型
- 取决于模型
.. tip:: 格式化器模块已集成了 token 计数器以支持提示截断。更多详细信息请参考 :ref:`prompt` 部分。
.. note::
- 对于 DashScope 模型,目前 dashscope 库不提供 token 计数 API。因此我们建议使用 HuggingFace token 计数器代替。
- 对于 OpenAI 模型,由于官方未提供 token 计数 API因此可能存在与官方计算结果不一致的情况。
下面展示使用 OpenAI token 计数器计算 token 数量的示例:
"""
import asyncio
from agentscope.token import OpenAITokenCounter
async def example_token_counting():
# 示例消息
messages = [
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi, how can I help you?"},
]
# OpenAI token 计数
openai_counter = OpenAITokenCounter(model_name="gpt-4.1")
n_tokens = await openai_counter.count(messages)
print(f"Token 数量: {n_tokens}")
asyncio.run(example_token_counting())
# %%
# 进一步阅读
# ------------------------------
#
# - :ref:`prompt`
#

View File

@@ -0,0 +1,457 @@
# -*- coding: utf-8 -*-
"""
.. _tool:
工具
=========================
为了确保准确可靠的工具解析AgentScope 全面支持工具 API 的使用,具有以下特性:
- 支持从文档字符串 **自动** 解析工具函数
- 支持 **同步和异步** 工具函数
- 支持 **流式** 工具响应(同步或异步生成器)
- 支持对工具 JSON Schema 的 **动态扩展**
- 支持用户实时 **中断** 工具的执行
- 支持智能体的 **自主工具管理**
所有上述功能都由 AgentScope 中的 ``Toolkit`` 类实现,该类负责管理工具函数及其执行。
.. tip:: MCP模型上下文协议的支持请参考 :ref:`mcp` 部分。
"""
import asyncio
import inspect
import json
from typing import Any, AsyncGenerator
from pydantic import BaseModel, Field
import agentscope
from agentscope.message import TextBlock, ToolUseBlock
from agentscope.tool import ToolResponse, Toolkit, execute_python_code
# %%
# 工具函数
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 在 AgentScope 中,工具函数是一个 Python 的可调用对象,它
#
# - 返回一个 ``ToolResponse`` 对象或产生 ``ToolResponse`` 对象的生成器(可以是异步或同步)
# - 具有描述工具功能和参数的文档字符串
#
# 工具函数的模板如下:
def tool_function(a: int, b: str) -> ToolResponse:
"""{函数描述}
Args:
a (int):
{第一个参数的描述}
b (str):
{第二个参数的描述}
"""
# %%
# .. tip:: 实例方法和类方法也可以用作工具函数,``Toolkit`` 中将自动忽略 ``self`` 和 ``cls`` 参数。
#
# AgentScope 在 ``agentscope.tool`` 模块下提供了几个内置工具函数,如 ``execute_python_code``、``execute_shell_command`` 和文本文件读写函数。
#
print("内置工具函数:")
for _ in agentscope.tool.__all__:
if _ not in ["Toolkit", "ToolResponse"]:
print(_)
# %%
# 工具模块Toolkit
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ``Toolkit`` 类设计用于管理工具函数,从文档字符串中提取它们的 JSON Schema并为工具执行提供统一接口。
#
# 基本用法
# ------------------------------
# ``Toolkit`` 类的基本功能是注册工具函数并执行它们。
#
# 准备一个自定义工具函数
async def my_search(query: str, api_key: str) -> ToolResponse:
"""一个简单的示例工具函数。
Args:
query (str):
搜索查询。
api_key (str):
用于身份验证的 API 密钥。
"""
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"正在使用 API 密钥 '{api_key}' 搜索 '{query}'",
),
],
)
# 在工具模块中注册工具函数
toolkit = Toolkit()
toolkit.register_tool_function(my_search)
# %%
# 注册工具函数后,可以通过调用 ``get_json_schemas`` 方法获取其 JSON Schema。
#
print("工具 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# ``Toolkit`` 还允许开发者为工具函数预设参数,这对于 API 密钥或其他敏感信息特别有用。
#
# 先清空工具模块
toolkit.clear()
# 使用预设关键字参数注册工具函数
toolkit.register_tool_function(my_search, preset_kwargs={"api_key": "xxx"})
print("带预设参数的工具 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 预设参数后,该参数将从 JSON schema 中被移除,并在工具调用时自动传递给该工具函数。
#
# 在 ``Toolkit`` 中,``call_tool_function`` 方法以 ``ToolUseBlock`` 作为输入执行指定的工具函数,统一返回一个 **异步生成器**,该生成器产生 ``ToolResponse`` 对象。
#
# .. note:: AgentScope 中,流式返回的工具函数应该是 **“累积的”**,即当前块的内容应包含之前所有块的内容。
#
async def example_tool_execution() -> None:
"""工具调用执行示例。"""
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="123",
name="my_search",
input={"query": "AgentScope"},
),
)
# 非流式返回的工具函数只有一个 ToolResponse 返回
print("工具响应:")
async for tool_response in res:
print(tool_response)
asyncio.run(example_tool_execution())
# %%
# 动态扩展 JSON Schema
# --------------------------------------
#
# Toolkit 允许通过调用 ``set_extended_model`` 方法动态扩展工具函数的 JSON schemas。
# 这种功能允许开发者在不修改工具函数原始定义的情况下,向工具函数添加更多参数。
#
# .. tip:: 相关场景包括动态 :ref:`structured-output` 和 CoT思维链推理
#
# .. note:: 要扩展的工具函数应该接受可变关键字参数(``**kwargs``),以便附加字段可以传递给它。
#
# 以 CoT 推理为例,我们可以用 ``thinking`` 字段扩展所有工具函数,允许智能体总结当前状态然后决定下一步做什么。
#
# 示例工具函数
def tool_function(**kwargs: Any) -> ToolResponse:
"""一个工具函数"""
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"接收到的参数:{kwargs}",
),
],
)
# 添加一个思考字段,以便智能体在给出其他参数之前可以思考。
class ThinkingModel(BaseModel):
"""用于附加字段的 Pydantic 模型。"""
thinking: str = Field(
description="总结当前状态并决定下一步做什么。",
)
# 注册
toolkit.set_extended_model("my_search", ThinkingModel)
print("扩展后的 JSON Schema")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 中断工具执行
# ------------------------------
# ``Toolkit`` 类支持 **异步工具函数** 的 **执行中断**,并提供 **面向智能体的后处理机制**。
# 这种中断基于 asyncio 取消机制实现,其后处理过程根据工具函数的返回类型而有所不同。
#
# .. note:: 对于同步(工具)函数,它们的执行无法通过 asyncio 取消来中断。因此其中断在智能体内而不是工具模块内处理。
# 有关更多信息,请参考 :ref:`agent` 部分。
#
# 具体来说,如果工具函数返回 ``ToolResponse`` 对象,将产生一个带有中断消息的 ``ToolResponse`` 对象。
# 这样智能体可以观察到这一中断并相应地处理它。
# 此外,该 ``ToolResponse`` 对象中的 ``is_interrupted`` 将设置为 ``True``,外部调用者可以决定是否将 ``CancelledError`` 异常抛出到外层。
#
# 可以被中断的异步工具函数示例如下:
#
async def non_streaming_function() -> ToolResponse:
"""一个可以被中断的非流式工具函数。"""
await asyncio.sleep(1) # 模拟长时间运行的任务
# 为演示目的模拟中断
raise asyncio.CancelledError()
# 由于取消,以下代码不会被执行
return ToolResponse(
content=[
TextBlock(
type="text",
text="运行成功!",
),
],
)
async def example_tool_interruption() -> None:
"""工具中断示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(non_streaming_function)
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="123",
name="non_streaming_function",
input={},
),
)
async for tool_response in res:
print("工具响应:")
print(tool_response)
print("中断标志:")
print(tool_response.is_interrupted)
asyncio.run(example_tool_interruption())
# %%
# 对于流式工具函数,``Toolkit`` 将把中断消息附加到中断发生时的 ``ToolResponse`` 上。
# 通过这种方式,智能体可以观察到工具在中断前返回的内容。
#
# 中断流式工具函数的示例如下:
#
async def streaming_function() -> AsyncGenerator[ToolResponse, None]:
"""一个可以被中断的流式工具函数。"""
# 模拟一块响应
yield ToolResponse(
content=[
TextBlock(
type="text",
text="1234",
),
],
stream=True,
)
# 模拟中断
raise asyncio.CancelledError()
# 由于取消,以下代码不会被执行
yield ToolResponse(
content=[
TextBlock(
type="text",
text="123456789",
),
],
)
async def example_streaming_tool_interruption() -> None:
"""流式工具中断示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(streaming_function)
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="xxx",
name="streaming_function",
input={},
),
)
i = 0
async for tool_response in res:
print(f"{i}")
print(tool_response)
print("中断标志:", tool_response.is_interrupted, "\n")
i += 1
asyncio.run(example_streaming_tool_interruption())
# %%
# 自动工具管理
# -------------------------------------
# .. image:: https://img.alicdn.com/imgextra/i3/O1CN013cvRpO27MfesMsTeh_!!6000000007783-2-tps-840-521.png
# :width: 100%
# :align: center
# :alt: 自动工具管理
#
#
# ``Toolkit`` 类通过引入 **工具组** Group 的概念,以及名为 ``reset_equipped_tools`` 的 **元工具函数** Meta Tool 来支持 **自动工具管理** 。
#
# 工具组是一组相关工具函数的集合,例如浏览器使用工具、地图服务工具等,它们将被一起管理。工具组有激活和非激活两种状态,
# 只有工具组被激活,其中的工具函数才对智能体可见,即可以通过 ``toolkit.get_json_schemas()`` 方法访问。
#
# 注意有一个名为 ``basic`` 的特殊组,它始终处于激活状态,注册工具时如果未指定组名,则工具函数将默认添加到此组。
#
# .. tip:: ``basic`` 组确保开发者不需要“组管理”的功能时,工具的基本使用不会受到影响。
#
# 现在我们尝试创建一个名为 ``browser_use`` 的工具组,其中包含一些网页浏览工具。
#
# 我们创建一些浏览器操作相关的工具
def navigate(url: str) -> ToolResponse:
"""导航到网页。
Args:
url (str):
要导航到的网页的 URL。
"""
pass
def click_element(element_id: str) -> ToolResponse:
"""点击网页上的元素。
Args:
element_id (str):
要点击的元素的 ID。
"""
pass
toolkit = Toolkit()
# 创建一个名为 browser_use 的工具组
toolkit.create_tool_group(
group_name="browser_use",
description="用于网页浏览的工具函数。",
active=False,
# 使用这些工具时的注意事项
notes="""1. 使用 ``navigate`` 打开网页。
2. 当需要用户身份验证时,请向用户询问凭据
3. ...""",
)
toolkit.register_tool_function(navigate, group_name="browser_use")
toolkit.register_tool_function(click_element, group_name="browser_use")
# 我们也可以注册一些基本工具
toolkit.register_tool_function(execute_python_code)
# %%
# 此时 ``browser_use`` 未被激活,如果我们检查工具 JSON schema只能看到 ``execute_python_code`` 工具:
print("此时对智能体可见的工具函数 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 使用 ``update_tool_groups`` 方法激活或停用工具组:
toolkit.update_tool_groups(group_names=["browser_use"], active=True)
print("激活后对智能体可见的工具函数 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 此外,``Toolkit`` 提供了一个名为 ``reset_equipped_tools`` 的元工具函数,它会将所有组名(除了 "basic")作为一个 bool 型的参数,
# 让智能体调用该工具来决定要激活哪些工具组:
#
# .. note:: 在 ``ReActAgent`` 类的实现中,只需要在构造函数中将 ``enable_meta_tool`` 设置为 ``True`` 即可启用元工具函数。
#
# 注册元工具函数
toolkit.register_tool_function(toolkit.reset_equipped_tools)
reset_equipped = next(
tool
for tool in toolkit.get_json_schemas()
if tool["function"]["name"] == "reset_equipped_tools"
)
print("``reset_equipped_tools`` 函数的 JSON schema")
print(
json.dumps(
reset_equipped,
indent=4,
ensure_ascii=False,
),
)
# %%
# 当智能体调用 ``reset_equipped_tools`` 时,对应工具组将被激活,同时返回的结果中将包含工具的使用注意事项。
#
async def mock_agent_reset_tools() -> None:
"""模拟智能体调用 reset_equipped_tools 函数。"""
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="456",
name="reset_equipped_tools",
input={
"browser_use": True, # 激活浏览器使用工具组
},
),
)
async for tool_response in res:
print("工具响应中的文字返回:")
print(tool_response.content[0]["text"])
asyncio.run(mock_agent_reset_tools())
# %%
# 此外,``Toolkit`` 还通过 ``get_activated_notes`` 函数提供已经被激活了的工具组的 notes开发者也可以将其组装到智能体的系统提示中从而达到动态管理工具的作用。
#
# .. tip:: 自动工具管理功能已在 ``ReActAgent`` 类中实现,有关更多详细信息,请参考 :ref:`agent` 部分。
#
# 再创建一个工具组
toolkit.create_tool_group(
group_name="map_service",
description="谷歌地图服务工具。",
active=True,
notes="""1. 使用 ``get_location`` 获取地点的位置。
2. ...""",
)
print("激活工具组的汇总注意事项:")
print(toolkit.get_activated_notes())
# %%
# 进一步阅读
# ---------------------
# - :ref:`agent`
# - :ref:`state`
# - :ref:`mcp`
#

View File

@@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
"""
.. _tracing:
追踪
==============================
AgentScope 实现了基于 OpenTelemetry 的追踪来监控和调试
智能体应用程序的执行,具有以下特性
- 为 LLM、工具、智能体、格式化器等提供内置追踪
- 支持错误和异常追踪
- 在 AgentScope Studio 中提供原生追踪 **可视化**
- 支持连接到 **第三方平台**,如阿里云云监控、`Arize-Phoenix <https://github.com/Arize-ai/phoenix>`_、`Langfuse <https://langfuse.com/>`_ 等
设置
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note:: 连接到 :ref:`studio` 或第三方平台应该在应用程序开始时通过调用 ``agentscope.init`` 函数完成。
AgentScope Studio
---------------------------------------
.. figure:: ../../_static/images/studio_tracing.webp
:width: 100%
:alt: AgentScope Studio 追踪页面
:class: bordered-image
:align: center
*AgentScope Studio 中的追踪页面*
当连接到 AgentScope Studio 时,只需在 ``agentscope.init`` 函数中提供 ``studio_url`` 参数。
.. code-block:: python
import agentscope
agentscope.init(studio_url="http://xxx:port")
第三方平台
---------------------------------------
要连接到第三方追踪平台,请在 ``agentscope.init`` 函数中设置 ``tracing_url`` 参数。
``tracing_url`` 是您的 OpenTelemetry 收集器或任何支持 OTLPOpenTelemetry 协议)的服务器 URL。
.. code-block:: python
import agentscope
# 连接到 OpenTelemetry 兼容的后端
agentscope.init(tracing_url="https://your-tracing-backend:port/traces")
以阿里云云监控、Arize-Phoenix 和 Langfuse 为例:
**阿里云云监控Alibaba Cloud CloudMonitor**:全托管可观测平台。
.. code-block:: python
:caption: 连接到阿里云云监控
agentscope.init(tracing_url="https://tracing-cn-hangzhou.arms.aliyuncs.com/adapt_xxx/api/otlp/traces")
.. tip::
**获取 Endpoint** 在 `ARMS 控制台 <https://arms.console.aliyun.com/>`_ 的 **接入中心** > **OpenTelemetry** 中,
根据实际部署地域选择对应的 **公网接入点**。可通过环境变量 ``OTEL_SERVICE_NAME`` 自定义应用名称。
阿里云云监控可通过 `LoongSuite <https://github.com/alibaba/loongsuite-python-agent>`_ 探针提供零侵入的自动化接入。
更多信息请参考 `云监控文档 <https://help.aliyun.com/zh/cms/cloudmonitor-2-0/user-guide/model-application>`_。
**Arize-Phoenix**:需要在环境变量中设置 ``PHOENIX_API_KEY``。
.. code-block:: python
:caption: 连接到 Arize Phoenix
# Arize Phoenix 集成
import os
PHOENIX_API_KEY = os.environ.get("PHOENIX_API_KEY")
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"api_key={PHOENIX_API_KEY}"
agentscope.init(tracing_url="https://app.phoenix.arize.com/v1/traces")
**LangFuse**:需要在环境变量中设置 ``LANGFUSE_PUBLIC_KEY`` 和 ``LANGFUSE_SECRET_KEY``。
授权头是使用这些密钥构建的。
.. code-block:: python
:caption: 连接到 LangFuse
import os, base64
LANGFUSE_PUBLIC_KEY = os.environ["LANGFUSE_PUBLIC_KEY"]
LANGFUSE_SECRET_KEY = os.environ["LANGFUSE_SECRET_KEY"]
LANGFUSE_AUTH_STRING = f"{LANGFUSE_PUBLIC_KEY}:{LANGFUSE_SECRET_KEY}"
LANGFUSE_AUTH = base64.b64encode(LANGFUSE_AUTH_STRING.encode("utf-8")).decode("ascii")
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {LANGFUSE_AUTH}"
# 欧盟数据区域
agentscope.init(tracing_url="https://cloud.langfuse.com/api/public/otel/v1/traces")
# 美国数据区域
# agentscope.init(tracing_url="https://us.cloud.langfuse.com/api/public/otel/v1/traces")
自定义追踪
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如前所述AgentScope 中的追踪功能是基于 OpenTelemetry 实现的。
这意味着 AgentScope 中的追踪与开发者基于 OpenTelemetry SDK 自己实现的的追踪代码**完全兼容**。
此外AgentScope 内置了以下装饰器来追踪相应的模块,它们对不同类的特殊属性,以及返回值做了相应的特殊处理:
- ``@trace_llm``:追踪 ``ChatModelBase`` 子类的 ``__call__`` 函数
- ``@trace_reply``:追踪 ``AgentBase`` 子类的 ``reply`` 函数
- ``@trace_format``:追踪 ``FormatterBase`` 子类的 ``format`` 函数
- ``@trace``:追踪一般函数
追踪大语言模型
----------------------------------------
``@trace_llm`` 装饰器用于追踪 ``ChatModelBase`` 类的 ``__call__`` 函数。
.. code-block:: python
:caption: 追踪新的 ChatModel 类
class ExampleChatModel(ChatModelBase):
\"\"\"示例模型\"\"\"
...
@trace_llm
async def __call__(
self,
*args: Any,
**kwargs: Any,
) -> AsyncGenerator[ChatResponse, None] | ChatResponse:
\"\"\"LLM 调用\"\"\"
...
追踪智能体
----------------------------------------
``@trace_reply`` 装饰器用于追踪智能体的 `reply` 函数。
.. code-block:: python
:caption: 追踪新的 Agent 类
class ExampleAgent(AgentBase):
\"\"\"示例智能体类\"\"\"
@trace_reply
async def reply(self, *args: Any, **kwargs: Any) -> Msg:
\"\"\"回复消息。\"\"\"
...
追踪格式化器
----------------------------------------
``@trace_format`` 装饰器用于格式化器实现并追踪 `format` 函数。
.. code-block:: python
:caption: 追踪新的 Formatter 类
class ExampleFormatter(FormatterBase):
\"\"\"简单的示例格式化器类\"\"\"
@trace_format
async def format(self, *args: Any, **kwargs: Any) -> list[dict]:
\"\"\"示例格式化\"\"\"
一般函数追踪
----------------------------------------
``@trace`` 装饰器与上述装饰器不同,它是一个通用的追踪装饰器,可以应用于任何函数。
它需要一个 `name` 参数来标识被追踪的函数,并且可以追踪各种类型的函数,包括:
- 同步函数
- 同步生成器函数
- 异步函数
- 异步生成器函数
.. code-block:: python
:caption: 一般追踪示例
# 1. 同步函数
@trace(name='simple_function')
def simple_function(name: str, age: int) -> str:
\"\"\"带有自动追踪的简单函数。\"\"\"
return f"你好, {name}! 你的年龄是 {age} 岁。"
# 2. 同步生成器函数
@trace(name='number_generator')
def number_generator(n: int) -> Generator[int, None, None]:
\"\"\"生成从 0 到 n-1 的数字。\"\"\"
for i in range(n):
yield i
# 3. 异步函数
@trace(name='async_function')
async def async_function(data: dict) -> dict:
\"\"\"异步处理数据。\"\"\"
return {"processed": data}
# 4. 异步生成器函数
@trace(name='async_stream')
async def async_stream(n: int) -> AsyncGenerator[str, None]:
\"\"\"异步生成数据流。\"\"\"
for i in range(n):
yield f"data_{i}"
"""

View File

@@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-
"""
.. _tts:
TTS
====================
AgentScope 为多个 API 提供商的文本转语音TTS模型提供了统一接口。
本章节演示如何在 AgentScope 中使用 TTS 模型。
AgentScope 支持以下 TTS API
.. list-table:: 内置 TTS 模型
:header-rows: 1
* - API
- 类
- 流式输入
- 非流式输入
- 流式输出
- 非流式输出
* - DashScope 实时 API
- ``DashScopeRealtimeTTSModel``
- ✅
- ✅
- ✅
- ✅
* - DashScope CosyVoice 实时 API
- ``DashScopeCosyVoiceRealtimeTTSModel``
- ✅
- ✅
- ✅
- ✅
* - DashScope API
- ``DashScopeTTSModel``
- ❌
- ✅
- ✅
- ✅
* - DashScope CosyVoice API
- ``DashScopeCosyVoiceTTSModel``
- ❌
- ✅
- ✅
- ✅
* - OpenAI API
- ``OpenAITTSModel``
- ❌
- ✅
- ✅
- ✅
* - Gemini API
- ``GeminiTTSModel``
- ❌
- ✅
- ✅
- ✅
.. note:: AgentScope TTS 模型中的流式输入和输出都是累积式的。
**选择合适的模型:**
- **使用非实时 TTS**:当已有完整文本时(例如预先编写的响应、完整的 LLM 输出)
- **使用实时 TTS**:当文本是逐步生成时(例如 LLM 的流式返回),以获得更低的延迟
"""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tts import (
DashScopeRealtimeTTSModel,
DashScopeTTSModel,
)
# %%
# 非实时 TTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 非实时 TTS 模型处理完整的文本输入,使用起来最简单,可以直接调用它们的 ``synthesize()`` 方法。
#
# 以 DashScope TTS 模型为例:
async def example_non_realtime_tts() -> None:
"""使用非实时 TTS 模型的基本示例。"""
# DashScope TTS 示例
tts_model = DashScopeTTSModel(
api_key=os.environ.get("DASHSCOPE_API_KEY", ""),
model_name="qwen3-tts-flash",
voice="Cherry",
stream=False, # 非流式输出
)
msg = Msg(
name="assistant",
content="你好,这是 DashScope TTS。",
role="assistant",
)
tts_response = await tts_model.synthesize(msg)
# tts_response.content 包含一个带有 base64 编码音频数据的音频块
print("音频数据长度:", len(tts_response.content["source"]["data"]))
asyncio.run(example_non_realtime_tts())
# %%
# **流式输出以降低延迟:**
#
# 当 ``stream=True`` 时,模型会逐步返回音频块,允许
# 您在合成完成前开始播放。这减少了感知延迟。
#
async def example_non_realtime_tts_streaming() -> None:
"""使用带流式输出的非实时 TTS 模型的示例。"""
# 使用流式输出的 DashScope TTS 示例
tts_model = DashScopeTTSModel(
api_key=os.environ.get("DASHSCOPE_API_KEY", ""),
model_name="qwen3-tts-flash",
voice="Cherry",
stream=True, # 启用流式输出
)
msg = Msg(
name="assistant",
content="你好,这是带流式输出的 DashScope TTS。",
role="assistant",
)
# 合成并接收用于流式输出的异步生成器
async for tts_response in await tts_model.synthesize(msg):
# 处理到达的每个音频块
print("接收到的音频块长度:", len(tts_response.content["source"]["data"]))
asyncio.run(example_non_realtime_tts_streaming())
# %%
# 实时 TTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 实时 TTS 模型专为文本增量生成的场景设计,
# 例如流式 LLM 响应。这通过在完整文本准备好之前
# 开始音频合成,实现尽可能低的延迟。
#
# **核心概念:**
#
# - **有状态处理**:实时 TTS 为单个流式会话维护状态,
# 由 ``msg.id`` 标识。一次只能有一个流式会话处于活动状态。
# - **两种方法**
#
# - ``push(msg)``:非阻塞方法,提交文本块并立即返回。
# 如果有可用的部分音频,可能会返回。
# - ``synthesize(msg)``:阻塞方法,完成会话并返回
# 所有剩余的音频。当 ``stream=True`` 时,返回异步生成器。
#
# .. code-block:: python
#
# async def example_realtime_tts_streaming():
# tts_model = DashScopeRealtimeTTSModel(
# api_key=os.environ.get("DASHSCOPE_API_KEY", ""),
# model_name="qwen3-tts-flash-realtime",
# voice="Cherry",
# stream=False,
# )
#
# # 实时 tts 模型接收累积的文本块
# res = await tts_model.push(msg_chunk_1) # 非阻塞
# res = await tts_model.push(msg_chunk_2) # 非阻塞
# ...
# res = await tts_model.synthesize(final_msg) # 阻塞,获取所有剩余音频
#
# 在初始化时设置 ``stream=True`` 时,``synthesize()`` 方法返回 ``TTSResponse`` 对象的异步生成器,允许您在音频块到达时处理它们。
#
#
# 与 ReActAgent 集成
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AgentScope 智能体在提供 TTS 模型时,可以自动将其响应合成为语音。
# 这与实时和非实时 TTS 模型都能无缝协作。
#
# **工作原理:**
#
# 1. 智能体生成文本响应(可能从 LLM 流式传输)
# 2. TTS 模型自动将文本合成为音频
# 3. 合成的音频附加到 ``Msg`` 对象的 ``speech`` 字段
# 4. 音频在智能体的 ``self.print()`` 方法期间播放
#
async def example_agent_with_tts() -> None:
"""使用带 TTS 的 ReActAgent 的示例。"""
# 创建启用了 TTS 的智能体
agent = ReActAgent(
name="Assistant",
sys_prompt="你是一个有用的助手。",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen-max",
stream=True,
),
formatter=DashScopeChatFormatter(),
# 启用 TTS
tts_model=DashScopeRealtimeTTSModel(
api_key=os.getenv("DASHSCOPE_API_KEY"),
model_name="qwen3-tts-flash-realtime",
voice="Cherry",
),
)
user = UserAgent("User")
# 像正常情况一样构建对话
msg = None
while True:
msg = await agent(msg)
msg = await user(msg)
if msg.get_text_content() == "exit":
break
# %%
# 自定义 TTS 模型
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 可以通过继承 ``TTSModelBase`` 来创建自定义 TTS 实现。
# 基类为实时和非实时 TTS 模型提供了灵活的接口。
# 我们使用属性 ``supports_streaming_input`` 来指示 TTS 模型是否为实时模型。
#
# 对于实时 TTS 模型,需要实现 ``connect``、``close``、``push`` 和 ``synthesize`` 方法来处理 API 的生命周期和流式输入。
#
# 而对于非实时 TTS 模型,只需实现 ``synthesize`` 方法。
#
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# - :ref:`agent` - 了解更多关于 AgentScope 中的智能体
# - :ref:`message` - 理解 AgentScope 中的消息格式
# - API 参考::class:`agentscope.tts.TTSModelBase`
#

View File

@@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
"""
.. _tuner:
Tuner
=================
AgentScope 提供了 ``tuner`` 模块用于通过强化学习RL训练智能体应用。
本教程将带你系统了解如何利用 ``tuner`` 提升智能体在特定任务上的表现,包括:
- 介绍 ``tuner`` 的核心组件
- 演示调优流程所需的关键代码实现
- 展示调优流程的配置与运行方法
主要组件
~~~~~~~~~~~~~~~~~~~
``tuner`` 模块为智能体训练工作流引入了三大核心组件:
- **任务数据集**:用于训练和评估智能体的任务集合。
- **工作流函数**:封装被调优智能体应用的决策逻辑。
- **评判函数**:评估智能体在特定任务上的表现,并为调优过程提供奖励信号。
此外,``tuner`` 还提供了若干用于自定义调优流程的配置类,包括:
- **TunerModelConfig**:用于指定被调优模型的相关配置。
- **AlgorithmConfig**:用于指定强化学习算法(如 GRPO、PPO 等)及其参数。
实现流程
~~~~~~~~~~~~~~~~~~~
本节以一个简单的数学智能体为例,演示如何用 ``tuner`` 进行训练。
任务数据集
--------------------
任务数据集包含用于训练和评估的任务集合。
``tuner`` 的任务数据集采用 Huggingface `datasets <https://huggingface.co/docs/datasets/quickstart>`_ 格式,并通过 ``datasets.load_dataset`` 加载。例如:
.. code-block:: text
my_dataset/
├── train.jsonl # 训练样本
└── test.jsonl # 测试样本
假设 `train.jsonl` 内容如下:
.. code-block:: json
{"question": "2 + 2 等于多少?", "answer": "4"}
{"question": "4 + 4 等于多少?", "answer": "8"}
在开始调优前,你可以用如下方法来确定你的数据集能够被正确加载:
.. code-block:: python
from agentscope.tuner import DatasetConfig
dataset = DatasetConfig(path="my_dataset", split="train")
dataset.preview(n=2)
# 输出前两个样本以验证数据集加载正确
# [
# {
# "question": "2 + 2 等于多少?",
# "answer": "4"
# },
# {
# "question": "4 + 4 等于多少?",
# "answer": "8"
# }
# ]
工作流函数
--------------------
工作流函数定义了智能体与环境的交互方式和决策过程。所有工作流函数需遵循 ``agentscope.tuner.WorkflowType`` 的输入/输出签名。
以下是一个用 ReAct 智能体回答数学问题的简单工作流函数示例:
"""
from typing import Dict, Optional
from agentscope.agent import ReActAgent
from agentscope.formatter import OpenAIChatFormatter
from agentscope.message import Msg
from agentscope.model import ChatModelBase
from agentscope.tuner import WorkflowOutput
async def example_workflow_function(
task: Dict,
model: ChatModelBase,
auxiliary_models: Optional[Dict[str, ChatModelBase]] = None,
) -> WorkflowOutput:
"""一个用于调优的工作流函数示例。
Args:
task (`Dict`): 任务信息。
model (`ChatModelBase`): 智能体使用的对话模型。
auxiliary_models (`Optional[Dict[str, ChatModelBase]]`):
用于辅助的额外对话模型,一般用于多智能体场景下模拟其他非训练智能体的行为。
Returns:
`WorkflowOutput`: 工作流生成的输出。
"""
agent = ReActAgent(
name="react_agent",
sys_prompt="你是一个善于解决数学问题的智能体。",
model=model,
formatter=OpenAIChatFormatter(),
)
response = await agent.reply(
msg=Msg(
"user",
task["question"],
role="user",
), # 从任务中提取问题
)
return WorkflowOutput( # 返回响应结果
response=response,
)
# %%
# 你可以直接用任务字典和日常调试使用的 ``DashScopeChatModel`` / ``OpenAIChatModel`` 运行此工作流函数,从而在正式训练前测试其流程的正确性。例如:
import asyncio
import os
from agentscope.model import DashScopeChatModel
task = {"question": "123 加 456 等于多少?", "answer": "579"}
model = DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
)
workflow_output = asyncio.run(example_workflow_function(task, model))
assert isinstance(
workflow_output.response,
Msg,
), "在此示例中,响应应为 Msg 实例。"
print("\n工作流响应:", workflow_output.response.get_text_content())
# %%
#
# 评判函数
# --------------------
# 评判函数用于评估智能体在特定任务上的表现,并为调优过程提供奖励信号。
# 所有评判函数需遵循 ``agentscope.tuner.JudgeType`` 的输入/输出签名。
# 下面是一个简单的评判函数示例,通过比较智能体响应与标准答案给出奖励:
from typing import Any
from agentscope.tuner import JudgeOutput
async def example_judge_function(
task: Dict,
response: Any,
auxiliary_models: Optional[Dict[str, ChatModelBase]] = None,
) -> JudgeOutput:
"""仅用于演示的简单评判函数。
Args:
task (`Dict`): 任务信息。
response (`Any`): WorkflowOutput 的响应字段。
auxiliary_models (`Optional[Dict[str, ChatModelBase]]`):
用于 LLM-as-a-Judge 的辅助模型。
Returns:
`JudgeOutput`: 评判函数分配的奖励。
"""
ground_truth = task["answer"]
reward = 1.0 if ground_truth in response.get_text_content() else 0.0
return JudgeOutput(reward=reward)
# 本地测试函数的正确性:
judge_output = asyncio.run(
example_judge_function(
task,
workflow_output.response,
),
)
print(f"评判奖励: {judge_output.reward}")
# %%
# 评判函数同样可以按照上述案例中展示的方式在正式训练前进行本地测试,以确保其逻辑正确。
#
# .. tip::
# 你可以在评判函数中利用已有的 `MetricBase <https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/evaluate/_metric_base.py>`_ 实现,计算更复杂的指标,并将其组合为复合奖励。
#
# 配置并运行
# ~~~~~~~~~~~~~~~
# 最后,你可以用 ``tuner`` 模块配置并运行调优流程。
# 在开始调优前,请确保环境已安装 `Trinity-RFT <https://github.com/agentscope-ai/Trinity-RFT>`_这是 ``tuner`` 的依赖。
#
# 下面是调优流程的配置与启动示例:
#
# .. note::
# 此示例仅供演示。完整可运行示例请参考 `Tune ReActAgent <https://github.com/agentscope-ai/agentscope/tree/main/examples/tuner/react_agent>`_
#
# .. code-block:: python
#
# from agentscope.tuner import tune, AlgorithmConfig, DatasetConfig, TunerModelConfig
# # 你的工作流 / 评判函数 ...
#
# if __name__ == "__main__":
# dataset = DatasetConfig(path="my_dataset", split="train")
# model = TunerModelConfig(model_path="Qwen/Qwen3-0.6B", max_model_len=16384)
# algorithm = AlgorithmConfig(
# algorithm_type="multi_step_grpo",
# group_size=8,
# batch_size=32,
# learning_rate=1e-6,
# )
# tune(
# workflow_func=example_workflow_function,
# judge_func=example_judge_function,
# model=model,
# train_dataset=dataset,
# algorithm=algorithm,
# )
#
# 这里用 ``DatasetConfig`` 配置训练数据集,用 ``TunerModelConfig`` 配置可训练模型相关参数,用 ``AlgorithmConfig`` 指定强化学习算法及其超参数。
#
# .. tip::
# ``tune`` 函数基于 `Trinity-RFT <https://github.com/agentscope-ai/Trinity-RFT>`_ 实现,内部会将输入参数转换为 YAML 配置。
# 高级用户可忽略 ``model``、``train_dataset``、``algorithm`` 参数,直接通过 ``config_path`` 指定 YAML 配置文件。
# 推荐使用配置文件方式以便更细粒度地控制训练过程,充分利用 Trinity-RFT 的高级功能。
# 你可参考 Trinity-RFT 的 `配置指南 <https://agentscope-ai.github.io/Trinity-RFT/en/main/tutorial/trinity_configs.html>`_ 了解更多配置选项。
#
# 你可以将上述代码保存为 ``main.py``,并用如下命令运行:
#
# .. code-block:: bash
#
# ray start --head
# python main.py
#
# 检查点和日志会自动保存到当前工作目录下的 ``checkpoints/AgentScope`` 目录,每次运行会以时间戳为后缀保存到子目录。
# tensorboard 日志可在检查点目录下的 ``monitor/tensorboard`` 中找到。
#
# .. code-block:: text
#
# your_workspace/
# └── checkpoints/
# └──AgentScope/
# └── Experiment-20260104185355/ # 每次运行以时间戳保存
# ├── monitor/
# │ └── tensorboard/ # tensorboard 日志
# └── global_step_x/ # 第 x 步保存的模型检查点
#
# .. tip::
# 更多调优样例请参考 AgentScope-Samples 库中的 `tuner 目录 <https://github.com/agentscope-ai/agentscope-samples/tree/main/tuner>`_

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""
Concurrent Agents
===================================
在异步编程的帮助下,多智能体并发可以通过 Python 中的 ``asyncio.gather`` 执行。
下面展示了一个简单的示例,其中创建了两个智能体并并发执行。
"""
import asyncio
from datetime import datetime
from typing import Any
from agentscope.agent import AgentBase
class ExampleAgent(AgentBase):
"""用于并发执行的示例智能体。"""
def __init__(self, name: str) -> None:
"""使用智能体名称初始化智能体。"""
super().__init__()
self.name = name
async def reply(self, *args: Any, **kwargs: Any) -> None:
"""回复消息。"""
start_time = datetime.now().strftime("%H:%M:%S.%f")[:-3]
print(f"{self.name} 开始于 {start_time}")
await asyncio.sleep(3) # 模拟长时间运行的任务
end_time = datetime.now().strftime("%H:%M:%S.%f")[:-3]
print(f"{self.name} 结束于 {end_time}")
async def run_concurrent_agents() -> None:
"""运行并发智能体。"""
agent1 = ExampleAgent("智能体 1")
agent2 = ExampleAgent("智能体 2")
await asyncio.gather(agent1(), agent2())
asyncio.run(run_concurrent_agents())

View File

@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
"""
.. _conversation:
Conversation
======================
Conversation 是一种智能体间交换和共享信息的设计模式,常见于游戏、聊天机器人和多智能体讨论场景。
在 AgentScope 中conversation 的构建在 **显式的消息传递** 基础上。在本章中,我们将演示如何构建:
- User-assistant 之间的对话(聊天机器人)
- 多实体对话(游戏、讨论等)
它们的主要区别在于
- **提示的构建方式**,以及
- 信息在智能体之间的 **传播/共享** 方式。
"""
import asyncio
import json
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.memory import InMemoryMemory
from agentscope.formatter import (
DashScopeChatFormatter,
DashScopeMultiAgentFormatter,
)
from agentscope.model import DashScopeChatModel
from agentscope.message import Msg
from agentscope.pipeline import MsgHub
from agentscope.tool import Toolkit
# %%
# User-Assistant 对话
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# User-assistant 对话也称为聊天机器人chatbot是最常见的智能体应用也是当前大多数 LLM API 的设计模式。
# 在这种对话只有两个参与者用户user和智能体assistant
#
# 在 AgentScope 中,名称中带有 **"Chat"** 的格式化器专为 user-assistant 对话设计,
# 如 ``DashScopeChatFormatter``、``AnthropicChatFormatter`` 等。
# 它们使用消息中的 ``role`` 字段来区分用户和智能体,并相应地格式化消息。
#
# 这里我们构建智能体 ``Friday`` 和用户之间的简单对话。
#
# .. tip:: AgentScope 提供了内置的 ``UserAgent`` 类用于人机交互HITL。更多详细信息请参考 :ref:`user-agent`。
#
friday = ReActAgent(
name="Friday",
sys_prompt="你是一个名为 Friday 的有用助手",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(), # 用于 user-assistant 对话的格式化器
memory=InMemoryMemory(),
toolkit=Toolkit(),
)
# 创建用户智能体
user = UserAgent(name="User")
# %%
# 现在,我们可以通过在这两个智能体之间交换消息来开始对话,直到用户输入"exit"结束对话。
#
# .. code-block:: python
#
# async def run_conversation() -> None:
# """运行 Friday 和用户之间的简单对话。"""
# msg = None
# while True:
# msg = await friday(msg)
# msg = await user(msg)
# if msg.get_text_content() == "exit":
# break
#
# asyncio.run(run_conversation())
#
# %%
# 多实体对话
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 如开头所述,我们演示如何在 **提示构建** 和 **信息共享** 方面构建多智能体对话。
#
# 构建提示
# -------------------------------
# 在 AgentScope 中,我们为多智能体对话提供了内置格式化器,其名称中带有 **"MultiAgent"**
# 如 ``DashScopeMultiAgentFormatter``、``AnthropicMultiAgentFormatter`` 等。
#
# 具体而言,它们使用消息中的 ``name`` 字段来区分不同的实体,并将对话历史格式化为单个用户消息。
# 以 ``DashScopeMultiAgentFormatter`` 为例:
#
# .. tip:: 有关格式化器的更多详细信息可以在 :ref:`prompt` 中找到。
#
async def example_multi_agent_prompt() -> None:
msgs = [
Msg("system", "你是一个名为 Bob 的有用助手。", "system"),
Msg("Alice", "嗨!", "user"),
Msg("Bob", "嗨!很高兴见到大家。", "assistant"),
Msg("Charlie", "我也是!顺便说一下,我是 Charlie。", "assistant"),
]
formatter = DashScopeMultiAgentFormatter()
prompt = await formatter.format(msgs)
print("格式化的提示:")
print(json.dumps(prompt, indent=4, ensure_ascii=False))
# 我们在这里打印组合用户消息的内容以便更好地理解:
print("-------------")
print("组合消息")
print(prompt[1]["content"])
asyncio.run(example_multi_agent_prompt())
# %%
# 消息共享
# -------------------------------
# 在多智能体对话中,显式交换消息可能不够高效和便利,
# 特别是在多个智能体之间广播消息时。
#
# 因此AgentScope 提供了一个名为 ``MsgHub`` 的异步上下文管理器来简化消息广播。
# 具体而言,同一个 ``MsgHub`` 中的智能体将自动接收其它参与者通过 ``reply`` 函数返回的消息。
#
# 下面我们构建一个多人聊天的场景,多个智能体扮演不同的角色:
#
model = DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
)
formatter = DashScopeMultiAgentFormatter()
alice = ReActAgent(
name="Alice",
sys_prompt="你是一个名为 Alice 的学生。",
model=model,
formatter=formatter,
)
bob = ReActAgent(
name="Bob",
sys_prompt="你是一个名为 Bob 的学生。",
model=model,
formatter=formatter,
)
charlie = ReActAgent(
name="Charlie",
sys_prompt="你是一个名为 Charlie 的学生。",
model=model,
formatter=formatter,
)
async def example_msghub() -> None:
"""使用 MsgHub 进行多智能体对话的示例。"""
async with MsgHub(
[alice, bob, charlie],
# 进入 MsgHub 时的公告消息
announcement=Msg(
"system",
"现在大家互相认识一下,简单自我介绍。",
"system",
),
):
await alice()
await bob()
await charlie()
asyncio.run(example_msghub())
# %%
# 现在我们打印 Alice 的记忆,检查她的记忆是否正确更新。
#
async def example_memory() -> None:
"""打印 Alice 的记忆。"""
print("Alice 的记忆:")
for msg in await alice.memory.get_memory():
print(
f"{msg.name}: {json.dumps(msg.content, indent=4, ensure_ascii=False)}",
)
asyncio.run(example_memory())
# %%
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# - :ref:`prompt`
# - :ref:`pipeline`
#

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
.. _handoffs:
Handoffs
========================================
Handoffs 是由 OpenAI 提出的工作流模式,通过调用子智能体的方式来完成目标任务。
在 AgentScope 中通过工具调用的方式实现 handoffs 非常简单。首先,我们创建一个函数来允许协调者动态创建子智能体。
.. figure:: ../../_static/images/handoffs.png
:width: 80%
:align: center
:alt: 协调者-子智能体工作流
*Handoffs 示例*
"""
import asyncio
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import (
ToolResponse,
Toolkit,
execute_python_code,
)
# 创建子智能体的工具函数
async def create_worker(
task_description: str,
) -> ToolResponse:
"""创建一个子智能体来完成给定的任务。子智能体配备了 Python 执行工具。
Args:
task_description (``str``):
子智能体要完成的任务描述。
"""
# 为子智能体智能体配备一些工具
toolkit = Toolkit()
toolkit.register_tool_function(execute_python_code)
# 创建子智能体智能体
worker = ReActAgent(
name="Worker",
sys_prompt="你是一个智能体。你的目标是完成给定的任务。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
)
# 让子智能体完成任务
res = await worker(Msg("user", task_description, "user"))
return ToolResponse(
content=res.get_content_blocks("text"),
)
async def run_handoffs() -> None:
"""交接工作流示例。"""
# 初始化协调者智能体
toolkit = Toolkit()
toolkit.register_tool_function(create_worker)
orchestrator = ReActAgent(
name="Orchestrator",
sys_prompt="你是一个协调者智能体。你的目标是通过将任务分解为更小的任务并创建子智能体来完成它们,从而完成给定的任务。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
memory=InMemoryMemory(),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
)
# 任务描述
task_description = "在 Python 中执行 hello world"
# 创建子智能体来完成任务
await orchestrator(Msg("user", task_description, "user"))
asyncio.run(run_handoffs())

View File

@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
"""
.. _multiagent-debate:
Multi-Agent Debate
========================
Multi-Agent debate 模拟不同智能体之间的多轮讨论场景,通常包括几个 solver 和一个 aggregator。
典型情况下solver 生成并交换他们的答案,而 aggregator 收集并总结答案。
我们实现了 `EMNLP 2024`_ 中的示例,其中两个 solver 智能体将按固定顺序讨论一个话题,根据先前的辩论历史表达他们的论点。
在每一轮中,主持人智能体将决定是否可以在当前轮获得最终的正确答案。
"""
import asyncio
import os
from pydantic import Field, BaseModel
from agentscope.agent import ReActAgent
from agentscope.formatter import (
DashScopeMultiAgentFormatter,
)
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import MsgHub
# 准备一个话题
topic = "两个圆外切且没有相对滑动。圆A的半径是圆B半径的1/3。圆A绕圆B滚动一圈回到起点。圆A总共会旋转多少次"
# 创建两个辩论者智能体Alice 和 Bob他们将讨论这个话题。
def create_solver_agent(name: str) -> ReActAgent:
"""获取一个解决者智能体。"""
return ReActAgent(
name=name,
sys_prompt=f"你是一个名为 {name} 的辩论者。你好,欢迎来到"
"辩论比赛。我们的目标是找到正确答案,因此你没有必要完全同意对方"
f"的观点。辩论话题如下所述:{topic}",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
formatter=DashScopeMultiAgentFormatter(),
)
alice, bob = [create_solver_agent(name) for name in ["Alice", "Bob"]]
# 创建主持人智能体
moderator = ReActAgent(
name="Aggregator",
sys_prompt=f"""你是一个主持人。将有两个辩论者参与辩论比赛。他们将就以下话题提出观点并进行讨论:
``````
{topic}
``````
在每轮讨论结束时,你将评估辩论是否结束,以及话题正确的答案。""",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
# 使用多智能体格式化器,因为主持人将接收来自多于用户和助手的消息
formatter=DashScopeMultiAgentFormatter(),
)
# 主持人的结构化输出模型
class JudgeModel(BaseModel):
"""主持人的结构化输出模型。"""
finished: bool = Field(description="辩论是否结束。")
correct_answer: str | None = Field(
description="辩论话题的正确答案,仅当辩论结束时提供该字段。否则保留为 None。",
default=None,
)
async def run_multiagent_debate() -> None:
"""运行多智能体辩论工作流。"""
while True:
# MsgHub 中参与者的回复消息将广播给所有参与者。
async with MsgHub(participants=[alice, bob, moderator]):
await alice(
Msg(
"user",
"你是正方,请表达你的观点。",
"user",
),
)
await bob(
Msg(
"user",
"你是反方。你不同意正方的观点。请表达你的观点和理由。",
"user",
),
)
# Alice 和 Bob 不需要知道主持人的消息,所以主持人在 MsgHub 外部调用。
msg_judge = await moderator(
Msg(
"user",
"现在你已经听到了他们的辩论,现在判断辩论是否结束,以及你能得到正确答案吗?",
"user",
),
structured_model=JudgeModel,
)
if msg_judge.metadata.get("finished"):
print(
"\n辩论结束,正确答案是:",
msg_judge.metadata.get("correct_answer"),
)
break
asyncio.run(run_multiagent_debate())
# %%
# 进一步阅读
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# - :ref:`pipeline`
#
# .. _EMNLP 2024:
# Encouraging Divergent Thinking in Large Language Models through Multi-Agent Debate. EMNLP 2024.
#

View File

@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
"""
.. _routing:
Routing
==========================
在 AgentScope 中有两种实现 Routing 的方法,都简单易实现:
- 使用结构化输出的显式 routing
- 使用工具调用的隐式 routing
.. tip:: 考虑到智能体 routing 没有统一的标准/定义,我们遵循 `Building effective agents <https://www.anthropic.com/engineering/building-effective-agents>`_ 中的设置
显式 Routing
~~~~~~~~~~~~~~~~~~~~~~~~~~
在显式 routing 中,我们可以直接使用智能体的结构化输出来确定将消息路由到哪个智能体。
初始化 routing 智能体
"""
import asyncio
import json
import os
from typing import Literal
from pydantic import BaseModel, Field
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit, ToolResponse
router = ReActAgent(
name="Router",
sys_prompt="你是一个路由智能体。你的目标是将用户查询路由到正确的后续任务,注意你不需要回答用户的问题。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
formatter=DashScopeChatFormatter(),
)
# 使用结构化输出指定路由任务
class RoutingChoice(BaseModel):
your_choice: Literal[
"Content Generation",
"Programming",
"Information Retrieval",
None,
] = Field(
description="选择正确的后续任务,如果任务太简单或没有合适的任务,则选择 ``None``",
)
task_description: str | None = Field(
description="任务描述",
default=None,
)
async def example_router_explicit() -> None:
"""使用结构化输出进行显式路由的示例。"""
msg_user = Msg(
"user",
"帮我写一首诗",
"user",
)
# 路由查询
msg_res = await router(
msg_user,
structured_model=RoutingChoice,
)
# 结构化输出存储在 metadata 字段中
print("结构化输出:")
print(json.dumps(msg_res.metadata, indent=4, ensure_ascii=False))
asyncio.run(example_router_explicit())
# %%
# 隐式 Routing
# ~~~~~~~~~~~~~~~~~~~~~~~~~
# 另一种方法是将下游智能体包装成工具函数,这样路由智能体就可以根据用户查询决定调用哪个工具。
#
# 我们首先定义几个工具函数:
#
async def generate_python(demand: str) -> ToolResponse:
"""根据需求生成 Python 代码。
Args:
demand (``str``):
对 Python 代码的需求。
"""
# 示例需求智能体
python_agent = ReActAgent(
name="PythonAgent",
sys_prompt="你是一个 Python 专家,你的目标是根据需求生成 Python 代码。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
memory=InMemoryMemory(),
formatter=DashScopeChatFormatter(),
toolkit=Toolkit(),
)
msg_res = await python_agent(Msg("user", demand, "user"))
return ToolResponse(
content=msg_res.get_content_blocks("text"),
)
# 为演示目的模拟一些其他工具函数
async def generate_poem(demand: str) -> ToolResponse:
"""根据需求生成诗歌。
Args:
demand (``str``):
对诗歌的需求。
"""
pass
async def web_search(query: str) -> ToolResponse:
"""在网络上搜索查询。
Args:
query (``str``):
要搜索的查询。
"""
pass
# %%
# 之后,我们定义一个路由智能体并为其配备上述工具函数。
#
toolkit = Toolkit()
toolkit.register_tool_function(generate_python)
toolkit.register_tool_function(generate_poem)
toolkit.register_tool_function(web_search)
# 使用工具模块初始化路由智能体
router_implicit = ReActAgent(
name="Router",
sys_prompt="你是一个路由智能体。你的目标是将用户查询路由到正确的后续任务。",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
async def example_router_implicit() -> None:
"""使用工具调用进行隐式路由的示例。"""
msg_user = Msg(
"user",
"帮我在 Python 中生成一个快速排序函数",
"user",
)
# 路由查询
await router_implicit(msg_user)
asyncio.run(example_router_implicit())