feat: Ollama qwen3:14b 기반 AI 뉴스 요약 + 텔레그램 통합 허브

- stock-lab: POST /api/stock/news/summarize 추가 (Ollama /api/generate 호출, 토큰/duration 추적)
- agent-office: telegram 패키지 분해 (client/formatter/messaging/webhook/router/agent_registry)
- send_agent_message 통합 API로 에이전트 중립 메시지 포맷 표준화
- 텔레그램 → 에이전트 명령 라우터 (/status, /stock news, /music credits 등)
- 토큰 사용량 집계 API 및 GET /agents/{id}/token-usage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 01:44:27 +09:00
parent de91f424a3
commit 86e7f727eb
17 changed files with 696 additions and 116 deletions

View File

@@ -261,6 +261,59 @@ def mark_telegram_responded(callback_id: str, action: str) -> None:
)
def get_token_usage_stats(agent_id: str, days: int = 1) -> dict:
"""지정 에이전트의 최근 N일 토큰 사용량 집계.
agent_tasks 테이블의 result_data JSON에서 tokens.total을 합산.
반환: {"total_tokens": int, "task_count": int, "by_day": [{"date": "YYYY-MM-DD", "tokens": int}]}
"""
with _conn() as conn:
rows = conn.execute(
"""
SELECT completed_at, result_data
FROM agent_tasks
WHERE agent_id = ?
AND status = 'succeeded'
AND completed_at IS NOT NULL
AND completed_at >= strftime('%Y-%m-%dT%H:%M:%fZ','now', ?)
""",
(agent_id, f"-{int(days)} days"),
).fetchall()
total_tokens = 0
task_count = 0
by_day_map: Dict[str, int] = {}
for r in rows:
result_data = r["result_data"]
if not result_data:
continue
try:
parsed = json.loads(result_data)
except Exception:
continue
tokens = parsed.get("tokens") if isinstance(parsed, dict) else None
total = 0
if isinstance(tokens, dict):
total = int(tokens.get("total", 0) or 0)
if total <= 0:
continue
total_tokens += total
task_count += 1
completed_at = r["completed_at"] or ""
day = completed_at[:10] if completed_at else "unknown"
by_day_map[day] = by_day_map.get(day, 0) + total
by_day = [
{"date": d, "tokens": t}
for d, t in sorted(by_day_map.items())
]
return {
"total_tokens": total_tokens,
"task_count": task_count,
"by_day": by_day,
}
def get_activity_feed(limit: int = 50, offset: int = 0) -> dict:
with _conn() as conn:
total_row = conn.execute("""