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的开发储备知识不足,已有更好方式来实现此类的需求,若有读者有更好实现方式,欢迎留言。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。