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

function call功能的简化 python版本 #561

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1ce7b88
添加func2tools函数
Oct 23, 2024
a50aa49
暂时完成了tools简化的设计和测试。后续需要整理一下代码
Oct 23, 2024
5789aad
在原有的字段上拓展了一个functions字段,优先级低于tools。
Oct 24, 2024
c294e01
Merge remote-tracking branch 'upstream/master' into dj-feature
Oct 24, 2024
1de9c0f
临时提交一版本
Oct 24, 2024
23d4d71
更新了文档和cookbook对新增的tool call的functions字段的说明
Oct 25, 2024
f009b12
清除一些不必要的信息
Oct 25, 2024
74b54a9
删除了新添加的functions字段,并同步了md文件的更改,后续需要修改cookbook和test
Oct 25, 2024
35019e9
漏掉一个
Oct 25, 2024
684f950
更新了测试脚本
Oct 28, 2024
e46512a
文档修改- bug修复
Oct 28, 2024
6f789dd
修复文档bug
Oct 28, 2024
6e9162c
屏蔽文档的敏感信息
Oct 28, 2024
6444409
单测bug修复
Oct 28, 2024
669cfe1
同步一下修改
Oct 28, 2024
240805d
单测bug修复,变量名称问题
Oct 28, 2024
dbb7bc5
回溯cookbook版本
Oct 28, 2024
d1b35d8
更新代码以满足增量覆盖率要求
Oct 28, 2024
3cf1d05
更新代码以满足增量覆盖率要求
Oct 28, 2024
89fb84a
替换原始的tool call cookbook
Oct 28, 2024
3a36451
添加了很多的测试案例,测试能力边界
Oct 28, 2024
5200c9b
提交一版
Nov 5, 2024
fcdc5c7
新的更新
Nov 8, 2024
dd2453d
源码更新了fc的basemodel、函数类型验证、优先级、参数描述提取
Nov 11, 2024
5c031c6
更新了函数名称为function_to_model,补充了单测
Nov 11, 2024
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,5 @@ go/coverage_diff.html
.gitignore
go/appbuilder/coverage_full.html
go/appbuilder/coverage_diff.html
*.py_res
*.html
1 change: 1 addition & 0 deletions appbuilder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def get_default_header():
from appbuilder.core.user_session import UserSession

from appbuilder.utils.logger_util import logger
from appbuilder.utils.func_utils import function_to_model

from appbuilder.core.utils import get_model_list

Expand Down
58 changes: 52 additions & 6 deletions appbuilder/tests/test_appbuilder_client_toolcall.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def test_appbuilder_client_tool_call(self):
None: 如果app_id不为空,则不会引发任何异常
unittest.SkipTest (optional): 如果app_id为空,则跳过单测执行
"""
builder = appbuilder.AppBuilderClient(self.app_id)
conversation_id = builder.create_conversation()
client = appbuilder.AppBuilderClient(self.app_id)
conversation_id = client.create_conversation()
tools = [
{
"type": "function",
Expand All @@ -54,7 +54,7 @@ def test_appbuilder_client_tool_call(self):
}
]

msg = builder.run(
msg = client.run(
conversation_id=conversation_id,
query="今天北京天气怎么样?",
tools=tools)
Expand All @@ -64,7 +64,7 @@ def test_appbuilder_client_tool_call(self):
assert event.status == "interrupt"
assert event.event_type == "Interrupt"

msg_2 = builder.run(
msg_2 = client.run(
conversation_id=conversation_id,
tool_outputs=[
{
Expand All @@ -75,8 +75,54 @@ def test_appbuilder_client_tool_call(self):
)
print(msg_2.model_dump_json(indent=4))




"""测试functions2tools功能"""
#定义本地函数
def get_current_weather(location: str, unit: str) -> str:
"""
查询指定中国城市的当前天气。

参数:
location (str): 城市名称,例如:"北京"
unit (str): 温度单位,可选 "celsius" 或 "fahrenheit"

返回:
str: 天气情况描述

抛出:
ValueError: 如果传入的城市不支持或单位不正确
"""
return "北京今天25度"
#定义函数列表
functions = [get_current_weather]
function_map = {f.__name__: f for f in functions}
#调用大模型
msg = client.run(
conversation_id=conversation_id,
query="今天北京的天气怎么样?",
tools = [appbuilder.function_to_json(f) for f in functions]
)
print(msg.model_dump_json(indent=4))
# 获取最后的事件和工具调用信息
event = msg.content.events[-1]
tool_call = event.tool_calls[-1]

# 获取函数名称和参数
name = tool_call.function.name
args = tool_call.function.arguments

# 将函数名称映射到具体的函数并执行
raw_result = function_map[name](**args)

# 传递工具的输出
msg_2 = client.run(
conversation_id=conversation_id,
tool_outputs=[{
"tool_call_id": tool_call.id,
"output": str(raw_result)
}],
)
print(msg_2.model_dump_json(indent=4))

if __name__ == '__main__':
unittest.main()
294 changes: 294 additions & 0 deletions appbuilder/tests/test_func2model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
import unittest
import appbuilder
import os
from typing import Any, Dict, List, Optional

#@unittest.skipUnless(os.getenv("TEST_CASE", "UNKNOWN") == "CPU_SERIAL", "")
class TestAgentRuntime(unittest.TestCase):
def setUp(self):
"""
设置环境变量。

Args:
无参数,默认值为空。

Returns:
无返回值,方法中执行了环境变量的赋值操作。
"""
os.environ["APPBUILDER_TOKEN"] = "bce-v3/ALTAK-DKaql4wY9ojwp2uMe8IEj/7ae1190aff0684153de365381d9b06beab3064c5"
self.app_id = "7cc4c21f-0e25-4a76-baf7-01a2b923a1a7"
self.client = appbuilder.AppBuilderClient(self.app_id)
self.conversation_id = self.client.create_conversation()

def test_google_style(self):
# Generated by vscode plugin
# https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring
def google_style(
name: str,
val: str = None,
val_obj: Optional[Any] = None,
val_list: List[str] = None,
data: Dict[str, int] = None,
) -> str:
"""Google style docstring.

Args:
name (str): Name of object.
val (str, optional): Value of obj. Defaults to None.
val_obj (Optional[Any], optional): Real object reference. Defaults to None.
val_list (List[str], optional): List of items with object. Defaults to None.
data (Dict[str, int], optional): Data along with object. Defaults to None.

Returns:
str: Styled string.
"""
return ""
function_model = appbuilder.function_to_model(google_style)

# 断言顶层的结构
assert function_model.type == "function", "Type does not match 'function'"
assert function_model.function["name"] == "google_style", "Function name does not match 'google_style'"
assert function_model.function["description"] == """Google style docstring.

Args:
name (str): Name of object.
val (str, optional): Value of obj. Defaults to None.
val_obj (Optional[Any], optional): Real object reference. Defaults to None.
val_list (List[str], optional): List of items with object. Defaults to None.
data (Dict[str, int], optional): Data along with object. Defaults to None.

Returns:
str: Styled string.
""", "Description does not match"

# 断言参数结构
parameters = function_model.function["parameters"]
assert parameters["type"] == "object", "Parameters type does not match 'object'"
assert "properties" in parameters, "Properties not found in parameters"

# 断言各个参数的类型和描述
properties = parameters["properties"]

# name 参数
assert "name" in properties, "'name' parameter missing"
assert properties["name"]["type"] == "string", "'name' type does not match 'string'"
assert properties["name"]["description"] == "Name of object.", "'name' description does not match"

# val 参数
assert "val" in properties, "'val' parameter missing"
assert properties["val"]["type"] == "string", "'val' type does not match 'string'"
assert properties["val"]["description"] == "Value of obj. Defaults to None.", "'val' description does not match"

# val_obj 参数
assert "val_obj" in properties, "'val_obj' parameter missing"
assert properties["val_obj"]["type"] == "Optional[Any]", "'val_obj' type does not match 'object'"
assert properties["val_obj"]["description"] == "Real object reference. Defaults to None.", "'val_obj' description does not match"

# val_list 参数
assert "val_list" in properties, "'val_list' parameter missing"
assert properties["val_list"]["type"] == "List[str]", "'val_list' type does not match 'array'"
assert properties["val_list"]["description"] == "List of items with object. Defaults to None.", "'val_list' description does not match"

# data 参数
assert "data" in properties, "'data' parameter missing"
assert properties["data"]["type"] == "Dict[str, int]", "'data' type does not match 'object'"
assert properties["data"]["description"] == "Data along with object. Defaults to None.", "'data' description does not match"

# 断言必需参数
assert "required" in parameters, "'required' field missing in parameters"
assert parameters["required"] == ["name"], "'required' does not match ['name']"

def test_google_style_bad_args_return_dict(self):
def func(
bad_param: str,
bad_generic_param: List[str],
bad_format: int,
val: str = None,
) -> Dict[str, str]:
"""Google style docstring.

Args:
bad param (str): Bad parameter, name contains whitespace.
bad_generic_param (List<str>): Bad generic parameter, use <> instead of []
bad_format (int) Bad arg doc format, lost :.
val (str , optional): Value of obj. Defaults to None.

Returns:
Dict[str, str]: Returns a dict.
"""
return ""
function_model = appbuilder.function_to_model(func)
# 断言顶层的结构
assert function_model.type == "function", "Type does not match 'function'"
assert function_model.function["name"] == "func", "Function name does not match 'func'"
assert function_model.function["description"] == """Google style docstring.

Args:
bad param (str): Bad parameter, name contains whitespace.
bad_generic_param (List<str>): Bad generic parameter, use <> instead of []
bad_format (int) Bad arg doc format, lost :.
val (str , optional): Value of obj. Defaults to None.

Returns:
Dict[str, str]: Returns a dict.
""", "Description does not match"

# 断言参数结构
parameters = function_model.function["parameters"]
assert parameters["type"] == "object", "Parameters type does not match 'object'"
assert "properties" in parameters, "Properties not found in parameters"

# 断言各个参数的类型和描述
properties = parameters["properties"]

# bad_param 参数
assert "bad_param" in properties, "'bad_param' parameter missing"
assert properties["bad_param"]["type"] == "string", "'bad_param' type does not match 'string'"
assert properties["bad_param"]["description"] == None, "'bad_param' description should be empty due to incorrect format"

# bad_format 参数
assert "bad_format" in properties, "'bad_format' parameter missing"
assert properties["bad_format"]["type"] == "integer", "'bad_format' type does not match 'integer'"
assert properties["bad_format"]["description"] == None, "'bad_format' description does not match"

# val 参数
assert "val" in properties, "'val' parameter missing"
assert properties["val"]["type"] == "string", "'val' type does not match 'string'"
assert properties["val"]["description"] == "Value of obj. Defaults to None.", "'val' description does not match"

# 断言必需参数
assert "required" in parameters, "'required' field missing in parameters"
assert parameters["required"] == ["bad_param", "bad_generic_param", "bad_format"], "'required' does not match expected required parameters"

# 断言没有多余参数
assert len(properties) == 4, "Unexpected number of parameters in properties"


def test_google_style_no_return(self):
def func(
name: str,
):
"""Google style docstring.

Args:
name (str): Name of object.

"""
return ""

function_model = appbuilder.function_to_model(func)
# 断言顶层的结构
assert function_model.type == "function", "Type does not match 'function'"
assert function_model.function["name"] == "func", "Function name does not match 'func'"
assert function_model.function["description"] == """Google style docstring.

Args:
name (str): Name of object.

""", "Description does not match"

# 断言参数结构
parameters = function_model.function["parameters"]
assert parameters["type"] == "object", "Parameters type does not match 'object'"
assert "properties" in parameters, "Properties not found in parameters"

# 断言参数类型和描述
properties = parameters["properties"]

# name 参数
assert "name" in properties, "'name' parameter missing"
assert properties["name"]["type"] == "string", "'name' type does not match 'string'"
assert properties["name"]["description"] == "Name of object.", "'name' description does not match"

# 断言必需参数
assert "required" in parameters, "'required' field missing in parameters"
assert parameters["required"] == ["name"], "'required' does not match ['name']"

#断言没有["parameters"][1]的参数了
assert not ("parameters" in function_model.function and len(function_model.function["parameters"]) == 1)


def test_google_style_no_args_no_return(self):
def func(
name: str,
/,
*args,
val: str = None,
val_obj: Optional[Any] = None,
data: Dict[str, int] = None,
**kwargs,
) -> str:
"""Google style docstring."""
return ""

#断言这里会抛出参数类型缺失导致的ValueError异常
try:
function_model = appbuilder.function_to_model(func)
except ValueError as e:
print(e)
assert str(e) == "参数 'args' 缺少类型信息,请在函数签名或注释中指定类型。"

def test_no_doc(self):
def func(
name: str,
/,
*args,
val: str = None,
val_obj: Optional[Any] = None,
data: Dict[str, int] = None,
**kwargs,
) -> str:
return ""

# 断言这里会抛出缺少文档字符串的 ValueError 异常
try:
function_model = appbuilder.function_to_model(func)
except ValueError as e:
assert str(e) == "函数 func 缺少文档字符串", "未抛出预期的 ValueError 或信息不匹配"

def test_priority(self):
from typing import Literal
def get_current_weather(location: str, unit: int) -> str:
"""获取指定中国城市的当前天气信息。

仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。

Args:
location (str): 城市名,例如:"北京"。
unit (str): 温度单位,支持 "celsius" 或 "fahrenheit"。

Returns:
str: 天气情况描述
"""
return ""
function_model = appbuilder.function_to_model(get_current_weather)
parameters = function_model.function["parameters"]
properties = parameters["properties"]
# name 参数
assert "unit" in properties, "'unit' parameter missing"
# 描述和函数签名不一致的时候,以函数签名为准
assert properties["unit"]["type"] == 'integer', "'unit' type does not match 'integer'"

def get_current_weather(location: str, unit: str) -> str:
"""获取指定中国城市的当前天气信息。

仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。

Args:
location (str): 城市名,例如:"北京"。
unit (int): 温度单位,支持 "celsius" 或 "fahrenheit"。

Returns:
str: 天气情况描述
"""
return ""
function_model = appbuilder.function_to_model(get_current_weather)
parameters = function_model.function["parameters"]
properties = parameters["properties"]
# name 参数
assert "unit" in properties, "'unit' parameter missing"
# 描述和函数签名不一致的时候,以函数签名为准
assert properties["unit"]["type"] == 'string', "'unit' type does not match 'string'"

if __name__ == '__main__':
unittest.main()
Loading
Loading