Commit ·
6c17c25
1
Parent(s): 627d167
Fix: Propagate timeout to AI backend and increase default to 60s
Browse files
src/nacc_orchestrator/agents.py
CHANGED
|
@@ -21,7 +21,7 @@ class AgentBackendError(RuntimeError):
|
|
| 21 |
|
| 22 |
|
| 23 |
class LLMBackend:
|
| 24 |
-
def complete(self, prompt: str, *, context: dict[str, Any] | None = None) -> str: # pragma: no cover - interface
|
| 25 |
raise NotImplementedError
|
| 26 |
|
| 27 |
|
|
@@ -40,7 +40,7 @@ class DockerMistralBackend(LLMBackend):
|
|
| 40 |
self.timeout = timeout
|
| 41 |
self.environment = environment
|
| 42 |
|
| 43 |
-
def complete(self, prompt: str, *, context: dict[str, Any] | None = None) -> str:
|
| 44 |
# Merge prompt with context
|
| 45 |
merged_prompt = prompt
|
| 46 |
if context:
|
|
@@ -54,7 +54,8 @@ class DockerMistralBackend(LLMBackend):
|
|
| 54 |
["docker", "model", "run", self.model_name, merged_prompt],
|
| 55 |
text=True,
|
| 56 |
capture_output=True,
|
| 57 |
-
|
|
|
|
| 58 |
env=env,
|
| 59 |
)
|
| 60 |
|
|
@@ -68,7 +69,7 @@ class DockerMistralBackend(LLMBackend):
|
|
| 68 |
class LocalHeuristicBackend(LLMBackend):
|
| 69 |
"""Deterministic fallback backend for development/testing."""
|
| 70 |
|
| 71 |
-
def complete(self, prompt: str, *, context: dict[str, Any] | None = None) -> str:
|
| 72 |
summary = {
|
| 73 |
"prompt_hash": hash(prompt) & 0xFFFFFFFF,
|
| 74 |
"context_keys": sorted(list((context or {}).keys())),
|
|
@@ -273,9 +274,9 @@ class AgentSuite:
|
|
| 273 |
request = RouterRequest(task=description, required_tags=preferred_tags, parallelism=1)
|
| 274 |
return self.router.select_nodes(request)
|
| 275 |
|
| 276 |
-
def probe_backend(self, message: str = "NACC orchestrator health check", context: dict[str, Any] | None = None) -> str:
|
| 277 |
probe_context = context or {"source": "nacc-orchestrator", "kind": "health-check"}
|
| 278 |
-
return self.backend.complete(message, context=probe_context)
|
| 279 |
|
| 280 |
|
| 281 |
__all__ = [
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
class LLMBackend:
|
| 24 |
+
def complete(self, prompt: str, *, context: dict[str, Any] | None = None, timeout: float | None = None) -> str: # pragma: no cover - interface
|
| 25 |
raise NotImplementedError
|
| 26 |
|
| 27 |
|
|
|
|
| 40 |
self.timeout = timeout
|
| 41 |
self.environment = environment
|
| 42 |
|
| 43 |
+
def complete(self, prompt: str, *, context: dict[str, Any] | None = None, timeout: float | None = None) -> str:
|
| 44 |
# Merge prompt with context
|
| 45 |
merged_prompt = prompt
|
| 46 |
if context:
|
|
|
|
| 54 |
["docker", "model", "run", self.model_name, merged_prompt],
|
| 55 |
text=True,
|
| 56 |
capture_output=True,
|
| 57 |
+
capture_output=True,
|
| 58 |
+
timeout=timeout or self.timeout,
|
| 59 |
env=env,
|
| 60 |
)
|
| 61 |
|
|
|
|
| 69 |
class LocalHeuristicBackend(LLMBackend):
|
| 70 |
"""Deterministic fallback backend for development/testing."""
|
| 71 |
|
| 72 |
+
def complete(self, prompt: str, *, context: dict[str, Any] | None = None, timeout: float | None = None) -> str:
|
| 73 |
summary = {
|
| 74 |
"prompt_hash": hash(prompt) & 0xFFFFFFFF,
|
| 75 |
"context_keys": sorted(list((context or {}).keys())),
|
|
|
|
| 274 |
request = RouterRequest(task=description, required_tags=preferred_tags, parallelism=1)
|
| 275 |
return self.router.select_nodes(request)
|
| 276 |
|
| 277 |
+
def probe_backend(self, message: str = "NACC orchestrator health check", context: dict[str, Any] | None = None, timeout: float | None = None) -> str:
|
| 278 |
probe_context = context or {"source": "nacc-orchestrator", "kind": "health-check"}
|
| 279 |
+
return self.backend.complete(message, context=probe_context, timeout=timeout)
|
| 280 |
|
| 281 |
|
| 282 |
__all__ = [
|
src/nacc_orchestrator/blaxel_backend.py
CHANGED
|
@@ -72,7 +72,7 @@ class BlaxelBackend:
|
|
| 72 |
# The first part uses the workload name (gemini-2-0-flash-exp), second part uses actual model
|
| 73 |
self.endpoint_url = f"{base_url}/{workspace}/models/gemini-2-0-flash-exp/v1beta/models/{model}:generateContent"
|
| 74 |
|
| 75 |
-
def complete(self, prompt: str, *, context: dict[str, Any] | None = None) -> str:
|
| 76 |
"""
|
| 77 |
Generate completion using Blaxel Model Gateway.
|
| 78 |
|
|
@@ -132,7 +132,8 @@ class BlaxelBackend:
|
|
| 132 |
self.endpoint_url,
|
| 133 |
headers=headers,
|
| 134 |
json=payload,
|
| 135 |
-
|
|
|
|
| 136 |
)
|
| 137 |
response.raise_for_status()
|
| 138 |
|
|
@@ -177,7 +178,8 @@ class BlaxelBackend:
|
|
| 177 |
self.endpoint_url,
|
| 178 |
headers=headers,
|
| 179 |
json=payload,
|
| 180 |
-
|
|
|
|
| 181 |
)
|
| 182 |
response.raise_for_status()
|
| 183 |
|
|
|
|
| 72 |
# The first part uses the workload name (gemini-2-0-flash-exp), second part uses actual model
|
| 73 |
self.endpoint_url = f"{base_url}/{workspace}/models/gemini-2-0-flash-exp/v1beta/models/{model}:generateContent"
|
| 74 |
|
| 75 |
+
def complete(self, prompt: str, *, context: dict[str, Any] | None = None, timeout: float | None = None) -> str:
|
| 76 |
"""
|
| 77 |
Generate completion using Blaxel Model Gateway.
|
| 78 |
|
|
|
|
| 132 |
self.endpoint_url,
|
| 133 |
headers=headers,
|
| 134 |
json=payload,
|
| 135 |
+
json=payload,
|
| 136 |
+
timeout=timeout or self.timeout
|
| 137 |
)
|
| 138 |
response.raise_for_status()
|
| 139 |
|
|
|
|
| 178 |
self.endpoint_url,
|
| 179 |
headers=headers,
|
| 180 |
json=payload,
|
| 181 |
+
json=payload,
|
| 182 |
+
timeout=timeout or self.timeout
|
| 183 |
)
|
| 184 |
response.raise_for_status()
|
| 185 |
|
src/nacc_orchestrator/server.py
CHANGED
|
@@ -46,7 +46,7 @@ class ChatPayload(BaseModel):
|
|
| 46 |
session_id: str = Field(default="default")
|
| 47 |
current_node: str | None = None
|
| 48 |
current_path: str = Field(default="/home")
|
| 49 |
-
timeout: float = Field(default=
|
| 50 |
|
| 51 |
|
| 52 |
def build_service(config_path: str | None = None) -> OrchestratorService:
|
|
@@ -276,7 +276,7 @@ def create_app(service: OrchestratorService) -> FastAPI:
|
|
| 276 |
If the query doesn't match any tool, set "tool" to "general_response" and include your response in "reasoning"."""
|
| 277 |
|
| 278 |
# Get AI decision
|
| 279 |
-
ai_result = service.check_agent_backend(ai_prompt, context)
|
| 280 |
ai_response = ai_result.get("response", "")
|
| 281 |
|
| 282 |
# Try to parse AI's structured response
|
|
|
|
| 46 |
session_id: str = Field(default="default")
|
| 47 |
current_node: str | None = None
|
| 48 |
current_path: str = Field(default="/home")
|
| 49 |
+
timeout: float = Field(default=60.0, gt=0.0, le=600.0)
|
| 50 |
|
| 51 |
|
| 52 |
def build_service(config_path: str | None = None) -> OrchestratorService:
|
|
|
|
| 276 |
If the query doesn't match any tool, set "tool" to "general_response" and include your response in "reasoning"."""
|
| 277 |
|
| 278 |
# Get AI decision
|
| 279 |
+
ai_result = service.check_agent_backend(ai_prompt, context, timeout=payload.timeout)
|
| 280 |
ai_response = ai_result.get("response", "")
|
| 281 |
|
| 282 |
# Try to parse AI's structured response
|