Skip to content

Commit c03159e

Browse files
author
Mateusz
committed
Fixes
1 parent 64a805c commit c03159e

File tree

9 files changed

+1358
-849
lines changed

9 files changed

+1358
-849
lines changed

src/core/app/controllers/chat_controller.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,73 @@ def _ensure_openai_chat_schema(
523523

524524
return response.model_dump()
525525

526+
if metadata and isinstance(metadata, dict):
527+
meta_role = metadata.get("role")
528+
if meta_role == "tool":
529+
import time as _time
530+
import uuid as _uuid
531+
532+
tool_call_id = metadata.get("tool_call_id")
533+
finish_reason = metadata.get("finish_reason") or "stop"
534+
535+
model_name = str(
536+
metadata.get("model")
537+
or getattr(domain_request, "model", "gpt-4")
538+
)
539+
response_id = str(
540+
metadata.get("id")
541+
or f"chatcmpl-{_uuid.uuid4().hex[:16]}"
542+
)
543+
created_ts = metadata.get("created")
544+
if isinstance(created_ts, int | float):
545+
created_val = int(created_ts)
546+
else:
547+
created_val = int(_time.time())
548+
549+
message_metadata = {
550+
k: v
551+
for k, v in metadata.items()
552+
if k
553+
not in {
554+
"role",
555+
"tool_call_id",
556+
"finish_reason",
557+
"model",
558+
"id",
559+
"created",
560+
}
561+
}
562+
if not message_metadata:
563+
message_metadata = None # type: ignore[assignment]
564+
565+
message = ChatCompletionChoiceMessage(
566+
role="tool",
567+
content=str(content or ""),
568+
tool_call_id=(
569+
str(tool_call_id) if tool_call_id else None
570+
),
571+
metadata=message_metadata,
572+
)
573+
574+
choice = ChatCompletionChoice(
575+
index=0,
576+
message=message,
577+
finish_reason=finish_reason,
578+
)
579+
580+
response = ChatResponse(
581+
id=response_id,
582+
created=created_val,
583+
model=model_name,
584+
choices=[choice],
585+
usage=cast(
586+
"dict[str, Any] | None",
587+
metadata.get("usage"),
588+
),
589+
)
590+
591+
return response.model_dump()
592+
526593
# Check if content is a JSON string of tool calls (common backend response format)
527594
if isinstance(content, str):
528595
try:

src/core/domain/chat.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,14 @@ def validate_tools(cls, v: Any) -> list[dict[str, Any]] | None:
208208
return result
209209

210210

211-
class ChatCompletionChoiceMessage(DomainModel):
212-
"""Represents the message content within a chat completion choice."""
213-
214-
role: str
215-
content: str | None = None
216-
tool_calls: list[ToolCall] | None = None
217-
metadata: dict[str, Any] | None = None
211+
class ChatCompletionChoiceMessage(DomainModel):
212+
"""Represents the message content within a chat completion choice."""
213+
214+
role: str
215+
content: str | None = None
216+
tool_calls: list[ToolCall] | None = None
217+
tool_call_id: str | None = None
218+
metadata: dict[str, Any] | None = None
218219

219220

220221
class ChatCompletionChoice(DomainModel):

src/core/ports/streaming.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,17 @@ def __init__(
4242
self.raw_data = raw_data
4343

4444
@property
45-
def is_empty(self) -> bool:
46-
"""Whether this chunk contains no actual content."""
47-
if self.content:
48-
return False
49-
tool_calls = self.metadata.get("tool_calls")
50-
if isinstance(tool_calls, list) and tool_calls:
51-
return False
52-
reasoning_content = self.metadata.get("reasoning_content")
53-
if isinstance(reasoning_content, str) and reasoning_content.strip():
45+
def is_empty(self) -> bool:
46+
"""Whether this chunk contains no actual content."""
47+
if self.content:
48+
return False
49+
if self.metadata.get("role") == "tool":
50+
return False
51+
tool_calls = self.metadata.get("tool_calls")
52+
if isinstance(tool_calls, list) and tool_calls:
53+
return False
54+
reasoning_content = self.metadata.get("reasoning_content")
55+
if isinstance(reasoning_content, str) and reasoning_content.strip():
5456
return False
5557
reasoning = self.metadata.get("reasoning")
5658
return not (isinstance(reasoning, str) and reasoning.strip())
@@ -69,13 +71,36 @@ def to_bytes(self) -> bytes:
6971
return f"data: {json.dumps(data)}\n\ndata: [DONE]\n\n".encode()
7072
return b"data: [DONE]\n\n"
7173

72-
# Simplified serialization for streaming
73-
data = {"choices": [{"delta": {"content": self.content}}]}
74-
75-
# Add metadata if available
76-
for key in ["id", "model", "created"]:
77-
if key in self.metadata:
78-
data[key] = self.metadata[key]
74+
# Simplified serialization for streaming
75+
delta: dict[str, Any] = {}
76+
77+
role = self.metadata.get("role")
78+
if isinstance(role, str) and role:
79+
delta["role"] = role
80+
81+
tool_call_id = self.metadata.get("tool_call_id")
82+
if isinstance(tool_call_id, str) and tool_call_id:
83+
delta["tool_call_id"] = tool_call_id
84+
85+
tool_calls = self.metadata.get("tool_calls")
86+
if isinstance(tool_calls, list) and tool_calls:
87+
delta["tool_calls"] = tool_calls
88+
89+
if self.content is not None:
90+
delta["content"] = self.content
91+
92+
data = {"choices": [{"delta": delta}]}
93+
94+
finish_reason = self.metadata.get("finish_reason")
95+
if finish_reason is not None:
96+
data["choices"][0]["finish_reason"] = finish_reason
97+
else:
98+
data["choices"][0]["finish_reason"] = None
99+
100+
# Add metadata if available
101+
for key in ["id", "model", "created"]:
102+
if key in self.metadata:
103+
data[key] = self.metadata[key]
79104

80105
return f"data: {json.dumps(data)}\n\n".encode()
81106

src/core/repositories/in_memory_session_repository.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ async def delete(self, id: str) -> bool:
9393
# Remove from user tracking
9494
for user_id, session_ids in list(self._user_sessions.items()):
9595
if id in session_ids:
96-
session_ids.remove(id)
96+
try:
97+
session_ids.remove(id)
98+
except ValueError:
99+
pass # Already removed, consistent state is the goal
97100
if not session_ids:
98101
del self._user_sessions[user_id]
99102

@@ -106,7 +109,10 @@ async def delete(self, id: str) -> bool:
106109
# Remove from client session tracking
107110
for client_key, session_ids in list(self._client_sessions.items()):
108111
if id in session_ids:
109-
session_ids.remove(id)
112+
try:
113+
session_ids.remove(id)
114+
except ValueError:
115+
pass # Already removed
110116
if not session_ids:
111117
del self._client_sessions[client_key]
112118

0 commit comments

Comments
 (0)