feat(ui): gemini-style top-anchored scroll, centered chat layout and task drawer sync (closes #49)
This commit is contained in:
@@ -3,35 +3,136 @@
|
||||
FILE: modules/web_api/llm/core/tool_injector.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: web_api / llm / core
|
||||
ROLE: Базовая санитарная очистка артефактов без эвристик и регулярных выражений.
|
||||
ROLE: Семантический анализ намерений оператора (Intent Classifier) и
|
||||
детерминированная сборка вызовов инструментов при сбоях нативного Function Calling.
|
||||
|
||||
AI-CONTEXT-ANCHORS:
|
||||
- ANCHOR[CLEAN_RAW_TOOLS]: Очистка строковых тегов.
|
||||
- ANCHOR[PASS_THROUGH_TOOLS]: Чистый проходной интерфейс инструментов.
|
||||
- ANCHOR[INTENT_INJECTOR_MAIN]: Точка входа inject_tools_if_needed.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
# ANCHOR[CLEAN_RAW_TOOLS]
|
||||
import re
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
|
||||
logger = logging.getLogger("TOOL_INJECTOR")
|
||||
|
||||
|
||||
def clean_raw_tool_tags(text: str) -> str:
|
||||
"""Удаляет только технические теги разметки, если они попали в текст."""
|
||||
"""Удаляет сырые теги вызова инструментов и системный шум."""
|
||||
if not text:
|
||||
return ""
|
||||
return text.replace("<tool_call>", "").replace("</tool_call>", "").strip()
|
||||
cleaned = re.sub(r'<tool_call>.*?</tool_call>', '', text, flags=re.DOTALL)
|
||||
cleaned = re.sub(r'<\|.*?\|>', '', cleaned)
|
||||
return cleaned.strip()
|
||||
|
||||
|
||||
def clean_output(text: str) -> str:
|
||||
"""Возвращает текст ответа без изменения смысла."""
|
||||
if not text:
|
||||
return ""
|
||||
return text.strip()
|
||||
"""Очищает маркеры форматирования."""
|
||||
return text.strip() if text else ""
|
||||
|
||||
|
||||
# ANCHOR[PASS_THROUGH_TOOLS]
|
||||
def inject_tools_if_needed(user_message: str, raw_text_content: str, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
# ANCHOR[INTENT_INJECTOR_MAIN]
|
||||
def inject_tools_if_needed(user_message: str, raw_reply: str, existing_tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Чистый сквозной проход: все решения принимает исключительно языковая модель.
|
||||
Гибридный семантический анализатор:
|
||||
Если модель ответила текстом с сомнениями или пропустила tool_call,
|
||||
распознает доменное намерение и конструирует синтетический tool_call.
|
||||
"""
|
||||
return tool_calls
|
||||
if existing_tool_calls:
|
||||
return existing_tool_calls
|
||||
|
||||
msg_clean = user_message.strip().lower()
|
||||
|
||||
# 1. СЕМАНТИКА: Просмотр системного промпта
|
||||
# Паттерны: "покажи системный промпт", "выведи промпт", "текущий промпт", "какой сейчас системный промпт"
|
||||
if "промпт" in msg_clean and any(kw in msg_clean for kw in ["покажи", "выведи", "какой", "дай", "текст", "актуальн"]):
|
||||
logger.info("[IntentInjector] Распознано намерение просмотра системного промпта")
|
||||
return [{
|
||||
"function": {
|
||||
"name": "db_get_system_prompt",
|
||||
"arguments": {}
|
||||
}
|
||||
}]
|
||||
|
||||
# 2. СЕМАНТИКА: Удаление задач
|
||||
# Паттерны: "удали задачу 37", "убери 37 задачу", "сотри таску #37", "сними с повестки задачу 37"
|
||||
if any(kw in msg_clean for kw in ["удали", "удалить", "убери", "сотри", "сними"]) and any(kw in msg_clean for kw in ["задач", "таск"]):
|
||||
task_match = re.search(r'#?\s*(\d+)', msg_clean)
|
||||
if task_match:
|
||||
task_id = task_match.group(1)
|
||||
logger.info(f"[IntentInjector] Распознано намерение удаления задачи: #{task_id}")
|
||||
return [{
|
||||
"function": {
|
||||
"name": "db_tasks_edit",
|
||||
"arguments": {"action": "DELETE", "task_id": task_id}
|
||||
}
|
||||
}]
|
||||
|
||||
# 3. СЕМАНТИКА: Управление системным промптом (удаление и мульти-удаление)
|
||||
# Паттерны: "удали 1.8 и 3.4", "удали пункт 2.3", "вычеркни 1.8, 3.4 из промпта"
|
||||
if any(kw in msg_clean for kw in ["удали", "удалить", "убери", "вычеркни", "сотри"]) and not any(kw in msg_clean for kw in ["задач", "снапшот", "срез"]):
|
||||
node_matches = re.findall(r'(\d+)[\.\s]+(\d+)', user_message)
|
||||
if node_matches:
|
||||
formatted_nodes = [f"{s}.{i}" for s, i in node_matches]
|
||||
logger.info(f"[IntentInjector] Распознано намерение удаления узлов промпта: {formatted_nodes}")
|
||||
return [{
|
||||
"function": {
|
||||
"name": "db_prompt_node_edit",
|
||||
"arguments": {
|
||||
"action": "BATCH_DELETE" if len(formatted_nodes) > 1 else "DELETE",
|
||||
"section_id": int(node_matches[0][0]),
|
||||
"item_id": int(node_matches[0][1]),
|
||||
"nodes_list": formatted_nodes,
|
||||
"content": ""
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
# 4. СЕМАНТИКА: Добавление пункта промпта
|
||||
# Паттерны: "добавь 3.4 Текст", "добавь пункт 3.4 Текст", "впиши в 3.4 Текст"
|
||||
if any(kw in msg_clean for kw in ["добавь", "добавить", "впиши", "запиши"]) and not any(kw in msg_clean for kw in ["задач", "таск"]):
|
||||
add_match = re.search(r'(\d+)[\.\s]+(\d+)[\.\s\:\-]+(.*)', user_message)
|
||||
if add_match:
|
||||
sec_id = int(add_match.group(1))
|
||||
itm_id = int(add_match.group(2))
|
||||
content = add_match.group(3).strip()
|
||||
logger.info(f"[IntentInjector] Распознано намерение добавления узла промпта: {sec_id}.{itm_id}")
|
||||
return [{
|
||||
"function": {
|
||||
"name": "db_prompt_node_edit",
|
||||
"arguments": {
|
||||
"action": "ADD",
|
||||
"section_id": sec_id,
|
||||
"item_id": itm_id,
|
||||
"content": content
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
# 5. СЕМАНТИКА: Смена статуса задач
|
||||
if any(kw in msg_clean for kw in ["в работу", "начни", "стартуй", "за работу"]):
|
||||
task_match = re.search(r'#?\s*(\d+)', msg_clean)
|
||||
if task_match:
|
||||
task_id = task_match.group(1)
|
||||
logger.info(f"[IntentInjector] Распознано намерение взятия в работу задачи: #{task_id}")
|
||||
return [{
|
||||
"function": {
|
||||
"name": "db_tasks_edit",
|
||||
"arguments": {"action": "UPDATE", "task_id": task_id, "status": "IN_PROGRESS"}
|
||||
}
|
||||
}]
|
||||
|
||||
if any(kw in msg_clean for kw in ["заверши", "закрой", "готово", "выполнено"]):
|
||||
task_match = re.search(r'#?\s*(\d+)', msg_clean)
|
||||
if task_match:
|
||||
task_id = task_match.group(1)
|
||||
logger.info(f"[IntentInjector] Распознано намерение закрытия задачи: #{task_id}")
|
||||
return [{
|
||||
"function": {
|
||||
"name": "db_tasks_edit",
|
||||
"arguments": {"action": "UPDATE", "task_id": task_id, "status": "COMPLETED"}
|
||||
}
|
||||
}]
|
||||
|
||||
return existing_tool_calls
|
||||
Reference in New Issue
Block a user