Harden local intent routing
This commit is contained in:
parent
dc8267880a
commit
662e6a6e0f
|
|
@ -104,6 +104,16 @@ class AsyncRouter:
|
|||
reason="Task context explicitly requested a tool execution.",
|
||||
)
|
||||
|
||||
parsed_intent = self._intent_parser.parse(task_summary)
|
||||
if parsed_intent:
|
||||
self._emit_event(
|
||||
ORCHESTRATOR_RESULT,
|
||||
{"reason": "deterministic_intent_parser", "directive": parsed_intent.model_dump(mode="json")},
|
||||
task_id,
|
||||
session_id,
|
||||
)
|
||||
return parsed_intent
|
||||
|
||||
if self._thinker is None:
|
||||
fallback = self._fallback_directive(task_summary)
|
||||
self._emit_event(
|
||||
|
|
@ -365,20 +375,10 @@ class AsyncRouter:
|
|||
|
||||
try:
|
||||
result = await classifier_model.generate(classification_prompt)
|
||||
result = result.strip().lower()
|
||||
|
||||
# Extract first word - LLM often adds explanation
|
||||
first_word = result.split()[0] if result.split() else ""
|
||||
|
||||
# Validate result is one of allowed values
|
||||
allowed = {"execution", "conversation", "clarification_needed"}
|
||||
if first_word in allowed:
|
||||
logger.info(f"Intent classified: {first_word} for task: {task_summary}")
|
||||
return first_word
|
||||
|
||||
if result in allowed:
|
||||
logger.info(f"Intent classified: {result} for task: {task_summary}")
|
||||
return result
|
||||
classification = self._extract_classification(result)
|
||||
if classification:
|
||||
logger.info(f"Intent classified: {classification} for task: {task_summary}")
|
||||
return classification
|
||||
|
||||
logger.warning(f"Invalid classification result: {result}, defaulting to conversation")
|
||||
return "conversation"
|
||||
|
|
@ -386,6 +386,23 @@ class AsyncRouter:
|
|||
logger.warning(f"Intent classification failed: {e}, defaulting to conversation")
|
||||
return "conversation"
|
||||
|
||||
def _extract_classification(self, raw_result: str) -> str | None:
|
||||
result = raw_result.strip().lower()
|
||||
allowed = {"execution", "conversation", "clarification_needed"}
|
||||
if result in allowed:
|
||||
return result
|
||||
|
||||
result = re.sub(r"<think>.*?</think>", " ", result, flags=re.DOTALL)
|
||||
tokens = re.findall(r"\b(execution|conversation|clarification_needed)\b", result)
|
||||
if tokens:
|
||||
return tokens[-1]
|
||||
|
||||
first_word = result.split()[0] if result.split() else ""
|
||||
if first_word in allowed:
|
||||
return first_word
|
||||
|
||||
return None
|
||||
|
||||
def _validate_directive(self, output: str, mode_hint: str) -> ExecutionDirective | None:
|
||||
if not output:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -29,6 +29,17 @@ MEMORY_SEARCH_PATTERNS = (
|
|||
r"find\s+(.+)",
|
||||
)
|
||||
|
||||
SYSTEM_COMMAND_PATTERNS = (
|
||||
(
|
||||
re.compile(r"(сколько\s+времени\s+запущен|как\s+долго\s+работает|uptime|аптайм)", re.IGNORECASE),
|
||||
"uptime -p",
|
||||
),
|
||||
(
|
||||
re.compile(r"(проверь|посмотри|покажи).*(обновлен|обновл|updates|upgradable)", re.IGNORECASE),
|
||||
"apt list --upgradable",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class IntentParser:
|
||||
"""Extracts explicit tool intents from natural-language task text."""
|
||||
|
|
@ -69,6 +80,19 @@ class IntentParser:
|
|||
reason="User explicitly requested to search memory.",
|
||||
)
|
||||
|
||||
for pattern, command in SYSTEM_COMMAND_PATTERNS:
|
||||
if pattern.search(normalized):
|
||||
return ExecutionDirective(
|
||||
type="tool",
|
||||
payload={
|
||||
"tool": "shell_exec",
|
||||
"args": {"command": command},
|
||||
},
|
||||
requires_permission=True,
|
||||
confidence=0.9,
|
||||
reason="User explicitly requested local system information.",
|
||||
)
|
||||
|
||||
for prefix in SHELL_PREFIXES:
|
||||
if lowered.startswith(prefix):
|
||||
command = normalized[len(prefix) :].strip()
|
||||
|
|
|
|||
|
|
@ -270,6 +270,8 @@ class RuntimeController:
|
|||
try:
|
||||
emb_config = self.config.models.embeddings or {}
|
||||
model_path = self.base_dir / emb_config.get("path", "models/all-MiniLM-L6-v2")
|
||||
if not model_path.exists() and not Path(emb_config.get("path", "")).is_absolute():
|
||||
model_path = self.base_dir / "models" / emb_config.get("path", "all-MiniLM-L6-v2")
|
||||
if not model_path.exists():
|
||||
print(f"Memory init skipped: embeddings model not found at {model_path}")
|
||||
self._memory_interface = None
|
||||
|
|
|
|||
Loading…
Reference in New Issue