baidubce / bce-qianfan-sdk

Provide best practices for LMOps, as well as elegant and convenient access to the features of the Qianfan MaaS Platform. (提供大模型工具链最佳实践,以及优雅且便捷地访问千帆大模型平台)
https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html
Apache License 2.0
318 stars 48 forks source link

Issue: function_call_agent使用自定工具接受json格式的字符串参数,似乎不能很好的获取参数 #67

Closed macoli closed 10 months ago

macoli commented 11 months ago

Issue you'd like to raise.

我给自定义工具指定的输入格式为json格式的字符串 image

在我问出问题时,无法将问题中的数据转换成指定的json格式,似乎只能识别输入一个参数 image

我的自定义tool在使用openai时是正常的,但是使用千帆function_call_agent就会报错

Suggestion:

No response

Dobiichi-Origami commented 10 months ago

这个问题看起来像是由于工具的声明有误导致的,也有小概率可能是模型的 Bad Case。 可以麻烦你把编写工具和调用 agent 的代码贴上来吗?

macoli commented 10 months ago

这是我编写的工具和调用agent。我是想通过自然自然语言像工具传递json格式的字符串'{"user": "root", "ip": "1.1.1.1"}',但是似乎并没有生效。而我使用openapi的接口,是可以的


def ldap_user_lock_check_data_retriever(
    ip: str = None,
    user: str = None,
) -> str:
    '''
    该函数是用于从第三方api接口获取数据
    给定参数:method、ip 和user,这个自定义函数以数据结构(JSON 格式)返回结果。

    参数:
        ip: 指定机器的ip地址, 示例: '127.0.0.1'
        user: 指定用户名, 示例: 'root'

    返回结果:
        JSON格式的结果描述, 示例: 
        { 'result': 'Login           Failures Latest failure     From\nroot                0    \n'}

    '''

    if not ip:
        return json.dumps({'result': 'ip未指定,请问您要查看机器的ip是多少呢?'})

    if not user:
        return json.dumps({'result': 'user未指定,请问您要查看机器上的哪个user呢?'})

    url = 'http://localhost:5000/api/v1' 
    data = {
        'method': 'ldap_user_lock_check',
        'ip': ip,
        'user': user
    }

    headers = {'Content-Type': 'application/json'}  # 设置请求头,指定使用 JSON 格式

    # requests = TextRequestsWrapper()

    response = requests.post(url, data=json.dumps(data), headers=headers)

    if response.status_code == 200:
        # 处理响应数据
        data = response.json()
        return json.dumps({'result': data['response']})
    else:
        return json.dumps({'result': 'POST request failed'})

def ldap_user_lock_check(json_request: str) -> str:
    '''
    对 ldap_user_lock_check_data_retriever 函数进行封装,
    将输入 JSON 转换为分隔的参数。

    参数:
        json_request(str):JSON字典输入字符串。

        采用 JSON 字典作为表单的输入:
            { "ip":"<ip>", "user":"user"}

        示例:
            { "ip":"127.0.0.1", "user":"root" }

    返回结果:
        JSON类型的结果。
    '''
    print(json_request)
    arguments = json.loads(json_request)

    ip = arguments.get('ip', None)
    user = arguments.get('user', None)

    return ldap_user_lock_check_data_retriever(ip=ip, user=user)

name = 'ldap_user_lock_check'
request_format = '{{ "ip": "<ip>", "user": "<user>"}}'
result_format = '{{"result": "<result>"}}'
description = f'''
帮助获取在指定机器上的用户账号登录失败次数。
输入应为以下格式的JSON格式的字符串: {request_format}
输出应为以下格式的JSON: {result_format}
'''

# create an instance of the custom langchain tool
Ldap_user_lock_check = Tool(
    name=name,
    func=ldap_user_lock_check,
    description=description,
    return_direct=False
)```

******************************************************************************************
```import os

os.environ['QIANFAN_AK'] = "xxx"
os.environ['QIANFAN_SK'] = "xxx"

from langchain.chat_models import QianfanChatEndpoint

qianfan_chat_model = QianfanChatEndpoint(model="ERNIE-Bot-4")

# import custom tools
from ldap_user_lock_check_qianfan import Ldap_user_lock_check

tools = [
    Ldap_user_lock_check
]

from langchain.agents import AgentExecutor
from qianfan.extensions.langchain.agents import QianfanSingleActionAgent

qianfan_agent = QianfanSingleActionAgent.from_system_prompt(tools, qianfan_chat_model)
excutor = AgentExecutor(agent=qianfan_agent, tools=tools, verbose=True)

ret = excutor.run("我想要查询ip为10.1.1.1的机器上user为root的登录失败次数")
print(ret)```
Dobiichi-Origami commented 10 months ago

QianfanSingleActionAgent 内部使用的 Tool 格式化函数会检查传入的工具对象中是否设置了 args_schema 这一参数,并且在设置了这一参数的情况下按 schema 填充工具的入参格式信息;否则全部统一使用 {"__arg1": {"title": "__arg1", "type": "string"}} 作为入参的字典列表,这点与 OpenAI 的逻辑是一致的。如果你需要传递自定义的入参列表信息,请设置该字段,或者使用 langchain 提供的 tool 装饰器。在 description 中描述工具的入参大概率无法获得所期望的结果,OpenAI 在使用工具时可能对 description 有着更好的理解。针对你的 case,以下代码是可行的:

class Model(pydantic.v1.BaseModel):
    '''
    对 ldap_user_lock_check_data_retriever 函数进行封装,
    将输入 JSON 转换为分隔的参数。

    参数:
        json_request(str):JSON字典输入字符串。

        采用 JSON 字典作为表单的输入:
            { "ip":"<ip>", "user":"user"}

        示例:
            { "ip":"127.0.0.1", "user":"root" }

    返回结果:
        JSON类型的结果。
    '''

    json_request: str = pydantic.v1.Field(desctiption="需要以 json 格式传递的入参字典")

@tool(args_schema=Model)
def ldap_user_lock_check(json_request: str) -> str:
    '''
    使用参数规定的 json 格式,帮助获取在指定机器上的用户账号登录失败次数。
    '''
    print(json_request)
    arguments = json.loads(json_request)

    ip = arguments.get('ip', None)
    user = arguments.get('user', None)

    return ldap_user_lock_check_data_retriever(ip=ip, user=user)
macoli commented 10 months ago

原来是没有指定args_schema,完全依赖大模型的自然语言理解是不科学的。我设置了args_schema之后,可以运行了。非常感谢!