fixed injection watcher
This commit is contained in:
@@ -48,8 +48,15 @@ async def send_message(
|
||||
if not service.get_session(session_id):
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
# Сохраняем user до стрима: иначе при обрыве SSE сообщение не попадает в БД.
|
||||
service.save_user_message(session_id, payload.content)
|
||||
|
||||
async def event_stream():
|
||||
async for chunk in service.stream_response(session_id, payload.content):
|
||||
async for chunk in service.stream_response(
|
||||
session_id,
|
||||
payload.content,
|
||||
user_message_saved=True,
|
||||
):
|
||||
yield chunk
|
||||
|
||||
return StreamingResponse(
|
||||
|
||||
@@ -172,12 +172,22 @@ class ChatService:
|
||||
self.db.refresh(message)
|
||||
return message
|
||||
|
||||
async def stream_response(self, session_id: int, user_text: str) -> AsyncIterator[str]:
|
||||
def save_user_message(self, session_id: int, user_text: str) -> None:
|
||||
self._save_message(session_id, "user", user_text)
|
||||
|
||||
async def stream_response(
|
||||
self,
|
||||
session_id: int,
|
||||
user_text: str,
|
||||
*,
|
||||
user_message_saved: bool = False,
|
||||
) -> AsyncIterator[str]:
|
||||
session = self.get_session(session_id)
|
||||
if not session:
|
||||
yield self._sse("error", {"message": "Session not found"})
|
||||
return
|
||||
|
||||
if not user_message_saved:
|
||||
self._save_message(session_id, "user", user_text)
|
||||
yield self._sse("status", {"phase": "preparing"})
|
||||
t0 = time.monotonic()
|
||||
@@ -299,10 +309,15 @@ class ChatService:
|
||||
if not final_content and reasoning:
|
||||
final_content = reasoning.strip()
|
||||
if not final_content and all_tool_notices:
|
||||
# Notices уже ушли в SSE event: notice; здесь только финальный текст в БД.
|
||||
final_content = "\n\n".join(all_tool_notices)
|
||||
yield self._sse("token", {"content": final_content})
|
||||
if not final_content and tools_executed:
|
||||
retry = await self.llm.complete(messages, tools=None, temperature=0.4)
|
||||
retry = await self.llm.complete(
|
||||
messages,
|
||||
tools=None,
|
||||
temperature=0.4,
|
||||
visible_reply=True,
|
||||
)
|
||||
final_content = (retry.get("content") or "").strip()
|
||||
if final_content:
|
||||
yield self._sse("token", {"content": final_content})
|
||||
|
||||
@@ -171,6 +171,7 @@ class LLMClient:
|
||||
temperature: float = 0.7,
|
||||
model: str | None = None,
|
||||
for_extraction: bool = False,
|
||||
visible_reply: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
use_tools = bool(tools) and self.tools_enabled and not for_extraction
|
||||
kwargs: dict[str, Any] = {
|
||||
@@ -198,7 +199,7 @@ class LLMClient:
|
||||
reasoning = str(value)
|
||||
break
|
||||
|
||||
if not content and reasoning:
|
||||
if not content and reasoning and not visible_reply:
|
||||
content = reasoning
|
||||
|
||||
result: dict[str, Any] = {
|
||||
|
||||
@@ -228,22 +228,17 @@ export const api = {
|
||||
});
|
||||
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error("Failed to send message");
|
||||
const detail = await response.text().catch(() => "");
|
||||
throw new Error(detail || `Ошибка отправки (${response.status})`);
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const parts = buffer.split("\n\n");
|
||||
buffer = parts.pop() ?? "";
|
||||
|
||||
const flushParts = function* (parts: string[]) {
|
||||
for (const part of parts) {
|
||||
if (!part.trim()) continue;
|
||||
const lines = part.split("\n");
|
||||
let event = "message";
|
||||
let data = "";
|
||||
@@ -257,6 +252,24 @@ export const api = {
|
||||
yield { event, data: JSON.parse(data) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (value) {
|
||||
buffer += decoder.decode(value, { stream: !done });
|
||||
}
|
||||
|
||||
const parts = buffer.split("\n\n");
|
||||
buffer = parts.pop() ?? "";
|
||||
yield* flushParts(parts);
|
||||
|
||||
if (done) {
|
||||
if (buffer.trim()) {
|
||||
yield* flushParts([buffer]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -190,7 +190,6 @@ export default function Chat() {
|
||||
setStreaming("");
|
||||
setLiveNotices([]);
|
||||
setChatError(null);
|
||||
setLoading(false);
|
||||
if (assistantText.trim()) {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
@@ -202,8 +201,8 @@ export default function Chat() {
|
||||
},
|
||||
]);
|
||||
}
|
||||
void loadMessages(activeId);
|
||||
void loadSessions();
|
||||
await loadMessages(activeId);
|
||||
await loadSessions();
|
||||
}
|
||||
if (chunk.event === "error") {
|
||||
throw new Error(chunk.data.message);
|
||||
@@ -214,6 +213,9 @@ export default function Chat() {
|
||||
const message = err instanceof Error ? err.message : "Ошибка чата";
|
||||
setChatError(message);
|
||||
setStreaming("");
|
||||
if (activeId) {
|
||||
await loadMessages(activeId);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user