"""SSE streaming chat — respostas token-a-token como assistente.""" from __future__ import annotations import json from collections.abc import Iterator from typing import Any from app import auth from app.agents import llm_client, store from app.agents import messages as agent_messages from app.agents.chat_context_builder import build_ops_context, build_suggested_actions def _sse(payload: dict[str, Any]) -> str: return f"data: {json.dumps(payload, ensure_ascii=False)}\n\n" def _thread_history_messages(conn, thread_id: int, limit: int = 16) -> list[dict]: msgs = agent_messages.thread_messages(conn, thread_id)[-limit:] out: list[dict] = [] for m in msgs: if m["from_type"] == "human": out.append({"role": "user", "content": m["body"]}) elif m["from_type"] == "agent": out.append({"role": "assistant", "content": m["body"]}) return out def chat_stream_generator( *, username: str, user_role: str, question: str, target_agent: str, agent_name: str, agent_role: str, include_findings: bool, thread_id: int | None = None, ) -> Iterator[str]: conn = auth.db() try: kb = store.search_kb(conn, question) kb_snippets = [k["snippet"] for k in kb] ops_context = build_ops_context( conn, question, target_agent, include_findings=include_findings, kb_snippets=kb_snippets, ) suggested_actions = build_suggested_actions(conn, question, target_agent) tid = thread_id history_msgs: list[dict] = [] if tid: row = conn.execute("SELECT * FROM agent_threads WHERE id=?", (tid,)).fetchone() if not row: yield _sse({"type": "error", "message": "thread not found"}) return history_msgs = _thread_history_messages(conn, tid) else: tid = agent_messages.create_thread( conn, subject=f"Chat: {question[:60]}", primary_agent=target_agent, severity="info", ) agent_messages.post_message( conn, thread_id=tid, from_type="human", from_id=username, to_type="agent", to_id=target_agent, body=question, ) conn.commit() messages = llm_client.build_chat_messages( question=question, kb_snippets=kb_snippets, ops_context=ops_context, user_role=user_role, target_agent=target_agent, agent_name=agent_name, agent_role=agent_role, history_messages=history_msgs, ) yield _sse({"type": "start", "thread_id": tid, "agent": target_agent, "actions": suggested_actions}) token_iter, model_label = llm_client.stream_llm_chat(messages) parts: list[str] = [] for tok in token_iter: parts.append(tok) yield _sse({"type": "token", "text": tok}) answer = "".join(parts).strip() if not answer: answer = ( "Não consegui gerar resposta agora. Verifique Groq/Ollama no health do Agentic Ops." ) model_label = "error" yield _sse({"type": "token", "text": answer}) agent_messages.post_message( conn, thread_id=tid, from_type="agent", from_id=target_agent, to_type="human", to_id=username, body=answer, context={"model": model_label, "kb_hits": len(kb), "streamed": True, "actions": suggested_actions}, ) store.log_event( conn, event_type="chat.stream" if thread_id else "chat.query", message=question[:120], agent_id=target_agent, payload={"user": username, "model": model_label, "thread_id": tid}, ) conn.commit() yield _sse({ "type": "done", "answer": answer, "model": model_label, "thread_id": tid, "kb_hits": len(kb), "actions": suggested_actions, }) except Exception as exc: yield _sse({"type": "error", "message": str(exc)[:300]}) finally: conn.close()