Add API usage tracking and dynamic task reloading
Features: - Usage tracking system (usage_tracker.py) - Tracks input/output tokens per API call - Calculates costs with support for cache pricing - Stores data in usage_data.json (gitignored) - Integrated into llm_interface.py - Dynamic task scheduler reloading - Auto-detects YAML changes every 60s - No restart needed for new tasks - reload_tasks() method for manual refresh - Example cost tracking scheduled task - Daily API usage report - Budget tracking ($5/month target) - Disabled by default in scheduled_tasks.yaml Improvements: - Fixed tool_use/tool_result pair splitting bug (CRITICAL) - Added thread safety to agent.chat() - Fixed N+1 query problem in hybrid search - Optimized database batch queries - Added conversation history pruning (50 messages max) Updated .gitignore: - Exclude user profiles (memory_workspace/users/*.md) - Exclude usage data (usage_data.json) - Exclude vector index (vectors.usearch) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,8 +84,20 @@ class AdapterRuntime:
|
||||
self._postprocessors.append(postprocessor)
|
||||
|
||||
def _on_message_received(self, message: InboundMessage) -> None:
|
||||
"""Handle incoming message from an adapter."""
|
||||
asyncio.create_task(self._message_queue.put(message))
|
||||
"""Handle incoming message from an adapter.
|
||||
|
||||
This may be called from different event loop contexts (e.g.,
|
||||
python-telegram-bot's internal loop vs. our main asyncio loop),
|
||||
so we use loop-safe scheduling instead of create_task().
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.call_soon_threadsafe(self._message_queue.put_nowait, message)
|
||||
except RuntimeError:
|
||||
# No running loop - should not happen in normal operation
|
||||
# but handle gracefully
|
||||
print("[Runtime] Warning: No event loop for message dispatch")
|
||||
self._message_queue.put_nowait(message)
|
||||
|
||||
async def _process_message_queue(self) -> None:
|
||||
"""Background task to process incoming messages."""
|
||||
|
||||
@@ -39,6 +39,7 @@ class SlackAdapter(BaseAdapter):
|
||||
super().__init__(config)
|
||||
self.app: Optional[AsyncApp] = None
|
||||
self.handler: Optional[AsyncSocketModeHandler] = None
|
||||
self._username_cache: Dict[str, str] = {} # user_id -> username
|
||||
|
||||
@property
|
||||
def platform_name(self) -> str:
|
||||
@@ -255,7 +256,15 @@ class SlackAdapter(BaseAdapter):
|
||||
}
|
||||
|
||||
async def _get_username(self, user_id: str) -> str:
|
||||
"""Get username from user ID."""
|
||||
"""Get username from user ID, with caching to avoid excessive API calls.
|
||||
|
||||
Sanitizes the returned username to contain only alphanumeric,
|
||||
hyphens, and underscores (matching memory_system validation rules).
|
||||
"""
|
||||
# Check cache first
|
||||
if user_id in self._username_cache:
|
||||
return self._username_cache[user_id]
|
||||
|
||||
if not self.app:
|
||||
return user_id
|
||||
|
||||
@@ -263,13 +272,20 @@ class SlackAdapter(BaseAdapter):
|
||||
result = await self.app.client.users_info(user=user_id)
|
||||
user = result["user"]
|
||||
profile = user.get("profile", {})
|
||||
return (
|
||||
raw_username = (
|
||||
profile.get("display_name")
|
||||
or profile.get("real_name")
|
||||
or user.get("name")
|
||||
or user_id
|
||||
)
|
||||
# Sanitize: replace spaces/special chars with underscores
|
||||
sanitized = "".join(
|
||||
c if c.isalnum() or c in "-_" else "_" for c in raw_username
|
||||
)
|
||||
self._username_cache[user_id] = sanitized
|
||||
return sanitized
|
||||
except SlackApiError:
|
||||
self._username_cache[user_id] = user_id
|
||||
return user_id
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -224,12 +224,22 @@ class TelegramAdapter(BaseAdapter):
|
||||
else None
|
||||
)
|
||||
|
||||
sent_message = await self.bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=chunk,
|
||||
parse_mode=parse_mode,
|
||||
reply_to_message_id=reply_to_id,
|
||||
)
|
||||
try:
|
||||
sent_message = await self.bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=chunk,
|
||||
parse_mode=parse_mode,
|
||||
reply_to_message_id=reply_to_id,
|
||||
)
|
||||
except TelegramError:
|
||||
# Markdown parse errors are common with LLM-generated
|
||||
# text (unbalanced *, _, etc). Fall back to plain text.
|
||||
sent_message = await self.bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=chunk,
|
||||
parse_mode=None,
|
||||
reply_to_message_id=reply_to_id,
|
||||
)
|
||||
|
||||
results.append({
|
||||
"message_id": sent_message.message_id,
|
||||
|
||||
Reference in New Issue
Block a user