Files
scud_context_api/llm/agent.py
T

172 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import urllib.request
import urllib.error
import logging
from typing import List, Dict, Any, Tuple
from datetime import datetime
from .db_tools import (
db_get_active_system_prompt,
db_add_system_prompt,
db_get_tasks,
db_update_task_status,
db_delete_task,
db_add_task,
db_get_rules,
db_set_session_state,
db_get_session_state,
db_clear_session_state,
get_db_connection
)
from .schemas import TOOLS_SCHEMA
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("SCUD_AGENT")
OLLAMA_URL = "http://192.168.11.3:11434/api/chat"
MODEL_NAME = "qwen2.5:14b"
def clean_output(text: str) -> str:
if not text:
return text
artifacts = ["почемучто", "почто", "почему что"]
lower_text = text.lower()
for art in artifacts:
if lower_text.startswith(art):
text = text[len(art):].lstrip(",.!?:; -")
return text.strip()
def process_chat_message(user_id: int, user_message: str, chat_history: List[Dict[str, Any]] = None, session_id: str = "web_session_main") -> Tuple[str, List[Dict[str, Any]]]:
if chat_history is None:
chat_history = []
logger.info(f"Получено сообщение от user_id={user_id}, session_id={session_id}: {user_message}")
current_now = datetime.now().strftime("%Y-%m-%d %H:%M")
dynamic_prompt_text = db_get_active_system_prompt()
session_state = db_get_session_state(session_id)
preview_status_note = ""
if session_state and session_state["state_type"] == "PROMPT_PREVIEW":
preview_status_note = "\n\n[АКТИВНО ПРЕВЬЮ ПРОМПТА: Ожидается подтверждение или отмена изменений пользователем]."
system_prompt = {
"role": "system",
"content": f"Текущая дата и время сервера: {current_now}.\n\nТЕКУЩИЙ АКТИВНЫЙ СИСТЕМНЫЙ ПРОМПТ:\n{dynamic_prompt_text}{preview_status_note}"
}
messages = [system_prompt] + chat_history + [{"role": "user", "content": user_message}]
payload = {
"model": MODEL_NAME,
"messages": messages,
"tools": TOOLS_SCHEMA,
"stream": False,
"options": {"num_predict": 8192, "num_ctx": 8192, "temperature": 0.1}
}
try:
req = urllib.request.Request(
OLLAMA_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req) as response:
res_data = json.loads(response.read().decode("utf-8"))
msg = res_data.get("message", {})
tool_calls = msg.get("tool_calls", [])
logger.info(f"Ответ от Ollama получен. Tool calls: {bool(tool_calls)}")
if tool_calls:
messages.append(msg)
for tool in tool_calls:
fn_name = tool["function"]["name"]
fn_args = tool["function"].get("arguments", {})
logger.info(f"Вызов функции (Tool): {fn_name} с аргументами: {fn_args}")
tool_result_content = ""
if fn_name == "db_get_tasks":
tasks = db_get_tasks(user_id)
tool_result_content = json.dumps(tasks, ensure_ascii=False)
elif fn_name in ["db_get_system_prompt", "db_get_system_prompts"]:
prompt_content = db_get_active_system_prompt()
tool_result_content = json.dumps({"system_prompt": prompt_content}, ensure_ascii=False)
elif fn_name == "db_preview_prompt_merge":
proposed_text = fn_args.get("proposed_prompt", "")
if proposed_text:
# Фиксируем превью в сессии и сразу возвращаем текст пользователю на экран
db_set_session_state(session_id, "PROMPT_PREVIEW", proposed_text)
preview_reply = f"Ваше изменение успешно предпросмотрено. Полный обновленный системный промпт теперь выглядит так:\n\n{proposed_text}\n\nДля применения изменений подтвердите действие («подтверждаю») или отмените («отмена»)."
return preview_reply, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": preview_reply}]
else:
tool_result_content = json.dumps({"status": "error", "message": "Текст превью пуст."}, ensure_ascii=False)
elif fn_name == "db_confirm_prompt_preview":
if session_state and session_state["state_type"] == "PROMPT_PREVIEW":
pending_text = session_state["pending_data"]
res = db_add_system_prompt("main_agent", pending_text)
db_clear_session_state(session_id)
tool_result_content = json.dumps(res, ensure_ascii=False)
else:
tool_result_content = json.dumps({"status": "error", "message": "Нет активного превью для подтверждения."}, ensure_ascii=False)
elif fn_name == "db_cancel_prompt_preview":
db_clear_session_state(session_id)
tool_result_content = json.dumps({"status": "success", "message": "Превью отменено."}, ensure_ascii=False)
elif fn_name == "db_add_system_prompt":
try:
prompt_text = fn_args.get("prompt_text") if isinstance(fn_args, dict) else str(fn_args)
name = fn_args.get("name", "main_agent") if isinstance(fn_args, dict) else "main_agent"
res = db_add_system_prompt(name=name, prompt_text=prompt_text)
db_clear_session_state(session_id)
tool_result_content = json.dumps(res, ensure_ascii=False)
except Exception as e:
tool_result_content = json.dumps({"status": "error", "error": str(e)}, ensure_ascii=False)
elif fn_name == "db_get_rules":
tool_result_content = json.dumps(db_get_rules(), ensure_ascii=False)
elif fn_name == "db_add_task":
res = db_add_task(user_id=user_id, module=fn_args.get("module", "general"), title=fn_args.get("title"), priority=fn_args.get("priority", "MEDIUM"), due_date=fn_args.get("due_date"))
tool_result_content = json.dumps(res, ensure_ascii=False)
elif fn_name == "db_update_task_status":
res = db_update_task_status(user_id=user_id, task_id=str(fn_args.get("task_id")), status=fn_args.get("status", "COMPLETED"), due_date=fn_args.get("due_date"))
tool_result_content = json.dumps(res, ensure_ascii=False)
elif fn_name == "db_delete_task":
res = db_delete_task(user_id=user_id, task_id=str(fn_args.get("task_id", "")).upper())
tool_result_content = json.dumps(res, ensure_ascii=False)
messages.append({
"role": "tool",
"content": tool_result_content
})
second_payload = {
"model": MODEL_NAME,
"messages": messages,
"stream": False,
"options": {"num_predict": 8192, "num_ctx": 8192, "temperature": 0.1}
}
sec_req = urllib.request.Request(
OLLAMA_URL,
data=json.dumps(second_payload).encode("utf-8"),
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(sec_req) as sec_response:
sec_res_data = json.loads(sec_response.read().decode("utf-8"))
final_content = clean_output(sec_res_data.get("message", {}).get("content", "").strip().replace("**", ""))
return final_content, chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": final_content}]
content_str = clean_output(msg.get("content", "").strip().replace("**", ""))
return content_str or "Запрос обработан.", chat_history + [{"role": "user", "content": user_message}, {"role": "assistant", "content": content_str}]
except Exception as ex:
logger.exception(f"Непредвиденная ошибка: {ex}")
return f"Внутренняя ошибка сервера: {ex}", chat_history