书接上文:AI Agent案例实践:三种智能体开发模式详解之一(手写代码)
简要回顾前文中高考查询小助手的业务背景:准备两张表(分别是每年的高考参加人数信息表college_entrance_examination,以及每年高考的录取信息表college_entrance_admission),然后开发一个AI Agent要求实现一个简单逻辑,就是依据用户查询意图,通过向量化语义匹配到最合适的表,然后基于匹配到的表生成相应SQL,再执行这个SQL得到结果,最后对结果做个简单分析和输出。
一、开发实践(基于AgentScope框架)
前文中,我们通过手写代码、LangChain框架(顺序链SequentialChain和Agent)、QwenAgent框架等方式实现了高考小助手智能体,下面我们探索使用AgentScope来实现我们的小助手,学习了解一下AgentScope框架。
什么是AgentScope?
AgentScope 是阿里通义实验室推出的开源智能体开发框架,聚焦多智能体应用场景,提供覆盖开发、部署与监控全生命周期的生产级解决方案。该框架以开发者为核心,采用模块化设计原则与ReAct范式构建智能体体系,支持动态工具配置、并行工具调用及自动化状态管理,包含消息、模型、记忆和工具四大基础组件。
官方文档地址:doc.agentscope.io/zh_CN/
核心优势
- 1. 对 ReAct 范式的深度集成与工程化支持:
-
- 实时介入(Realtime Steering):允许用户在智能体执行过程中(例如,当工具调用耗时过长或结果偏离预期时)通过 Ctrl+C 或调用 interrupt 方法进行中断。框架会自动捕获中断信号,保存当前状态和记忆,并通过 handle_interrupt 方法进行优雅的后处理,极大地提升了人机交互的灵活性和可控性。
- 并行工具调用(Parallel Tool Calls):通过设置 parallel_tool_calls=True,当大模型生成多个工具调用指令时,AgentScope 会使用 asyncio.gather 并行执行这些工具,显著提高效率,尤其适用于处理多个独立的、耗时的任务。
- 结构化输出(Structured Output):与工具调用紧密结合,允许开发者传入一个 pydantic.BaseModel 的子类作为参数。智能体会严格按照该模型的结构生成输出,并将结果存储在返回消息的 metadata 字段中,方便后续程序化处理,确保了输出的可靠性和一致性。
- 自主记忆与状态管理:支持智能体自主管理其长期记忆和自动进行状态管理,这对于构建复杂的、多轮对话的智能体至关重要。
-
- 开箱即用的 ReActAgent:框架直接提供了 ReActAgent 类,将 ReAct 范式所需的“思考(Reasoning)”和“行动(Acting)”环节进行了系统性的封装和实现,开发者无需从零开始搭建复杂的逻辑。
- 丰富的内置功能:ReActAgent 集成了多项高级功能,包括但不限于:
- 2. 高度的灵活性与可扩展性:
-
- 清晰的抽象基类:提供了 AgentBase 和 ReActAgentBase 两个抽象基类。AgentBase 定义了所有智能体的基本接口(如 reply, observe),而 ReActAgentBase 则在此基础上增加了 _reasoning 和 _acting 等抽象方法,专为 ReAct 范式设计。
- 强大的钩子(Hook)机制:在基类中预置了丰富的前置(pre_)和后置(post_)钩子函数,例如 pre_reply, post_reasoning 等。开发者可以通过覆盖这些钩子,在不修改核心逻辑的情况下,轻松地插入自定义的监控、日志记录、数据预处理或后处理等逻辑。
- 易于自定义:开发者可以根据具体需求,选择继承 AgentBase 或 ReActAgentBase 来创建完全自定义的智能体,框架提供了坚实的底层支持,让开发者可以专注于业务逻辑的实现。
- 3. 开发者友好:
-
-
优雅的错误与中断处理:如前所述,其基于 asyncio 的中断机制和抽象的 handle_interrupt 方法,使得处理异常和用户干预变得非常规范和简单,保证了程序的稳定性和用户体验。
-
模块化设计:框架将模型(model)、记忆(memory)、格式化器(formatter)、工具包(toolkit)等组件解耦,开发者可以自由组合和替换,例如使用不同的大模型API或内存存储方案。
-
架构设计
我们基于AgentScope实现我们的智能体,主要实现多轮对话、问题拆分,学习一些工具调用能力和记忆能力。总体设计如下:
代码实践
具体代码实现如下:
import json
import asyncio
from agentscope.message import TextBlock, Msg
from agentscope.tool import ToolResponse, Toolkit
from agentscope.agent import AgentBase, ReActAgent
from agentscope.formatter import OllamaChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.model import OllamaChatModel
from api_service import QueryService, SemanticService, AnalysisService
# 初始化服务
queryService = QueryService()
semanticService = SemanticService()
analysisService = AnalysisService()
# 自定义工具类
# 根据输入文本语义匹配表结构,每次返回一个最相关的表结构。对于需要多表查询的问题,需要多次调用此工具。
async def match_metadata(user_query: str) -> ToolResponse:
table = semanticService.hybrid_search(user_query, 1)
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"{[t['table_info'] for t in table]}",
),
],
)
# 根据输入的SQL进行执行。
async def execute_sql(query_sql: str) -> ToolResponse:
result = json.dumps(queryService.query_with_column(query_sql))
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"{result}",
),
],
)
async def interactive_react_agent() -> None:
"""创建一个支持多轮对话的ReAct智能体。"""
# 准备工具
toolkit = Toolkit()
toolkit.register_tool_function(match_metadata)
toolkit.register_tool_function(execute_sql)
jarvis = ReActAgent(
name="Jarvis",
sys_prompt="""
你是一个数据分析助手,负责帮助用户查询数据库信息。
请特别注意:用户的问题可能需要从多个表中查询数据。
1. 首先确定需要查询哪些数据
2. 使用match_metadata工具分别匹配包含这些数据的表结构
3. 对每个表生成相应的SQL查询语句
4. 执行查询并汇总结果
5. 最后计算并给出答案
请确保逐步执行,不要跳过任何步骤。
""",
model=OllamaChatModel(
model_name="qwen3:32b", # 指定模型名称
stream=True, # 根据需要设置是否流式输出
enable_thinking=True, # 为Qwen3启用思考功能(可选)
# host="http://localhost:11434" # 如果Ollama不在默认地址,需指定
),
formatter=OllamaChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
print("数据分析助手已启动!输入问题进行查询(输入'exit'退出)")
print("-" * 50)
while True:
# 使用run_in_executor处理阻塞的input调用
user_input = await asyncio.get_event_loop().run_in_executor(
None,
input,
"用户: "
)
# 检查是否退出指令 [[1]]
if user_input.strip().lower() == 'exit':
print("正在退出程序...")
break
if not user_input.strip():
continue
# 创建消息并发送给智能体
msg = Msg(
name="user",
content=user_input,
role="user",
)
print("\nJarvis正在思考...")
# 获取智能体响应
response = await jarvis(msg)
# 显示回答
print(f"\nJarvis: {response.content}\n")
print("-" * 50)
# 运行交互式智能体
if __name__ == "__main__":
asyncio.run(interactive_react_agent())
AgentScope依赖于Python的3.10版本及以上版本,这里我们使用Python3.13版本。
pip官方库版本0.x比较旧,直接源码安装,AgentScope最新1.x版本安装:
git clone -b main https://github.com/agentscope-ai/agentscope
cd agentscope
pip install -e .
该智能体基于 AgentScope 框架,使用了以下核心组件:
- ReActAgent:具备“思考-行动”循环能力的智能体,可调用工具并基于结果推理下一步,指定推理次数max_iters,默认循环10次。
- OllamaChatModel:本地运行的大语言模型(Qwen3:32b),负责推理与生成 SQL,封装ollama调用。
- Toolkit + 自定义工具:封装数据库操作能力(执行 SQL)、语义检索能力(执行RAG检索)。
- InMemoryMemory:会话记忆,保存对话历史。ReActAgent内,每次问答后缓存,推理时自动获取缓存的对话历史。
- OllamaChatFormatter:格式化模型输入输出。
- 自定义服务层:QueryService, SemanticService, AnalysisService —— 提供底层数据库查询与语义检索能力。
此智能体的核心是 ReActAgent 类,它实现了 ReAct 范式,即通过交替进行推理(Reasoning)和行动(Acting)来解决问题 。智能体会接收用户请求,并启动“思考→行动→观察”的循环,直到生成最终响应。
测试结果
QA1: 2016年考生人数 -- 测试AgentScope基本工具调用能力
QA2: 那一年录取人数有多少? -- 测试AgentScope上下文记忆能力
QA3: 2017年考生人数和录取人数分别有多少?-- 测试推理能力
数据分析助手已启动!输入问题进行查询(输入'exit'退出)
--------------------------------------------------
用户: 2016年考生人数
Jarvis正在思考...
Jarvis(thinking): 好的,我现在需要处理用户的问题:“2016年考生人数”。首先,我需要明确用户想要查询的是2016年的考生总人数,还是某个特定地区或类型的考生人数。不过根据问题本身,用户可能只是想知道全国范围内的总考生人数。
接下来,按照步骤,我需要确定需要查询哪些数据。这里显然需要考生的记录,可能涉及考生表,表中应该包含年份和考生人数的信息。因此,第一步是使用match_metadata工具来查找包含这些数据的表结构。
调用match_metadata函数,参数是用户的问题“2016年考生人数”。假设返回的表结构是exam_applicants,其中包含year和number_of_applicants两个字段。接下来,我需要生成相应的SQL查询语句,从exam_applicants表中查询year为2016年的记录的number_of_applicants值。
然后,使用execute_sql工具执行这个SQL查询。假设查询结果返回的是2016年的考生人数为940万人。最后,将结果通过generate_response工具返回给用户,给出明确的答案,并说明数据来源和可能的注意事项,比如数据是否为最终统计等。
需要注意的是,如果存在多个相关表,比如不同地区的考生表,可能需要进行汇总。但根据问题描述,用户可能只需要总人数,因此可能只需要查询主表。如果存在数据不一致或多个表的情况,可能需要进一步确认,但当前情况下按最简处理。
{
"type": "tool_use",
"id": "0_match_metadata",
"name": "match_metadata",
"input": {
"user_query": "2016年考生人数"
}
}
调用大模型llama2向量化:2016年考生人数,地址:http://localhost:11434/api/embeddings
自然语言语义检索字段成功,匹配到的元数据信息:[{'score': 0.6198425, 'table_info': '{"表名": "college_entrance_admission", "表备注": "录取人数与普通高校数信息表,包含字段:录取年份(主键)、录取人数(万人)、招生高\n校数、本科录取人数(万人)、专科录取人数(万人)", "字段列表": [{"字段名": "admission_year", "字段类型": "int", "字段备注": "录取年份"}, ...}, {'score': 0.6008911, 'table_info': '{"表名": "college_entrance_examination", "表备注": "考生人数与复读人数信息表,包含字段:高考年份(主键)、考生人数(万人)、复读人数\n(万人)", "字段列表": [{"字段名": "examination_year", "字段类型": "int", "字段备注": "高考年份"}, ...}]
自然语言分词搜索字段成功,匹配到的元数据信息:[{'score': 1.4999503, 'table_info': '{"表名": "college_entrance_examination", "表备注": "考生人数与复读人数信息表,包含字段:高考年份(主键)、考生人数(万人)、复读人数\n(万人)", "字段列表": [{"字段名": "examination_year", "字段类型": "int", "字段备注": "高考年份"}, ...]}']}, {'score': 0.33530667, 'table_info': '{"表名": "college_entrance_admission", "表备注": "录取人数与普通高校数信息表,包含字段:录取年份(主键)、录取人数(万人)、招生高\n校数、本科录取人数(万人)、专科录取人数(万人)", "字段列表": [{"字段名": "admission_year", ...]}']}]
自然语言混合检索字段成功,匹配到的元数据信息:[{'id': 'JAFTZpkBNpD6YtdVxbQU', 'table_info': '{"表名": "college_entrance_examination", "表备注": "考生人数与复读人数信息表,包含字段:高考年份(主键)、考生人数(万人)、复读人数\n(万人)", "字段列表": [{"字段名": "examination_year", "字段类型": "int", "字段备注": "高考年份"}, {"字段名": "candidates_count", "字段类型": "decimal(10,2)", "字段备注": "考生人数(万\n人)"}, {"字段名": "retake_count", "字段类型": "decimal(10,2)", "字段备注": "复读人数(万人)"}]}', 'semantic_score': 0.6008911, 'keyword_score': 1.4999503, 'combined_score': 0.8706088600000002, ...]}]
system: {
"type": "tool_result",
"id": "0_match_metadata",
"name": "match_metadata",
"output": [
{
"type": "text",
"text": "0['{"表名": "college_entrance_examination", "表备注": "考生人数与复读人数信息表,包含字段:高考年份(主键)、考生人数(万人)、复读人数\\n(万人)", "字段列表": [{"字段名": "examination_year", "字段类型": "int", "字段备注": "高考年份"}, {"字段名": "candidates_count", "字段类型": "decimal(10,2)", "字段备注": "考生人数(万\\n人)"}, {"字段名": "retake_count", "字段类型": "decimal(10,2)", "字段备注": "复读人数(万人)"}]}']"
}
]
}
2025-09-24 19:54:24,041 | WARNING | _ollama_formatter:_format:121 - Unsupported block type thinking in the message, skipped.
Jarvis(thinking): 好的,用户需要查询2016年的考生人数。首先,我需要确定数据存储的表结构。通过调用match_metadata工具,发现有一个名为college_entrance_examination的表,其中包含examination_year、candidates_count和retake_count三个字段。用户的问题只需要考生人数,所以接下来需要生成对应的SQL查询语句。应该使用SELECT语句从该表中查询examination_year等于2016年的candidates_count。然后执行这个SQL,获取结果。假设查询结果返回了940万人,那么最终答案就是2016年的考生人数为940万人。需要确保步骤正确,没有遗漏。
{
"type": "tool_use",
"id": "0_execute_sql",
"name": "execute_sql",
"input": {
"query_sql": "SELECT candidates_count FROM college_entrance_examination WHERE examination_year = 2016;"
}
}
system: {
"type": "tool_result",
"id": "0_execute_sql",
"name": "execute_sql",
"output": [
{
"type": "text",
"text": "0[{"candidates_count": 940.0}]"
}
]
}
Jarvis(thinking): 好的,用户问的是2016年的考生人数。首先我需要确定数据存储在哪个表里。之前调用了match_metadata工具,找到了college_entrance_examination表,里面有examination_year、candidates_count和retake_count字段。接下来,我需要构造一个SQL查询,从该表中筛选出examination_year等于2016年的记录,并获取candidates_count的值。执行这个SQL后,返回的结果是940.0,单位是万人。所以,2016年的考生人数就是940万人。最后,将结果以自然的方式呈现给用户,确保数值和单位都正确无误。
Jarvis: 2016年的考生人数为940万人。
--------------------------------------------------
用户: 那一年录取人数有多少?
Jarvis正在思考...
Jarvis(thinking): 好的,用户现在问的是“那一年录取人数有多少?”,这里的“那一年”应该指的是之前提到的2016年。我需要先确认用户的问题是否需要从数据库中查询数据。
首先,用户之前询问了2016年的考生人数,现在想知道同一年的录取人数。因此,我需要查找2016年的录取人数数据。接下来,我需要确定数据库中是否有相关的表和字段。
使用match_metadata工具来匹配包含录取人数信息的表结构。用户的问题涉及“录取人数”,所以需要查找表中是否有相关字段。可能的表名可能是与高考相关的,比如之前提到的college_entrance_examination,或者另一个表,比如admission_statistics。
执行match_metadata工具,参数是“2016年录取人数”。根据返回的结果,如果存在相关表,比如admission_statistics,包含字段如enrollment_year(年份)和admitted_count(录取人数),那么就可以生成相应的SQL查询。
假设返回的表结构是admission_statistics,包含年份和录取人数字段。然后需要构造SQL查询,选择2016年的录取人数。例如:SELECT admitted_count FROM admission_statistics WHERE enrollment_year = 2016;
执行这个SQL查询后,获取结果,比如返回录取人数是749万人。最后,生成响应,告诉用户2016年的录取人数,并可能计算录取率,比如749/940≈79.7%。
需要确保步骤正确,先匹配元数据,再执行查询,最后生成回答。同时注意字段名和表名的正确性,以及可能的计算步骤。
{
"type": "tool_use",
"id": "0_match_metadata",
"name": "match_metadata",
"input": {
"user_query": "2016年录取人数"
}
}
调用大模型llama2向量化:2016年录取人数,地址:http://localhost:11434/api/embeddings
自然语言语义检索字段成功,匹配到的元数据信息:[{'score': 0.5988846, 'table_info': '{"表名": "college_entrance_admission", "表备注": "录取人数与普通高校数信息表,包含字段:录取年份(主键)、录取人数(万人)、招生高\n校数、本科录取人数(万人)、专科录取人数(万人)", "字段列表": [{"字段名": "admission_year", "字段类型": "int", "字段备注": "录取年份"},...}, {'score': 0.59373474, 'table_info': '{"表名": "college_entrance_examination", "表备注": "考生人数与复读人数信息表,包含字段:高考年份(主键)、考生人数(万人)、复读人数\n(万人)", "字段列表": [{"字段名": "examination_year", "字段类型": "int", "字段备注": "高考年份"},...}]
自然语言分词搜索字段成功,匹配到的元数据信息:[{'score': 1.2747635, 'table_info': '{"表名": "college_entrance_admission", "表备注": "录取人数与普通高校数信息表,包含字段:录取年份(主键)、录取人数(万人)、招生高\n校数、本科录取人数(万人)、专科录取人数(万人)", "字段列表": [...{"字段名": "admission_count", "字段类型": "decimal(10,2)", "字段备注": "录取人数(万\n人)"},...}]
自然语言混合检索字段成功,匹配到的元数据信息:[{'id': 'IwFTZpkBNpD6YtdVw7R2', 'table_info': '{"表名": "college_entrance_admission", "表备注": "录取人数与普通高校数信息表,包含字段:录取年份(主键)、录取人数(万人)、招生高\n校数、本科录取人数(万人)、专科录取人数(万人)", "字段列表": [..., {"字段名": "admission_count", "字段类型": "decimal(10,2)", "字段备注": "录取人数(万\n人)"}, ...]}', 'semantic_score': 0.5988846, 'keyword_score': 1.2747635, 'combined_score': 0.80164827, 'highlight': ...]
system: {
"type": "tool_result",
"id": "0_match_metadata",
"name": "match_metadata",
"output": [
{
"type": "text",
"text": "0['{"表名": "college_entrance_admission", "表备注": "录取人数与普通高校数信息表,包含字段:录取年份(主键)、录取人数(万人)、招生高\\n校数、本科录取人数(万人)、专科录取人数(万人)", "字段列表": [{"字段名": "admission_year", "字段类型": "int", "字段备注": "录取年份"}, {"字段名": "admission_count", "字段类型": "decimal(10,2)", "字段备注": "录取人数(万\\n人)"}, {"字段名": "university_count", "字段类型": "int", "字段备注": "招生高校数"}, {"字段名": "undergraduate_admission_count", "字段类型": "decimal(10,2)", "字段备注": "本科录取人数(万人)"}, {"字段名": "specialty_admission_count", "字段类型": "decimal(10,2)", "字段备注": "专科录取人数(万人)"}]}']"
}
]
}
Jarvis(thinking): 好的,用户现在问的是“那一年录取人数有多少?”,也就是2016年的录取人数。之前已经处理过2016年考生人数的查询,现在需要处理录取人数。首先,我需要确定用户的问题涉及哪些数据,这里显然是2016年的录取人数。
接下来,使用match_metadata工具来查找包含录取人数的表结构。用户的问题关键词是“录取人数”,所以调用match_metadata函数,参数是“2016年录取人数”。根据返回的结果,找到了表college_entrance_admission,其中包含admission_count字段,即录取人数(万人),并且表中有admission_year字段作为年份。
现在需要生成对应的SQL查询语句。需要从college_entrance_admission表中查询admission_count,条件是admission_year等于2016。因此,构造的SQL语句应该是SELECT admission_count FROM college_entrance_admission WHERE admission_year = 2016;。
执行这个SQL语句后,预期会得到2016年的录取人数数据。假设返回的结果是749万人,那么最终的回答就是2016年的录取人数为749万人。需要确保所有步骤正确,特别是表名和字段名的准确性,以及条件的正确应用。如果有任何错误,比如表名或字段名不正确,或者年份条件错误,都会导致结果不准确,所以需要仔细检查每个步骤。
{
"type": "tool_use",
"id": "0_execute_sql",
"name": "execute_sql",
"input": {
"query_sql": "SELECT admission_count FROM college_entrance_admission WHERE admission_year = 2016;"
}
}
system: {
"type": "tool_result",
"id": "0_execute_sql",
"name": "execute_sql",
"output": [
{
"type": "text",
"text": "0[{"admission_count": 772.0}]"
}
]
}
Jarvis(thinking): 好的,用户现在问的是“那一年录取人数有多少?”,也就是2016年的录取人数。之前已经处理过2016年考生人数的查询,现在需要处理录取人数。
首先,我需要确定用户的问题涉及哪些数据。用户提到的“录取人数”应该对应数据库中的某个表。根据之前的步骤,用户的问题可能需要查询不同的表,所以需要先用match_metadata工具来找到正确的表结构。
调用match_metadata工具,参数是“2016年录取人数”。根据返回的结果,找到了表college_entrance_admission,其中包含admission_count字段,即录取人数。接下来需要生成SQL查询语句,从该表中选取admission_year为2016年的admission_count。
然后执行这个SQL查询,得到结果。根据返回的数据,2016年的录取人数是772万人。最后,使用generate_response工具将结果以自然语言回复给用户,确保信息准确且完整。
Jarvis: 2016年的录取人数为772万人。
--------------------------------------------------
用户: 2017年考生人数和录取人数分别有多少
Jarvis正在思考...
Jarvis(thinking): 好的,用户现在问的是2017年的考生人数和录取人数。首先我需要处理考生人数的部分。根据之前的对话,用户询问过2016年的考生人数,当时的表是college_entrance_examination,字段是examination_year和candidates_count。所以2017年的考生人数应该也是从这个表中查询,只需要把年份改成2017。
接下来是录取人数。用户之前问过2016年的录取人数,对应的表是college_entrance_admission,字段是admission_year和admission_count。同样,2017年的录取人数应该查询这个表,年份改为2017。
现在需要确认这两个表是否都有2017年的数据。可能需要分别调用match_metadata工具来验证表结构是否包含所需的字段,但根据之前的回复,这两个表的结构已经明确包含年份和对应的人数字段,所以可以直接生成SQL查询。
对于考生人数,SQL语句应该是SELECT candidates_count FROM college_entrance_examination WHERE examination_year = 2017; 对于录取人数,SQL语句是SELECT admission_count FROM college_entrance_admission WHERE admission_year = 2017;。
执行这两个查询后,将结果汇总,分别给出考生人数和录取人数。假设数据库中有数据,应该返回对应的数值。最后用generate_response将结果组合成自然语言的回答。
......
Jarvis(thinking): 好的,用户现在问的是2017年的考生人数和录取人数分别有多少。我需要先处理考生人数的部分。根据之前的对话流程,用户之前询问过2016年的考生人数和录取人数,我分别通过查询两个不同的表得到了结果。现在用户转向2017年,应该同样需要查询这两个表。
首先,用户的问题提到了“考生人数”,这应该对应到“college_entrance_examination”表,这个表包含高考年份和考生人数等字段。我需要使用match_metadata工具来确认表结构是否正确。根据用户之前的问题,已经知道这个表的字段有examination_year、candidates_count和retake_count。接下来需要生成SQL查询语句,选择2017年的考生人数。之前的例子中,用户查询2016年时用了SELECT candidates_count FROM college_entrance_examination WHERE examination_year = 2016;,所以这里应该类似,把年份改为2017。
接下来是录取人数的部分,用户之前询问2016年录取人数时,我通过match_metadata找到了“college_entrance_admission”表,该表包含admission_year和admission_count等字段。同样,现在需要查询2017年的录取人数,应该使用相同的表,并生成对应的SQL语句:SELECT admission_count FROM college_entrance_admission WHERE admission_year = 2017;。
需要注意的是,用户的问题同时询问了考生人数和录取人数,所以需要分别执行两次查询,分别获取这两个数据。然后将结果汇总,给出一个包含两者的回答。另外,必须确保两次查询的年份都是2017年,避免混淆。如果其中一个表没有2017年的数据,可能需要进一步处理,但根据之前的表结构,假设数据是存在的。最后,生成响应时要明确说明两个数字,确保用户的问题得到完整回答。
{
"type": "tool_use",
"id": "0_execute_sql",
"name": "execute_sql",
"input": {
"query_sql": "SELECT candidates_count FROM college_entrance_examination WHERE examination_year = 2017;"
}
}
system: {
"type": "tool_result",
"id": "0_execute_sql",
"name": "execute_sql",
"output": [
{
"type": "text",
"text": "0[{"candidates_count": 940.0}]"
}
]
}
现在需要分别执行这两个SQL查询。先执行考生人数的查询,得到结果后,再执行录取人数的查询。然后把两个结果合并,形成最终的回答。需要确保两个年份都是2017年,避免混淆。最后用generate_response返回结果。
{
"type": "tool_use",
"id": "0_execute_sql",
"name": "execute_sql",
"input": {
"query_sql": "SELECT admission_count FROM college_entrance_admission WHERE admission_year = 2017;"
}
}
system: {
"type": "tool_result",
"id": "0_execute_sql",
"name": "execute_sql",
"output": [
{
"type": "text",
"text": "0[{"admission_count": 761.49}]"
}
]
}
Jarvis(thinking): 好的,用户现在问的是2017年的考生人数和录取人数。我需要先处理这两个数据点。
首先,考生人数。之前用户问过2016年的,所以结构应该是类似的。使用match_metadata工具查找对应的表,应该是college_entrance_examination表,字段是candidates_count,条件是examination_year=2017。执行SQL后得到的结果是940.0万人,和2016年一样,可能那几年考生人数稳定。
然后是录取人数。对应的表是college_entrance_admission,字段admission_count,条件admission_year=2017。执行后得到761.49万人,比2016年的772万略少。需要注意这里的数据是否有小数点后的变化,可能反映了录取率的变化。
接下来需要将这两个结果整合到回答中,确保数字准确,并且用清晰的语句分开说明考生和录取人数。同时,检查是否有其他相关数据需要补充,比如本科和专科的录取人数,但用户只问了总录取人数,所以可能不需要额外信息。最后生成回答,确认无误后返回。
Jarvis: 2017年的考生人数为940万人,录取人数为761.49万人。
--------------------------------------------------
用户:
Process finished with exit code 137 (interrupted by signal 9:SIGKILL)
QA1: 2016年考生人数 -- 测试AgentScope基本工具调用能力
- 回答正确,流程:调用match_metadata语义检索匹配元数据,然后生成SQL再调用execute_sql执行,查询数据后分析。
QA2: 那一年录取人数有多少? -- 测试AgentScope上下文记忆能力
- 回答符合我们的预期,根据上下文判断那一年是2026年。
QA3: 2017年考生人数和录取人数分别有多少?-- 测试推理能力
- 回答正确,根据历史对话的回答中的表结构,判断需要进行问题的拆分,然后使用之前相同的表,只需要把条件从2016改为2017,然后分别生成SQL再调用execute_sql执行,查询数据后再合并分析。
总结
从上述测试中,我们体验了AgentScope框架的优雅之处,包括上下文记忆,支持多轮对话数据传递等能力,此外AgentScope还有长期记忆、MCP调用等更多实用能力,同学们可以自行学习探索~
本文作者:Chaiys
本文原载:公众号“木昆子记录AI”