yym68686 commited on
Commit
e09244d
·
1 Parent(s): c405f98

Add Claude tool support.

Browse files
json_str/claude/tool_use.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ data: {"type":"message_start","message":{"id":"msg_01Jp7JVrr2MFfTzUBL9hrgoH","type":"message","role":"assistant","model":"claude-3-5-sonnet-20240620","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":558,"output_tokens":1}} }
2
+ data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} }
3
+ data: {"type": "ping"}
4
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"I"} }
5
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" apolog"} }
6
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"ize, but I"} }
7
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'ll"} }
8
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" need to"} }
9
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" respon"} }
10
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d in"} }
11
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" English as that"} }
12
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'s"} }
13
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the language I've"} }
14
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" been instruct"} }
15
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"ed to use."} }
16
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Let"} }
17
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" me"} }
18
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" r"} }
19
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"ephrase your"} }
20
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" request"} }
21
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" an"} }
22
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d procee"} }
23
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d with searching"} }
24
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for today"} }
25
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'s news."} }
26
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\nTo"} }
27
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" search for today"} }
28
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'s news, I"} }
29
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'ll"} }
30
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" use the Google search"} }
31
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" function"} }
32
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":". Here"} }
33
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'s"} }
34
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" how"} }
35
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" I'll do that"} }
36
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":":"} }
37
+ data: {"type":"content_block_stop","index":0 }
38
+ data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01M17un8HfqkS3uDKBPuBr35","name":"get_search_results","input":{}} }
39
+ data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""} }
40
+ data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"promp"} }
41
+ data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"t\""} }
42
+ data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":": \"toda"} }
43
+ data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"y's "} }
44
+ data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"top news\"}"} }
45
+ data: {"type":"content_block_stop","index":1 }
46
+ data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":124} }
47
+ data: {"type":"message_stop" }
json_str/claude/tools.json ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "claude-3-5-sonnet-20240620",
3
+ "messages": [
4
+ {
5
+ "role": "user",
6
+ "content": [
7
+ {
8
+ "type": "text",
9
+ "text": "搜索今天的新闻"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "role": "assistant",
15
+ "content": [
16
+ {
17
+ "type": "tool_use",
18
+ "id": "toolu_01RofFmKHUKsEaZvqESG5Hwz",
19
+ "name": "get_search_results",
20
+ "input": {
21
+ "prompt": "latest news today"
22
+ }
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "role": "user",
28
+ "content": [
29
+ {
30
+ "type": "tool_result",
31
+ "tool_use_id": "toolu_01RofFmKHUKsEaZvqESG5Hwz",
32
+ "content": "latest news today"
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "temperature": 0.5,
38
+ "top_p": 0.7,
39
+ "max_tokens": 4096,
40
+ "stream": true,
41
+ "system": "You are Claude, a large language model trained by Anthropic. Use simple characters to represent mathematical symbols. Do not use LaTeX commands. Respond conversationally in English.",
42
+ "tools": [
43
+ {
44
+ "name": "get_search_results",
45
+ "description": "Search Google to enhance knowledge.",
46
+ "input_schema": {
47
+ "type": "object",
48
+ "properties": {
49
+ "prompt": {
50
+ "type": "string",
51
+ "description": "The prompt to search."
52
+ }
53
+ },
54
+ "required": [
55
+ "prompt"
56
+ ]
57
+ }
58
+ },
59
+ {
60
+ "name": "get_url_content",
61
+ "description": "Get the webpage content of a URL",
62
+ "input_schema": {
63
+ "type": "object",
64
+ "properties": {
65
+ "url": {
66
+ "type": "string",
67
+ "description": "the URL to request"
68
+ }
69
+ },
70
+ "required": [
71
+ "url"
72
+ ]
73
+ }
74
+ },
75
+ {
76
+ "name": "download_read_arxiv_pdf",
77
+ "description": "Get the content of the paper corresponding to the arXiv ID",
78
+ "input_schema": {
79
+ "type": "object",
80
+ "properties": {
81
+ "prompt": {
82
+ "type": "string",
83
+ "description": "the arXiv ID of the paper"
84
+ }
85
+ },
86
+ "required": [
87
+ "prompt"
88
+ ]
89
+ }
90
+ }
91
+ ],
92
+ "tool_choice": {
93
+ "type": "auto"
94
+ }
95
+ }
json_str/gpt/tool_use.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_hbFDbIHYbimw1J0v9d1qvpgl","type":"function","function":{"name":"get_search_results","arguments":""}}]},"logprobs":null,"finish_reason":null}]}
2
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
3
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"prompt"}}]},"logprobs":null,"finish_reason":null}]}
4
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
5
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"today"}}]},"logprobs":null,"finish_reason":null}]}
6
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"'s"}}]},"logprobs":null,"finish_reason":null}]}
7
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" news"}}]},"logprobs":null,"finish_reason":null}]}
8
+ data: {"id":"chatcmpl-9inWv0yEtgn873CxMBzHeCeiHctTV","object":"chat.completion.chunk","created":1720463853,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_abc28019ad","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
json_str/gpt/tools.json ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "gpt-4o",
3
+ "messages": [
4
+ {
5
+ "role": "system",
6
+ "content": "You are ChatGPT, a large language model trained by OpenAI. Respond conversationally in English. Use simple characters to represent mathematical symbols. Do not use LaTeX commands. Knowledge cutoff: 2023-12. Current date: [ 2024-07-09 ]"
7
+ },
8
+ {
9
+ "role": "user",
10
+ "content": [
11
+ {
12
+ "type": "text",
13
+ "text": "搜索今天的新闻"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "role": "function",
19
+ "name": "get_search_results",
20
+ "content": "latest news today"
21
+ }
22
+ ],
23
+ "max_tokens": 4096,
24
+ "stream": true,
25
+ "temperature": 0.5,
26
+ "top_p": 1.0,
27
+ "presence_penalty": 0.0,
28
+ "frequency_penalty": 0.0,
29
+ "n": 1,
30
+ "user": "function",
31
+ "tools": [
32
+ {
33
+ "type": "function",
34
+ "function": {
35
+ "name": "get_search_results",
36
+ "description": "Search Google to enhance knowledge.",
37
+ "parameters": {
38
+ "type": "object",
39
+ "properties": {
40
+ "prompt": {
41
+ "type": "string",
42
+ "description": "The prompt to search."
43
+ }
44
+ },
45
+ "required": [
46
+ "prompt"
47
+ ]
48
+ }
49
+ }
50
+ },
51
+ {
52
+ "type": "function",
53
+ "function": {
54
+ "name": "get_url_content",
55
+ "description": "Get the webpage content of a URL",
56
+ "parameters": {
57
+ "type": "object",
58
+ "properties": {
59
+ "url": {
60
+ "type": "string",
61
+ "description": "the URL to request"
62
+ }
63
+ },
64
+ "required": [
65
+ "url"
66
+ ]
67
+ }
68
+ }
69
+ },
70
+ {
71
+ "type": "function",
72
+ "function": {
73
+ "name": "download_read_arxiv_pdf",
74
+ "description": "Get the content of the paper corresponding to the arXiv ID",
75
+ "parameters": {
76
+ "type": "object",
77
+ "properties": {
78
+ "prompt": {
79
+ "type": "string",
80
+ "description": "the arXiv ID of the paper"
81
+ }
82
+ },
83
+ "required": [
84
+ "prompt"
85
+ ]
86
+ }
87
+ }
88
+ }
89
+ ],
90
+ "tool_choice": "auto"
91
+ }
main.py CHANGED
@@ -69,7 +69,7 @@ async def process_request(request: RequestModel, provider: Dict):
69
  "headers": headers,
70
  "payload": payload
71
  }
72
- print(f"Request details: {json.dumps(request_info, indent=2, ensure_ascii=False)}")
73
 
74
  if request.stream:
75
  return StreamingResponse(fetch_response_stream(app.state.client, url, headers, payload, engine, request.model), media_type="text/event-stream")
@@ -86,7 +86,7 @@ class ModelRequestHandler:
86
  async def request_model(self, request: RequestModel, token: str):
87
  model_name = request.model
88
  matching_providers = self.get_matching_providers(model_name)
89
- # print("matching_providers", json.dumps(matching_providers, indent=2, ensure_ascii=False))
90
 
91
  if not matching_providers:
92
  raise HTTPException(status_code=404, detail="No matching model found")
 
69
  "headers": headers,
70
  "payload": payload
71
  }
72
+ print(f"Request details: {json.dumps(request_info, indent=4, ensure_ascii=False)}")
73
 
74
  if request.stream:
75
  return StreamingResponse(fetch_response_stream(app.state.client, url, headers, payload, engine, request.model), media_type="text/event-stream")
 
86
  async def request_model(self, request: RequestModel, token: str):
87
  model_name = request.model
88
  matching_providers = self.get_matching_providers(model_name)
89
+ # print("matching_providers", json.dumps(matching_providers, indent=4, ensure_ascii=False))
90
 
91
  if not matching_providers:
92
  raise HTTPException(status_code=404, detail="No matching model found")
request.py CHANGED
@@ -191,7 +191,32 @@ async def get_claude_payload(request, engine, provider):
191
  content = msg.content
192
  name = msg.name
193
  if name:
194
- messages.append({"role": msg.role, "name": name, "content": content})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  elif msg.role != "system":
196
  messages.append({"role": msg.role, "content": content})
197
  elif msg.role == "system":
@@ -228,19 +253,18 @@ async def get_claude_payload(request, engine, provider):
228
  if field not in miss_fields and value is not None:
229
  payload[field] = value
230
 
231
- tools = []
232
- for tool in request.tools:
233
- print("tool", type(tool), tool)
 
234
 
235
- json_tool = await gpt2claude_tools_json(tool.dict()["function"])
236
- tools.append(json_tool)
237
- payload["tools"] = tools
238
- # del payload["type"]
239
- # del payload["function"]
240
- if "tool_choice" in payload:
241
- payload["tool_choice"] = {
242
- "type": "auto"
243
- }
244
  import json
245
  print("payload", json.dumps(payload, indent=2, ensure_ascii=False))
246
 
 
191
  content = msg.content
192
  name = msg.name
193
  if name:
194
+ # messages.append({"role": "assistant", "name": name, "content": content})
195
+ messages.append(
196
+ {
197
+ "role": "assistant",
198
+ "content": [
199
+ {
200
+ "type": "tool_use",
201
+ "id": "toolu_01RofFmKHUKsEaZvqESG5Hwz",
202
+ "name": name,
203
+ "input": {"text": messages[-1]["content"][0]["text"]},
204
+ }
205
+ ]
206
+ }
207
+ )
208
+ messages.append(
209
+ {
210
+ "role": "user",
211
+ "content": [
212
+ {
213
+ "type": "tool_result",
214
+ "tool_use_id": "toolu_01RofFmKHUKsEaZvqESG5Hwz",
215
+ "content": content
216
+ }
217
+ ]
218
+ }
219
+ )
220
  elif msg.role != "system":
221
  messages.append({"role": msg.role, "content": content})
222
  elif msg.role == "system":
 
253
  if field not in miss_fields and value is not None:
254
  payload[field] = value
255
 
256
+ if request.tools:
257
+ tools = []
258
+ for tool in request.tools:
259
+ print("tool", type(tool), tool)
260
 
261
+ json_tool = await gpt2claude_tools_json(tool.dict()["function"])
262
+ tools.append(json_tool)
263
+ payload["tools"] = tools
264
+ if "tool_choice" in payload:
265
+ payload["tool_choice"] = {
266
+ "type": "auto"
267
+ }
 
 
268
  import json
269
  print("payload", json.dumps(payload, indent=2, ensure_ascii=False))
270
 
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ fastapi
response.py CHANGED
@@ -2,7 +2,7 @@ from datetime import datetime
2
  import json
3
  import httpx
4
 
5
- async def generate_sse_response(timestamp, model, content):
6
  sample_data = {
7
  "id": "chatcmpl-9ijPeRHa0wtyA2G8wq5z8FC3wGMzc",
8
  "object": "chat.completion.chunk",
@@ -19,6 +19,11 @@ async def generate_sse_response(timestamp, model, content):
19
  ],
20
  "usage": None
21
  }
 
 
 
 
 
22
  json_data = json.dumps(sample_data, ensure_ascii=False)
23
 
24
  # 构建SSE响应
@@ -81,7 +86,7 @@ async def fetch_claude_response_stream(client, url, headers, payload, model):
81
  for chunk in chunk_line:
82
  if chunk.startswith("data:"):
83
  line = chunk[6:]
84
- # print(line)
85
  resp: dict = json.loads(line)
86
  message = resp.get("message")
87
  if message:
@@ -89,24 +94,29 @@ async def fetch_claude_response_stream(client, url, headers, payload, model):
89
  if tokens_use:
90
  total_tokens = tokens_use["input_tokens"] + tokens_use["output_tokens"]
91
  # print("\n\rtotal_tokens", total_tokens)
92
- # tool_use = resp.get("content_block")
93
- # if tool_use and "tool_use" == tool_use['type']:
94
- # # print("tool_use", tool_use)
95
- # tools_id = tool_use["id"]
96
- # need_function_call = True
97
- # if "name" in tool_use:
98
- # function_call_name = tool_use["name"]
 
 
 
99
  delta = resp.get("delta")
100
  # print("delta", delta)
101
  if not delta:
102
  continue
103
  if "text" in delta:
104
  content = delta["text"]
105
- sse_string = await generate_sse_response(timestamp, model, content)
106
- print(sse_string)
 
 
 
 
107
  yield sse_string
108
- # if "partial_json" in delta:
109
- # function_call_content = delta["partial_json"]
110
  yield "data: [DONE]\n\n"
111
  except httpx.ConnectError as e:
112
  print(f"连接错误: {e}")
 
2
  import json
3
  import httpx
4
 
5
+ async def generate_sse_response(timestamp, model, content=None, tools_id=None, function_call_name=None, function_call_content=None):
6
  sample_data = {
7
  "id": "chatcmpl-9ijPeRHa0wtyA2G8wq5z8FC3wGMzc",
8
  "object": "chat.completion.chunk",
 
19
  ],
20
  "usage": None
21
  }
22
+ if function_call_content:
23
+ sample_data["choices"][0]["delta"] = {"tool_calls":[{"index":0,"function":{"arguments": function_call_content}}]}
24
+ if tools_id and function_call_name:
25
+ sample_data["choices"][0]["delta"] = {"tool_calls":[{"index":0,"id":tools_id,"type":"function","function":{"name":function_call_name,"arguments":""}}]}
26
+ # sample_data["choices"][0]["delta"] = {"tool_calls":[{"index":0,"function":{"id": tools_id, "name": function_call_name}}]}
27
  json_data = json.dumps(sample_data, ensure_ascii=False)
28
 
29
  # 构建SSE响应
 
86
  for chunk in chunk_line:
87
  if chunk.startswith("data:"):
88
  line = chunk[6:]
89
+ print(line)
90
  resp: dict = json.loads(line)
91
  message = resp.get("message")
92
  if message:
 
94
  if tokens_use:
95
  total_tokens = tokens_use["input_tokens"] + tokens_use["output_tokens"]
96
  # print("\n\rtotal_tokens", total_tokens)
97
+ tool_use = resp.get("content_block")
98
+ tools_id = None
99
+ function_call_name = None
100
+ if tool_use and "tool_use" == tool_use['type']:
101
+ # print("tool_use", tool_use)
102
+ tools_id = tool_use["id"]
103
+ if "name" in tool_use:
104
+ function_call_name = tool_use["name"]
105
+ sse_string = await generate_sse_response(timestamp, model, None, tools_id, function_call_name, None)
106
+ yield sse_string
107
  delta = resp.get("delta")
108
  # print("delta", delta)
109
  if not delta:
110
  continue
111
  if "text" in delta:
112
  content = delta["text"]
113
+ sse_string = await generate_sse_response(timestamp, model, content, None, None)
114
+ yield sse_string
115
+ if "partial_json" in delta:
116
+ # {"type":"input_json_delta","partial_json":""}
117
+ function_call_content = delta["partial_json"]
118
+ sse_string = await generate_sse_response(timestamp, model, None, None, None, function_call_content)
119
  yield sse_string
 
 
120
  yield "data: [DONE]\n\n"
121
  except httpx.ConnectError as e:
122
  print(f"连接错误: {e}")