Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Feature]update fix-tool-input #280

Merged
merged 10 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion erniebot-agent/cookbook/local_tool.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"\n",
"### 1.2 代码实现\n",
"\n",
"创建一个 LocalTool 需要几个关键信息,详细可参考:[Tool 关键信息](../modules/tools/#31),对应代码模块需要实现:\n",
"创建一个 LocalTool 需要几个关键信息,详细可参考:[Tool 关键信息](/modules/tools/#31),对应代码模块需要实现:\n",
"\n",
"* 创建 Tool 输入和输出参数的类\n",
"* 继承 Tool 并构建派生类\n",
Expand Down
88 changes: 34 additions & 54 deletions erniebot-agent/cookbook/remote_tool.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,16 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: flask_cors in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (4.0.0)\n",
sijunhe marked this conversation as resolved.
Show resolved Hide resolved
"Requirement already satisfied: pydantic in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (2.5.3)\n",
"Requirement already satisfied: flask[async] in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (3.0.0)\n",
"Requirement already satisfied: Werkzeug>=3.0.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from flask[async]) (3.0.1)\n",
"Requirement already satisfied: Jinja2>=3.1.2 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from flask[async]) (3.1.2)\n",
"Requirement already satisfied: itsdangerous>=2.1.2 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from flask[async]) (2.1.2)\n",
"Requirement already satisfied: click>=8.1.3 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from flask[async]) (8.1.7)\n",
"Requirement already satisfied: blinker>=1.6.2 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from flask[async]) (1.7.0)\n",
"Requirement already satisfied: asgiref>=3.2 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from flask[async]) (3.7.2)\n",
"Requirement already satisfied: annotated-types>=0.4.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from pydantic) (0.6.0)\n",
"Requirement already satisfied: pydantic-core==2.14.6 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from pydantic) (2.14.6)\n",
"Requirement already satisfied: typing-extensions>=4.6.1 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from pydantic) (4.9.0)\n",
"Requirement already satisfied: MarkupSafe>=2.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask[async]) (2.1.3)\n"
"Requirement already satisfied: fastapi in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (0.108.0)\n",
"Requirement already satisfied: pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from fastapi) (2.5.3)\n",
"Requirement already satisfied: starlette<0.33.0,>=0.29.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from fastapi) (0.32.0.post1)\n",
"Requirement already satisfied: typing-extensions>=4.8.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from fastapi) (4.9.0)\n",
"Requirement already satisfied: annotated-types>=0.4.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4->fastapi) (0.6.0)\n",
"Requirement already satisfied: pydantic-core==2.14.6 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4->fastapi) (2.14.6)\n",
"Requirement already satisfied: anyio<5,>=3.4.0 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from starlette<0.33.0,>=0.29.0->fastapi) (4.2.0)\n",
"Requirement already satisfied: idna>=2.8 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from anyio<5,>=3.4.0->starlette<0.33.0,>=0.29.0->fastapi) (3.6)\n",
"Requirement already satisfied: sniffio>=1.1 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from anyio<5,>=3.4.0->starlette<0.33.0,>=0.29.0->fastapi) (1.3.0)\n",
"Requirement already satisfied: exceptiongroup>=1.0.2 in /Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages (from anyio<5,>=3.4.0->starlette<0.33.0,>=0.29.0->fastapi) (1.2.0)\n"
]
},
{
Expand All @@ -90,7 +87,7 @@
}
],
"source": [
"!pip install flask[async] flask_cors pydantic erniebot erniebot-agent"
"!pip install fastapi"
]
},
{
Expand All @@ -115,7 +112,7 @@
"name": "stderr",
"output_type": "stream",
"text": [
"INFO: Started server process [24888]\n",
"INFO: Started server process [19881]\n",
"INFO: Waiting for application startup.\n",
"INFO: Application startup complete.\n",
"INFO: Uvicorn running on http://0.0.0.0:8020 (Press CTRL+C to quit)\n"
Expand Down Expand Up @@ -193,8 +190,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: 127.0.0.1:63618 - \"GET /.well-known/openapi.yaml HTTP/1.1\" 200 OK\n",
"INFO: 127.0.0.1:63619 - \"HEAD /.well-known/examples.yaml HTTP/1.1\" 404 Not Found\n"
"INFO: 127.0.0.1:57207 - \"GET /.well-known/openapi.yaml HTTP/1.1\" 200 OK\n",
"INFO: 127.0.0.1:57208 - \"HEAD /.well-known/examples.yaml HTTP/1.1\" 404 Not Found\n"
]
},
{
Expand Down Expand Up @@ -224,7 +221,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: 127.0.0.1:63627 - \"POST /add_word?version=0.1.0 HTTP/1.1\" 200 OK\n"
"INFO: 127.0.0.1:57213 - \"POST /add_word?version=0.1.0 HTTP/1.1\" 200 OK\n"
]
},
{
Expand Down Expand Up @@ -297,7 +294,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: 127.0.0.1:63638 - \"GET /get_words?version=0.1.0 HTTP/1.1\" 200 OK\n"
"INFO: 127.0.0.1:57221 - \"GET /get_words?version=0.1.0 HTTP/1.1\" 200 OK\n"
]
},
{
Expand All @@ -317,17 +314,15 @@
" content: \u001b[95m{\"words\": [\"red\"], \"prompt\": \"请避免使用\\\"根据提供的内容、文章、检索结果……\\\"等表述,不要做过多的解释。\"}\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [LLM][End] ERNIEBot finished running with output:\n",
" role: \u001b[93massistant\u001b[92m \n",
" content: \u001b[93m单词本中的单词有:\n",
"red\u001b[92m \u001b[0m\n",
" content: \u001b[93m单词本中的单词包括“red”。\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [Run][End] FunctionAgent finished running.\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"单词本中的单词有:\n",
"red\n"
"单词本中的单词包括“red”。\n"
]
}
],
Expand Down Expand Up @@ -358,7 +353,7 @@
"\n",
"#### 介绍\n",
"\n",
"以上展示了如何在本地开发 RESTFul API并在 ERNIE-Bot-Agent 中使用,可这个通常是在现有的服务傻姑娘调整的,如果想要从零开发一个 RESTFul API 的服务成本有点多大,可通过ERNIE-Bot-Agent 中的 LocalTool 模块自定义本地 Tool,然后将其部署成服务即可。"
"以上展示了如何在本地开发 RESTFul API并在 ERNIE-Bot-Agent 中使用,可这个通常是在现有的服务上调整的,如果想要从零开发一个 RESTFul API 的服务成本有点大,可通过ERNIE-Bot-Agent 中的 LocalTool 模块自定义本地 Tool,然后将其部署成服务即可。"
]
},
{
Expand Down Expand Up @@ -432,28 +427,11 @@
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[Route(path='/openapi.json', name='openapi', methods=['GET', 'HEAD']), Route(path='/docs', name='swagger_ui_html', methods=['GET', 'HEAD']), Route(path='/docs/oauth2-redirect', name='swagger_ui_redirect', methods=['GET', 'HEAD']), Route(path='/redoc', name='redoc_html', methods=['GET', 'HEAD']), APIRoute(path='/erniebot-agent-tools/0.0/AddWordTool', name='partial', methods=['POST']), APIRoute(path='/erniebot-agent-tools/0.0/GetWordsTool', name='partial', methods=['POST']), APIRoute(path='/.well-known/openapi.yaml', name='get_openapi_yaml', methods=['GET'])]\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages/pydantic/json_schema.py:2080: PydanticJsonSchemaWarning: Default value <name: AddWordTool, description: 在单词本中添加一个单词> is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n",
" warnings.warn(message, PydanticJsonSchemaWarning)\n",
"/Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages/pydantic/json_schema.py:2080: PydanticJsonSchemaWarning: Default value <name: GetWordsTool, description: 获取单词本所有的单词> is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n",
" warnings.warn(message, PydanticJsonSchemaWarning)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO: Started server process [24888]\n",
"INFO: Started server process [19881]\n",
"INFO: Waiting for application startup.\n",
"INFO: Application startup complete.\n",
"INFO: Uvicorn running on http://0.0.0.0:8021 (Press CTRL+C to quit)\n"
Expand Down Expand Up @@ -490,14 +468,18 @@
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: 127.0.0.1:63656 - \"GET /.well-known/openapi.yaml HTTP/1.1\" 200 OK\n",
"INFO: 127.0.0.1:63657 - \"HEAD /.well-known/examples.yaml HTTP/1.1\" 404 Not Found\n"
"INFO: 127.0.0.1:57235 - \"GET /.well-known/openapi.yaml HTTP/1.1\" 200 OK\n",
"INFO: 127.0.0.1:57236 - \"HEAD /.well-known/examples.yaml HTTP/1.1\" 404 Not Found\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages/pydantic/json_schema.py:2080: PydanticJsonSchemaWarning: Default value <name: AddWordTool, description: 在单词本中添加一个单词> is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n",
" warnings.warn(message, PydanticJsonSchemaWarning)\n",
"/Users/wujingjing05/miniconda3/envs/eb-agent/lib/python3.10/site-packages/pydantic/json_schema.py:2080: PydanticJsonSchemaWarning: Default value <name: GetWordsTool, description: 获取单词本所有的单词> is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n",
" warnings.warn(message, PydanticJsonSchemaWarning)\n",
"\u001b[92mINFO - [Run][Start] FunctionAgent is about to start running with input:\n",
"\u001b[94m添加一个单词“red”到我的单词本\u001b[92m\u001b[0m\n",
"\u001b[92mINFO - [LLM][Start] ERNIEBot is about to start running with input:\n",
Expand All @@ -521,7 +503,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: 127.0.0.1:63662 - \"POST /erniebot-agent-tools/0.0/AddWordTool?version=0.0 HTTP/1.1\" 200 OK\n"
"INFO: 127.0.0.1:57242 - \"POST /erniebot-agent-tools/0.0/AddWordTool?version=0.0 HTTP/1.1\" 200 OK\n"
]
},
{
Expand All @@ -539,15 +521,15 @@
" content: \u001b[95m{\"message\": \"单词添加成功\", \"prompt\": \"请避免使用\\\"根据提供的内容、文章、检索结果……\\\"等表述,不要做过多的解释。\"}\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [LLM][End] ERNIEBot finished running with output:\n",
" role: \u001b[93massistant\u001b[92m \n",
" content: \u001b[93m单词“red”已添加到您的单词本中。\u001b[92m \u001b[0m\n",
" content: \u001b[93m单词“red”已成功添加到您的单词本中。\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [Run][End] FunctionAgent finished running.\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"单词“red”已添加到您的单词本中。\n"
"单词“red”已成功添加到您的单词本中。\n"
]
}
],
Expand Down Expand Up @@ -597,7 +579,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: 127.0.0.1:63671 - \"POST /erniebot-agent-tools/0.0/GetWordsTool?version=0.0 HTTP/1.1\" 200 OK\n"
"INFO: 127.0.0.1:57254 - \"POST /erniebot-agent-tools/0.0/GetWordsTool?version=0.0 HTTP/1.1\" 200 OK\n"
]
},
{
Expand All @@ -617,17 +599,15 @@
" content: \u001b[95m{\"words\": [\"red\"], \"prompt\": \"请避免使用\\\"根据提供的内容、文章、检索结果……\\\"等表述,不要做过多的解释。\"}\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [LLM][End] ERNIEBot finished running with output:\n",
" role: \u001b[93massistant\u001b[92m \n",
" content: \u001b[93m单词本中的单词有:\n",
"red\u001b[92m \u001b[0m\n",
" content: \u001b[93m单词本中的单词有:red。\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [Run][End] FunctionAgent finished running.\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"单词本中的单词有:\n",
"red\n"
"单词本中的单词有:red。\n"
]
}
],
Expand Down Expand Up @@ -696,15 +676,15 @@
" content: \u001b[95m{\"conclusion\": \"合规\", \"isHitMd5\": false, \"conclusionType\": 1}\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [LLM][End] ERNIEBot finished running with output:\n",
" role: \u001b[93massistant\u001b[92m \n",
" content: \u001b[93m根据您提供的句子“我明天出去玩”这句话是合规的。\u001b[92m \u001b[0m\n",
" content: \u001b[93m根据工具审核结果,“我明天出去玩”这句话是合规的。\u001b[92m \u001b[0m\n",
"\u001b[92mINFO - [Run][End] FunctionAgent finished running.\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"根据您提供的句子“我明天出去玩”这句话是合规的。\n"
"根据工具审核结果,“我明天出去玩”这句话是合规的。\n"
]
}
],
Expand Down
2 changes: 1 addition & 1 deletion erniebot-agent/src/erniebot_agent/tools/remote_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async def convert_to_file_data(file_data: str, format: str):

# 2. call tool get response
if self.tool_view.parameters is not None:
tool_arguments = dict(self.tool_view.parameters(**tool_arguments))
tool_arguments = self.tool_view.parameters(**tool_arguments).model_dump(mode="json")

return tool_arguments

Expand Down
11 changes: 7 additions & 4 deletions erniebot-agent/src/erniebot_agent/tools/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from pydantic.fields import FieldInfo

from erniebot_agent.utils.common import create_enum_class
from erniebot_agent.utils.exceptions import RemoteToolError
from erniebot_agent.utils.exceptions import RemoteToolError, ToolError

INVALID_FIELD_NAME = "__invalid_field_name__"

Expand Down Expand Up @@ -240,8 +240,11 @@ def from_openapi_dict(cls, schema: dict) -> Type[ToolParameterView]:
fields = {}
for field_name, field_dict in schema.get("properties", {}).items():
# skip loading invalid field to improve compatibility
if "type" not in field_dict or "description" not in field_dict:
continue
if "type" not in field_dict:
raise ToolError(f"`type` field not found in `{field_name}` property", stage="Loading")

if "description" not in field_dict:
raise ToolError(f"`description` field not found in `{field_name}` property", stage="Loading")

if field_name.startswith("__"):
continue
Expand Down Expand Up @@ -460,7 +463,7 @@ def from_openapi_dict(
returns_description = list(path_info["responses"].values())[0].get("description", None)

return RemoteToolView(
name=path_info["operationId"],
name=path_info.get("operationId", uri.strip("/").replace("/", "-")),
parameters=parameters,
version=version,
parameters_description=parameters_description,
Expand Down
2 changes: 2 additions & 0 deletions erniebot-agent/src/erniebot_agent/tools/tool_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing import Callable, Dict, Iterable, List, final

from erniebot_agent.tools.base import BaseTool, Tool
from erniebot_agent.tools.utils import custom_openapi


@final
Expand Down Expand Up @@ -134,4 +135,5 @@ def get_openapi_yaml():
"""get openapi json schema from fastapi"""
return app.openapi()

custom_openapi(app)
uvicorn.run(app, host="0.0.0.0", port=port)
41 changes: 41 additions & 0 deletions erniebot-agent/src/erniebot_agent/tools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from copy import deepcopy
from typing import Any, Dict, Optional, Type

from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个不行吧,直接把fastapi搞到主import路径上了,没有fastapi这个依赖直接报错了

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个调整了。

from openapi_spec_validator import validate
from openapi_spec_validator.readers import read_from_filename
from requests import Response
Expand Down Expand Up @@ -241,3 +243,42 @@ async def parse_file_from_response(
"and can not find `Content-Disposition` or `Content-Type` field from response header.",
stage="Output parsing",
)


def custom_openapi(app: FastAPI):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个函数名字不好,看不出是在干什么

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"""get openapi dict of fastapi application

refer to: https://github.com/tiangolo/fastapi/issues/3424#issuecomment-1283484665

Args:
app (FastAPI): the instance of fastapi application
"""
if not app.openapi_schema:
app.openapi_schema = get_openapi(
title=app.title,
version=app.version,
openapi_version=app.openapi_version,
description=app.description,
terms_of_service=app.terms_of_service,
contact=app.contact,
license_info=app.license_info,
routes=app.routes,
tags=app.openapi_tags,
servers=app.servers,
)

# remove validation response
for _, method_item in app.openapi_schema.get("paths", {}).items():
for _, param in method_item.items():
responses = param.get("responses")
# remove 422 response, also can remove other status code
if "422" in responses:
del responses["422"]

# remove Validation Schema
schemas = deepcopy(app.openapi_schema["components"]["schemas"])
for key in list(schemas.keys()):
if "ValidationError" in key:
schemas.pop(key)

app.openapi_schema["components"]["schemas"] = schemas
13 changes: 13 additions & 0 deletions erniebot-agent/src/erniebot_agent/utils/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ def __str__(self):
return f"An error occured in stage <{self.stage}>. The error message is {self.message}"


class ToolError(Exception):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

啥时候应该抛ToolError, 啥时候应该抛RemoteToolError呢

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果是 LocalTool 就抛 ToolError,如果是 RemoteTool 就抛 RemoteToolError 了。

def __init__(self, message: str, stage: Optional[str] = None):
super().__init__(message)
self.message = message
self.stage = stage

def __str__(self):
if not self.stage:
return self.message

return f"An error occured in stage <{self.stage}>. The error message is {self.message}"


class FileError(Exception):
def __init__(self, message: str):
super().__init__(message)
Expand Down