Baida07 commited on
Commit
4d4b1d1
·
1 Parent(s): e06b331

sync: 189 file da Baida98/AI@ba97a2d4 (2026-08-26 08:44 UTC) [deploy-all] (#106)

Browse files

- sync: 189 file da Baida98/AI@ba97a2d4 (2026-08-26 08:44 UTC) [deploy-all] (9c23e83b42554b8a9988959e707769b6e3f246e7)

agents/unified_loop.py CHANGED
@@ -3777,6 +3777,29 @@ class UnifiedAgentLoop(DirectToolsMixin, PromptBuilderMixin, LLMSelectionMixin,
3777
  except Exception:
3778
  pass # decision_memory non disponibile — continua normalmente
3779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3780
  # P29-B1: gate ambiguità strutturale — _is_goal_ambiguous() era P28-B2 dead code (mai chiamata).
3781
  # Zero LLM, <0.1ms. Lingua-aware via self._run_lang (P28-B1). Fires dopo blacklist e prima del routing.
3782
  if _is_goal_ambiguous(goal):
@@ -3940,9 +3963,6 @@ class UnifiedAgentLoop(DirectToolsMixin, PromptBuilderMixin, LLMSelectionMixin,
3940
  return _r_bl
3941
 
3942
 
3943
- # Sprint 5 ITEM 13: classify_ms — tempo routing/classificazione goal (sync, <1ms)
3944
- _t0_classify = _time.monotonic()
3945
-
3946
  # S402: Fast Path — greeting/ack/identità semplice → bypass tutto l'overhead
3947
  if self._is_simple_query(goal):
3948
  try:
@@ -3992,27 +4012,6 @@ class UnifiedAgentLoop(DirectToolsMixin, PromptBuilderMixin, LLMSelectionMixin,
3992
  # già — "direct tools + fallback" — ma il codice faceva solo _run_fallback senza tool).
3993
  # Bug: query meteo/news/cerca non chiamavano mai i tool reali → LLM allucinava i dati
3994
  # → ResponseVerifier girava su risposta inventata → retry → 20-60s inutili.
3995
- # B5: query spiegazione pura → _run_fallback diretta (-20-30s risparmio)
3996
- # Scenari: "cos'è X", "spiegami Y", "how does Z work?", "explain W"
3997
- # Fail-open: se regex troppo larga → path normale (nessuna perdita)
3998
- if self._is_pure_explanation(goal):
3999
- try:
4000
- from api.state import record_timing as _rtcB5
4001
- _rtcB5("classify_ms", (_time.monotonic() - _t0_classify) * 1000)
4002
- except Exception:
4003
- pass
4004
- await self._transition_state(state, AgentState.THINKING, on_step)
4005
- _r = await _finish(await self._run_fallback(state, on_step))
4006
- _r["timing_ms"] = int((_time.monotonic() - _t_run) * 1000)
4007
- _r["effective_max_steps"] = state.max_steps
4008
- if _sid_token is not None:
4009
- try: _sid_var.reset(_sid_token)
4010
- except Exception: pass
4011
- if self._session_files:
4012
- asyncio.ensure_future(self._vfs_git_backup())
4013
- return _r
4014
-
4015
-
4016
  # P36: Hybrid Execution Router — Python code analysis fast path.
4017
  # Se goal contiene keyword analisi + codice Python nel context/goal,
4018
  # chiama python_analyze direttamente (<5ms) saltando planner+LLM (5-15s).
 
3777
  except Exception:
3778
  pass # decision_memory non disponibile — continua normalmente
3779
 
3780
+ # B5-ORDER: una spiegazione completa e non operativa è già un intento
3781
+ # sufficiente. Deve bypassare le guardie di ambiguità, che sono riservate a
3782
+ # comandi realmente vaghi; le guardie in _is_pure_explanation() proteggono
3783
+ # file, mutazioni, dati realtime e richieste troppo lunghe.
3784
+ # Sprint 5 ITEM 13: classify_ms — tempo routing/classificazione goal (sync, <1ms)
3785
+ _t0_classify = _time.monotonic()
3786
+ if self._is_pure_explanation(goal):
3787
+ try:
3788
+ from api.state import record_timing as _rtcB5
3789
+ _rtcB5("classify_ms", (_time.monotonic() - _t0_classify) * 1000)
3790
+ except Exception:
3791
+ pass
3792
+ await self._transition_state(state, AgentState.THINKING, on_step)
3793
+ _r = await _finish(await self._run_fallback(state, on_step))
3794
+ _r["timing_ms"] = int((_time.monotonic() - _t_run) * 1000)
3795
+ _r["effective_max_steps"] = state.max_steps
3796
+ if _sid_token is not None:
3797
+ try: _sid_var.reset(_sid_token)
3798
+ except Exception: pass
3799
+ if self._session_files:
3800
+ asyncio.ensure_future(self._vfs_git_backup())
3801
+ return _r
3802
+
3803
  # P29-B1: gate ambiguità strutturale — _is_goal_ambiguous() era P28-B2 dead code (mai chiamata).
3804
  # Zero LLM, <0.1ms. Lingua-aware via self._run_lang (P28-B1). Fires dopo blacklist e prima del routing.
3805
  if _is_goal_ambiguous(goal):
 
3963
  return _r_bl
3964
 
3965
 
 
 
 
3966
  # S402: Fast Path — greeting/ack/identità semplice → bypass tutto l'overhead
3967
  if self._is_simple_query(goal):
3968
  try:
 
4012
  # già — "direct tools + fallback" — ma il codice faceva solo _run_fallback senza tool).
4013
  # Bug: query meteo/news/cerca non chiamavano mai i tool reali → LLM allucinava i dati
4014
  # → ResponseVerifier girava su risposta inventata → retry → 20-60s inutili.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4015
  # P36: Hybrid Execution Router — Python code analysis fast path.
4016
  # Se goal contiene keyword analisi + codice Python nel context/goal,
4017
  # chiama python_analyze direttamente (<5ms) saltando planner+LLM (5-15s).
agents/unified_loop_llm.py CHANGED
@@ -119,12 +119,26 @@ class LLMSelectionMixin:
119
  return self._verifier_llm
120
 
121
  def _is_pure_explanation(self, goal: str) -> bool:
122
- """B5: True se goal è domanda concettuale pura nessun tool necessario.
123
- 4 guard fail-open: len<300 | pattern interrogativo | no action verb | no file ref."""
 
 
 
 
124
  if len(goal) > 300: return False
125
- if not self._PURE_EXPLANATION_RE.search(goal[:200]): return False
126
- if self._EXPL_ACTION_RE.search(goal[:200]): return False
127
- if self._EXPL_FILE_REF_RE.search(goal[:200]): return False
 
 
 
 
 
 
 
 
 
 
128
  return True
129
 
130
  # S371: _SKIP_SMOL_RE — skippa smolagents per query semplici (notizie, cerca) → direct tools
@@ -543,7 +557,7 @@ class LLMSelectionMixin:
543
  r"^\s*(?:"
544
  r"(?:cos'?[e\xe8]\s+)"
545
  r"|(?:che\s+cos'?[a\xe0]?\s*[e\xe8]\s+)"
546
- r"|(?:spiegami\b)"
547
  r"|(?:dimmi\s+(?:come|cosa|cos|perch[e\xe8]|qual[e\xe8])\b)"
548
  r"|(?:qual[e\xe8]\s+|qual\s+[e\xe8]\s+)(?:la\s+)?(?:differenz[ae]|scopo|significato)"
549
  r"|(?:come\s+funziona\s+(?!il\s+(?:mio|tuo|nostro|codice|progetto|login|sito|sistema|questo)\b))"
@@ -556,6 +570,11 @@ class LLMSelectionMixin:
556
  r")",
557
  re.IGNORECASE | re.DOTALL,
558
  )
 
 
 
 
 
559
  _EXPL_ACTION_RE = re.compile(
560
  r"\b(crea|scrivi|genera|implementa|esegui|correggi|fix|run|create|write|"
561
  r"generate|implement|execute|installa|deploy|avvia|configura|aggiorna|update|"
 
119
  return self._verifier_llm
120
 
121
  def _is_pure_explanation(self, goal: str) -> bool:
122
+ """True per una spiegazione concettuale completa che non richiede mutazioni.
123
+
124
+ I riferimenti nominali contestuali, per esempio ``dopo una modifica al
125
+ codice``, non trasformano una domanda esplicativa in un task operativo.
126
+ Una seconda azione imperativa resta invece un percorso operativo.
127
+ """
128
  if len(goal) > 300: return False
129
+ text = goal[:200]
130
+ if not self._PURE_EXPLANATION_RE.search(text): return False
131
+ if self._EXPL_FILE_REF_RE.search(text): return False
132
+ contextual_spans = [
133
+ match.span()
134
+ for match in self._EXPL_CONTEXTUAL_ACTION_REF_RE.finditer(text)
135
+ ]
136
+ for action in self._EXPL_ACTION_RE.finditer(text):
137
+ if not any(
138
+ start <= action.start() < end
139
+ for start, end in contextual_spans
140
+ ):
141
+ return False
142
  return True
143
 
144
  # S371: _SKIP_SMOL_RE — skippa smolagents per query semplici (notizie, cerca) → direct tools
 
557
  r"^\s*(?:"
558
  r"(?:cos'?[e\xe8]\s+)"
559
  r"|(?:che\s+cos'?[a\xe0]?\s*[e\xe8]\s+)"
560
+ r"|(?:spiega(?:mi)?\b)"
561
  r"|(?:dimmi\s+(?:come|cosa|cos|perch[e\xe8]|qual[e\xe8])\b)"
562
  r"|(?:qual[e\xe8]\s+|qual\s+[e\xe8]\s+)(?:la\s+)?(?:differenz[ae]|scopo|significato)"
563
  r"|(?:come\s+funziona\s+(?!il\s+(?:mio|tuo|nostro|codice|progetto|login|sito|sistema|questo)\b))"
 
570
  r")",
571
  re.IGNORECASE | re.DOTALL,
572
  )
573
+ _EXPL_CONTEXTUAL_ACTION_REF_RE = re.compile(
574
+ r"\b(?:dopo|prima|durante|in seguito a|a seguito di)\s+una\s+modifica\s+"
575
+ r"(?:al|del|nel)\s+(?:codice|file|progetto)\b",
576
+ re.IGNORECASE,
577
+ )
578
  _EXPL_ACTION_RE = re.compile(
579
  r"\b(crea|scrivi|genera|implementa|esegui|correggi|fix|run|create|write|"
580
  r"generate|implement|execute|installa|deploy|avvia|configura|aggiorna|update|"
tests/test_pure_explanation_routing.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ from agents.unified_loop_llm import LLMSelectionMixin
4
+ from agents.unified_loop_tools import DirectToolsMixin
5
+
6
+
7
+ class _ExplanationClassifier(LLMSelectionMixin, DirectToolsMixin):
8
+ """Harness minimale: il classificatore non richiede provider o stato runtime."""
9
+
10
+
11
+ def _classifier() -> _ExplanationClassifier:
12
+ return _ExplanationClassifier()
13
+
14
+
15
+ def test_observed_mobile_prompt_is_a_pure_explanation() -> None:
16
+ goal = (
17
+ "Spiega in una frase concreta come i test di regressione riducono il rischio "
18
+ "di introdurre bug dopo una modifica al codice."
19
+ )
20
+
21
+ assert _classifier()._is_pure_explanation(goal) is True
22
+
23
+
24
+ def test_technical_keywords_do_not_override_a_complete_explanation() -> None:
25
+ goal = (
26
+ "Spiega in una frase concreta come i test di regressione riducono il rischio "
27
+ "di introdurre bug dopo una modifica al codice."
28
+ )
29
+
30
+ assert _classifier()._needs_tools(goal) is True
31
+ assert _classifier()._is_pure_explanation(goal) is True
32
+
33
+
34
+ def test_pure_explanation_precedes_ambiguity_gates() -> None:
35
+ loop_source = (Path(__file__).parents[1] / "agents" / "unified_loop.py").read_text(
36
+ encoding="utf-8"
37
+ )
38
+
39
+ assert loop_source.index("if self._is_pure_explanation(goal):") < loop_source.index(
40
+ "if _is_goal_ambiguous(goal):"
41
+ )
42
+
43
+
44
+ def test_spiega_prefix_is_recognized_like_spiegami() -> None:
45
+ assert _classifier()._is_pure_explanation(
46
+ "Spiega perché la cache migliora le prestazioni di una applicazione."
47
+ ) is True
48
+ assert _classifier()._is_pure_explanation(
49
+ "Spiegami perché la cache migliora le prestazioni di una applicazione."
50
+ ) is True
51
+
52
+
53
+ def test_secondary_operational_action_stays_out_of_pure_explanation_route() -> None:
54
+ assert _classifier()._is_pure_explanation(
55
+ "Spiega la differenza tra cache e storage, poi modifica il file di configurazione."
56
+ ) is False
57
+
58
+
59
+ def test_explicit_file_reference_stays_out_of_pure_explanation_route() -> None:
60
+ assert _classifier()._is_pure_explanation(
61
+ "Spiega cosa fa questa funzione nel codice."
62
+ ) is False
63
+
64
+
65
+ def test_overlong_explanation_stays_on_normal_route() -> None:
66
+ assert _classifier()._is_pure_explanation("Spiega " + ("il concetto. " * 30)) is False