Add LLM request/response logging to database
Log every LLM call (analysis, chat, image, raw_analyze) to a new LlmLog table with request type, model, token counts, duration, success/failure, and truncated request/response payloads. Enables debugging prompt issues and tracking usage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,23 @@ class Database:
|
||||
ALTER TABLE UserState ADD UserNotes NVARCHAR(MAX) NULL
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'LlmLog')
|
||||
CREATE TABLE LlmLog (
|
||||
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
|
||||
RequestType NVARCHAR(50) NOT NULL,
|
||||
Model NVARCHAR(100) NOT NULL,
|
||||
InputTokens INT NULL,
|
||||
OutputTokens INT NULL,
|
||||
DurationMs INT NOT NULL,
|
||||
Success BIT NOT NULL,
|
||||
Request NVARCHAR(MAX) NOT NULL,
|
||||
Response NVARCHAR(MAX) NULL,
|
||||
Error NVARCHAR(MAX) NULL,
|
||||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
|
||||
)
|
||||
""")
|
||||
|
||||
cursor.close()
|
||||
|
||||
def _parse_database_name(self) -> str:
|
||||
@@ -348,6 +365,55 @@ class Database:
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# LLM Log (fire-and-forget via asyncio.create_task)
|
||||
# ------------------------------------------------------------------
|
||||
async def save_llm_log(
|
||||
self,
|
||||
request_type: str,
|
||||
model: str,
|
||||
duration_ms: int,
|
||||
success: bool,
|
||||
request: str,
|
||||
response: str | None = None,
|
||||
error: str | None = None,
|
||||
input_tokens: int | None = None,
|
||||
output_tokens: int | None = None,
|
||||
) -> None:
|
||||
"""Save an LLM request/response log entry."""
|
||||
if not self._available:
|
||||
return
|
||||
try:
|
||||
await asyncio.to_thread(
|
||||
self._save_llm_log_sync,
|
||||
request_type, model, duration_ms, success, request,
|
||||
response, error, input_tokens, output_tokens,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to save LLM log")
|
||||
|
||||
def _save_llm_log_sync(
|
||||
self, request_type, model, duration_ms, success, request,
|
||||
response, error, input_tokens, output_tokens,
|
||||
):
|
||||
conn = self._connect()
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""INSERT INTO LlmLog
|
||||
(RequestType, Model, InputTokens, OutputTokens, DurationMs,
|
||||
Success, Request, Response, Error)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
request_type, model, input_tokens, output_tokens, duration_ms,
|
||||
1 if success else 0,
|
||||
request[:4000] if request else "",
|
||||
response[:4000] if response else None,
|
||||
error[:4000] if error else None,
|
||||
)
|
||||
cursor.close()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
async def close(self):
|
||||
"""No persistent connection to close (connections are per-operation)."""
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user