feat(api): Add human feedback API (#6) + async_utils module
Phase 6.6 人類回饋 API:
- PUT /api/v1/incidents/{id}/feedback endpoint
- effectiveness_score (1-5), human_feedback, learning_notes fields
- Sync to Redis (Working Memory) + PostgreSQL (Episodic Memory)
- For stats aggregation at /api/v1/stats/feedback/summary
async_utils module:
- fire_and_forget() for safe background tasks
- Prevents swallowed exceptions in asyncio.create_task()
- Addresses P2 #8 tech debt
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
54
apps/api/src/core/async_utils.py
Normal file
54
apps/api/src/core/async_utils.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Async Utilities
|
||||
===============
|
||||
Safe async patterns for production use.
|
||||
|
||||
ADR-013: 此模組包含關鍵異步模式,修改前請先讀懂註解!
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
from typing import Any
|
||||
|
||||
from src.core.logging import get_logger
|
||||
|
||||
logger = get_logger("awoooi.async_utils")
|
||||
|
||||
|
||||
def fire_and_forget(coro: Coroutine[Any, Any, Any], name: str | None = None) -> asyncio.Task[Any]:
|
||||
"""
|
||||
安全的 fire-and-forget 模式。
|
||||
|
||||
🔴 重要:不使用此函數的 asyncio.create_task() 會吞掉例外!
|
||||
|
||||
Args:
|
||||
coro: 要執行的協程
|
||||
name: 任務名稱 (用於日誌)
|
||||
|
||||
Returns:
|
||||
已建立的任務 (可忽略)
|
||||
|
||||
Example:
|
||||
# ✅ 正確用法
|
||||
fire_and_forget(send_notification(user_id), name="notification")
|
||||
|
||||
# ❌ 錯誤用法 (例外會被吞掉)
|
||||
asyncio.create_task(send_notification(user_id))
|
||||
"""
|
||||
task = asyncio.create_task(coro, name=name)
|
||||
|
||||
def _handle_exception(t: asyncio.Task[Any]) -> None:
|
||||
try:
|
||||
exc = t.exception()
|
||||
if exc is not None:
|
||||
logger.error(
|
||||
"fire_and_forget_exception",
|
||||
task_name=name or "unnamed",
|
||||
error=str(exc),
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
logger.debug("fire_and_forget_cancelled", task_name=name or "unnamed")
|
||||
|
||||
task.add_done_callback(_handle_exception)
|
||||
return task
|
||||
Reference in New Issue
Block a user