Qwen-Agent开发课程助手实践
懂点投资的码农 2024-09-03 12:01:01 阅读 95
笔者在学了Qwen-Agent相关的内容后,觉得其未来可能会改变人们与信息化系统的交互方式,目前基本是GUI交互方式,这种交互的专业性较强,有一定的学习门槛。特别是当一个信息化系统做的越来越复杂的时候——比如to B的系统,对于用户来讲,使用起来就不太方便,可能只是想要查一个简单的数据呢。那如果是这种场景,能不能直接通过自然语言对话的方式来满足需求呢?
在看了Qwen-Agent的内容和其它一些相关课程后,笔者觉得可以试着先尝试做做,目前可能还不太成熟,但是先跟踪一下技术的前沿发展,保持不掉队。
一、Qwen-Agent自带的天气预报助手
笔者主要看了Qwen-Agent自身提供的天气预报助手assistant_weather_bot,它是一个具有查询天气和画图能力的智能体,分别由查询天气的工具amap_weather和画图工具image_gen来提供相应的能力。
当笔者提问“北京的天气怎么样?”时,它会提取“北京”这个地名,并将其作为location参数传递给工具amap_weather,此工具会将地名转换为地区编码字段adcode,随后调用高德查询天气的相应接口并传参adcode,高德接口返回weather和temperature字段,由工具拼接为自然语言后返回给assistant_weather_bot。
接下来再将上述返回的信息,作为参数传递给画图工具image_gen,由该工具生成图片,并将链接地址返回。
以上实现逻辑比较简单,源码可在github上下载——Qwen-Agent源码下载。
二、自定义开发课程助手(智能体)
笔者首先对课程助手进行定义,指定本地的大模型配置信息,定义system_prompt和调用的工具等。详细代码如下:
import os
from qwen_agent.agents import Assistant
def init_agent_service():
llm_cfg = {
# 读者需要将以下代码修改为自己的本地配置
'model': '/opt/models/Qwen2-7B-Instruct',
'model_server': 'http://192.168.70.190:8000/v1', # api_base
'api_key': 'EMPTY',
}
system = '你叫MOMO,是AI课堂的助手。你只回答跟AI大模型有关的问题,不相关的问题不要回答。每次回答问题前,你要拆解问题并输出每一步的思考过程。'
bot = Assistant(
llm=llm_cfg,
name='AI课堂助手',code>
description='你是AI课堂的助手,对学生的提问进行解答。',code>
system_message=system,
function_list=["course_tool"],
)
return bot
读者在执行此代码前,注意先安装qwen_agent工具包。
从上述代码可以看到,笔者定义了一个course_tool的工具,当用户进行提问时,它会去从course_tool中寻找答案。以下来看看笔者的助手定义。
import os
from typing import Dict, Optional, Union
from database import MySQLConnectionPool
from qwen_agent.tools.base import BaseTool, register_tool
import json
from decimal import Decimal
from datetime import timedelta, date
@register_tool('course_tool')
class CourseTool(BaseTool):
description = '课程助手'
parameters = [{
'name': 'sql_text',
'type': 'string',
'description': "SQL查询语句提取信息,来回答用户的问题。"
"SQL语句需要基于以下数据库模式来编写:"
"CREATE TABLE Courses ("
"id INT AUTO_INCREMENT PRIMARY KEY,"
"course_date DATE NOT NULL,"
"start_time TIME NOT NULL,"
"end_time TIME NOT NULL,"
"course_name VARCHAR(255) NOT NULL,"
"instructor_id VARCHAR(255) NOT NULL COMMENT '指导老师id') COMMENT = '课程表';"
"CREATE TABLE Instructor ("
"instructor_id int(11) NOT NULL COMMENT '指导老师id',"
"instructor_name varchar(255) COMMENT '指导老师姓名',"
") COMMENT '指导老师信息表';"
"Courses表与Instructor表通过字段instructor_id来关联;"
"用户不会直接输入id值,所以不要直接用id值来查询;"
"该查询应该以纯文本形式返回,而不是JSON格式。"
"该查询只能包含MySQL支持的语法。",
'required': True
}]
笔者定义了两张表,一张是课程表Courses,一张是指导老师信息表Instructor,并在数据库中提前创建并录入了数据(见下图中的数据)。两张表之间通过字段instructor_id来关联,笔者将这两张表的schema作为参数sql_text的描述知识写入供大模型进行学习。
在这样完成定义后,用户提的问题将被转换为Mysql语法的sql语句传递给CourseTool类的call方法,以下是call方法的实现。
<code> def call(self, params: Union[str, dict], **kwargs) -> str:
params = self._verify_json_format_args(params)
# 创建数据库连接
conn = MySQLConnectionPool()
records = conn.select_all(params['sql_text'], None)
for record in records:
for key, value in record.items():
if isinstance(value, Decimal):
record[key] = str(value)
elif isinstance(value, timedelta):
# 将 timedelta 对象转换为总秒数的浮点数
record[key] = value.total_seconds()
elif isinstance(value, date):
record[key] = value.isoformat()
return records
call方法的实现比较简单,主要逻辑就是将转换出来的sql语句执行,并返回执行结果,另外对无法转换为json字符串的数据提前进行格式转换。
三、自然语言交互式问答体验
agent和tool定义好后,就可以来体验自然语言交互式问答效果了。譬如笔者提了如下一个“李四老师比张三老师多上几堂课”,正确答案应该是5堂课。
def test(query='李四老师比张三老师多上几堂课'):code>
# Define the agent
bot = init_agent_service()
# Chat
messages = [{'role': 'user', 'content': query}]
for response in bot.run(messages):
print('bot response:', response)
if __name__ == '__main__':
test()
以下是课程助手输出的内容,为便于查阅,笔者将其转换为标准json格式:
[
{
'role': 'assistant',
'content': '要回答这个问题,我们需要从数据库中获取李四老师(instructor_name为李四)和张三老师(instructor_name为张三)各自上课的课程数量。我们可以采取以下步骤:\n\n1.查询李四老师的所有课程。\n2.查询张三老师的所有课程。\n3.计算李四老师和张三老师上课的数量差值。\n\n首先,我们从`Instructor`表中通过`instructor_name`来关联获取`Courses`表中的数据。让我们进行这些查询。',
'name': 'AI课堂助手'
},
{
'role': 'assistant',
'content': '',
'name': 'AI课堂助手',
'function_call': {
'name': 'course_tool',
'arguments': '{
"sql_text": "SELECT COUNT(Courses.course_name) as course_count FROM Courses INNER JOIN Instructor ON Courses.instructor_id = Instructor.instructor_id WHERE Instructor.instructor_name = \'李四\';"
}'
}
},
{
'role': 'function',
'content': '[
\n{
\n"course_count": 9\n
}\n
]',
'name': 'course_tool'
},
{
'role': 'assistant',
'content': '',
'name': 'AI课堂助手',
'function_call': {
'name': 'course_tool',
'arguments': '{
"sql_text": "SELECT COUNT(Courses.course_name) as course_count FROM Courses INNER JOIN Instructor ON Courses.instructor_id = Instructor.instructor_id WHERE Instructor.instructor_name = \'张三\';"
}'
}
},
{
'role': 'function',
'content': '[
\n{
\n"course_count": 4\n
}\n
]',
'name': 'course_tool'
},
{
'role': 'assistant',
'content': '根据查询结果,李四老师上了9堂课,而张三老师上了4堂课。因此,李四老师比张三老师多上了\\(9-4=5\\)堂课。',
'name': 'AI课堂助手'
}
]
以上可以清晰看到智能体的思考过程以及解决思路,非常符合开发人员的习惯,最重要的是,结果也是对的。
当然,同样的代码,如果笔者多运行几次,发现可靠性并不是非常高,偶尔也还是会出现转换成的sql语句中的表不存在或者解决的思路有一些小错误等。笔者测试过,如果是单表查询,准确率会更高一些,读者可以先尝试单表,再尝试多表。
期待Qwen团队把这块的稳定性提高,当然,也可能是笔者水平有限,对Qwen-Agent的开发储备知识不足,已有更好方式来实现此类的需求,若有读者有更好实现方式,欢迎留言。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。