Skip to content

Commit 4d45bc0

Browse files
Merge pull request #40 from James4Ever0/fix-openrouter
Fix tool call related issues and improve deepseek model support
2 parents 4009de0 + 1a322e5 commit 4d45bc0

File tree

5 files changed

+88
-10
lines changed

5 files changed

+88
-10
lines changed

.vscode/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"request": "launch",
1111
"django": true,
1212
"module": "mcp_bridge.main",
13+
"pythonArgs": ["-Xutf8"]
1314
}
1415
]
15-
}
16+
}

mcp_bridge/openai_clients/chatCompletion.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
FinishReason1,
1111
)
1212

13-
from .utils import call_tool, chat_completion_add_tools
13+
from .utils import call_tool, chat_completion_add_tools, validate_if_json_object_parsable, json_pretty_print
1414
from mcp_bridge.http_clients import get_client
1515
from mcp_bridge.inference_engine_mappers.chat.requester import chat_completion_requester
1616
from mcp_bridge.inference_engine_mappers.chat.responder import chat_completion_responder
@@ -86,11 +86,22 @@ async def chat_completions(
8686
return response
8787

8888
logger.debug("tool calls found")
89+
90+
logger.debug("clearing tool contexts to prevent tool call loops")
91+
request.tools = None
92+
8993
for tool_call in response.choices[0].message.tool_calls.root:
9094
logger.debug(
91-
f"tool call: {tool_call.function.name} arguments: {json.loads(tool_call.function.arguments)}"
95+
f"tool call: {tool_call.function.name}"
9296
)
9397

98+
if validate_if_json_object_parsable(tool):
99+
logger.debug(f"arguments:\n{json_pretty_print(tool_call.function.arguments)}")
100+
else:
101+
logger.debug("non-json arguments given: %s" % tool_call.function.arguments)
102+
logger.debug("unable to parse tool call argument as json. skipping...")
103+
continue
104+
94105
# FIXME: this can probably be done in parallel using asyncio gather
95106
tool_call_result = await call_tool(
96107
tool_call.function.name, tool_call.function.arguments

mcp_bridge/openai_clients/streamChatCompletion.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import datetime
12
import json
3+
import os
24
import time
5+
import traceback
36
from typing import Optional
47
from secrets import token_hex
58
from lmos_openai_types import (
@@ -8,13 +11,20 @@
811
CreateChatCompletionRequest,
912
CreateChatCompletionStreamResponse,
1013
Function1,
14+
FinishReason1,
1115
)
1216

1317
from mcp_bridge.inference_engine_mappers.chat.requester import chat_completion_requester
1418
from mcp_bridge.inference_engine_mappers.chat.stream_responder import (
1519
chat_completion_stream_responder,
1620
)
17-
from .utils import call_tool, chat_completion_add_tools
21+
from .utils import (
22+
call_tool,
23+
chat_completion_add_tools,
24+
json_pretty_print,
25+
salvage_parsable_json_object,
26+
validate_if_json_object_parsable,
27+
)
1828
from mcp_bridge.models import SSEData, upstream_error
1929
from mcp_bridge.http_clients import get_client
2030
from loguru import logger
@@ -68,9 +78,9 @@ async def chat_completions(request: CreateChatCompletionRequest):
6878
# exclude_defaults=True, exclude_none=True, exclude_unset=True
6979
# )
7080

71-
json_data = json.dumps(chat_completion_requester(request))
81+
json_data = json_pretty_print(chat_completion_requester(request))
7282

73-
# logger.debug(json_data)
83+
logger.debug("Request JSON:\n%s" % json_data)
7484

7585
last: Optional[CreateChatCompletionStreamResponse] = None # last message
7686

@@ -211,6 +221,29 @@ async def chat_completions(request: CreateChatCompletionRequest):
211221
# save the last message
212222
last = parsed_data
213223

224+
# perform early stopping on parsable tool_call_json
225+
if tool_call_json:
226+
if tool_call_json.strip().startswith("{"):
227+
if validate_if_json_object_parsable(tool_call_json):
228+
logger.debug(
229+
f"tool call json '{tool_call_json}' is parsable now."
230+
)
231+
logger.debug("exiting message receive loop")
232+
last.choices[0].finish_reason = FinishReason1.tool_calls
233+
break
234+
salvaged_json_object = salvage_parsable_json_object(
235+
tool_call_json
236+
)
237+
if salvaged_json_object:
238+
tool_call_json = salvaged_json_object
239+
logger.debug(
240+
f"tool call json '{tool_call_json}' is salvagable now."
241+
)
242+
logger.debug("salvaged json content:", tool_call_json)
243+
logger.debug("exiting message receive loop")
244+
last.choices[0].finish_reason = FinishReason1.tool_calls
245+
break
246+
214247
# ideally we should check this properly
215248
assert last is not None
216249

@@ -229,6 +262,9 @@ async def chat_completions(request: CreateChatCompletionRequest):
229262
f"{tool_call_name=} {tool_call_json=}"
230263
) # this should not be error but its easier to debug
231264

265+
logger.debug("clearing tool contexts to prevent tool call loops")
266+
request.tools = None
267+
232268
# add received message to the history
233269
msg = ChatCompletionRequestMessage(
234270
role="assistant",

mcp_bridge/openai_clients/streamCompletion.py

Whitespace-only changes.

mcp_bridge/openai_clients/utils.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,53 @@
33
from lmos_openai_types import CreateChatCompletionRequest
44
import mcp.types
55
import json
6+
import traceback
67

78
from mcp_bridge.mcp_clients.McpClientManager import ClientManager
89
from mcp_bridge.tool_mappers import mcp2openai
910

1011

12+
def json_pretty_print(obj) -> str:
13+
if type(obj) == bytes:
14+
obj = obj.decode()
15+
if type(obj) == str:
16+
obj = json.loads(obj)
17+
ret = json.dumps(obj, indent=4, ensure_ascii=False)
18+
return ret
19+
20+
def validate_if_json_object_parsable(content: str):
21+
try:
22+
json.loads(content)
23+
return True
24+
except ValueError:
25+
return False
26+
27+
28+
def salvage_parsable_json_object(content: str):
29+
content = content.strip()
30+
for i in range(0, len(content)):
31+
snippet = content[: len(content) - i]
32+
if validate_if_json_object_parsable(snippet):
33+
return snippet
34+
1135
async def chat_completion_add_tools(request: CreateChatCompletionRequest):
1236
request.tools = []
37+
logger.info("adding tools to request")
1338

1439
for _, session in ClientManager.get_clients():
1540
# if session is None, then the client is not running
1641
if session.session is None:
17-
logger.error(f"session is `None` for {session.name}")
42+
logger.error(f"session is `None` for {session.name}") # Date:2025/01/25 why not running?
1843
continue
19-
44+
logger.debug(f"session ready for {session.name}")
2045
tools = await session.session.list_tools()
2146
for tool in tools.tools:
2247
request.tools.append(mcp2openai(tool))
23-
48+
49+
if request.tools == []:
50+
logger.info("this request loads no tools")
51+
# raise Exception("no tools found. unable to initiate chat completion.")
52+
request.tools = None
2453
return request
2554

2655

@@ -42,9 +71,10 @@ async def call_tool(
4271
return None
4372

4473
try:
45-
tool_call_args = json.loads(tool_call_json)
74+
tool_call_args = json.loads(tool_call_json) # Date: 2025/01/26 cannot load this tool call json?
4675
except json.JSONDecodeError:
4776
logger.error(f"failed to decode json for {tool_call_name}")
77+
traceback.print_exc()
4878
return None
4979

5080
return await session.call_tool(tool_call_name, tool_call_args, timeout)

0 commit comments

Comments
 (0)