fix: tool registry emits Anthropic schema, question lifecycle cleanup
- Tool.to_schema() emits input_schema (Anthropic Messages API) instead of OpenAI-style parameters - QuestionManager: remove questions from _pending on answer and timeout, reject double answers, add list_pending() - core/skills/__init__.py so skills are importable as a package - revert optional-import fallbacks in logger/message_bus: nats-py and loguru are hard requirements, a broken install should fail loudly - tests for wait_for_answer happy path and timeout cleanup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012AJLYcbTHCe45fyFnMfEin
This commit is contained in:
@@ -110,24 +110,33 @@ class QuestionManager:
|
||||
raise TimeoutError(f"Question {question.question_id} timed out waiting for human input")
|
||||
finally:
|
||||
self._futures.pop(question.question_id, None)
|
||||
self._pending.pop(question.question_id, None)
|
||||
|
||||
def answer_question(self, question_id: str, answer: Any) -> bool:
|
||||
"""Called when human operator submits an answer from the UI."""
|
||||
"""Called when human operator submits an answer from the UI.
|
||||
|
||||
Returns False if the question is unknown or was already answered."""
|
||||
question = self._pending.get(question_id)
|
||||
if not question:
|
||||
logger.warning(f"No pending question found for id {question_id}")
|
||||
return False
|
||||
if question.answered:
|
||||
logger.warning(f"Question {question_id} already answered; ignoring")
|
||||
return False
|
||||
|
||||
question.answered = True
|
||||
question.answer = answer
|
||||
|
||||
fut = self._futures.get(question_id)
|
||||
if fut and not fut.done():
|
||||
fut.set_result(answer)
|
||||
return True
|
||||
|
||||
fut.set_result(answer) # wait_for_answer() removes it from _pending
|
||||
else:
|
||||
self._pending.pop(question_id, None) # nobody waiting; don't leak
|
||||
return True
|
||||
|
||||
def list_pending(self) -> List[Question]:
|
||||
return [q for q in self._pending.values() if not q.answered]
|
||||
|
||||
def get_pending(self, question_id: str) -> Optional[Question]:
|
||||
return self._pending.get(question_id)
|
||||
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
"""Structured logging for LogiFlow AI — loguru setup with standard logging fallback."""
|
||||
"""Structured logging for LogiFlow AI — single loguru setup imported by all modules."""
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
try:
|
||||
from loguru import logger
|
||||
Path("logs").mkdir(exist_ok=True)
|
||||
|
||||
Path("logs").mkdir(exist_ok=True)
|
||||
logger.remove()
|
||||
|
||||
logger.remove()
|
||||
logger.add(
|
||||
sys.stderr,
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||
level=os.getenv("LOG_LEVEL", "INFO"),
|
||||
colorize=False,
|
||||
)
|
||||
|
||||
logger.add(
|
||||
sys.stderr,
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||
level=os.getenv("LOG_LEVEL", "INFO"),
|
||||
colorize=False,
|
||||
)
|
||||
|
||||
logger.add(
|
||||
"logs/logiflow_{time:YYYY-MM-DD}.log",
|
||||
rotation="100 MB",
|
||||
retention="30 days",
|
||||
compression="gz",
|
||||
level="DEBUG",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||
)
|
||||
except ImportError:
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)-8s | %(message)s")
|
||||
logger = logging.getLogger("logiflow")
|
||||
logger.add(
|
||||
"logs/logiflow_{time:YYYY-MM-DD}.log",
|
||||
rotation="100 MB",
|
||||
retention="30 days",
|
||||
compression="gz",
|
||||
level="DEBUG",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||
)
|
||||
|
||||
__all__ = ["logger"]
|
||||
|
||||
@@ -8,11 +8,8 @@ from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
try:
|
||||
import nats
|
||||
import nats.js.errors
|
||||
except ImportError:
|
||||
nats = None
|
||||
import nats
|
||||
import nats.js.errors
|
||||
|
||||
from core.types import AgentMessage, MessageType
|
||||
from core.logger import logger
|
||||
@@ -59,9 +56,6 @@ class MessageBus:
|
||||
|
||||
async def connect(self):
|
||||
"""Connect to NATS and create the logistics JetStream stream."""
|
||||
if nats is None:
|
||||
logger.warning("nats-py not installed; message bus running in local in-memory mode")
|
||||
return
|
||||
self._nc = await nats.connect(
|
||||
servers=[f"nats://{NATS_HOST}:{NATS_PORT}"],
|
||||
user=NATS_USER,
|
||||
|
||||
0
core/skills/__init__.py
Normal file
0
core/skills/__init__.py
Normal file
@@ -33,7 +33,7 @@ class Tool:
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_schema(self) -> Dict[str, Any]:
|
||||
"""Generate OpenAI/Claude compatible JSON Schema for tool calling."""
|
||||
"""Generate an Anthropic Messages API tool definition (name / description / input_schema)."""
|
||||
properties = {}
|
||||
required = []
|
||||
|
||||
@@ -56,7 +56,7 @@ class Tool:
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"parameters": {
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required,
|
||||
|
||||
Reference in New Issue
Block a user