Skip to content

Commit 7a45ba6

Browse files
committed
fix(oci): complete casing audit for OCI field transformations
Fix remaining casing issues found during systematic audit: - V1 tools: uppercase type field (same fix as V2) - tool_calls in messages: uppercase type when sending tool results back in multi-turn conversations - Response tool_calls: lowercase type from OCI's "FUNCTION" back to "function" for Cohere SDK compatibility - safety_mode: uppercase defensively (CONTEXTUAL/STRICT/OFF) Integration tests added for each: - test_chat_tool_use_response_type_lowered: verifies tool_call.type is "function" (not "FUNCTION") in responses - test_chat_multi_turn_tool_use_v2: full tool use round-trip (call → result → final response) - test_chat_safety_mode_v2: verifies safety_mode works on OCI
1 parent ec9e58b commit 7a45ba6

2 files changed

Lines changed: 121 additions & 5 deletions

File tree

src/cohere/oci_client.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,13 @@ def transform_request_to_oci(
728728
oci_msg["content"] = msg.get("content") or []
729729

730730
if "tool_calls" in msg:
731-
oci_msg["toolCalls"] = msg["tool_calls"]
731+
oci_tool_calls = []
732+
for tc in msg["tool_calls"]:
733+
oci_tc = {**tc}
734+
if "type" in oci_tc:
735+
oci_tc["type"] = oci_tc["type"].upper()
736+
oci_tool_calls.append(oci_tc)
737+
oci_msg["toolCalls"] = oci_tool_calls
732738
if "tool_call_id" in msg:
733739
oci_msg["toolCallId"] = msg["tool_call_id"]
734740
if "tool_plan" in msg:
@@ -772,7 +778,7 @@ def transform_request_to_oci(
772778
if "response_format" in cohere_body:
773779
chat_request["responseFormat"] = cohere_body["response_format"]
774780
if "safety_mode" in cohere_body:
775-
chat_request["safetyMode"] = cohere_body["safety_mode"]
781+
chat_request["safetyMode"] = cohere_body["safety_mode"].upper()
776782
if "logprobs" in cohere_body:
777783
chat_request["logprobs"] = cohere_body["logprobs"]
778784
if "tool_choice" in cohere_body:
@@ -816,13 +822,19 @@ def transform_request_to_oci(
816822
if "documents" in cohere_body:
817823
chat_request["documents"] = cohere_body["documents"]
818824
if "tools" in cohere_body:
819-
chat_request["tools"] = cohere_body["tools"]
825+
oci_tools = []
826+
for tool in cohere_body["tools"]:
827+
oci_tool = {**tool}
828+
if "type" in oci_tool:
829+
oci_tool["type"] = oci_tool["type"].upper()
830+
oci_tools.append(oci_tool)
831+
chat_request["tools"] = oci_tools
820832
if "tool_results" in cohere_body:
821833
chat_request["toolResults"] = cohere_body["tool_results"]
822834
if "response_format" in cohere_body:
823835
chat_request["responseFormat"] = cohere_body["response_format"]
824836
if "safety_mode" in cohere_body:
825-
chat_request["safetyMode"] = cohere_body["safety_mode"]
837+
chat_request["safetyMode"] = cohere_body["safety_mode"].upper()
826838
if "priority" in cohere_body:
827839
chat_request["priority"] = cohere_body["priority"]
828840

@@ -917,7 +929,12 @@ def transform_oci_response_to_cohere(
917929
message = {**message, "content": transformed_content}
918930

919931
if "toolCalls" in message:
920-
tool_calls = message["toolCalls"]
932+
tool_calls = []
933+
for tc in message["toolCalls"]:
934+
lowered_tc = {**tc}
935+
if "type" in lowered_tc:
936+
lowered_tc["type"] = lowered_tc["type"].lower()
937+
tool_calls.append(lowered_tc)
921938
message = {k: v for k, v in message.items() if k != "toolCalls"}
922939
message["tool_calls"] = tool_calls
923940
if "toolPlan" in message:

tests/test_oci_client.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,105 @@ def test_chat_tool_use_v2(self):
215215
self.assertEqual(tool_call.function.name, "get_weather")
216216
self.assertIn("Toronto", tool_call.function.arguments)
217217

218+
def test_chat_tool_use_response_type_lowered(self):
219+
"""Test that tool_call type is lowercased in response (OCI returns FUNCTION)."""
220+
response = self.client.chat(
221+
model="command-a-03-2025",
222+
messages=[{"role": "user", "content": "What's the weather in Toronto?"}],
223+
max_tokens=200,
224+
tools=[{
225+
"type": "function",
226+
"function": {
227+
"name": "get_weather",
228+
"description": "Get current weather for a location",
229+
"parameters": {
230+
"type": "object",
231+
"properties": {
232+
"location": {"type": "string", "description": "City name"}
233+
},
234+
"required": ["location"],
235+
},
236+
},
237+
}],
238+
)
239+
240+
self.assertEqual(response.finish_reason, "TOOL_CALL")
241+
tool_call = response.message.tool_calls[0]
242+
# OCI returns "FUNCTION" — SDK must lowercase to "function" for Cohere compat
243+
self.assertEqual(tool_call.type, "function")
244+
245+
def test_chat_multi_turn_tool_use_v2(self):
246+
"""Test multi-turn tool use: send tool result back after tool call."""
247+
# Step 1: Get a tool call
248+
response = self.client.chat(
249+
model="command-a-03-2025",
250+
messages=[{"role": "user", "content": "What's the weather in Toronto?"}],
251+
max_tokens=200,
252+
tools=[{
253+
"type": "function",
254+
"function": {
255+
"name": "get_weather",
256+
"description": "Get current weather for a location",
257+
"parameters": {
258+
"type": "object",
259+
"properties": {
260+
"location": {"type": "string", "description": "City name"}
261+
},
262+
"required": ["location"],
263+
},
264+
},
265+
}],
266+
)
267+
self.assertEqual(response.finish_reason, "TOOL_CALL")
268+
tool_call = response.message.tool_calls[0]
269+
270+
# Step 2: Send tool result back
271+
response2 = self.client.chat(
272+
model="command-a-03-2025",
273+
messages=[
274+
{"role": "user", "content": "What's the weather in Toronto?"},
275+
{
276+
"role": "assistant",
277+
"tool_calls": [{"id": tool_call.id, "type": "function", "function": {"name": "get_weather", "arguments": tool_call.function.arguments}}],
278+
"tool_plan": response.message.tool_plan,
279+
},
280+
{
281+
"role": "tool",
282+
"tool_call_id": tool_call.id,
283+
"content": [{"type": "text", "text": "15°C, sunny"}],
284+
},
285+
],
286+
max_tokens=200,
287+
tools=[{
288+
"type": "function",
289+
"function": {
290+
"name": "get_weather",
291+
"description": "Get current weather for a location",
292+
"parameters": {
293+
"type": "object",
294+
"properties": {
295+
"location": {"type": "string", "description": "City name"}
296+
},
297+
"required": ["location"],
298+
},
299+
},
300+
}],
301+
)
302+
303+
self.assertIsNotNone(response2.message)
304+
# Model should respond with text incorporating the tool result
305+
self.assertTrue(len(response2.message.content) > 0)
306+
307+
def test_chat_safety_mode_v2(self):
308+
"""Test that safety_mode is uppercased for OCI."""
309+
# Cohere SDK enum values are already uppercase, but test lowercase too
310+
response = self.client.chat(
311+
model="command-a-03-2025",
312+
messages=[{"role": "user", "content": "Say hi"}],
313+
safety_mode="STRICT",
314+
)
315+
self.assertIsNotNone(response.message)
316+
218317
def test_chat_stream_v2(self):
219318
"""Test streaming chat with v2 client."""
220319
events = []

0 commit comments

Comments
 (0)