Qwen-Agent开发课程助手实践

笔者在学了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课堂助手',
        description='你是AI课堂的助手,对学生的提问进行解答。',
        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方法的实现。

    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='李四老师比张三老师多上几堂课'):
    # 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的开发储备知识不足,已有更好方式来实现此类的需求,若有读者有更好实现方式,欢迎留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值