Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2eba98dff4 | |||
| c437ae0173 |
11
HISTORY.md
11
HISTORY.md
@@ -5,11 +5,22 @@ Changelog
|
||||
(unreleased)
|
||||
------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Increase LLM timeouts, refs NOISSUE. [Simon Diesenreiter]
|
||||
|
||||
|
||||
0.9.14 (2026-04-11)
|
||||
-------------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Add Ollama connection health details in UI, refs NOISSUE. [Simon
|
||||
Diesenreiter]
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
|
||||
|
||||
0.9.13 (2026-04-11)
|
||||
-------------------
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.9.14
|
||||
0.9.15
|
||||
|
||||
@@ -185,6 +185,7 @@ class LLMServiceClient:
|
||||
def __init__(self, ollama_url: str | None = None, model: str | None = None):
|
||||
self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/')
|
||||
self.model = model or settings.OLLAMA_MODEL
|
||||
self.request_timeout_seconds = settings.llm_request_timeout_seconds
|
||||
self.toolbox = LLMToolbox()
|
||||
self.live_tool_executor = LLMLiveToolExecutor()
|
||||
|
||||
@@ -290,13 +291,16 @@ class LLMServiceClient:
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.request_timeout_seconds)) as session:
|
||||
async with session.post(f'{self.ollama_url}/api/chat', json=request_payload) as resp:
|
||||
payload = await resp.json()
|
||||
if 200 <= resp.status < 300:
|
||||
return (payload.get('message') or {}).get('content', ''), payload, None
|
||||
return None, payload, str(payload.get('error') or payload)
|
||||
except Exception as exc:
|
||||
if exc.__class__.__name__ == 'TimeoutError':
|
||||
message = f'LLM request timed out after {self.request_timeout_seconds} seconds'
|
||||
return None, {'error': message}, message
|
||||
return None, {'error': str(exc)}, str(exc)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -222,6 +222,7 @@ class Settings(BaseSettings):
|
||||
# Ollama settings computed from environment
|
||||
OLLAMA_URL: str = "http://ollama:11434"
|
||||
OLLAMA_MODEL: str = "llama3"
|
||||
LLM_REQUEST_TIMEOUT_SECONDS: int = 240
|
||||
LLM_GUARDRAIL_PROMPT: str = (
|
||||
"You are operating inside AI Software Factory. Follow the requested schema exactly, "
|
||||
"treat provided tool outputs as authoritative, and do not invent repositories, issues, pull requests, or delivery facts."
|
||||
@@ -613,6 +614,11 @@ class Settings(BaseSettings):
|
||||
"""Get the maximum number of queued prompts to process in one batch."""
|
||||
return max(int(_resolve_runtime_setting_value('PROMPT_QUEUE_MAX_BATCH_SIZE', self.PROMPT_QUEUE_MAX_BATCH_SIZE)), 1)
|
||||
|
||||
@property
|
||||
def llm_request_timeout_seconds(self) -> int:
|
||||
"""Get the outbound provider timeout for one LLM request."""
|
||||
return max(int(_resolve_runtime_setting_value('LLM_REQUEST_TIMEOUT_SECONDS', self.LLM_REQUEST_TIMEOUT_SECONDS)), 1)
|
||||
|
||||
@property
|
||||
def projects_root(self) -> Path:
|
||||
"""Get the root directory for generated project artifacts."""
|
||||
|
||||
@@ -36,6 +36,7 @@ try:
|
||||
from .agents.orchestrator import AgentOrchestrator
|
||||
from .agents.n8n_setup import N8NSetupAgent
|
||||
from .agents.prompt_workflow import PromptWorkflowManager
|
||||
from .agents.telegram import TelegramHandler
|
||||
from .agents.ui_manager import UIManager
|
||||
from .models import ProjectHistory, ProjectLog, SystemLog
|
||||
except ImportError:
|
||||
@@ -49,6 +50,7 @@ except ImportError:
|
||||
from agents.orchestrator import AgentOrchestrator
|
||||
from agents.n8n_setup import N8NSetupAgent
|
||||
from agents.prompt_workflow import PromptWorkflowManager
|
||||
from agents.telegram import TelegramHandler
|
||||
from agents.ui_manager import UIManager
|
||||
from models import ProjectHistory, ProjectLog, SystemLog
|
||||
|
||||
@@ -256,6 +258,63 @@ def _ensure_summary_mentions_pull_request(summary_message: str, pull_request: di
|
||||
return f"{summary_message}{separator} Review PR: {pr_url}"
|
||||
|
||||
|
||||
def _should_queue_telegram_request(request: FreeformSoftwareRequest) -> bool:
|
||||
"""Return whether a Telegram request should be accepted for background processing."""
|
||||
return (
|
||||
request.source == 'telegram'
|
||||
and bool(request.chat_id)
|
||||
and bool(database_module.settings.telegram_bot_token)
|
||||
and not request.process_now
|
||||
)
|
||||
|
||||
|
||||
def _schedule_prompt_queue_processing() -> None:
|
||||
"""Kick off background queue processing without blocking the current HTTP request."""
|
||||
if database_module.settings.prompt_queue_enabled and not database_module.settings.prompt_queue_auto_process:
|
||||
return
|
||||
limit = database_module.settings.prompt_queue_max_batch_size if database_module.settings.prompt_queue_enabled else 1
|
||||
force = database_module.settings.prompt_queue_force_process if database_module.settings.prompt_queue_enabled else True
|
||||
task = asyncio.create_task(_process_prompt_queue_batch(limit=limit, force=force))
|
||||
|
||||
def _log_task_result(completed_task: asyncio.Task) -> None:
|
||||
try:
|
||||
completed_task.result()
|
||||
except Exception as exc:
|
||||
db = database_module.get_db_sync()
|
||||
try:
|
||||
DatabaseManager(db).log_system_event('prompt-queue', 'ERROR', f'Background queue processing failed: {exc}')
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
task.add_done_callback(_log_task_result)
|
||||
|
||||
|
||||
async def _notify_telegram_queue_result(request: FreeformSoftwareRequest, *, response: dict | None = None, error_message: str | None = None) -> None:
|
||||
"""Send the final queued result back to Telegram when chat metadata is available."""
|
||||
if request.source != 'telegram' or not request.chat_id or not database_module.settings.telegram_bot_token:
|
||||
return
|
||||
if response is not None:
|
||||
message = (
|
||||
response.get('summary_message')
|
||||
or (response.get('data') or {}).get('summary_message')
|
||||
or response.get('message')
|
||||
or 'Software generation completed.'
|
||||
)
|
||||
else:
|
||||
message = f"Software generation failed: {error_message or 'Unknown error'}"
|
||||
result = await TelegramHandler(webhook_url=database_module.settings.backend_public_url).send_message(
|
||||
bot_token=database_module.settings.telegram_bot_token,
|
||||
chat_id=request.chat_id,
|
||||
text=message,
|
||||
)
|
||||
if result.get('status') == 'error':
|
||||
db = database_module.get_db_sync()
|
||||
try:
|
||||
DatabaseManager(db).log_system_event('telegram', 'ERROR', f"Unable to send queued Telegram update: {result.get('message')}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _serialize_system_log(log: SystemLog) -> dict:
|
||||
"""Serialize a system log row."""
|
||||
return {
|
||||
@@ -732,6 +791,7 @@ async def _process_prompt_queue_batch(limit: int = 1, force: bool = False) -> di
|
||||
process_now=True,
|
||||
)
|
||||
response = await _run_freeform_generation(request, work_db, queue_item_id=claimed['id'])
|
||||
await _notify_telegram_queue_result(request, response=response)
|
||||
processed.append(
|
||||
{
|
||||
'queue_item_id': claimed['id'],
|
||||
@@ -741,6 +801,7 @@ async def _process_prompt_queue_batch(limit: int = 1, force: bool = False) -> di
|
||||
)
|
||||
except Exception as exc:
|
||||
DatabaseManager(work_db).fail_queued_prompt(claimed['id'], str(exc))
|
||||
await _notify_telegram_queue_result(request, error_message=str(exc))
|
||||
processed.append({'queue_item_id': claimed['id'], 'status': 'failed', 'error': str(exc)})
|
||||
finally:
|
||||
work_db.close()
|
||||
@@ -960,7 +1021,7 @@ async def generate_software_from_text(request: FreeformSoftwareRequest, db: DbSe
|
||||
},
|
||||
}
|
||||
|
||||
if request.source == 'telegram' and database_module.settings.prompt_queue_enabled and not request.process_now:
|
||||
if _should_queue_telegram_request(request):
|
||||
manager = DatabaseManager(db)
|
||||
queue_item = manager.enqueue_prompt(
|
||||
prompt_text=request.prompt_text,
|
||||
@@ -969,12 +1030,19 @@ async def generate_software_from_text(request: FreeformSoftwareRequest, db: DbSe
|
||||
chat_type=request.chat_type,
|
||||
source_context={'chat_id': request.chat_id, 'chat_type': request.chat_type},
|
||||
)
|
||||
queue_gate = await _get_queue_gate_status(force=False)
|
||||
if not database_module.settings.prompt_queue_enabled or database_module.settings.prompt_queue_auto_process:
|
||||
_schedule_prompt_queue_processing()
|
||||
return {
|
||||
'status': 'queued',
|
||||
'message': 'Prompt queued for energy-aware processing.',
|
||||
'message': (
|
||||
'Prompt accepted for background processing.'
|
||||
if not database_module.settings.prompt_queue_enabled else
|
||||
'Prompt queued for background processing.'
|
||||
),
|
||||
'queue_item': queue_item,
|
||||
'queue_summary': manager.get_prompt_queue_summary(),
|
||||
'queue_gate': await _get_queue_gate_status(force=False),
|
||||
'queue_gate': queue_gate,
|
||||
'source': {
|
||||
'type': request.source,
|
||||
'chat_id': request.chat_id,
|
||||
|
||||
Reference in New Issue
Block a user