mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-01 17:17:24 -04:00
feat: add GET /reasoning API endpoint
- Add Pydantic models for reasoning API responses - Implement GET /reasoning with job_id, date, model filters - Support include_full_conversation parameter - Add comprehensive unit tests (8 tests) - Return deployment mode info in responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
221
api/main.py
221
api/main.py
@@ -114,6 +114,51 @@ class HealthResponse(BaseModel):
|
||||
preserve_dev_data: Optional[bool] = None
|
||||
|
||||
|
||||
class ReasoningMessage(BaseModel):
|
||||
"""Individual message in a reasoning conversation."""
|
||||
message_index: int
|
||||
role: str
|
||||
content: str
|
||||
summary: Optional[str] = None
|
||||
tool_name: Optional[str] = None
|
||||
tool_input: Optional[str] = None
|
||||
timestamp: str
|
||||
|
||||
|
||||
class PositionSummary(BaseModel):
|
||||
"""Trading position summary."""
|
||||
action_id: int
|
||||
action_type: Optional[str] = None
|
||||
symbol: Optional[str] = None
|
||||
amount: Optional[int] = None
|
||||
price: Optional[float] = None
|
||||
cash_after: float
|
||||
portfolio_value: float
|
||||
|
||||
|
||||
class TradingSessionResponse(BaseModel):
|
||||
"""Single trading session with positions and optional conversation."""
|
||||
session_id: int
|
||||
job_id: str
|
||||
date: str
|
||||
model: str
|
||||
session_summary: Optional[str] = None
|
||||
started_at: str
|
||||
completed_at: Optional[str] = None
|
||||
total_messages: Optional[int] = None
|
||||
positions: List[PositionSummary]
|
||||
conversation: Optional[List[ReasoningMessage]] = None
|
||||
|
||||
|
||||
class ReasoningResponse(BaseModel):
|
||||
"""Response body for GET /reasoning."""
|
||||
sessions: List[TradingSessionResponse]
|
||||
count: int
|
||||
deployment_mode: str
|
||||
is_dev_mode: bool
|
||||
preserve_dev_data: Optional[bool] = None
|
||||
|
||||
|
||||
def create_app(
|
||||
db_path: str = "data/jobs.db",
|
||||
config_path: str = "/tmp/runtime_config.json" if Path("/tmp/runtime_config.json").exists() else "configs/default_config.json"
|
||||
@@ -482,6 +527,182 @@ def create_app(
|
||||
logger.error(f"Failed to query results: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@app.get("/reasoning", response_model=ReasoningResponse)
|
||||
async def get_reasoning(
|
||||
job_id: Optional[str] = Query(None, description="Filter by job ID"),
|
||||
date: Optional[str] = Query(None, description="Filter by date (YYYY-MM-DD)"),
|
||||
model: Optional[str] = Query(None, description="Filter by model signature"),
|
||||
include_full_conversation: bool = Query(False, description="Include full conversation history")
|
||||
):
|
||||
"""
|
||||
Query reasoning logs from trading sessions.
|
||||
|
||||
Supports filtering by job_id, date, and/or model.
|
||||
Returns session summaries with positions and optionally full conversation history.
|
||||
|
||||
Args:
|
||||
job_id: Optional job UUID filter
|
||||
date: Optional date filter (YYYY-MM-DD)
|
||||
model: Optional model signature filter
|
||||
include_full_conversation: Include all messages (default: false, only returns summaries)
|
||||
|
||||
Returns:
|
||||
List of trading sessions with positions and optional conversation
|
||||
|
||||
Raises:
|
||||
HTTPException 400: Invalid date format
|
||||
HTTPException 404: No sessions found matching filters
|
||||
"""
|
||||
try:
|
||||
# Validate date format if provided
|
||||
if date:
|
||||
try:
|
||||
datetime.strptime(date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid date format: {date}. Expected YYYY-MM-DD"
|
||||
)
|
||||
|
||||
conn = get_db_connection(app.state.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Build query for trading sessions with filters
|
||||
query = """
|
||||
SELECT
|
||||
ts.id,
|
||||
ts.job_id,
|
||||
ts.date,
|
||||
ts.model,
|
||||
ts.session_summary,
|
||||
ts.started_at,
|
||||
ts.completed_at,
|
||||
ts.total_messages
|
||||
FROM trading_sessions ts
|
||||
WHERE 1=1
|
||||
"""
|
||||
params = []
|
||||
|
||||
if job_id:
|
||||
query += " AND ts.job_id = ?"
|
||||
params.append(job_id)
|
||||
|
||||
if date:
|
||||
query += " AND ts.date = ?"
|
||||
params.append(date)
|
||||
|
||||
if model:
|
||||
query += " AND ts.model = ?"
|
||||
params.append(model)
|
||||
|
||||
query += " ORDER BY ts.date, ts.model"
|
||||
|
||||
cursor.execute(query, params)
|
||||
session_rows = cursor.fetchall()
|
||||
|
||||
if not session_rows:
|
||||
conn.close()
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="No trading sessions found matching the provided filters"
|
||||
)
|
||||
|
||||
sessions = []
|
||||
for session_row in session_rows:
|
||||
session_id = session_row[0]
|
||||
|
||||
# Fetch positions for this session
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
p.action_id,
|
||||
p.action_type,
|
||||
p.symbol,
|
||||
p.amount,
|
||||
p.price,
|
||||
p.cash,
|
||||
p.portfolio_value
|
||||
FROM positions p
|
||||
WHERE p.session_id = ?
|
||||
ORDER BY p.action_id
|
||||
""", (session_id,))
|
||||
|
||||
position_rows = cursor.fetchall()
|
||||
positions = [
|
||||
PositionSummary(
|
||||
action_id=row[0],
|
||||
action_type=row[1],
|
||||
symbol=row[2],
|
||||
amount=row[3],
|
||||
price=row[4],
|
||||
cash_after=row[5],
|
||||
portfolio_value=row[6]
|
||||
)
|
||||
for row in position_rows
|
||||
]
|
||||
|
||||
# Optionally fetch full conversation
|
||||
conversation = None
|
||||
if include_full_conversation:
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
rl.message_index,
|
||||
rl.role,
|
||||
rl.content,
|
||||
rl.summary,
|
||||
rl.tool_name,
|
||||
rl.tool_input,
|
||||
rl.timestamp
|
||||
FROM reasoning_logs rl
|
||||
WHERE rl.session_id = ?
|
||||
ORDER BY rl.message_index
|
||||
""", (session_id,))
|
||||
|
||||
message_rows = cursor.fetchall()
|
||||
conversation = [
|
||||
ReasoningMessage(
|
||||
message_index=row[0],
|
||||
role=row[1],
|
||||
content=row[2],
|
||||
summary=row[3],
|
||||
tool_name=row[4],
|
||||
tool_input=row[5],
|
||||
timestamp=row[6]
|
||||
)
|
||||
for row in message_rows
|
||||
]
|
||||
|
||||
sessions.append(
|
||||
TradingSessionResponse(
|
||||
session_id=session_row[0],
|
||||
job_id=session_row[1],
|
||||
date=session_row[2],
|
||||
model=session_row[3],
|
||||
session_summary=session_row[4],
|
||||
started_at=session_row[5],
|
||||
completed_at=session_row[6],
|
||||
total_messages=session_row[7],
|
||||
positions=positions,
|
||||
conversation=conversation
|
||||
)
|
||||
)
|
||||
|
||||
conn.close()
|
||||
|
||||
# Get deployment mode info
|
||||
deployment_info = get_deployment_mode_dict()
|
||||
|
||||
return ReasoningResponse(
|
||||
sessions=sessions,
|
||||
count=len(sessions),
|
||||
**deployment_info
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to query reasoning logs: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@app.get("/health", response_model=HealthResponse)
|
||||
async def health_check():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user