Coverage for src / local_deep_research / database / models / metrics.py: 96%
96 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2Metrics and usage tracking models.
3"""
5from sqlalchemy import (
6 JSON,
7 Boolean,
8 Column,
9 Float,
10 Index,
11 Integer,
12 String,
13 Text,
14)
15from sqlalchemy_utc import UtcDateTime, utcnow
17from .base import Base
20class TokenUsage(Base):
21 """
22 Track token usage for LLM calls.
23 """
25 __tablename__ = "token_usage"
27 id = Column(Integer, primary_key=True, autoincrement=True)
28 research_id = Column(String(36), nullable=False, index=True)
29 timestamp = Column(UtcDateTime, nullable=False, default=utcnow())
31 # Model information
32 model_provider = Column(String(100), nullable=False)
33 model_name = Column(String(255), nullable=False)
35 # Token counts
36 prompt_tokens = Column(Integer, nullable=False)
37 completion_tokens = Column(Integer, nullable=False)
38 total_tokens = Column(Integer, nullable=False)
40 # Cost tracking (in USD)
41 prompt_cost = Column(Float, default=0.0)
42 completion_cost = Column(Float, default=0.0)
43 total_cost = Column(Float, default=0.0)
45 # Context
46 operation_type = Column(String(100)) # search, summarize, report, etc.
47 operation_details = Column(JSON)
48 research_mode = Column(String(50)) # standard, deep, expert, etc.
50 # Enhanced metrics columns
51 response_time_ms = Column(Integer)
52 success_status = Column(String(50))
53 error_type = Column(String(100))
54 research_query = Column(Text)
55 research_phase = Column(String(100))
56 search_iteration = Column(Integer)
57 search_engines_planned = Column(JSON)
58 search_engine_selected = Column(String(100))
59 calling_file = Column(String(255))
60 calling_function = Column(String(255))
61 call_stack = Column(JSON)
63 # Context overflow detection columns
64 context_limit = Column(Integer) # The configured num_ctx or max tokens
65 context_truncated = Column(
66 Boolean, default=False
67 ) # True if request was truncated due to context limit
68 tokens_truncated = Column(Integer) # Estimated tokens lost to truncation
69 truncation_ratio = Column(Float) # Percentage of prompt that was truncated
71 # Raw Ollama response values for debugging
72 ollama_prompt_eval_count = Column(
73 Integer
74 ) # Raw prompt_eval_count from Ollama
75 ollama_eval_count = Column(Integer) # Raw eval_count from Ollama
76 ollama_total_duration = Column(
77 Integer
78 ) # Total time in nanoseconds (raw from Ollama API)
79 ollama_load_duration = Column(
80 Integer
81 ) # Model load time in nanoseconds (raw from Ollama API)
82 ollama_prompt_eval_duration = Column(
83 Integer
84 ) # Prompt eval time in nanoseconds (raw from Ollama API)
85 ollama_eval_duration = Column(
86 Integer
87 ) # Generation time in nanoseconds (raw from Ollama API)
89 # Compound indexes for query performance optimization
90 __table_args__ = (
91 Index("idx_token_research_timestamp", "research_id", "timestamp"),
92 Index(
93 "idx_token_model_timestamp",
94 "model_provider",
95 "model_name",
96 "timestamp",
97 ),
98 Index("idx_token_operation_timestamp", "operation_type", "timestamp"),
99 Index("idx_token_research_phase", "research_id", "research_phase"),
100 )
102 def __repr__(self):
103 return f"<TokenUsage(model={self.model_name}, total_tokens={self.total_tokens}, cost=${self.total_cost:.4f})>"
106class ModelUsage(Base):
107 """
108 Aggregate model usage statistics.
109 """
111 __tablename__ = "model_usage"
113 id = Column(Integer, primary_key=True)
114 model_provider = Column(String(100), nullable=False)
115 model_name = Column(String(255), nullable=False)
117 # Aggregate stats
118 total_calls = Column(Integer, default=0)
119 total_tokens = Column(Integer, default=0)
120 total_cost = Column(Float, default=0.0)
122 # Performance metrics
123 avg_response_time_ms = Column(Float)
124 error_count = Column(Integer, default=0)
125 success_rate = Column(Float, default=100.0)
127 # Time tracking
128 first_used_at = Column(UtcDateTime, default=utcnow())
129 last_used_at = Column(UtcDateTime, default=utcnow(), onupdate=utcnow())
131 def __repr__(self):
132 return f"<ModelUsage(model={self.model_name}, calls={self.total_calls}, cost=${self.total_cost:.2f})>"
135class ResearchRating(Base):
136 """
137 User ratings for research results.
138 """
140 __tablename__ = "research_ratings"
142 id = Column(Integer, primary_key=True)
143 research_id = Column(String(36), nullable=False, unique=True, index=True)
145 # Star rating (1-5)
146 rating = Column(Integer, nullable=False)
148 # Feedback categories
149 accuracy = Column(Integer) # 1-5
150 completeness = Column(Integer) # 1-5
151 relevance = Column(Integer) # 1-5
152 readability = Column(Integer) # 1-5
154 # Written feedback
155 feedback = Column(Text)
157 # Timestamps
158 created_at = Column(UtcDateTime, default=utcnow())
159 updated_at = Column(UtcDateTime, default=utcnow(), onupdate=utcnow())
161 def __repr__(self):
162 return f"<ResearchRating(research_id={self.research_id}, rating={self.rating})>"
165class SearchCall(Base):
166 """
167 Track individual search engine calls.
168 """
170 __tablename__ = "search_calls"
172 id = Column(Integer, primary_key=True, autoincrement=True)
173 research_id = Column(String(36), nullable=False, index=True)
174 timestamp = Column(UtcDateTime, nullable=False, default=utcnow())
176 # Search details
177 search_engine = Column(String(100), nullable=False)
178 query = Column(Text, nullable=False)
179 num_results_requested = Column(Integer)
180 num_results_returned = Column(Integer)
182 # Performance
183 response_time_ms = Column(Float)
184 success = Column(Integer, default=1) # 1 for success, 0 for failure
185 error_message = Column(Text)
187 # Rate limiting
188 rate_limited = Column(Integer, default=0) # 1 if rate limited
189 wait_time_ms = Column(Float)
191 # Research context
192 research_mode = Column(String(50)) # standard, deep, expert, etc.
193 research_query = Column(Text)
194 research_phase = Column(String(100))
195 search_iteration = Column(Integer)
196 success_status = Column(String(50))
197 error_type = Column(String(100))
198 results_count = Column(Integer)
200 # Compound indexes for query performance optimization
201 __table_args__ = (
202 Index("idx_search_research_timestamp", "research_id", "timestamp"),
203 Index("idx_search_engine_timestamp", "search_engine", "timestamp"),
204 Index("idx_search_success_timestamp", "success", "timestamp"),
205 Index("idx_search_research_engine", "research_id", "search_engine"),
206 )
208 def __repr__(self):
209 return f"<SearchCall(engine={self.search_engine}, query='{self.query[:50]}...', success={self.success})>"