Skip to content

Commit 533b97d

Browse files
committed
openai mock correcting
1 parent 0085cd1 commit 533b97d

File tree

2 files changed

+71
-13
lines changed

2 files changed

+71
-13
lines changed

sgr_agent_core/agents/sgr_tool_calling_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async def _reasoning_phase(self) -> ReasoningTool:
6666
],
6767
}
6868
)
69-
tool_call_result = await reasoning(self._context)
69+
tool_call_result = await reasoning(self._context, self.config)
7070
self.streaming_generator.add_tool_call(
7171
f"{self._context.iteration}-reasoning", reasoning.tool_name, tool_call_result
7272
)

tests/test_agent_e2e.py

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,69 @@
1616

1717

1818
class MockStream:
19-
"""Mock OpenAI stream object."""
19+
"""Mock OpenAI stream object that emulates OpenAI streaming API.
20+
21+
This mock properly handles:
22+
- Context manager protocol (async with)
23+
- Stream iteration (async for event in stream)
24+
- Final completion retrieval with parsed_arguments support
25+
"""
2026

2127
def __init__(self, final_completion_data: dict):
28+
"""Initialize mock stream with final completion data.
29+
30+
Args:
31+
final_completion_data: Dictionary containing:
32+
- content: Optional message content
33+
- tool_calls: List of tool call objects (already with parsed_arguments set)
34+
"""
2235
self._final_completion_data = final_completion_data
36+
self._iterated = False
2337

2438
async def __aenter__(self):
39+
"""Enter context manager."""
2540
return self
2641

2742
async def __aexit__(self, exc_type, exc_val, exc_tb):
43+
"""Exit context manager."""
2844
pass
2945

3046
def __aiter__(self):
47+
"""Return iterator for stream events."""
3148
return self
3249

3350
async def __anext__(self):
51+
"""Return next stream event (empty iterator for simplicity).
52+
53+
In real OpenAI API, this would yield chunk events. For testing,
54+
we return empty iterator since the code handles missing chunks
55+
gracefully.
56+
"""
57+
if self._iterated:
58+
raise StopAsyncIteration
59+
self._iterated = True
3460
raise StopAsyncIteration
3561

3662
async def get_final_completion(self) -> ChatCompletion:
63+
"""Get final completion with parsed tool call arguments or structured
64+
output.
65+
66+
Supports both formats:
67+
- Structured output: message.parsed (for SGRAgent)
68+
- Function calling: tool_calls[0].function.parsed_arguments (for SGRToolCallingAgent)
69+
70+
Returns:
71+
ChatCompletion object with appropriate parsed data
72+
"""
73+
tool_calls = self._final_completion_data.get("tool_calls", [])
74+
3775
message = ChatCompletionMessage(
3876
role="assistant",
3977
content=self._final_completion_data.get("content"),
40-
tool_calls=self._final_completion_data.get("tool_calls"),
78+
tool_calls=tool_calls if tool_calls else None,
4179
)
80+
81+
# Support structured output format (SGRAgent uses message.parsed)
4282
if "parsed" in self._final_completion_data:
4383
setattr(message, "parsed", self._final_completion_data["parsed"])
4484

@@ -155,6 +195,15 @@ def mock_stream(**kwargs):
155195

156196

157197
def create_mock_openai_client_for_sgr_tool_calling_agent(action_tool_1: Type, action_tool_2: Type) -> AsyncOpenAI:
198+
"""Create a mock OpenAI client for SGRToolCallingAgent tests.
199+
200+
Args:
201+
action_tool_1: First action tool to return (e.g., AdaptPlanTool)
202+
action_tool_2: Second action tool to return (e.g., FinalAnswerTool)
203+
204+
Returns:
205+
Mocked AsyncOpenAI client configured for SGRToolCallingAgent execution cycle
206+
"""
158207
client = Mock(spec=AsyncOpenAI)
159208

160209
reasoning_tools = [
@@ -196,32 +245,41 @@ def create_mock_openai_client_for_sgr_tool_calling_agent(action_tool_1: Type, ac
196245
action_count = {"count": 0}
197246

198247
def mock_stream(**kwargs):
199-
is_reasoning = (
200-
"tool_choice" in kwargs
201-
and isinstance(kwargs.get("tool_choice"), dict)
202-
and kwargs["tool_choice"].get("function", {}).get("name") == ReasoningTool.tool_name
203-
)
248+
"""Mock stream function that determines reasoning vs action phase.
249+
250+
Checks the first tool name in the tools list to determine the
251+
phase.
252+
"""
253+
tools_param = kwargs.get("tools", [])
254+
255+
# Check first tool name - if it's ReasoningTool, it's reasoning phase
256+
tool_name = None
257+
if isinstance(tools_param, list) and tools_param:
258+
first_tool = tools_param[0]
259+
if isinstance(first_tool, dict):
260+
tool_name = first_tool.get("function", {}).get("name")
261+
262+
is_reasoning = tool_name == ReasoningTool.tool_name
204263

205264
if is_reasoning:
206265
reasoning_count["count"] += 1
207266
tool = reasoning_tools[reasoning_count["count"] - 1]
208-
call_id = f"reasoning_{reasoning_count['count']}"
267+
call_id = f"{reasoning_count['count']}-reasoning"
209268
else:
210-
tools_param = kwargs.get("tools")
211-
if tools_param is not None and not isinstance(tools_param, list):
269+
# Validate that tools is a list (for action phase)
270+
if not isinstance(tools_param, list):
212271
raise TypeError(
213272
f"SGRToolCallingAgent._prepare_tools() must return a list, "
214273
f"but got {type(tools_param).__name__}. "
215274
f"Override _prepare_tools() to return list instead of NextStepToolStub."
216275
)
217276
action_count["count"] += 1
218277
tool = action_tools[action_count["count"] - 1]
219-
call_id = f"action_{action_count['count']}"
278+
call_id = f"{action_count['count']}-action"
220279

221280
return MockStream(
222281
final_completion_data={
223282
"content": None,
224-
"role": "assistant",
225283
"tool_calls": [_create_tool_call(tool, call_id)],
226284
}
227285
)

0 commit comments

Comments
 (0)