tasks 033 updated
This commit is contained in:
@@ -8,9 +8,12 @@
|
||||
# from exceeding 400 lines and centralises risk classification in one place.
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from src.agent._tool_resolver import (
|
||||
_SAFE_AGENT_TOOLS,
|
||||
_DANGEROUS_AGENT_TOOLS,
|
||||
@@ -109,6 +112,80 @@ def confirmation_payload(conv_id: str, state, user_text: str) -> str:
|
||||
# #endregion AgentChat.Confirmation.Payload
|
||||
|
||||
|
||||
# #region AgentChat.Confirmation.FormatOutput [C:3] [TYPE Function] [SEMANTICS agent-chat,hitl,llm,formatting]
|
||||
# @ingroup AgentChat
|
||||
# @BRIEF Format tool output via LLM for a natural-language response, with fallback to
|
||||
# prettified JSON. Yields streaming tokens.
|
||||
# @POST Yields stream_token events with formatted text.
|
||||
# @RELATION DEPENDS_ON -> [AgentChat.LangGraph.Setup]
|
||||
# @RATIONALE Fast-path confirmation bypasses the LangGraph agent — the tool result is
|
||||
# raw JSON. This function adds an LLM formatting layer so the user sees a
|
||||
# readable response instead of raw data. Falls back to rule-based formatting
|
||||
# when LLM is unavailable.
|
||||
# @REJECTED Yielding raw JSON directly was rejected — users expect LLM-styled answers,
|
||||
# not machine-readable data dumps.
|
||||
async def _format_tool_output_via_llm(
|
||||
tool_name: str, output: str,
|
||||
) -> AsyncGenerator[str]:
|
||||
from src.agent.langgraph_setup import _fetch_llm_config
|
||||
from src.core.logger import logger
|
||||
|
||||
text = output.strip()
|
||||
if not text:
|
||||
yield json.dumps({
|
||||
"content": "_(нет данных)_",
|
||||
"metadata": {"type": "stream_token", "token": "_(нет данных)_"},
|
||||
})
|
||||
return
|
||||
|
||||
# ── Try LLM formatting ──
|
||||
config = await _fetch_llm_config()
|
||||
if config and config.get("configured"):
|
||||
try:
|
||||
llm = ChatOpenAI(
|
||||
model=config.get("default_model", "gpt-4o-mini"),
|
||||
base_url=config.get("base_url", "https://api.openai.com/v1"),
|
||||
api_key=config["api_key"],
|
||||
temperature=0,
|
||||
max_tokens=1024,
|
||||
)
|
||||
prompt = (
|
||||
f"Tool '{tool_name}' returned this data:\n\n{text}\n\n"
|
||||
"Summarize this data in a concise, human-readable format. "
|
||||
"Use bullet points or a short paragraph. "
|
||||
"Keep it brief — under 5 sentences. "
|
||||
"Answer in Russian unless the data is in English."
|
||||
)
|
||||
async for chunk in llm.astream(prompt):
|
||||
if hasattr(chunk, "content") and chunk.content:
|
||||
yield json.dumps({
|
||||
"content": chunk.content,
|
||||
"metadata": {"type": "stream_token", "token": chunk.content},
|
||||
})
|
||||
return
|
||||
except Exception as exc:
|
||||
logger.explore(
|
||||
"LLM formatting failed, falling back to prettified output",
|
||||
payload={"tool": tool_name}, error=str(exc),
|
||||
extra={"src": "AgentChat.Confirmation.FormatOutput"},
|
||||
)
|
||||
|
||||
# ── Fallback: prettified JSON or raw text ──
|
||||
try:
|
||||
data = json.loads(text)
|
||||
pretty = json.dumps(data, indent=2, ensure_ascii=False)
|
||||
yield json.dumps({
|
||||
"content": pretty,
|
||||
"metadata": {"type": "stream_token", "token": pretty},
|
||||
})
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
yield json.dumps({
|
||||
"content": text,
|
||||
"metadata": {"type": "stream_token", "token": text},
|
||||
})
|
||||
# #endregion AgentChat.Confirmation.FormatOutput
|
||||
|
||||
|
||||
# #region AgentChat.Confirmation.HandleResume [C:4] [TYPE Function] [SEMANTICS agent-chat,hitl,resume,streaming]
|
||||
# @ingroup AgentChat
|
||||
# @BRIEF Resume from HITL checkpoint — execute confirmed tool or abort on deny.
|
||||
@@ -179,10 +256,9 @@ async def handle_resume(
|
||||
"content": f"✅ {tool_name}",
|
||||
"metadata": {"type": "tool_end", "tool": tool_name, "output": {"result": str(output)[:500]}},
|
||||
})
|
||||
yield json.dumps({
|
||||
"content": str(output),
|
||||
"metadata": {"type": "stream_token", "token": str(output)},
|
||||
})
|
||||
# Format tool output via LLM for a human-readable response
|
||||
async for chunk in _format_tool_output_via_llm(tool_name, str(output)):
|
||||
yield chunk
|
||||
logger.reflect(
|
||||
"Fast-path confirmation completed",
|
||||
payload={"tool": tool_name},
|
||||
|
||||
@@ -47,7 +47,7 @@ from src.agent.context import set_user_jwt
|
||||
from src.agent.document_parser import parse_upload
|
||||
from src.agent.langgraph_setup import create_agent
|
||||
from src.agent.middleware import log_tool_event
|
||||
from src.agent.tools import get_tools_for_query
|
||||
from src.agent.tools import get_all_tools
|
||||
from src.core.auth.jwt import decode_token
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.logger import logger
|
||||
@@ -174,6 +174,11 @@ async def agent_handler( # noqa: C901 — intentionally complex C4 orchestratio
|
||||
|
||||
# ── Parse message ──
|
||||
text = message.get("text", "") if isinstance(message, dict) else str(message)
|
||||
# Preserve original user text for intent detection BEFORE any augmentation
|
||||
# (truncation, file upload content, prefetch data). Substring-based keyword
|
||||
# matching in get_tools_for_query / fast_confirmation_tool would otherwise
|
||||
# match system-injected text (e.g. "tool" ⊂ "tools" in prefetch marker).
|
||||
user_message_text = text
|
||||
files = message.get("files", []) if isinstance(message, dict) else []
|
||||
if not text.strip() and not files:
|
||||
return
|
||||
@@ -253,12 +258,12 @@ async def agent_handler( # noqa: C901 — intentionally complex C4 orchestratio
|
||||
conv_id = conversation_id or str(uuid.uuid4())
|
||||
_conv_locks[conv_id] = asyncio.Event()
|
||||
|
||||
fast_tool_name = fast_confirmation_tool(text)
|
||||
fast_tool_name = fast_confirmation_tool(user_message_text)
|
||||
if fast_tool_name:
|
||||
_pending_confirmations[conv_id] = {
|
||||
"tool_name": fast_tool_name,
|
||||
"tool_args": {},
|
||||
"user_text": text,
|
||||
"user_text": user_message_text,
|
||||
}
|
||||
yield json.dumps({
|
||||
"content": "⏸️ Требуется подтверждение",
|
||||
@@ -267,18 +272,18 @@ async def agent_handler( # noqa: C901 — intentionally complex C4 orchestratio
|
||||
return
|
||||
|
||||
# ── Pre-fetch dashboards ──
|
||||
text_lower = text.lower()
|
||||
prefetch_available = False
|
||||
text_lower = user_message_text.lower()
|
||||
if any(kw in text_lower for kw in ["дашборд", "dashboard", "dashboards", "дашборды"]):
|
||||
try:
|
||||
dash_data = await prefetch_dashboards(env_id or "")
|
||||
if dash_data:
|
||||
text += f"\n\n[PRE-FETCHED DATA — use this directly, do NOT call tools]\n{dash_data}\n[/PRE-FETCHED DATA]"
|
||||
prefetch_available = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
agent_tools = get_tools_for_query(text, prefetch_available=prefetch_available)
|
||||
# All tools exposed — Gemma context window is now sufficient.
|
||||
# Intent-based subset filtering (get_tools_for_query) retired.
|
||||
agent_tools = get_all_tools()
|
||||
agent = await create_agent(agent_tools, env_id)
|
||||
config = {"configurable": {"thread_id": conv_id}}
|
||||
|
||||
@@ -333,7 +338,7 @@ async def agent_handler( # noqa: C901 — intentionally complex C4 orchestratio
|
||||
state = await agent.aget_state(config)
|
||||
if getattr(state, "next", None):
|
||||
emitted_any = True
|
||||
yield confirmation_payload(conv_id, state, text)
|
||||
yield confirmation_payload(conv_id, state, user_message_text)
|
||||
return
|
||||
elif not emitted_any:
|
||||
yield json.dumps({
|
||||
|
||||
@@ -184,7 +184,11 @@ async def create_agent(
|
||||
# System prompt — env_id injected deterministically, not in user message
|
||||
prompt = (
|
||||
"You are a Superset Tools assistant. You have access to tools for searching "
|
||||
"dashboards, checking health, listing environments, and checking task status. "
|
||||
"dashboards, managing maintenance, running migrations and backups, "
|
||||
"executing SQL and exploring databases, auditing permissions, "
|
||||
"managing Git operations (branch/commit/deploy), running LLM validation "
|
||||
"and documentation, creating and copying dashboards and datasets, "
|
||||
"and checking system health, environments, and task status. "
|
||||
"If the data you need is already provided in the user message, use that directly "
|
||||
"rather than calling tools. Only call tools when the data is not present."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user