qgyd2021 commited on
Commit
83e0115
1 Parent(s): 44e1a5b

[update]add function calling test

Browse files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Openai Assistant
3
  emoji: 🏢
4
  colorFrom: pink
5
  colorTo: blue
 
1
  ---
2
+ title: OpenAI Assistant
3
  emoji: 🏢
4
  colorFrom: pink
5
  colorTo: blue
description.md ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## OpenAI Assistant
2
+
3
+ 基于 [OpenAI platform](https://platform.openai.com/docs/introduction) 开发的 assistant 界面及示例。
4
+
5
+ ### 使用方法
6
+
7
+ 填写 `openai_api_key` 之后,点击任意 `Example`,然后点击 `Add and run` 就可以运行起来。
8
+
9
+ 界面使用主要分为左中右三个部分。
10
+
11
+ 左边,主要包含对系统的增删查功能。其中最重要的是 `create assistant` 标签,你可以在此填写内容并创建新的 assistant。
12
+
13
+ 中间,是 `conversation` 对话窗口。点击 `Add and run` 按钮就可以发送消息。由于一些原因,后端返回的内容可能无法自动更新到 chatbot 对话记录中,因此你可能需要用到 `Refresh` 按钮。
14
+
15
+ 右边,是一些对话的状态和提示信息。每个对话都需要有一个机器人,即 `assistant_id` 。如果你点击 `Add and run` 按钮发送消息时它的值为空,则会根据 `create assistant` 中的内容自动创建一个机器人,并填充 `assistant_id`,这个参数决定了你正在与哪个机器人聊天,因此不要乱改它,因为它很重要。
16
+ `thread_id` 参数,好像是一个会话的概念,因此也不要乱改。
17
+
18
+
19
+ ### 对话记录
20
+
21
+
22
+ #### 披萨订餐机器人
23
+
24
+ ```text
25
+ User: 你好。
26
+ ```
27
+ ```text
28
+ Assistant: 嗨!欢迎光临我们的披萨店。 😊 准备好下单了吗?您想要点哪种披萨?
29
+ ```
30
+ ```text
31
+ User: 可以先看一下菜单吗、
32
+ ```
33
+ ```text
34
+ Assistant: 好的,这是我们的菜单:
35
+
36
+ 披萨:
37
+
38
+ 意大利辣香肠披萨(大 $12.95/中 $10.00/小 $7.00)
39
+ 芝士披萨(大 $10.95/中 $9.25/小 $6.50)
40
+ 茄子披萨(大 $11.95/中 $9.75/小 $6.75)
41
+ 小吃:
42
+
43
+ 薯条(大 $4.50/小 $3.50)
44
+ 希腊沙拉 $7.25
45
+ 额外配料:
46
+
47
+ 奶酪 $2.00
48
+ 蘑菇 $1.50
49
+ 香肠 $3.00
50
+ 加拿大培根 $3.50
51
+ AI辣酱 $1.50
52
+ 辣椒 $1.00
53
+ 饮料:
54
+
55
+ 可乐(大 $3.00/中 $2.00/小 $1.00)
56
+ 雪碧(大 $3.00/中 $2.00/小 $1.00)
57
+ 瓶装水 $5.00
58
+ 请告诉我您想要点哪些项目,如果您想要额外的配料或其他产品,也请随时告诉我。 😄
59
+ ```
60
+ ```text
61
+ User: 来一个芝士披萨,和一杯雪碧大份。
62
+ ```
63
+ ```text
64
+ Assistant: 非常好的选择!一个大份的芝士披萨和一大杯雪碧。
65
+ 那还需要加点别的吗,比如额外的奶酪或者其他配料?还是这样就可以了? 😊
66
+ ```
67
+ ```text
68
+ User: 可以了
69
+ ```
70
+ ```text
71
+ Assistant: 太棒了!接下来,请问是自取还是需要我们外送?
72
+ 如果需要外送的话,麻烦提供一下您的地址。🚚💨
73
+ ```
74
+ ```text
75
+ User: 我堂食。
76
+ ```
77
+ ```text
78
+ Assistant: 好的,堂食!您的订单总金额为 $10.95(大份芝士披萨) + $3.00(大杯雪碧) = $13.95
79
+ 谢谢您的点单,希望您在这里享受到愉快的用餐体验!🍕🥤🎉
80
+ ```
81
+ ```text
82
+ User: 哦哦,不好意思,我的披萨要小份就好了。
83
+ ```
84
+ ```text
85
+ Assistant: 没问题,我已经帮您更改为小份芝士披萨。
86
+ 您的订单现在是小份芝士披萨 $6.50 加上大杯雪碧 $3.00,总计金额为 $9.50。
87
+ 是否还有其他需要调整的地方? 😊
88
+ ```
89
+ ```text
90
+ User: 没有了,很好。
91
+ ```
92
+ ```text
93
+ Assistant: 好的,订单确认为小份芝士披萨和大杯雪碧,总计 $9.50。
94
+ 您的餐点很快就能准备好。祝您在这里用餐愉快!🍕🥤✨
95
+ ```
examples.json CHANGED
@@ -30,5 +30,15 @@
30
  "gpt-4-1106-preview",
31
  "What's the weather san francisco",
32
  "Weather Bot 天气查询,一个基于函数调用的机器人。\n在左边栏的 function script 标签中可以查看有哪些函数可用。\n例如有一个函数叫作 “get_current_weather” 则有一个名为 “get_current_weather.py” 的 python 脚本,其中包含一个名为 “get_current_weather” 的函数。\n当函数被调用时,程序会自动导入该函数并调用。\n因此,你也可以上传自己的函数。"
 
 
 
 
 
 
 
 
 
 
33
  ]
34
  ]
 
30
  "gpt-4-1106-preview",
31
  "What's the weather san francisco",
32
  "Weather Bot 天气查询,一个基于函数调用的机器人。\n在左边栏的 function script 标签中可以查看有哪些函数可用。\n例如有一个函数叫作 “get_current_weather” 则有一个名为 “get_current_weather.py” 的 python 脚本,其中包含一个名为 “get_current_weather” 的函数。\n当函数被调用时,程序会自动导入该函数并调用。\n因此,你也可以上传自己的函数。"
33
+ ],
34
+ [
35
+ "订餐机器人",
36
+ "你是订餐机器人,为披萨餐厅自动收集订单信息。\n你要首先问候顾客。然后等待用户回复收集订单信息。收集完信息需确认顾客是否还需要添加其他内容。\n最后需要询问是否自取或外送,如果是外送,你要询问地址。\n最后告诉顾客订单总金额,并送上祝福。\n\n请确保明确所有选项、附加项和尺寸,以便从菜单中识别出该项唯一的内容。\n你的回应应该以简短、非常随意和友好的风格呈现。",
37
+ null,
38
+ "{\"type\": \"function\", \"function\": {\"name\": \"get_pizza_restaurant_menu\", \"description\": \"获取披萨餐厅的菜单。\", \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []}}}",
39
+ null,
40
+ "gpt-4-1106-preview",
41
+ "你好。",
42
+ "相比于知识问答机器人,客服机器人需要主题向客户提问以引导客户,此示例主要测试客服机器人的配置技巧。"
43
  ]
44
  ]
functions/get_current_weather.py CHANGED
@@ -1,9 +1,12 @@
1
  #!/usr/bin/python3
2
  # -*- coding: utf-8 -*-
3
  import json
 
4
 
5
 
6
- def get_current_weather(location, unit="fahrenheit"):
 
 
7
  if "tokyo" in location.lower():
8
  return json.dumps({"location": location, "temperature": "10", "unit": "celsius"})
9
  elif "san francisco" in location.lower():
@@ -12,7 +15,38 @@ def get_current_weather(location, unit="fahrenheit"):
12
  return json.dumps({"location": location, "temperature": "22", "unit": "celsius"})
13
 
14
 
 
 
 
 
 
 
 
 
15
  def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  result = get_current_weather("beijing")
17
  print(result)
18
  return
 
1
  #!/usr/bin/python3
2
  # -*- coding: utf-8 -*-
3
  import json
4
+ from typing import List
5
 
6
 
7
+ def get_current_weather(location: str, unit: str = None):
8
+ unit = unit or "fahrenheit"
9
+
10
  if "tokyo" in location.lower():
11
  return json.dumps({"location": location, "temperature": "10", "unit": "celsius"})
12
  elif "san francisco" in location.lower():
 
15
  return json.dumps({"location": location, "temperature": "22", "unit": "celsius"})
16
 
17
 
18
+ def kwargs() -> List[str]:
19
+ """
20
+ 函数调用时会根据此处返回的 key 来创建 kwargs 关键字参数,如果存在 key 的值没有提供,则会将其赋值为 None。
21
+ 因此在定义函数时有默认值的参数的值应该为 None,而其真正的默认值可在函数内部赋值,以免被 kwargs 中的 None 覆盖。
22
+ """
23
+ return ["location", "unit"]
24
+
25
+
26
  def main():
27
+ tools = [
28
+ {
29
+ "type": "function",
30
+ "function": {
31
+ "name": "get_current_weather",
32
+ "description": "Get the current weather in a given location",
33
+ "parameters": {
34
+ "type": "object",
35
+ "properties": {
36
+ "location": {
37
+ "type": "string",
38
+ "description": "The city and state, e.g. San Francisco, CA",
39
+ },
40
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
41
+ },
42
+ "required": ["location"],
43
+ },
44
+ },
45
+ }
46
+ ]
47
+ tools_ = json.dumps(tools, ensure_ascii=False)
48
+ print(tools_.replace("\"", "\\\""))
49
+
50
  result = get_current_weather("beijing")
51
  print(result)
52
  return
functions/get_pizza_restaurant_menu.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/python3
2
+ # -*- coding: utf-8 -*-
3
+ import json
4
+ from typing import List
5
+
6
+
7
+ def get_pizza_restaurant_menu():
8
+ result = """
9
+ 菜单包括:
10
+
11
+ 菜品:
12
+ 意式辣香肠披萨(大、中、小) 12.95、10.00、7.00
13
+ 芝士披萨(大、中、小) 10.95、9.25、6.50
14
+ 茄子披萨(大、中、小) 11.95、9.75、6.75
15
+ 薯条(大、小) 4.50、3.50
16
+ 希腊沙拉 7.25
17
+
18
+ 配料:
19
+ 奶酪 2.00
20
+ 蘑菇 1.50
21
+ 香肠 3.00
22
+ 加拿大熏肉 3.50
23
+ AI酱 1.50
24
+ 辣椒 1.00
25
+
26
+ 饮料:
27
+ 可乐(大、中、小) 3.00、2.00、1.00
28
+ 雪碧(大、中、小) 3.00、2.00、1.00
29
+ 瓶装水 5.00
30
+ """
31
+ return result
32
+
33
+
34
+ def kwargs() -> List[str]:
35
+ """
36
+ 函数调用时会根据此处返回的 key 来创建 kwargs 关键字参数,如果存在 key 的值没有提供,则会将其赋值为 None。
37
+ 因此在定义函数时有默认值的参数的值应该为 None,而其真正的默认值可在函数内部赋值,以免被 kwargs 中的 None 覆盖。
38
+ """
39
+ return []
40
+
41
+
42
+ def main():
43
+ tools = [
44
+ {
45
+ "type": "function",
46
+ "function": {
47
+ "name": "get_pizza_restaurant_menu",
48
+ "description": "获取披萨餐厅的菜单。",
49
+ "parameters": {
50
+ "type": "object",
51
+ "properties": {},
52
+ "required": [],
53
+ },
54
+ },
55
+ }
56
+ ]
57
+ tools_ = json.dumps(tools, ensure_ascii=False)
58
+ print(tools_)
59
+ print(tools_.replace("\"", "\\\""))
60
+
61
+ result = get_pizza_restaurant_menu()
62
+ print(result)
63
+ return
64
+
65
+
66
+ if __name__ == '__main__':
67
+ main()
main.py CHANGED
@@ -40,6 +40,11 @@ def get_args():
40
  default="examples.json",
41
  type=str
42
  )
 
 
 
 
 
43
  parser.add_argument(
44
  "--openai_api_key",
45
  default=settings.environment.get("openai_api_key", default=None, dtype=str),
@@ -52,7 +57,6 @@ def get_args():
52
  def dynamic_import_function(package_name: str, function_name: str):
53
  try:
54
  lib = importlib.import_module("functions.{}".format(package_name))
55
- print(lib)
56
  except ModuleNotFoundError as e:
57
  raise e
58
 
@@ -253,6 +257,18 @@ def click_delete_function_python_script(filename: str):
253
  return result
254
 
255
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  def convert_message_list_to_conversation(message_list: List[dict]) -> List[Tuple[str, str]]:
257
  conversation = list()
258
  for message in message_list:
@@ -364,11 +380,10 @@ def add_and_run(openai_api_key: str,
364
  for tool_call in run.required_action.submit_tool_outputs.tool_calls:
365
  function_name = tool_call.function.name
366
  function_to_call = dynamic_import_function(function_name, function_name)
 
367
  function_args = json.loads(tool_call.function.arguments)
368
- function_response = function_to_call(
369
- location=function_args.get("location"),
370
- unit=function_args.get("unit"),
371
- )
372
  tool_outputs.append({
373
  "tool_call_id": tool_call.id,
374
  "output": function_response,
@@ -396,6 +411,7 @@ def add_and_run(openai_api_key: str,
396
  result = [
397
  assistant_id, thread_id,
398
  conversation,
 
399
  ]
400
  yield result
401
 
@@ -403,10 +419,15 @@ def add_and_run(openai_api_key: str,
403
  def main():
404
  args = get_args()
405
 
406
- gr_description = """
407
- OpenAI Assistant
 
 
408
  """
409
 
 
 
 
410
  # example json
411
  with open(args.examples_json_file, "r", encoding="utf-8") as f:
412
  examples = json.load(f)
@@ -419,7 +440,7 @@ def main():
419
 
420
  # ui
421
  with gr.Blocks() as blocks:
422
- gr.Markdown(value=gr_description)
423
 
424
  with gr.Row():
425
  # settings
@@ -446,7 +467,7 @@ def main():
446
  retrieval_file_ids = gr.TextArea(label="retrieval_file_ids")
447
 
448
  # create assistant
449
- create_assistant_button = gr.Button("create assistant")
450
 
451
  with gr.TabItem("assistants"):
452
  list_assistant_button = gr.Button("list assistant")
@@ -474,8 +495,9 @@ def main():
474
  upload_function_python_script_files = gr.Files(label="upload_python_script_files")
475
  upload_function_python_script_button = gr.Button("upload python script")
476
 
477
- delete_function_python_script_file = gr.Textbox(max_lines=1, label="delete_python_script_file")
478
  delete_function_python_script_button = gr.Button("delete python script")
 
479
 
480
  # chat
481
  with gr.Column(scale=5):
@@ -484,7 +506,7 @@ def main():
484
 
485
  with gr.Row():
486
  with gr.Column(scale=1):
487
- add_and_run_button = gr.Button("Add and run")
488
  with gr.Column(scale=1):
489
  refresh_button = gr.Button("Refresh")
490
 
@@ -506,6 +528,8 @@ def main():
506
  examples_per_page=5
507
  )
508
 
 
 
509
  # create assistant
510
  create_assistant_button.click(
511
  click_create_assistant,
@@ -611,13 +635,41 @@ def main():
611
  delete_function_python_script_button.click(
612
  click_delete_function_python_script,
613
  inputs=[
614
- delete_function_python_script_file
615
  ],
616
  outputs=[
617
- delete_function_python_script_file
618
  ]
619
  )
620
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  # add and run
622
  add_and_run_button.click(
623
  add_and_run,
@@ -629,7 +681,8 @@ def main():
629
  ],
630
  outputs=[
631
  assistant_id, thread_id,
632
- chat_bot
 
633
  ],
634
  )
635
 
 
40
  default="examples.json",
41
  type=str
42
  )
43
+ parser.add_argument(
44
+ "--description_md_file",
45
+ default="description.md",
46
+ type=str
47
+ )
48
  parser.add_argument(
49
  "--openai_api_key",
50
  default=settings.environment.get("openai_api_key", default=None, dtype=str),
 
57
  def dynamic_import_function(package_name: str, function_name: str):
58
  try:
59
  lib = importlib.import_module("functions.{}".format(package_name))
 
60
  except ModuleNotFoundError as e:
61
  raise e
62
 
 
257
  return result
258
 
259
 
260
+ def click_download_function_python_script(name: str):
261
+ function_script_dir = project_path / "functions"
262
+ filename = function_script_dir / name.strip()
263
+ if not filename.exists():
264
+ files = None
265
+ flag = "File Not Found: {}".format(name.strip())
266
+ else:
267
+ files = [filename.as_posix()]
268
+ flag = "You can download it on `upload_python_script_files` now."
269
+ return files, flag
270
+
271
+
272
  def convert_message_list_to_conversation(message_list: List[dict]) -> List[Tuple[str, str]]:
273
  conversation = list()
274
  for message in message_list:
 
380
  for tool_call in run.required_action.submit_tool_outputs.tool_calls:
381
  function_name = tool_call.function.name
382
  function_to_call = dynamic_import_function(function_name, function_name)
383
+ kwargs_required: List[str] = dynamic_import_function(function_name, "kwargs")()
384
  function_args = json.loads(tool_call.function.arguments)
385
+ kwargs = {k: function_args.get(k) for k in kwargs_required}
386
+ function_response = function_to_call(**kwargs)
 
 
387
  tool_outputs.append({
388
  "tool_call_id": tool_call.id,
389
  "output": function_response,
 
411
  result = [
412
  assistant_id, thread_id,
413
  conversation,
414
+ None
415
  ]
416
  yield result
417
 
 
419
  def main():
420
  args = get_args()
421
 
422
+ brief_description = """
423
+ ## OpenAI Assistant
424
+
425
+ 基于 [OpenAI platform](https://platform.openai.com/docs/introduction) 开发的 assistant 界面及示例。使用方法等详细介绍在最下面。
426
  """
427
 
428
+ with open(args.description_md_file, "r", encoding="utf-8") as f:
429
+ md_description = f.read()
430
+
431
  # example json
432
  with open(args.examples_json_file, "r", encoding="utf-8") as f:
433
  examples = json.load(f)
 
440
 
441
  # ui
442
  with gr.Blocks() as blocks:
443
+ gr.Markdown(value=brief_description)
444
 
445
  with gr.Row():
446
  # settings
 
467
  retrieval_file_ids = gr.TextArea(label="retrieval_file_ids")
468
 
469
  # create assistant
470
+ create_assistant_button = gr.Button("create assistant", variant="secondary")
471
 
472
  with gr.TabItem("assistants"):
473
  list_assistant_button = gr.Button("list assistant")
 
495
  upload_function_python_script_files = gr.Files(label="upload_python_script_files")
496
  upload_function_python_script_button = gr.Button("upload python script")
497
 
498
+ function_python_script_file = gr.Textbox(max_lines=1, label="python_script_file")
499
  delete_function_python_script_button = gr.Button("delete python script")
500
+ download_function_python_script_button = gr.Button("download python script")
501
 
502
  # chat
503
  with gr.Column(scale=5):
 
506
 
507
  with gr.Row():
508
  with gr.Column(scale=1):
509
+ add_and_run_button = gr.Button("Add and run", variant="primary")
510
  with gr.Column(scale=1):
511
  refresh_button = gr.Button("Refresh")
512
 
 
528
  examples_per_page=5
529
  )
530
 
531
+ gr.Markdown(value=md_description)
532
+
533
  # create assistant
534
  create_assistant_button.click(
535
  click_create_assistant,
 
635
  delete_function_python_script_button.click(
636
  click_delete_function_python_script,
637
  inputs=[
638
+ function_python_script_file
639
  ],
640
  outputs=[
641
+ function_python_script_file
642
  ]
643
  )
644
 
645
+ # download function python script
646
+ download_function_python_script_button.click(
647
+ click_download_function_python_script,
648
+ inputs=[
649
+ function_python_script_file
650
+ ],
651
+ outputs=[
652
+ upload_function_python_script_files, function_python_script_file
653
+ ]
654
+ )
655
+
656
+ # query submit
657
+ query.submit(
658
+ add_and_run,
659
+ inputs=[
660
+ openai_api_key,
661
+ assistant_id, thread_id,
662
+ name, instructions, description, tools, retrieval_files, retrieval_file_ids, model,
663
+ query,
664
+ ],
665
+ outputs=[
666
+ assistant_id, thread_id,
667
+ chat_bot,
668
+ query
669
+ ],
670
+ api_name="query_submit",
671
+ )
672
+
673
  # add and run
674
  add_and_run_button.click(
675
  add_and_run,
 
681
  ],
682
  outputs=[
683
  assistant_id, thread_id,
684
+ chat_bot,
685
+ query
686
  ],
687
  )
688