kryten-llm 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kryten_llm/__init__.py +22 -0
- kryten_llm/__main__.py +148 -0
- kryten_llm/components/__init__.py +24 -0
- kryten_llm/components/config_reloader.py +286 -0
- kryten_llm/components/context_manager.py +186 -0
- kryten_llm/components/formatter.py +383 -0
- kryten_llm/components/health_monitor.py +266 -0
- kryten_llm/components/heartbeat.py +122 -0
- kryten_llm/components/listener.py +79 -0
- kryten_llm/components/llm_manager.py +349 -0
- kryten_llm/components/prompt_builder.py +148 -0
- kryten_llm/components/rate_limiter.py +478 -0
- kryten_llm/components/response_logger.py +105 -0
- kryten_llm/components/spam_detector.py +388 -0
- kryten_llm/components/trigger_engine.py +278 -0
- kryten_llm/components/validator.py +269 -0
- kryten_llm/config.py +93 -0
- kryten_llm/models/__init__.py +25 -0
- kryten_llm/models/config.py +496 -0
- kryten_llm/models/events.py +16 -0
- kryten_llm/models/phase3.py +59 -0
- kryten_llm/service.py +572 -0
- kryten_llm/utils/__init__.py +0 -0
- kryten_llm-0.2.2.dist-info/METADATA +271 -0
- kryten_llm-0.2.2.dist-info/RECORD +28 -0
- kryten_llm-0.2.2.dist-info/WHEEL +4 -0
- kryten_llm-0.2.2.dist-info/entry_points.txt +3 -0
- kryten_llm-0.2.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""Configuration management for kryten-llm."""
|
|
2
|
+
|
|
3
|
+
from kryten import KrytenConfig # type: ignore[import-untyped]
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
# ============================================================================
|
|
7
|
+
# LLM-Specific Configuration Models
|
|
8
|
+
# ============================================================================
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PersonalityConfig(BaseModel):
|
|
12
|
+
"""Bot personality configuration."""
|
|
13
|
+
|
|
14
|
+
character_name: str = Field(default="CynthiaRothbot", description="Bot character name")
|
|
15
|
+
character_description: str = Field(
|
|
16
|
+
default="legendary martial artist and actress",
|
|
17
|
+
description="Character description for LLM context",
|
|
18
|
+
)
|
|
19
|
+
personality_traits: list[str] = Field(
|
|
20
|
+
default=["confident", "action-oriented", "pithy", "martial arts expert"],
|
|
21
|
+
description="List of personality traits",
|
|
22
|
+
)
|
|
23
|
+
expertise: list[str] = Field(
|
|
24
|
+
default=["kung fu", "action movies", "martial arts", "B-movies"],
|
|
25
|
+
description="Areas of expertise",
|
|
26
|
+
)
|
|
27
|
+
response_style: str = Field(default="short and punchy", description="Desired response style")
|
|
28
|
+
name_variations: list[str] = Field(
|
|
29
|
+
default=["cynthia", "rothrock", "cynthiarothbot"],
|
|
30
|
+
description="Alternative names that trigger mentions",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LLMProvider(BaseModel):
|
|
35
|
+
"""LLM provider configuration.
|
|
36
|
+
|
|
37
|
+
Phase 3 enhancement: Added priority, max_retries, and custom_headers
|
|
38
|
+
to support multi-provider fallback strategy (REQ-001, REQ-003, REQ-007, REQ-024).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str = Field(description="Provider identifier")
|
|
42
|
+
type: str = Field(description="Provider type: openai_compatible, openrouter, anthropic")
|
|
43
|
+
base_url: str = Field(description="API base URL")
|
|
44
|
+
api_key: str = Field(description="API key for authentication")
|
|
45
|
+
model: str = Field(description="Model identifier")
|
|
46
|
+
max_tokens: int = Field(default=256, description="Maximum tokens in response", ge=1, le=4096)
|
|
47
|
+
temperature: float = Field(default=0.8, description="Sampling temperature", ge=0.0, le=2.0)
|
|
48
|
+
timeout_seconds: int = Field(default=30, description="Request timeout", ge=1, le=120)
|
|
49
|
+
max_retries: int = Field(
|
|
50
|
+
default=3, description="Maximum retry attempts per provider", ge=0, le=10
|
|
51
|
+
)
|
|
52
|
+
priority: int = Field(
|
|
53
|
+
default=99, description="Provider priority (lower number = higher priority)", ge=1
|
|
54
|
+
)
|
|
55
|
+
custom_headers: dict[str, str] | None = Field(
|
|
56
|
+
default=None, description="Custom HTTP headers for provider"
|
|
57
|
+
)
|
|
58
|
+
fallback: str | None = Field(
|
|
59
|
+
default=None,
|
|
60
|
+
description="Fallback provider name on failure (deprecated, use priority instead)",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Trigger(BaseModel):
|
|
65
|
+
"""Trigger word configuration.
|
|
66
|
+
|
|
67
|
+
Phase 3 enhancement: Added preferred_provider to support trigger-specific
|
|
68
|
+
provider selection (REQ-004, REQ-022).
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
name: str = Field(description="Trigger identifier")
|
|
72
|
+
patterns: list[str] = Field(description="List of regex patterns or strings to match")
|
|
73
|
+
probability: float = Field(
|
|
74
|
+
default=1.0, description="Probability of responding (0.0-1.0)", ge=0.0, le=1.0
|
|
75
|
+
)
|
|
76
|
+
cooldown_seconds: int = Field(
|
|
77
|
+
default=300, description="Cooldown between trigger activations", ge=0
|
|
78
|
+
)
|
|
79
|
+
context: str = Field(default="", description="Additional context to inject into prompt")
|
|
80
|
+
response_style: str | None = Field(
|
|
81
|
+
default=None, description="Override response style for this trigger"
|
|
82
|
+
)
|
|
83
|
+
max_responses_per_hour: int = Field(
|
|
84
|
+
default=10, description="Maximum responses per hour for this trigger", ge=0
|
|
85
|
+
)
|
|
86
|
+
priority: int = Field(
|
|
87
|
+
default=5, description="Trigger priority (higher = more important)", ge=1, le=10
|
|
88
|
+
)
|
|
89
|
+
enabled: bool = Field(default=True, description="Whether trigger is active")
|
|
90
|
+
llm_provider: str | None = Field(
|
|
91
|
+
default=None, description="Specific LLM provider for this trigger (deprecated)"
|
|
92
|
+
)
|
|
93
|
+
preferred_provider: str | None = Field(
|
|
94
|
+
default=None, description="Preferred LLM provider for this trigger (Phase 3)"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class RateLimits(BaseModel):
|
|
99
|
+
"""Rate limiting configuration."""
|
|
100
|
+
|
|
101
|
+
global_max_per_minute: int = Field(default=2, ge=0)
|
|
102
|
+
global_max_per_hour: int = Field(default=20, ge=0)
|
|
103
|
+
global_cooldown_seconds: int = Field(default=15, ge=0)
|
|
104
|
+
user_max_per_hour: int = Field(default=5, ge=0)
|
|
105
|
+
user_cooldown_seconds: int = Field(default=60, ge=0)
|
|
106
|
+
mention_cooldown_seconds: int = Field(default=120, ge=0)
|
|
107
|
+
admin_cooldown_multiplier: float = Field(default=0.5, ge=0.0, le=1.0)
|
|
108
|
+
admin_limit_multiplier: float = Field(default=2.0, ge=1.0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class MessageProcessing(BaseModel):
|
|
112
|
+
"""Message processing configuration."""
|
|
113
|
+
|
|
114
|
+
max_message_length: int = Field(default=240, ge=1, le=255)
|
|
115
|
+
split_delay_seconds: int = Field(default=2, ge=0, le=10)
|
|
116
|
+
filter_emoji: bool = Field(default=False)
|
|
117
|
+
max_emoji_per_message: int = Field(default=3, ge=0)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestingConfig(BaseModel):
|
|
121
|
+
"""Testing and development configuration."""
|
|
122
|
+
|
|
123
|
+
dry_run: bool = Field(default=False)
|
|
124
|
+
log_responses: bool = Field(default=True)
|
|
125
|
+
log_file: str = Field(default="logs/llm-responses.jsonl")
|
|
126
|
+
send_to_chat: bool = Field(default=True)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ContextConfig(BaseModel):
|
|
130
|
+
"""Context management configuration.
|
|
131
|
+
|
|
132
|
+
Phase 3: Controls video and chat history context injection into prompts
|
|
133
|
+
(REQ-008 through REQ-013, REQ-023).
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
chat_history_size: int = Field(
|
|
137
|
+
default=30, ge=0, le=100, description="Number of messages to buffer"
|
|
138
|
+
)
|
|
139
|
+
context_window_chars: int = Field(
|
|
140
|
+
default=12000, ge=1000, description="Approximate context limit in characters"
|
|
141
|
+
)
|
|
142
|
+
include_video_context: bool = Field(
|
|
143
|
+
default=True, description="Include current video in prompts"
|
|
144
|
+
)
|
|
145
|
+
include_chat_history: bool = Field(default=True, description="Include recent chat in prompts")
|
|
146
|
+
max_video_title_length: int = Field(
|
|
147
|
+
default=200, ge=50, le=500, description="Maximum video title length"
|
|
148
|
+
)
|
|
149
|
+
max_chat_history_in_prompt: int = Field(
|
|
150
|
+
default=10, ge=0, le=50, description="Maximum chat messages in prompt"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class FormattingConfig(BaseModel):
|
|
155
|
+
"""Response formatting configuration.
|
|
156
|
+
|
|
157
|
+
Phase 4: Controls intelligent response formatting (REQ-001 through REQ-008).
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
max_message_length: int = Field(
|
|
161
|
+
default=255, ge=100, le=500, description="Maximum message length"
|
|
162
|
+
)
|
|
163
|
+
continuation_indicator: str = Field(
|
|
164
|
+
default=" ...", description="Continuation indicator for multi-part messages"
|
|
165
|
+
)
|
|
166
|
+
enable_emoji_limiting: bool = Field(default=False, description="Enable emoji count limiting")
|
|
167
|
+
max_emoji_per_message: int | None = Field(
|
|
168
|
+
default=None, ge=1, description="Maximum emoji per message (if enabled)"
|
|
169
|
+
)
|
|
170
|
+
remove_self_references: bool = Field(
|
|
171
|
+
default=True, description="Remove self-referential phrases"
|
|
172
|
+
)
|
|
173
|
+
remove_llm_artifacts: bool = Field(default=True, description="Remove common LLM artifacts")
|
|
174
|
+
artifact_patterns: list[str] = Field(
|
|
175
|
+
default=[
|
|
176
|
+
r"^Here's ",
|
|
177
|
+
r"^Let me ",
|
|
178
|
+
r"^Sure!\\s*",
|
|
179
|
+
r"\\bAs an AI\\b",
|
|
180
|
+
r"^I think ",
|
|
181
|
+
r"^In my opinion ",
|
|
182
|
+
],
|
|
183
|
+
description="Regex patterns for LLM artifacts to remove",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ValidationConfig(BaseModel):
|
|
188
|
+
"""Response validation configuration.
|
|
189
|
+
|
|
190
|
+
Phase 4: Controls response quality validation (REQ-009 through REQ-015).
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
min_length: int = Field(default=10, ge=1, description="Minimum response length in characters")
|
|
194
|
+
max_length: int = Field(
|
|
195
|
+
default=2000, ge=100, description="Maximum response length before splitting"
|
|
196
|
+
)
|
|
197
|
+
check_repetition: bool = Field(default=True, description="Check for repetitive responses")
|
|
198
|
+
repetition_history_size: int = Field(
|
|
199
|
+
default=10, ge=1, le=50, description="Number of responses to track for repetition"
|
|
200
|
+
)
|
|
201
|
+
repetition_threshold: float = Field(
|
|
202
|
+
default=0.9, ge=0.0, le=1.0, description="Similarity threshold for repetition (0.0-1.0)"
|
|
203
|
+
)
|
|
204
|
+
check_relevance: bool = Field(default=False, description="Check response relevance to input")
|
|
205
|
+
relevance_threshold: float = Field(
|
|
206
|
+
default=0.5, ge=0.0, le=1.0, description="Minimum relevance score"
|
|
207
|
+
)
|
|
208
|
+
inappropriate_patterns: list[str] = Field(
|
|
209
|
+
default=[], description="Regex patterns for inappropriate content"
|
|
210
|
+
)
|
|
211
|
+
check_inappropriate: bool = Field(default=False, description="Check for inappropriate content")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class MessageWindow(BaseModel):
|
|
215
|
+
"""Time window for message rate limiting.
|
|
216
|
+
|
|
217
|
+
Phase 4: Used by spam detection (REQ-016).
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
seconds: int = Field(ge=1, description="Time window in seconds")
|
|
221
|
+
max_messages: int = Field(ge=1, description="Maximum messages allowed in window")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class SpamDetectionConfig(BaseModel):
|
|
225
|
+
"""Spam detection configuration.
|
|
226
|
+
|
|
227
|
+
Phase 4: Controls user spam detection and penalties (REQ-016 through REQ-022).
|
|
228
|
+
Supports both structured MessageWindow format and simple threshold format from config.json.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
enabled: bool = Field(default=True, description="Enable spam detection")
|
|
232
|
+
|
|
233
|
+
# Rate limiting windows
|
|
234
|
+
message_windows: list[MessageWindow] = Field(
|
|
235
|
+
default_factory=lambda: [
|
|
236
|
+
MessageWindow(seconds=60, max_messages=5),
|
|
237
|
+
MessageWindow(seconds=300, max_messages=10),
|
|
238
|
+
MessageWindow(seconds=900, max_messages=20),
|
|
239
|
+
],
|
|
240
|
+
description="Message rate limit windows",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Identical message detection - supports both formats
|
|
244
|
+
identical_message_window: MessageWindow | None = Field(
|
|
245
|
+
default=None, description="Window for identical message detection (structured format)"
|
|
246
|
+
)
|
|
247
|
+
identical_message_threshold: int = Field(
|
|
248
|
+
default=3, ge=1, description="Max identical messages before spam (simple format)"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Mention spam detection - supports both formats
|
|
252
|
+
mention_spam_window: MessageWindow | int = Field(
|
|
253
|
+
default=30, description="Window for mention spam - int (seconds) or MessageWindow"
|
|
254
|
+
)
|
|
255
|
+
mention_spam_threshold: int = Field(
|
|
256
|
+
default=3, ge=1, description="Max mentions in window before spam"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Penalty configuration
|
|
260
|
+
initial_penalty: int = Field(
|
|
261
|
+
default=30, ge=1, description="Initial penalty duration in seconds"
|
|
262
|
+
)
|
|
263
|
+
penalty_multiplier: float = Field(
|
|
264
|
+
default=2.0, ge=1.0, description="Penalty duration multiplier"
|
|
265
|
+
)
|
|
266
|
+
max_penalty: int = Field(default=600, ge=60, description="Maximum penalty duration in seconds")
|
|
267
|
+
penalty_durations: list[int] | None = Field(
|
|
268
|
+
default=None,
|
|
269
|
+
description="Explicit penalty durations (overrides initial_penalty/multiplier if set)",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Reset and exemptions
|
|
273
|
+
clean_period: int = Field(
|
|
274
|
+
default=600, ge=60, description="Clean period to reset offense counts in seconds"
|
|
275
|
+
)
|
|
276
|
+
admin_exempt_ranks: list[int] = Field(
|
|
277
|
+
default=[3, 4, 5], description="User ranks exempt from spam detection"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Backwards compatibility aliases
|
|
281
|
+
@property
|
|
282
|
+
def max_penalty_duration(self) -> int:
|
|
283
|
+
"""Alias for max_penalty."""
|
|
284
|
+
return self.max_penalty
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def clean_period_for_reset(self) -> int:
|
|
288
|
+
"""Alias for clean_period."""
|
|
289
|
+
return self.clean_period
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def admin_ranks(self) -> list[int]:
|
|
293
|
+
"""Alias for admin_exempt_ranks."""
|
|
294
|
+
return self.admin_exempt_ranks
|
|
295
|
+
|
|
296
|
+
def get_identical_message_window(self) -> MessageWindow:
|
|
297
|
+
"""Get identical message window, handling both formats."""
|
|
298
|
+
if self.identical_message_window:
|
|
299
|
+
return self.identical_message_window
|
|
300
|
+
# Create from simple threshold
|
|
301
|
+
return MessageWindow(seconds=300, max_messages=self.identical_message_threshold)
|
|
302
|
+
|
|
303
|
+
def get_mention_spam_window(self) -> MessageWindow:
|
|
304
|
+
"""Get mention spam window, handling both formats."""
|
|
305
|
+
if isinstance(self.mention_spam_window, MessageWindow):
|
|
306
|
+
return self.mention_spam_window
|
|
307
|
+
# Create from simple int (seconds) + threshold
|
|
308
|
+
return MessageWindow(
|
|
309
|
+
seconds=self.mention_spam_window, max_messages=self.mention_spam_threshold
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def get_penalty_durations(self) -> list[int]:
|
|
313
|
+
"""Get penalty durations, calculating if not explicit."""
|
|
314
|
+
if self.penalty_durations:
|
|
315
|
+
return self.penalty_durations
|
|
316
|
+
# Calculate from initial_penalty and multiplier
|
|
317
|
+
durations = []
|
|
318
|
+
current: float = self.initial_penalty
|
|
319
|
+
while current <= self.max_penalty:
|
|
320
|
+
durations.append(int(current))
|
|
321
|
+
current = current * self.penalty_multiplier
|
|
322
|
+
return durations or [self.initial_penalty]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class ErrorHandlingConfig(BaseModel):
|
|
326
|
+
"""Error handling configuration.
|
|
327
|
+
|
|
328
|
+
Phase 4: Controls error handling and fallback responses (REQ-023 through REQ-028).
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
enable_fallback_responses: bool = Field(
|
|
332
|
+
default=False, description="Enable fallback responses on errors"
|
|
333
|
+
)
|
|
334
|
+
fallback_messages: list[str] = Field(
|
|
335
|
+
default=[
|
|
336
|
+
"I'm having trouble thinking right now. Try again later!",
|
|
337
|
+
"My circuits are a bit scrambled. Give me a moment!",
|
|
338
|
+
"ERROR: Brain.exe has stopped responding.",
|
|
339
|
+
],
|
|
340
|
+
description="Fallback messages for errors",
|
|
341
|
+
)
|
|
342
|
+
log_full_context: bool = Field(default=True, description="Log full context on errors")
|
|
343
|
+
generate_correlation_ids: bool = Field(
|
|
344
|
+
default=True, description="Generate correlation IDs for request tracking"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class ServiceMetadata(BaseModel):
|
|
349
|
+
"""Service discovery and monitoring configuration.
|
|
350
|
+
|
|
351
|
+
Phase 5: Service discovery configuration (REQ-009).
|
|
352
|
+
Controls how the service announces itself to the Kryten ecosystem
|
|
353
|
+
and publishes health/heartbeat information.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
service_name: str = Field(default="llm", description="Service identifier for discovery")
|
|
357
|
+
|
|
358
|
+
service_version: str = Field(default="1.0.0", description="Service version string")
|
|
359
|
+
|
|
360
|
+
heartbeat_interval_seconds: int = Field(
|
|
361
|
+
default=10, ge=1, le=60, description="Heartbeat publishing interval in seconds"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
enable_service_discovery: bool = Field(
|
|
365
|
+
default=True, description="Enable service discovery announcements"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
enable_heartbeats: bool = Field(
|
|
369
|
+
default=True, description="Enable periodic heartbeat publishing"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
graceful_shutdown_timeout_seconds: int = Field(
|
|
373
|
+
default=30, ge=5, le=120, description="Maximum time to wait for graceful shutdown"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ============================================================================
|
|
378
|
+
# Main Configuration (Extends KrytenConfig)
|
|
379
|
+
# ============================================================================
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class RetryStrategy(BaseModel):
|
|
383
|
+
"""Retry strategy configuration for LLM providers.
|
|
384
|
+
|
|
385
|
+
Phase 3: Exponential backoff configuration (REQ-003).
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
initial_delay: float = Field(
|
|
389
|
+
default=1.0, ge=0.1, le=10.0, description="Initial retry delay in seconds"
|
|
390
|
+
)
|
|
391
|
+
multiplier: float = Field(
|
|
392
|
+
default=2.0, ge=1.0, le=5.0, description="Delay multiplier for exponential backoff"
|
|
393
|
+
)
|
|
394
|
+
max_delay: float = Field(
|
|
395
|
+
default=30.0, ge=1.0, le=120.0, description="Maximum retry delay in seconds"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class LLMConfig(KrytenConfig):
|
|
400
|
+
"""Extended configuration for kryten-llm service.
|
|
401
|
+
|
|
402
|
+
Inherits NATS and channel configuration from KrytenConfig.
|
|
403
|
+
Adds LLM-specific settings for personality, providers, triggers, etc.
|
|
404
|
+
|
|
405
|
+
Phase 3 enhancements: Multi-provider support with fallback, retry strategy,
|
|
406
|
+
and default provider priority order (REQ-002, REQ-003, REQ-021).
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
# LLM-specific configuration
|
|
410
|
+
personality: PersonalityConfig = Field(
|
|
411
|
+
default_factory=PersonalityConfig, description="Bot personality configuration"
|
|
412
|
+
)
|
|
413
|
+
llm_providers: dict[str, LLMProvider] = Field(description="LLM provider configurations")
|
|
414
|
+
default_provider: str = Field(default="local", description="Default LLM provider name")
|
|
415
|
+
default_provider_priority: list[str] = Field(
|
|
416
|
+
default_factory=list, description="Default provider priority order (Phase 3)"
|
|
417
|
+
)
|
|
418
|
+
retry_strategy: RetryStrategy = Field(
|
|
419
|
+
default_factory=RetryStrategy, description="Retry strategy for provider failures (Phase 3)"
|
|
420
|
+
)
|
|
421
|
+
triggers: list[Trigger] = Field(default_factory=list, description="Trigger word configurations")
|
|
422
|
+
rate_limits: RateLimits = Field(
|
|
423
|
+
default_factory=RateLimits, description="Rate limiting configuration"
|
|
424
|
+
)
|
|
425
|
+
message_processing: MessageProcessing = Field(
|
|
426
|
+
default_factory=MessageProcessing, description="Message processing settings"
|
|
427
|
+
)
|
|
428
|
+
testing: TestingConfig = Field(
|
|
429
|
+
default_factory=TestingConfig, description="Testing configuration"
|
|
430
|
+
)
|
|
431
|
+
context: ContextConfig = Field(
|
|
432
|
+
default_factory=ContextConfig, description="Context management settings"
|
|
433
|
+
)
|
|
434
|
+
formatting: FormattingConfig = Field(
|
|
435
|
+
default_factory=FormattingConfig, description="Response formatting settings (Phase 4)"
|
|
436
|
+
)
|
|
437
|
+
validation: ValidationConfig = Field(
|
|
438
|
+
default_factory=ValidationConfig, description="Response validation settings (Phase 4)"
|
|
439
|
+
)
|
|
440
|
+
spam_detection: SpamDetectionConfig = Field(
|
|
441
|
+
default_factory=SpamDetectionConfig, description="Spam detection settings (Phase 4)"
|
|
442
|
+
)
|
|
443
|
+
error_handling: ErrorHandlingConfig = Field(
|
|
444
|
+
default_factory=ErrorHandlingConfig, description="Error handling settings (Phase 4)"
|
|
445
|
+
)
|
|
446
|
+
service_metadata: ServiceMetadata = Field(
|
|
447
|
+
default_factory=ServiceMetadata,
|
|
448
|
+
description="Service discovery and monitoring settings (Phase 5)",
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def validate_config(self) -> tuple[bool, list[str]]:
|
|
452
|
+
"""Validate configuration and return (is_valid, errors)."""
|
|
453
|
+
errors = []
|
|
454
|
+
|
|
455
|
+
# Validate default provider exists
|
|
456
|
+
if self.default_provider not in self.llm_providers:
|
|
457
|
+
errors.append(f"Default provider '{self.default_provider}' not found in llm_providers")
|
|
458
|
+
|
|
459
|
+
# Validate fallback providers exist
|
|
460
|
+
for provider_name, provider in self.llm_providers.items():
|
|
461
|
+
if provider.fallback and provider.fallback not in self.llm_providers:
|
|
462
|
+
errors.append(
|
|
463
|
+
f"Provider '{provider_name}' has invalid fallback '{provider.fallback}'"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Validate trigger LLM providers
|
|
467
|
+
for trigger in self.triggers:
|
|
468
|
+
if trigger.llm_provider and trigger.llm_provider not in self.llm_providers:
|
|
469
|
+
errors.append(
|
|
470
|
+
f"Trigger '{trigger.name}' has invalid llm_provider '{trigger.llm_provider}'"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return (len(errors) == 0, errors)
|
|
474
|
+
|
|
475
|
+
def model_dump(self, **kwargs: object) -> dict[str, object]:
|
|
476
|
+
"""Override to transform service_metadata to service for KrytenClient compatibility.
|
|
477
|
+
|
|
478
|
+
KrytenClient expects a 'service' key with specific field names.
|
|
479
|
+
This transforms our 'service_metadata' structure to match.
|
|
480
|
+
"""
|
|
481
|
+
data: dict[str, object] = super().model_dump(**kwargs)
|
|
482
|
+
|
|
483
|
+
# Transform service_metadata to service format expected by KrytenClient
|
|
484
|
+
if "service_metadata" in data:
|
|
485
|
+
sm = data["service_metadata"]
|
|
486
|
+
if isinstance(sm, dict):
|
|
487
|
+
data["service"] = {
|
|
488
|
+
"name": sm.get("service_name", "llm"),
|
|
489
|
+
"version": sm.get("service_version", "1.0.0"),
|
|
490
|
+
"heartbeat_interval": sm.get("heartbeat_interval_seconds", 30),
|
|
491
|
+
"enable_heartbeat": sm.get("enable_heartbeats", True),
|
|
492
|
+
"enable_discovery": sm.get("enable_service_discovery", True),
|
|
493
|
+
"enable_lifecycle": True, # Always enable lifecycle events
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return data
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class TriggerResult:
|
|
6
|
+
"""Result of trigger detection."""
|
|
7
|
+
|
|
8
|
+
triggered: bool
|
|
9
|
+
trigger_type: str | None = None
|
|
10
|
+
trigger_name: str | None = None
|
|
11
|
+
cleaned_message: str | None = None
|
|
12
|
+
context: str | None = None
|
|
13
|
+
priority: int = 5
|
|
14
|
+
|
|
15
|
+
def __bool__(self) -> bool:
|
|
16
|
+
return self.triggered
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Phase 3 data models for multi-provider LLM and context management."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class VideoMetadata:
|
|
10
|
+
"""Current video information from CyTube.
|
|
11
|
+
|
|
12
|
+
Phase 3: Tracks current video for context injection (REQ-008, REQ-009).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
title: str
|
|
16
|
+
duration: int # seconds
|
|
17
|
+
type: str # "yt", "vm", "dm", etc.
|
|
18
|
+
queued_by: str
|
|
19
|
+
timestamp: datetime
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ChatMessage:
|
|
24
|
+
"""A chat message for history buffer.
|
|
25
|
+
|
|
26
|
+
Phase 3: Stored in rolling buffer for context injection (REQ-010).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
username: str
|
|
30
|
+
message: str
|
|
31
|
+
timestamp: datetime
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class LLMRequest:
|
|
36
|
+
"""Request to LLM provider.
|
|
37
|
+
|
|
38
|
+
Phase 3: Enhanced with preferred_provider for trigger-specific routing (REQ-004).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
system_prompt: str
|
|
42
|
+
user_prompt: str
|
|
43
|
+
temperature: float = 0.7
|
|
44
|
+
max_tokens: int = 500
|
|
45
|
+
preferred_provider: Optional[str] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class LLMResponse:
|
|
50
|
+
"""Response from LLM provider.
|
|
51
|
+
|
|
52
|
+
Phase 3: Includes provider metrics for logging and monitoring (REQ-006).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
content: str
|
|
56
|
+
provider_used: str
|
|
57
|
+
model_used: str
|
|
58
|
+
tokens_used: Optional[int] = None
|
|
59
|
+
response_time: float = 0.0
|