intentkit 0.7.5.dev13__py3-none-any.whl → 0.7.5.dev15__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.
Potentially problematic release.
This version of intentkit might be problematic. Click here for more details.
- intentkit/__init__.py +1 -1
- intentkit/clients/cdp.py +17 -25
- intentkit/config/config.py +5 -4
- intentkit/core/engine.py +1 -4
- intentkit/core/prompt.py +3 -9
- intentkit/models/agent.py +111 -133
- intentkit/models/llm.csv +20 -0
- intentkit/models/llm.py +94 -320
- intentkit/models/skill.py +120 -13
- intentkit/models/skills.csv +175 -0
- intentkit/skills/cdp/__init__.py +22 -8
- intentkit/skills/enso/base.py +3 -3
- {intentkit-0.7.5.dev13.dist-info → intentkit-0.7.5.dev15.dist-info}/METADATA +3 -4
- {intentkit-0.7.5.dev13.dist-info → intentkit-0.7.5.dev15.dist-info}/RECORD +16 -14
- {intentkit-0.7.5.dev13.dist-info → intentkit-0.7.5.dev15.dist-info}/WHEEL +0 -0
- {intentkit-0.7.5.dev13.dist-info → intentkit-0.7.5.dev15.dist-info}/licenses/LICENSE +0 -0
intentkit/models/llm.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import csv
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
from datetime import datetime, timezone
|
|
4
5
|
from decimal import ROUND_HALF_UP, Decimal
|
|
5
6
|
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
from typing import Annotated, Any, Optional
|
|
7
9
|
|
|
8
10
|
from intentkit.models.app_setting import AppSetting
|
|
@@ -13,6 +15,7 @@ from intentkit.utils.error import IntentKitLookUpError
|
|
|
13
15
|
from langchain_core.language_models import LanguageModelLike
|
|
14
16
|
from pydantic import BaseModel, ConfigDict, Field
|
|
15
17
|
from sqlalchemy import Boolean, Column, DateTime, Integer, Numeric, String, func, select
|
|
18
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
16
19
|
|
|
17
20
|
logger = logging.getLogger(__name__)
|
|
18
21
|
|
|
@@ -20,6 +23,74 @@ _credit_per_usdc = None
|
|
|
20
23
|
FOURPLACES = Decimal("0.0001")
|
|
21
24
|
|
|
22
25
|
|
|
26
|
+
def _parse_bool(value: Optional[str]) -> bool:
|
|
27
|
+
if value is None:
|
|
28
|
+
return False
|
|
29
|
+
return value.strip().lower() in {"true", "1", "yes"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _parse_optional_int(value: Optional[str]) -> Optional[int]:
|
|
33
|
+
if value is None:
|
|
34
|
+
return None
|
|
35
|
+
value = value.strip()
|
|
36
|
+
return int(value) if value else None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _load_default_llm_models() -> dict[str, "LLMModelInfo"]:
|
|
40
|
+
"""Load default LLM models from a CSV file."""
|
|
41
|
+
|
|
42
|
+
path = Path(__file__).with_name("llm.csv")
|
|
43
|
+
if not path.exists():
|
|
44
|
+
logger.warning("Default LLM CSV not found at %s", path)
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
defaults: dict[str, "LLMModelInfo"] = {}
|
|
48
|
+
with path.open(newline="", encoding="utf-8") as csvfile:
|
|
49
|
+
reader = csv.DictReader(csvfile)
|
|
50
|
+
for row in reader:
|
|
51
|
+
try:
|
|
52
|
+
timestamp = datetime.now(timezone.utc)
|
|
53
|
+
model = LLMModelInfo(
|
|
54
|
+
id=row["id"],
|
|
55
|
+
name=row["name"],
|
|
56
|
+
provider=LLMProvider(row["provider"]),
|
|
57
|
+
enabled=_parse_bool(row.get("enabled")),
|
|
58
|
+
input_price=Decimal(row["input_price"]),
|
|
59
|
+
output_price=Decimal(row["output_price"]),
|
|
60
|
+
price_level=_parse_optional_int(row.get("price_level")),
|
|
61
|
+
context_length=int(row["context_length"]),
|
|
62
|
+
output_length=int(row["output_length"]),
|
|
63
|
+
intelligence=int(row["intelligence"]),
|
|
64
|
+
speed=int(row["speed"]),
|
|
65
|
+
supports_image_input=_parse_bool(row.get("supports_image_input")),
|
|
66
|
+
supports_skill_calls=_parse_bool(row.get("supports_skill_calls")),
|
|
67
|
+
supports_structured_output=_parse_bool(
|
|
68
|
+
row.get("supports_structured_output")
|
|
69
|
+
),
|
|
70
|
+
has_reasoning=_parse_bool(row.get("has_reasoning")),
|
|
71
|
+
supports_search=_parse_bool(row.get("supports_search")),
|
|
72
|
+
supports_temperature=_parse_bool(row.get("supports_temperature")),
|
|
73
|
+
supports_frequency_penalty=_parse_bool(
|
|
74
|
+
row.get("supports_frequency_penalty")
|
|
75
|
+
),
|
|
76
|
+
supports_presence_penalty=_parse_bool(
|
|
77
|
+
row.get("supports_presence_penalty")
|
|
78
|
+
),
|
|
79
|
+
api_base=row.get("api_base", "").strip() or None,
|
|
80
|
+
timeout=int(row.get("timeout", "") or 180),
|
|
81
|
+
created_at=timestamp,
|
|
82
|
+
updated_at=timestamp,
|
|
83
|
+
)
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
logger.error(
|
|
86
|
+
"Failed to load default LLM model %s: %s", row.get("id"), exc
|
|
87
|
+
)
|
|
88
|
+
continue
|
|
89
|
+
defaults[model.id] = model
|
|
90
|
+
|
|
91
|
+
return defaults
|
|
92
|
+
|
|
93
|
+
|
|
23
94
|
class LLMProvider(str, Enum):
|
|
24
95
|
OPENAI = "openai"
|
|
25
96
|
DEEPSEEK = "deepseek"
|
|
@@ -210,6 +281,26 @@ class LLMModelInfo(BaseModel):
|
|
|
210
281
|
# Not found anywhere
|
|
211
282
|
raise IntentKitLookUpError(f"Model {model_id} not found")
|
|
212
283
|
|
|
284
|
+
@classmethod
|
|
285
|
+
async def get_all(cls, session: AsyncSession | None = None) -> list["LLMModelInfo"]:
|
|
286
|
+
"""Return all models merged from defaults and database overrides."""
|
|
287
|
+
|
|
288
|
+
if session is None:
|
|
289
|
+
async with get_session() as db:
|
|
290
|
+
return await cls.get_all(session=db)
|
|
291
|
+
|
|
292
|
+
models: dict[str, "LLMModelInfo"] = {
|
|
293
|
+
model_id: model.model_copy(deep=True)
|
|
294
|
+
for model_id, model in AVAILABLE_MODELS.items()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
result = await session.execute(select(LLMModelInfoTable))
|
|
298
|
+
for row in result.scalars():
|
|
299
|
+
model_info = cls.model_validate(row)
|
|
300
|
+
models[model_info.id] = model_info
|
|
301
|
+
|
|
302
|
+
return list(models.values())
|
|
303
|
+
|
|
213
304
|
async def calculate_cost(self, input_tokens: int, output_tokens: int) -> Decimal:
|
|
214
305
|
global _credit_per_usdc
|
|
215
306
|
if not _credit_per_usdc:
|
|
@@ -230,325 +321,8 @@ class LLMModelInfo(BaseModel):
|
|
|
230
321
|
return (input_cost + output_cost).quantize(FOURPLACES, rounding=ROUND_HALF_UP)
|
|
231
322
|
|
|
232
323
|
|
|
233
|
-
#
|
|
234
|
-
AVAILABLE_MODELS =
|
|
235
|
-
# OpenAI models
|
|
236
|
-
"gpt-4o": LLMModelInfo(
|
|
237
|
-
id="gpt-4o",
|
|
238
|
-
name="GPT-4o",
|
|
239
|
-
provider=LLMProvider.OPENAI,
|
|
240
|
-
input_price=Decimal("2.50"), # per 1M input tokens
|
|
241
|
-
output_price=Decimal("10.00"), # per 1M output tokens
|
|
242
|
-
context_length=128000,
|
|
243
|
-
output_length=4096,
|
|
244
|
-
intelligence=4,
|
|
245
|
-
speed=3,
|
|
246
|
-
supports_image_input=True,
|
|
247
|
-
supports_skill_calls=True,
|
|
248
|
-
supports_structured_output=True,
|
|
249
|
-
supports_search=True,
|
|
250
|
-
supports_frequency_penalty=False,
|
|
251
|
-
supports_presence_penalty=False,
|
|
252
|
-
),
|
|
253
|
-
"gpt-4o-mini": LLMModelInfo(
|
|
254
|
-
id="gpt-4o-mini",
|
|
255
|
-
name="GPT-4o Mini",
|
|
256
|
-
provider=LLMProvider.OPENAI,
|
|
257
|
-
input_price=Decimal("0.15"), # per 1M input tokens
|
|
258
|
-
output_price=Decimal("0.60"), # per 1M output tokens
|
|
259
|
-
context_length=128000,
|
|
260
|
-
output_length=4096,
|
|
261
|
-
intelligence=3,
|
|
262
|
-
speed=4,
|
|
263
|
-
supports_image_input=False,
|
|
264
|
-
supports_skill_calls=True,
|
|
265
|
-
supports_structured_output=True,
|
|
266
|
-
supports_search=True,
|
|
267
|
-
supports_frequency_penalty=False,
|
|
268
|
-
supports_presence_penalty=False,
|
|
269
|
-
),
|
|
270
|
-
"gpt-5-nano": LLMModelInfo(
|
|
271
|
-
id="gpt-5-nano",
|
|
272
|
-
name="GPT-5 Nano",
|
|
273
|
-
provider=LLMProvider.OPENAI,
|
|
274
|
-
input_price=Decimal("0.05"), # per 1M input tokens
|
|
275
|
-
output_price=Decimal("0.4"), # per 1M output tokens
|
|
276
|
-
context_length=400000,
|
|
277
|
-
output_length=128000,
|
|
278
|
-
intelligence=3,
|
|
279
|
-
speed=5,
|
|
280
|
-
supports_image_input=True,
|
|
281
|
-
supports_skill_calls=True,
|
|
282
|
-
supports_structured_output=True,
|
|
283
|
-
supports_temperature=False,
|
|
284
|
-
supports_frequency_penalty=False,
|
|
285
|
-
supports_presence_penalty=False,
|
|
286
|
-
),
|
|
287
|
-
"gpt-5-mini": LLMModelInfo(
|
|
288
|
-
id="gpt-5-mini",
|
|
289
|
-
name="GPT-5 Mini",
|
|
290
|
-
provider=LLMProvider.OPENAI,
|
|
291
|
-
input_price=Decimal("0.25"), # per 1M input tokens
|
|
292
|
-
output_price=Decimal("2"), # per 1M output tokens
|
|
293
|
-
context_length=400000,
|
|
294
|
-
output_length=128000,
|
|
295
|
-
intelligence=4,
|
|
296
|
-
speed=4,
|
|
297
|
-
supports_image_input=True,
|
|
298
|
-
supports_skill_calls=True,
|
|
299
|
-
supports_structured_output=True,
|
|
300
|
-
supports_search=True,
|
|
301
|
-
supports_temperature=False,
|
|
302
|
-
supports_frequency_penalty=False,
|
|
303
|
-
supports_presence_penalty=False,
|
|
304
|
-
),
|
|
305
|
-
"gpt-5": LLMModelInfo(
|
|
306
|
-
id="gpt-5",
|
|
307
|
-
name="GPT-5",
|
|
308
|
-
provider=LLMProvider.OPENAI,
|
|
309
|
-
input_price=Decimal("1.25"), # per 1M input tokens
|
|
310
|
-
output_price=Decimal("10.00"), # per 1M output tokens
|
|
311
|
-
context_length=400000,
|
|
312
|
-
output_length=128000,
|
|
313
|
-
intelligence=5,
|
|
314
|
-
speed=3,
|
|
315
|
-
supports_image_input=True,
|
|
316
|
-
supports_skill_calls=True,
|
|
317
|
-
supports_structured_output=True,
|
|
318
|
-
supports_search=True,
|
|
319
|
-
supports_temperature=False,
|
|
320
|
-
supports_frequency_penalty=False,
|
|
321
|
-
supports_presence_penalty=False,
|
|
322
|
-
),
|
|
323
|
-
"gpt-4.1-nano": LLMModelInfo(
|
|
324
|
-
id="gpt-4.1-nano",
|
|
325
|
-
name="GPT-4.1 Nano",
|
|
326
|
-
provider=LLMProvider.OPENAI,
|
|
327
|
-
input_price=Decimal("0.1"), # per 1M input tokens
|
|
328
|
-
output_price=Decimal("0.4"), # per 1M output tokens
|
|
329
|
-
context_length=128000,
|
|
330
|
-
output_length=4096,
|
|
331
|
-
intelligence=3,
|
|
332
|
-
speed=5,
|
|
333
|
-
supports_image_input=False,
|
|
334
|
-
supports_skill_calls=True,
|
|
335
|
-
supports_structured_output=True,
|
|
336
|
-
supports_frequency_penalty=False,
|
|
337
|
-
supports_presence_penalty=False,
|
|
338
|
-
),
|
|
339
|
-
"gpt-4.1-mini": LLMModelInfo(
|
|
340
|
-
id="gpt-4.1-mini",
|
|
341
|
-
name="GPT-4.1 Mini",
|
|
342
|
-
provider=LLMProvider.OPENAI,
|
|
343
|
-
input_price=Decimal("0.4"), # per 1M input tokens
|
|
344
|
-
output_price=Decimal("1.6"), # per 1M output tokens
|
|
345
|
-
context_length=128000,
|
|
346
|
-
output_length=4096,
|
|
347
|
-
intelligence=4,
|
|
348
|
-
speed=4,
|
|
349
|
-
supports_image_input=False,
|
|
350
|
-
supports_skill_calls=True,
|
|
351
|
-
supports_structured_output=True,
|
|
352
|
-
supports_search=True,
|
|
353
|
-
supports_frequency_penalty=False,
|
|
354
|
-
supports_presence_penalty=False,
|
|
355
|
-
),
|
|
356
|
-
"gpt-4.1": LLMModelInfo(
|
|
357
|
-
id="gpt-4.1",
|
|
358
|
-
name="GPT-4.1",
|
|
359
|
-
provider=LLMProvider.OPENAI,
|
|
360
|
-
input_price=Decimal("2.00"), # per 1M input tokens
|
|
361
|
-
output_price=Decimal("8.00"), # per 1M output tokens
|
|
362
|
-
context_length=128000,
|
|
363
|
-
output_length=4096,
|
|
364
|
-
intelligence=5,
|
|
365
|
-
speed=3,
|
|
366
|
-
supports_image_input=True,
|
|
367
|
-
supports_skill_calls=True,
|
|
368
|
-
supports_structured_output=True,
|
|
369
|
-
supports_search=True,
|
|
370
|
-
supports_frequency_penalty=False,
|
|
371
|
-
supports_presence_penalty=False,
|
|
372
|
-
),
|
|
373
|
-
"o4-mini": LLMModelInfo(
|
|
374
|
-
id="o4-mini",
|
|
375
|
-
name="OpenAI o4-mini",
|
|
376
|
-
provider=LLMProvider.OPENAI,
|
|
377
|
-
input_price=Decimal("1.10"), # per 1M input tokens
|
|
378
|
-
output_price=Decimal("4.40"), # per 1M output tokens
|
|
379
|
-
context_length=128000,
|
|
380
|
-
output_length=4096,
|
|
381
|
-
intelligence=4,
|
|
382
|
-
speed=3,
|
|
383
|
-
supports_image_input=False,
|
|
384
|
-
supports_skill_calls=True,
|
|
385
|
-
supports_structured_output=True,
|
|
386
|
-
has_reasoning=True, # Has strong reasoning capabilities
|
|
387
|
-
supports_temperature=False,
|
|
388
|
-
supports_frequency_penalty=False,
|
|
389
|
-
supports_presence_penalty=False,
|
|
390
|
-
),
|
|
391
|
-
# Deepseek models
|
|
392
|
-
"deepseek-chat": LLMModelInfo(
|
|
393
|
-
id="deepseek-chat",
|
|
394
|
-
name="Deepseek V3 (0324)",
|
|
395
|
-
provider=LLMProvider.DEEPSEEK,
|
|
396
|
-
input_price=Decimal("0.27"),
|
|
397
|
-
output_price=Decimal("1.10"),
|
|
398
|
-
context_length=60000,
|
|
399
|
-
output_length=4096,
|
|
400
|
-
intelligence=4,
|
|
401
|
-
speed=3,
|
|
402
|
-
supports_image_input=False,
|
|
403
|
-
supports_skill_calls=True,
|
|
404
|
-
supports_structured_output=True,
|
|
405
|
-
api_base="https://api.deepseek.com",
|
|
406
|
-
timeout=300,
|
|
407
|
-
),
|
|
408
|
-
"deepseek-reasoner": LLMModelInfo(
|
|
409
|
-
id="deepseek-reasoner",
|
|
410
|
-
name="Deepseek R1",
|
|
411
|
-
provider=LLMProvider.DEEPSEEK,
|
|
412
|
-
input_price=Decimal("0.55"),
|
|
413
|
-
output_price=Decimal("2.19"),
|
|
414
|
-
context_length=60000,
|
|
415
|
-
output_length=4096,
|
|
416
|
-
intelligence=4,
|
|
417
|
-
speed=2,
|
|
418
|
-
supports_image_input=False,
|
|
419
|
-
supports_skill_calls=True,
|
|
420
|
-
supports_structured_output=True,
|
|
421
|
-
has_reasoning=True, # Has strong reasoning capabilities
|
|
422
|
-
api_base="https://api.deepseek.com",
|
|
423
|
-
timeout=300,
|
|
424
|
-
),
|
|
425
|
-
# XAI models
|
|
426
|
-
"grok-2": LLMModelInfo(
|
|
427
|
-
id="grok-2",
|
|
428
|
-
name="Grok 2",
|
|
429
|
-
provider=LLMProvider.XAI,
|
|
430
|
-
input_price=Decimal("2"),
|
|
431
|
-
output_price=Decimal("10"),
|
|
432
|
-
context_length=120000,
|
|
433
|
-
output_length=4096,
|
|
434
|
-
intelligence=3,
|
|
435
|
-
speed=3,
|
|
436
|
-
supports_image_input=False,
|
|
437
|
-
supports_skill_calls=True,
|
|
438
|
-
supports_structured_output=True,
|
|
439
|
-
timeout=180,
|
|
440
|
-
),
|
|
441
|
-
"grok-3": LLMModelInfo(
|
|
442
|
-
id="grok-3",
|
|
443
|
-
name="Grok 3",
|
|
444
|
-
provider=LLMProvider.XAI,
|
|
445
|
-
input_price=Decimal("3"),
|
|
446
|
-
output_price=Decimal("15"),
|
|
447
|
-
context_length=131072,
|
|
448
|
-
output_length=4096,
|
|
449
|
-
intelligence=5,
|
|
450
|
-
speed=3,
|
|
451
|
-
supports_image_input=False,
|
|
452
|
-
supports_skill_calls=True,
|
|
453
|
-
supports_structured_output=True,
|
|
454
|
-
supports_search=True,
|
|
455
|
-
timeout=180,
|
|
456
|
-
),
|
|
457
|
-
"grok-3-mini": LLMModelInfo(
|
|
458
|
-
id="grok-3-mini",
|
|
459
|
-
name="Grok 3 Mini",
|
|
460
|
-
provider=LLMProvider.XAI,
|
|
461
|
-
input_price=Decimal("0.3"),
|
|
462
|
-
output_price=Decimal("0.5"),
|
|
463
|
-
context_length=131072,
|
|
464
|
-
output_length=4096,
|
|
465
|
-
intelligence=5,
|
|
466
|
-
speed=3,
|
|
467
|
-
supports_image_input=False,
|
|
468
|
-
supports_skill_calls=True,
|
|
469
|
-
supports_structured_output=True,
|
|
470
|
-
has_reasoning=True, # Has strong reasoning capabilities
|
|
471
|
-
supports_frequency_penalty=False,
|
|
472
|
-
supports_presence_penalty=False, # Grok-3-mini doesn't support presence_penalty
|
|
473
|
-
timeout=180,
|
|
474
|
-
),
|
|
475
|
-
# Eternal AI models
|
|
476
|
-
"eternalai": LLMModelInfo(
|
|
477
|
-
id="eternalai",
|
|
478
|
-
name="Eternal AI (Llama-3.3-70B)",
|
|
479
|
-
provider=LLMProvider.ETERNAL,
|
|
480
|
-
input_price=Decimal("0.25"),
|
|
481
|
-
output_price=Decimal("0.75"),
|
|
482
|
-
context_length=60000,
|
|
483
|
-
output_length=4096,
|
|
484
|
-
intelligence=4,
|
|
485
|
-
speed=3,
|
|
486
|
-
supports_image_input=False,
|
|
487
|
-
supports_skill_calls=True,
|
|
488
|
-
supports_structured_output=True,
|
|
489
|
-
api_base="https://api.eternalai.org/v1",
|
|
490
|
-
timeout=300,
|
|
491
|
-
),
|
|
492
|
-
# Reigent models
|
|
493
|
-
"reigent": LLMModelInfo(
|
|
494
|
-
id="reigent",
|
|
495
|
-
name="REI Network",
|
|
496
|
-
provider=LLMProvider.REIGENT,
|
|
497
|
-
input_price=Decimal("0.50"), # Placeholder price, update with actual pricing
|
|
498
|
-
output_price=Decimal("1.50"), # Placeholder price, update with actual pricing
|
|
499
|
-
context_length=32000,
|
|
500
|
-
output_length=4096,
|
|
501
|
-
intelligence=4,
|
|
502
|
-
speed=3,
|
|
503
|
-
supports_image_input=False,
|
|
504
|
-
supports_skill_calls=True,
|
|
505
|
-
supports_structured_output=True,
|
|
506
|
-
supports_temperature=False,
|
|
507
|
-
supports_frequency_penalty=False,
|
|
508
|
-
supports_presence_penalty=False,
|
|
509
|
-
api_base="https://api.reisearch.box/v1",
|
|
510
|
-
timeout=300,
|
|
511
|
-
),
|
|
512
|
-
# Venice models
|
|
513
|
-
"venice-uncensored": LLMModelInfo(
|
|
514
|
-
id="venice-uncensored",
|
|
515
|
-
name="Venice Uncensored",
|
|
516
|
-
provider=LLMProvider.VENICE,
|
|
517
|
-
input_price=Decimal("0.50"), # Placeholder price, update with actual pricing
|
|
518
|
-
output_price=Decimal("2.00"), # Placeholder price, update with actual pricing
|
|
519
|
-
context_length=32000,
|
|
520
|
-
output_length=4096,
|
|
521
|
-
intelligence=3,
|
|
522
|
-
speed=3,
|
|
523
|
-
supports_image_input=False,
|
|
524
|
-
supports_skill_calls=True,
|
|
525
|
-
supports_structured_output=True,
|
|
526
|
-
supports_temperature=True,
|
|
527
|
-
supports_frequency_penalty=False,
|
|
528
|
-
supports_presence_penalty=False,
|
|
529
|
-
api_base="https://api.venice.ai/api/v1",
|
|
530
|
-
timeout=300,
|
|
531
|
-
),
|
|
532
|
-
"venice-llama-4-maverick-17b": LLMModelInfo(
|
|
533
|
-
id="venice-llama-4-maverick-17b",
|
|
534
|
-
name="Venice Llama-4 Maverick 17B",
|
|
535
|
-
provider=LLMProvider.VENICE,
|
|
536
|
-
input_price=Decimal("1.50"),
|
|
537
|
-
output_price=Decimal("6.00"),
|
|
538
|
-
context_length=32000,
|
|
539
|
-
output_length=4096,
|
|
540
|
-
intelligence=3,
|
|
541
|
-
speed=3,
|
|
542
|
-
supports_image_input=False,
|
|
543
|
-
supports_skill_calls=True,
|
|
544
|
-
supports_structured_output=True,
|
|
545
|
-
supports_temperature=True,
|
|
546
|
-
supports_frequency_penalty=False,
|
|
547
|
-
supports_presence_penalty=False,
|
|
548
|
-
api_base="https://api.venice.ai/api/v1",
|
|
549
|
-
timeout=300,
|
|
550
|
-
),
|
|
551
|
-
}
|
|
324
|
+
# Default models loaded from CSV
|
|
325
|
+
AVAILABLE_MODELS = _load_default_llm_models()
|
|
552
326
|
|
|
553
327
|
|
|
554
328
|
class LLMModel(BaseModel):
|
|
@@ -563,7 +337,7 @@ class LLMModel(BaseModel):
|
|
|
563
337
|
async def model_info(self) -> LLMModelInfo:
|
|
564
338
|
"""Get the model information with caching.
|
|
565
339
|
|
|
566
|
-
First tries to get from cache, then database, then
|
|
340
|
+
First tries to get from cache, then database, then default models loaded from CSV.
|
|
567
341
|
Raises ValueError if model is not found anywhere.
|
|
568
342
|
"""
|
|
569
343
|
model_info = await LLMModelInfo.get(self.model_name)
|
intentkit/models/skill.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import csv
|
|
1
2
|
import json
|
|
3
|
+
import logging
|
|
2
4
|
from datetime import datetime, timezone
|
|
3
5
|
from decimal import Decimal
|
|
6
|
+
from pathlib import Path
|
|
4
7
|
from typing import Annotated, Any, Dict, Optional
|
|
5
8
|
|
|
6
9
|
from intentkit.models.base import Base
|
|
@@ -19,6 +22,9 @@ from sqlalchemy import (
|
|
|
19
22
|
select,
|
|
20
23
|
)
|
|
21
24
|
from sqlalchemy.dialects.postgresql import JSON, JSONB
|
|
25
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
class AgentSkillDataTable(Base):
|
|
@@ -349,6 +355,76 @@ class ThreadSkillData(ThreadSkillDataCreate):
|
|
|
349
355
|
await db.commit()
|
|
350
356
|
|
|
351
357
|
|
|
358
|
+
def _skill_parse_bool(value: Optional[str]) -> bool:
|
|
359
|
+
if value is None:
|
|
360
|
+
return False
|
|
361
|
+
return value.strip().lower() in {"true", "1", "yes"}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _skill_parse_optional_int(value: Optional[str]) -> Optional[int]:
|
|
365
|
+
if value is None:
|
|
366
|
+
return None
|
|
367
|
+
value = value.strip()
|
|
368
|
+
return int(value) if value else None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _skill_parse_decimal(value: Optional[str], default: str = "0") -> Decimal:
|
|
372
|
+
value = (value or "").strip()
|
|
373
|
+
if not value:
|
|
374
|
+
value = default
|
|
375
|
+
return Decimal(value)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _load_default_skills() -> tuple[dict[str, "Skill"], dict[tuple[str, str], "Skill"]]:
|
|
379
|
+
"""Load default skills from CSV into lookup maps."""
|
|
380
|
+
|
|
381
|
+
path = Path(__file__).with_name("skills.csv")
|
|
382
|
+
if not path.exists():
|
|
383
|
+
logger.warning("Default skills CSV not found at %s", path)
|
|
384
|
+
return {}, {}
|
|
385
|
+
|
|
386
|
+
by_name: dict[str, "Skill"] = {}
|
|
387
|
+
by_category_config: dict[tuple[str, str], "Skill"] = {}
|
|
388
|
+
|
|
389
|
+
with path.open(newline="", encoding="utf-8") as csvfile:
|
|
390
|
+
reader = csv.DictReader(csvfile)
|
|
391
|
+
for row in reader:
|
|
392
|
+
try:
|
|
393
|
+
timestamp = datetime.now(timezone.utc)
|
|
394
|
+
price_default = row.get("price") or "1"
|
|
395
|
+
skill = Skill(
|
|
396
|
+
name=row["name"],
|
|
397
|
+
category=row["category"],
|
|
398
|
+
config_name=row.get("config_name") or None,
|
|
399
|
+
enabled=_skill_parse_bool(row.get("enabled")),
|
|
400
|
+
price_level=_skill_parse_optional_int(row.get("price_level")),
|
|
401
|
+
price=_skill_parse_decimal(row.get("price"), default="1"),
|
|
402
|
+
price_self_key=_skill_parse_decimal(
|
|
403
|
+
row.get("price_self_key"), default=price_default
|
|
404
|
+
),
|
|
405
|
+
rate_limit_count=_skill_parse_optional_int(
|
|
406
|
+
row.get("rate_limit_count")
|
|
407
|
+
),
|
|
408
|
+
rate_limit_minutes=_skill_parse_optional_int(
|
|
409
|
+
row.get("rate_limit_minutes")
|
|
410
|
+
),
|
|
411
|
+
author=row.get("author") or None,
|
|
412
|
+
created_at=timestamp,
|
|
413
|
+
updated_at=timestamp,
|
|
414
|
+
)
|
|
415
|
+
except Exception as exc:
|
|
416
|
+
logger.error(
|
|
417
|
+
"Failed to load default skill %s: %s", row.get("name"), exc
|
|
418
|
+
)
|
|
419
|
+
continue
|
|
420
|
+
|
|
421
|
+
by_name[skill.name] = skill
|
|
422
|
+
if skill.config_name:
|
|
423
|
+
by_category_config[(skill.category, skill.config_name)] = skill
|
|
424
|
+
|
|
425
|
+
return by_name, by_category_config
|
|
426
|
+
|
|
427
|
+
|
|
352
428
|
class SkillTable(Base):
|
|
353
429
|
"""Database table model for Skill."""
|
|
354
430
|
|
|
@@ -447,18 +523,21 @@ class Skill(BaseModel):
|
|
|
447
523
|
stmt = select(SkillTable).where(SkillTable.name == name)
|
|
448
524
|
skill = await session.scalar(stmt)
|
|
449
525
|
|
|
450
|
-
# If skill
|
|
451
|
-
if
|
|
452
|
-
|
|
526
|
+
# If skill exists in database, convert and cache it
|
|
527
|
+
if skill:
|
|
528
|
+
skill_model = Skill.model_validate(skill)
|
|
529
|
+
await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
|
|
530
|
+
return skill_model
|
|
453
531
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
532
|
+
# Fallback to default skills loaded from CSV
|
|
533
|
+
default_skill = DEFAULT_SKILLS_BY_NAME.get(name)
|
|
534
|
+
if default_skill:
|
|
535
|
+
skill_model = default_skill.model_copy(deep=True)
|
|
458
536
|
await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
|
|
459
|
-
|
|
460
537
|
return skill_model
|
|
461
538
|
|
|
539
|
+
return None
|
|
540
|
+
|
|
462
541
|
@staticmethod
|
|
463
542
|
async def get_by_config_name(category: str, config_name: str) -> Optional["Skill"]:
|
|
464
543
|
"""Get a skill by category and config_name.
|
|
@@ -477,9 +556,37 @@ class Skill(BaseModel):
|
|
|
477
556
|
)
|
|
478
557
|
skill = await session.scalar(stmt)
|
|
479
558
|
|
|
480
|
-
# If skill
|
|
481
|
-
if
|
|
482
|
-
return
|
|
559
|
+
# If skill exists in database, return it
|
|
560
|
+
if skill:
|
|
561
|
+
return Skill.model_validate(skill)
|
|
562
|
+
|
|
563
|
+
# Fallback to default skills loaded from CSV
|
|
564
|
+
default_skill = DEFAULT_SKILLS_BY_CATEGORY_CONFIG.get((category, config_name))
|
|
565
|
+
if default_skill:
|
|
566
|
+
return default_skill.model_copy(deep=True)
|
|
567
|
+
|
|
568
|
+
return None
|
|
569
|
+
|
|
570
|
+
@classmethod
|
|
571
|
+
async def get_all(cls, session: AsyncSession | None = None) -> list["Skill"]:
|
|
572
|
+
"""Return all skills merged from defaults and database overrides."""
|
|
573
|
+
|
|
574
|
+
if session is None:
|
|
575
|
+
async with get_session() as db:
|
|
576
|
+
return await cls.get_all(session=db)
|
|
577
|
+
|
|
578
|
+
skills: dict[str, "Skill"] = {
|
|
579
|
+
name: skill.model_copy(deep=True)
|
|
580
|
+
for name, skill in DEFAULT_SKILLS_BY_NAME.items()
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
result = await session.execute(select(SkillTable))
|
|
584
|
+
for row in result.scalars():
|
|
585
|
+
skill_model = cls.model_validate(row)
|
|
586
|
+
skills[skill_model.name] = skill_model
|
|
587
|
+
|
|
588
|
+
return list(skills.values())
|
|
589
|
+
|
|
483
590
|
|
|
484
|
-
|
|
485
|
-
|
|
591
|
+
# Default skills loaded from CSV
|
|
592
|
+
DEFAULT_SKILLS_BY_NAME, DEFAULT_SKILLS_BY_CATEGORY_CONFIG = _load_default_skills()
|