Private
Public Access
feat(openai): add src/openai_schemas.py + refactor openai_compatible.py (t2_1-t2_7)
Phase 2 of any_type_componentization_20260621. Promotes NormalizedResponse
+ OpenAICompatibleRequest from src/openai_compatible.py to typed
dataclasses. The 17 Any sites become 5 dataclasses:
NEW src/openai_schemas.py (138 lines):
- ToolCallFunction dataclass (name, arguments)
- ToolCall dataclass (id, function: ToolCallFunction, type='function')
- ChatMessage dataclass (role, content, tool_calls, tool_call_id, name)
- UsageStats dataclass (input_tokens, output_tokens, cache_read_*, cache_creation_*)
- NormalizedResponse dataclass (text, tool_calls: tuple, usage, raw_response: Any)
- OpenAICompatibleRequest dataclass (messages: list[ChatMessage], model, ...)
NEW tests/test_openai_schemas.py (19 tests, all pass):
- ToolCallFunction, ToolCall, ChatMessage round-trips
- UsageStats field access + frozen=True semantics
- NormalizedResponse.to_legacy_dict preserves shape
- raw_response stays Any (Pattern 3 preserved)
- tools field stays list[dict[str, Any]] for Phase 1 ToolSpec follow-up
MODIFIED src/openai_compatible.py:
- Removed inline NormalizedResponse + OpenAICompatibleRequest definitions
- Re-imported from src.openai_schemas
- _send_blocking: tool_calls -> tuple[ToolCall, ...]; usage_*_tokens -> UsageStats
- _send_streaming: same migration
- send_openai_compatible: messages_dicts = [m.to_dict() for m in request.messages]
- Exception handler: empty NormalizedResponse uses UsageStats
- All NormalizedResponse consumers still work (legacy dict shape preserved)
Verified:
uv run pytest tests/test_openai_schemas.py tests/test_mcp_tool_specs.py tests/test_audit_dataclass_coverage.py tests/test_type_aliases.py tests/test_mcp_client_beads.py tests/test_mcp_client_paths.py tests/test_arch_boundary_phase2.py --timeout=60
64 passed in 6.28s
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py') as f:
|
||||
lines = f.readlines()
|
||||
# Find duplicate 'return NormalizedResponse('
|
||||
seen = False
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
if line.rstrip() == ' return NormalizedResponse(':
|
||||
if seen:
|
||||
continue
|
||||
seen = True
|
||||
new_lines.append(line)
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py', 'w', encoding='utf-8', newline='') as f:
|
||||
f.writelines(new_lines)
|
||||
print(f'Removed duplicates; {len(new_lines)} lines')
|
||||
@@ -0,0 +1,19 @@
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py') as f:
|
||||
lines = f.readlines()
|
||||
# Find and deduplicate
|
||||
# The structure should end at ' )' once, not twice
|
||||
# Find all return NormalizedResponse blocks
|
||||
import re
|
||||
# Remove lines that come after the first ' return NormalizedResponse(' and its matching ')'
|
||||
result = []
|
||||
in_normalized = False
|
||||
for line in lines:
|
||||
if line.rstrip() == ' return NormalizedResponse(':
|
||||
if in_normalized:
|
||||
# Skip duplicate
|
||||
continue
|
||||
in_normalized = True
|
||||
result.append(line)
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py', 'w', encoding='utf-8', newline='') as f:
|
||||
f.writelines(result)
|
||||
print(f'Deduped; {len(result)} lines')
|
||||
@@ -0,0 +1,46 @@
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py') as f:
|
||||
lines = f.readlines()
|
||||
# Replace lines 139 to end of NormalizedResponse(...) call
|
||||
# Original block (lines 139-160) - need to fix indentation:
|
||||
# chunk_usage at 2sp (for chunk body, after for choice ends)
|
||||
# if chunk_usage at 3sp (wait, that's wrong - it should be at 2sp sibling of chunk_usage)
|
||||
# usage_input/output at 3sp (inside if)
|
||||
# return NormalizedResponse at 1sp
|
||||
# Args at 2sp
|
||||
|
||||
new_block = [
|
||||
' chunk_usage = getattr(chunk, "usage", None)\n',
|
||||
' if chunk_usage is not None:\n',
|
||||
' usage_input = int(getattr(chunk_usage, "prompt_tokens", 0) or 0)\n',
|
||||
' usage_output = int(getattr(chunk_usage, "completion_tokens", 0) or 0)\n',
|
||||
' tool_calls_typed: tuple[ToolCall, ...] = tuple(\n',
|
||||
' ToolCall(\n',
|
||||
' id=acc["id"] or "",\n',
|
||||
' type=acc["type"],\n',
|
||||
' function=ToolCallFunction(\n',
|
||||
' name=acc["function"]["name"] or "",\n',
|
||||
' arguments=acc["function"]["arguments"] or "{}",\n',
|
||||
' ),\n',
|
||||
' )\n',
|
||||
' for acc in (tool_calls_acc[k] for k in sorted(tool_calls_acc.keys()))\n',
|
||||
' )\n',
|
||||
' return NormalizedResponse(\n',
|
||||
' text="".join(text_parts),\n',
|
||||
' tool_calls=tool_calls_typed,\n',
|
||||
' usage=UsageStats(input_tokens=usage_input, output_tokens=usage_output),\n',
|
||||
' raw_response=None,\n',
|
||||
' )\n',
|
||||
]
|
||||
# Find ' return NormalizedResponse(' end - line with ' )'
|
||||
end_idx = None
|
||||
for i in range(138, len(lines)):
|
||||
if lines[i].rstrip() == ' )':
|
||||
end_idx = i
|
||||
break
|
||||
if end_idx is None:
|
||||
print('Could not find end')
|
||||
else:
|
||||
new_lines = lines[:138] + new_block + lines[end_idx+1:]
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py', 'w', encoding='utf-8', newline='') as f:
|
||||
f.writelines(new_lines)
|
||||
print(f'Replaced lines 139-{end_idx+1}; new file has {len(new_lines)} lines')
|
||||
@@ -0,0 +1,43 @@
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py') as f:
|
||||
lines = f.readlines()
|
||||
# Fix the indentation of the chunk_usage block (lines 139-152)
|
||||
# L139 chunk_usage: 1 space (inside for chunk)
|
||||
# L140 if chunk_usage: 2 spaces
|
||||
# L141-142 usage_* body: 3 spaces (inside if)
|
||||
# L143+ tool_calls_typed: 1 space (sibling of for choice, inside for chunk)
|
||||
|
||||
# Replace lines 139-152 with corrected indentation
|
||||
new_block = [
|
||||
' chunk_usage = getattr(chunk, "usage", None)\n',
|
||||
' if chunk_usage is not None:\n',
|
||||
' usage_input = int(getattr(chunk_usage, "prompt_tokens", 0) or 0)\n',
|
||||
' usage_output = int(getattr(chunk_usage, "completion_tokens", 0) or 0)\n',
|
||||
' tool_calls_typed: tuple[ToolCall, ...] = tuple(\n',
|
||||
' ToolCall(\n',
|
||||
' id=acc["id"] or "",\n',
|
||||
' type=acc["type"],\n',
|
||||
' function=ToolCallFunction(\n',
|
||||
' name=acc["function"]["name"] or "",\n',
|
||||
' arguments=acc["function"]["arguments"] or "{}",\n',
|
||||
' ),\n',
|
||||
' )\n',
|
||||
' for acc in (tool_calls_acc[k] for k in sorted(tool_calls_acc.keys()))\n',
|
||||
' )\n',
|
||||
' return NormalizedResponse(\n',
|
||||
]
|
||||
|
||||
# Find the end of the block (return NormalizedResponse)
|
||||
return_idx = None
|
||||
for i in range(139, len(lines)):
|
||||
if lines[i].rstrip().startswith(' return NormalizedResponse('):
|
||||
return_idx = i
|
||||
break
|
||||
|
||||
if return_idx is None:
|
||||
print('Could not find return NormalizedResponse line')
|
||||
else:
|
||||
# Replace from line 139 (index 138) to the return line (exclusive)
|
||||
new_lines = lines[:138] + new_block + lines[return_idx:]
|
||||
with open(r'C:\projects\manual_slop_tier2\src\openai_compatible.py', 'w', encoding='utf-8', newline='') as f:
|
||||
f.writelines(new_lines)
|
||||
print(f'Fixed lines 139-{return_idx+1}; new file has {len(new_lines)} lines')
|
||||
Reference in New Issue
Block a user