symbolicai 1.1.0__py3-none-any.whl → 1.2.0__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.
- symai/__init__.py +1 -1
- symai/backend/engines/index/engine_qdrant.py +135 -11
- symai/backend/engines/neurosymbolic/__init__.py +12 -0
- symai/backend/engines/neurosymbolic/engine_openai_responses.py +429 -0
- symai/backend/engines/search/engine_parallel.py +34 -3
- symai/backend/mixin/__init__.py +6 -0
- symai/backend/mixin/anthropic.py +5 -2
- symai/backend/mixin/cerebras.py +9 -0
- symai/backend/mixin/openai.py +8 -0
- symai/components.py +37 -1
- symai/server/qdrant_server.py +65 -22
- symai/utils.py +19 -4
- {symbolicai-1.1.0.dist-info → symbolicai-1.2.0.dist-info}/METADATA +1 -1
- {symbolicai-1.1.0.dist-info → symbolicai-1.2.0.dist-info}/RECORD +18 -16
- {symbolicai-1.1.0.dist-info → symbolicai-1.2.0.dist-info}/WHEEL +0 -0
- {symbolicai-1.1.0.dist-info → symbolicai-1.2.0.dist-info}/entry_points.txt +0 -0
- {symbolicai-1.1.0.dist-info → symbolicai-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {symbolicai-1.1.0.dist-info → symbolicai-1.2.0.dist-info}/top_level.txt +0 -0
symai/__init__.py
CHANGED
|
@@ -22,7 +22,9 @@ try:
|
|
|
22
22
|
from qdrant_client.http.models import (
|
|
23
23
|
Distance,
|
|
24
24
|
Filter,
|
|
25
|
+
NamedVector,
|
|
25
26
|
PointStruct,
|
|
27
|
+
Query,
|
|
26
28
|
ScoredPoint,
|
|
27
29
|
VectorParams,
|
|
28
30
|
)
|
|
@@ -33,6 +35,8 @@ except ImportError:
|
|
|
33
35
|
VectorParams = None
|
|
34
36
|
PointStruct = None
|
|
35
37
|
Filter = None
|
|
38
|
+
Query = None
|
|
39
|
+
NamedVector = None
|
|
36
40
|
ScoredPoint = None
|
|
37
41
|
|
|
38
42
|
try:
|
|
@@ -322,6 +326,50 @@ class QdrantIndexEngine(Engine):
|
|
|
322
326
|
# Reinitialize client to refresh collection list
|
|
323
327
|
self._init_client()
|
|
324
328
|
|
|
329
|
+
def _build_query_filter(self, raw_filter: Any) -> Filter | None:
|
|
330
|
+
"""Normalize various filter representations into a Qdrant Filter.
|
|
331
|
+
|
|
332
|
+
Supports:
|
|
333
|
+
- None: returns None
|
|
334
|
+
- Existing Filter instance: returned as-is
|
|
335
|
+
- Dict[str, Any]: converted to equality-based Filter over payload keys
|
|
336
|
+
|
|
337
|
+
The dict form is intentionally simple and maps directly to `payload.<key>`
|
|
338
|
+
equality conditions, which covers the majority of RAG use cases while
|
|
339
|
+
remaining easy to serialize and pass through higher-level APIs.
|
|
340
|
+
"""
|
|
341
|
+
if raw_filter is None or Filter is None:
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
# Already a Filter instance → use directly
|
|
345
|
+
if isinstance(raw_filter, Filter):
|
|
346
|
+
return raw_filter
|
|
347
|
+
|
|
348
|
+
# Simple dict → build equality-based must filter
|
|
349
|
+
if isinstance(raw_filter, dict):
|
|
350
|
+
if models is None:
|
|
351
|
+
UserMessage(
|
|
352
|
+
"Qdrant filter models are not available. "
|
|
353
|
+
"Please install `qdrant-client` to use filtering.",
|
|
354
|
+
raise_with=ImportError,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
conditions = []
|
|
358
|
+
for key, value in raw_filter.items():
|
|
359
|
+
# We keep semantics simple and robust: every entry is treated as an
|
|
360
|
+
# equality condition on the payload key (logical AND across keys).
|
|
361
|
+
conditions.append(
|
|
362
|
+
models.FieldCondition(
|
|
363
|
+
key=key,
|
|
364
|
+
match=models.MatchValue(value=value),
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
return Filter(must=conditions) if conditions else None
|
|
369
|
+
|
|
370
|
+
# Fallback: pass through other representations (e.g. already-built Filter-like)
|
|
371
|
+
return raw_filter
|
|
372
|
+
|
|
325
373
|
def _prepare_points_for_upsert(
|
|
326
374
|
self,
|
|
327
375
|
embeddings: list | np.ndarray | Any,
|
|
@@ -338,7 +386,7 @@ class QdrantIndexEngine(Engine):
|
|
|
338
386
|
embeddings = [embeddings]
|
|
339
387
|
|
|
340
388
|
for i, vec in enumerate(embeddings):
|
|
341
|
-
point_id = ids[i] if ids and i < len(ids) else i
|
|
389
|
+
point_id = self._normalize_point_id(ids[i]) if ids and i < len(ids) else i
|
|
342
390
|
payload = payloads[i] if payloads and i < len(payloads) else {}
|
|
343
391
|
points.append(
|
|
344
392
|
PointStruct(id=point_id, vector=self._normalize_vector(vec), payload=payload)
|
|
@@ -349,6 +397,14 @@ class QdrantIndexEngine(Engine):
|
|
|
349
397
|
def forward(self, argument):
|
|
350
398
|
kwargs = argument.kwargs
|
|
351
399
|
embedding = argument.prop.prepared_input
|
|
400
|
+
if embedding is None:
|
|
401
|
+
embedding = getattr(argument.prop, "prompt", None)
|
|
402
|
+
if embedding is None:
|
|
403
|
+
msg = (
|
|
404
|
+
"Qdrant forward() requires an embedding vector. "
|
|
405
|
+
"Provide it via prepared_input or prompt before calling forward()."
|
|
406
|
+
)
|
|
407
|
+
raise ValueError(msg)
|
|
352
408
|
query = argument.prop.ori_query
|
|
353
409
|
operation = argument.prop.operation
|
|
354
410
|
collection_name = argument.prop.index_name if argument.prop.index_name else self.index_name
|
|
@@ -369,8 +425,20 @@ class QdrantIndexEngine(Engine):
|
|
|
369
425
|
# Ensure collection exists - fail fast if it doesn't
|
|
370
426
|
self._ensure_collection_exists(collection_name)
|
|
371
427
|
index_top_k = kwargs.get("index_top_k", self.index_top_k)
|
|
372
|
-
#
|
|
373
|
-
|
|
428
|
+
# Optional search parameters
|
|
429
|
+
score_threshold = kwargs.get("score_threshold")
|
|
430
|
+
# Accept both `query_filter` and `filter` for convenience
|
|
431
|
+
raw_filter = kwargs.get("query_filter", kwargs.get("filter"))
|
|
432
|
+
query_filter = self._build_query_filter(raw_filter)
|
|
433
|
+
|
|
434
|
+
# Use shared search helper that already handles retries and normalization
|
|
435
|
+
rsp = self._search_sync(
|
|
436
|
+
collection_name=collection_name,
|
|
437
|
+
query_vector=embedding,
|
|
438
|
+
limit=index_top_k,
|
|
439
|
+
score_threshold=score_threshold,
|
|
440
|
+
query_filter=query_filter,
|
|
441
|
+
)
|
|
374
442
|
elif operation == "add":
|
|
375
443
|
# Create collection if it doesn't exist (only for write operations)
|
|
376
444
|
self._create_collection_sync(collection_name, collection_dims, self.index_metric)
|
|
@@ -446,14 +514,19 @@ class QdrantIndexEngine(Engine):
|
|
|
446
514
|
)
|
|
447
515
|
def _func():
|
|
448
516
|
query_vector_normalized = self._normalize_vector(query_vector)
|
|
449
|
-
|
|
517
|
+
# For single vector collections, pass vector directly to query parameter
|
|
518
|
+
# For named vector collections, use Query(near_vector=NamedVector(name="vector_name", vector=...))
|
|
519
|
+
# query_points API uses query_filter (not filter) for filtering
|
|
520
|
+
response = self.client.query_points(
|
|
450
521
|
collection_name=collection_name,
|
|
451
|
-
|
|
522
|
+
query=query_vector_normalized,
|
|
452
523
|
limit=top_k,
|
|
453
524
|
with_payload=True,
|
|
454
525
|
with_vectors=self.index_values,
|
|
455
526
|
**kwargs,
|
|
456
527
|
)
|
|
528
|
+
# query_points returns QueryResponse with .points attribute, extract it
|
|
529
|
+
return response.points
|
|
457
530
|
|
|
458
531
|
return _func()
|
|
459
532
|
|
|
@@ -563,16 +636,67 @@ class QdrantIndexEngine(Engine):
|
|
|
563
636
|
for name, vec in vector_config.items()
|
|
564
637
|
}
|
|
565
638
|
}
|
|
566
|
-
|
|
639
|
+
# Qdrant 1.16.1+ compatibility: vectors_count and indexed_vectors_count may not exist
|
|
640
|
+
# Use points_count as the primary count, and try to get vectors_count if available
|
|
641
|
+
result = {
|
|
567
642
|
"name": collection_name,
|
|
568
|
-
"vectors_count": collection_info.vectors_count,
|
|
569
|
-
"indexed_vectors_count": collection_info.indexed_vectors_count,
|
|
570
643
|
"points_count": collection_info.points_count,
|
|
571
644
|
"config": {"params": {"vectors": vectors_info}},
|
|
572
645
|
}
|
|
573
646
|
|
|
647
|
+
# Try to get vectors_count if available (for older Qdrant versions)
|
|
648
|
+
if hasattr(collection_info, "vectors_count"):
|
|
649
|
+
result["vectors_count"] = collection_info.vectors_count
|
|
650
|
+
else:
|
|
651
|
+
# In Qdrant 1.16.1+, vectors_count is not available, use points_count as approximation
|
|
652
|
+
result["vectors_count"] = collection_info.points_count
|
|
653
|
+
|
|
654
|
+
# Try to get indexed_vectors_count if available
|
|
655
|
+
if hasattr(collection_info, "indexed_vectors_count"):
|
|
656
|
+
result["indexed_vectors_count"] = collection_info.indexed_vectors_count
|
|
657
|
+
else:
|
|
658
|
+
# In Qdrant 1.16.1+, indexed_vectors_count may not be available
|
|
659
|
+
result["indexed_vectors_count"] = collection_info.points_count
|
|
660
|
+
|
|
661
|
+
return result
|
|
662
|
+
|
|
574
663
|
# ==================== Point Operations ====================
|
|
575
664
|
|
|
665
|
+
def _normalize_point_id(self, point_id: Any) -> int | uuid.UUID:
|
|
666
|
+
"""Normalize point ID to integer or UUID for Qdrant 1.16.1+ compatibility.
|
|
667
|
+
|
|
668
|
+
Qdrant 1.16.1+ requires point IDs to be either unsigned integers or UUIDs.
|
|
669
|
+
This function converts string IDs (like 'vec-1') to integers or UUIDs.
|
|
670
|
+
"""
|
|
671
|
+
# If already int or UUID, return as-is
|
|
672
|
+
if isinstance(point_id, (int, uuid.UUID)):
|
|
673
|
+
return point_id
|
|
674
|
+
|
|
675
|
+
# If string, try to convert
|
|
676
|
+
if isinstance(point_id, str):
|
|
677
|
+
# Try to parse as integer first
|
|
678
|
+
try:
|
|
679
|
+
# Handle string IDs like "vec-1" by extracting the number
|
|
680
|
+
if point_id.startswith("vec-"):
|
|
681
|
+
num_str = point_id.split("-", 1)[-1]
|
|
682
|
+
return int(num_str)
|
|
683
|
+
# Try direct integer conversion
|
|
684
|
+
return int(point_id)
|
|
685
|
+
except (ValueError, AttributeError):
|
|
686
|
+
# If not a valid integer, try UUID
|
|
687
|
+
try:
|
|
688
|
+
return uuid.UUID(point_id)
|
|
689
|
+
except (ValueError, AttributeError):
|
|
690
|
+
# Fallback: generate UUID from string hash
|
|
691
|
+
return uuid.uuid5(uuid.NAMESPACE_DNS, point_id)
|
|
692
|
+
|
|
693
|
+
# For other types, try to convert to int
|
|
694
|
+
try:
|
|
695
|
+
return int(point_id)
|
|
696
|
+
except (ValueError, TypeError):
|
|
697
|
+
# Last resort: generate UUID from string representation
|
|
698
|
+
return uuid.uuid5(uuid.NAMESPACE_DNS, str(point_id))
|
|
699
|
+
|
|
576
700
|
def _upsert_points_sync(
|
|
577
701
|
self,
|
|
578
702
|
collection_name: str,
|
|
@@ -589,17 +713,17 @@ class QdrantIndexEngine(Engine):
|
|
|
589
713
|
if isinstance(points[0], dict):
|
|
590
714
|
points = [
|
|
591
715
|
PointStruct(
|
|
592
|
-
id=point["id"],
|
|
716
|
+
id=self._normalize_point_id(point["id"]),
|
|
593
717
|
vector=self._normalize_vector(point["vector"]),
|
|
594
718
|
payload=point.get("payload", {}),
|
|
595
719
|
)
|
|
596
720
|
for point in points
|
|
597
721
|
]
|
|
598
722
|
else:
|
|
599
|
-
# Normalize vectors in existing PointStruct objects
|
|
723
|
+
# Normalize vectors and IDs in existing PointStruct objects
|
|
600
724
|
points = [
|
|
601
725
|
PointStruct(
|
|
602
|
-
id=point.id,
|
|
726
|
+
id=self._normalize_point_id(point.id),
|
|
603
727
|
vector=self._normalize_vector(point.vector),
|
|
604
728
|
payload=point.payload,
|
|
605
729
|
)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from ...mixin import (
|
|
2
2
|
ANTHROPIC_CHAT_MODELS,
|
|
3
3
|
ANTHROPIC_REASONING_MODELS,
|
|
4
|
+
CEREBRAS_CHAT_MODELS,
|
|
5
|
+
CEREBRAS_REASONING_MODELS,
|
|
4
6
|
DEEPSEEK_CHAT_MODELS,
|
|
5
7
|
DEEPSEEK_REASONING_MODELS,
|
|
6
8
|
GOOGLE_CHAT_MODELS,
|
|
@@ -9,23 +11,29 @@ from ...mixin import (
|
|
|
9
11
|
GROQ_REASONING_MODELS,
|
|
10
12
|
OPENAI_CHAT_MODELS,
|
|
11
13
|
OPENAI_REASONING_MODELS,
|
|
14
|
+
OPENAI_RESPONSES_MODELS,
|
|
12
15
|
)
|
|
13
16
|
from .engine_anthropic_claudeX_chat import ClaudeXChatEngine
|
|
14
17
|
from .engine_anthropic_claudeX_reasoning import ClaudeXReasoningEngine
|
|
18
|
+
from .engine_cerebras import CerebrasEngine
|
|
15
19
|
from .engine_deepseekX_reasoning import DeepSeekXReasoningEngine
|
|
16
20
|
from .engine_google_geminiX_reasoning import GeminiXReasoningEngine
|
|
17
21
|
from .engine_groq import GroqEngine
|
|
18
22
|
from .engine_openai_gptX_chat import GPTXChatEngine
|
|
19
23
|
from .engine_openai_gptX_reasoning import GPTXReasoningEngine
|
|
24
|
+
from .engine_openai_responses import OpenAIResponsesEngine
|
|
20
25
|
|
|
21
26
|
# create the mapping
|
|
22
27
|
ENGINE_MAPPING = {
|
|
23
28
|
**dict.fromkeys(ANTHROPIC_CHAT_MODELS, ClaudeXChatEngine),
|
|
24
29
|
**dict.fromkeys(ANTHROPIC_REASONING_MODELS, ClaudeXReasoningEngine),
|
|
30
|
+
**dict.fromkeys(CEREBRAS_CHAT_MODELS, CerebrasEngine),
|
|
31
|
+
**dict.fromkeys(CEREBRAS_REASONING_MODELS, CerebrasEngine),
|
|
25
32
|
**dict.fromkeys(DEEPSEEK_REASONING_MODELS, DeepSeekXReasoningEngine),
|
|
26
33
|
**dict.fromkeys(GOOGLE_REASONING_MODELS, GeminiXReasoningEngine),
|
|
27
34
|
**dict.fromkeys(OPENAI_CHAT_MODELS, GPTXChatEngine),
|
|
28
35
|
**dict.fromkeys(OPENAI_REASONING_MODELS, GPTXReasoningEngine),
|
|
36
|
+
**dict.fromkeys(OPENAI_RESPONSES_MODELS, OpenAIResponsesEngine),
|
|
29
37
|
**dict.fromkeys(GROQ_CHAT_MODELS, GroqEngine),
|
|
30
38
|
**dict.fromkeys(GROQ_REASONING_MODELS, GroqEngine),
|
|
31
39
|
}
|
|
@@ -33,6 +41,8 @@ ENGINE_MAPPING = {
|
|
|
33
41
|
__all__ = [
|
|
34
42
|
"ANTHROPIC_CHAT_MODELS",
|
|
35
43
|
"ANTHROPIC_REASONING_MODELS",
|
|
44
|
+
"CEREBRAS_CHAT_MODELS",
|
|
45
|
+
"CEREBRAS_REASONING_MODELS",
|
|
36
46
|
"DEEPSEEK_CHAT_MODELS",
|
|
37
47
|
"DEEPSEEK_REASONING_MODELS",
|
|
38
48
|
"ENGINE_MAPPING",
|
|
@@ -42,6 +52,7 @@ __all__ = [
|
|
|
42
52
|
"GROQ_REASONING_MODELS",
|
|
43
53
|
"OPENAI_CHAT_MODELS",
|
|
44
54
|
"OPENAI_REASONING_MODELS",
|
|
55
|
+
"OPENAI_RESPONSES_MODELS",
|
|
45
56
|
"ClaudeXChatEngine",
|
|
46
57
|
"ClaudeXReasoningEngine",
|
|
47
58
|
"DeepSeekXReasoningEngine",
|
|
@@ -49,4 +60,5 @@ __all__ = [
|
|
|
49
60
|
"GPTXReasoningEngine",
|
|
50
61
|
"GeminiXReasoningEngine",
|
|
51
62
|
"GroqEngine",
|
|
63
|
+
"OpenAIResponsesEngine",
|
|
52
64
|
]
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
import openai
|
|
7
|
+
import tiktoken
|
|
8
|
+
|
|
9
|
+
from ....components import SelfPrompt
|
|
10
|
+
from ....utils import UserMessage, encode_media_frames
|
|
11
|
+
from ...base import Engine
|
|
12
|
+
from ...mixin.openai import SUPPORTED_REASONING_MODELS, OpenAIMixin
|
|
13
|
+
from ...settings import SYMAI_CONFIG
|
|
14
|
+
|
|
15
|
+
logging.getLogger("openai").setLevel(logging.ERROR)
|
|
16
|
+
logging.getLogger("requests").setLevel(logging.ERROR)
|
|
17
|
+
logging.getLogger("urllib").setLevel(logging.ERROR)
|
|
18
|
+
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
19
|
+
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_NON_VERBOSE_OUTPUT = (
|
|
23
|
+
"<META_INSTRUCTION/>\n"
|
|
24
|
+
"You do not output anything else, like verbose preambles or post explanation, such as "
|
|
25
|
+
'"Sure, let me...", "Hope that was helpful...", "Yes, I can help you with that...", etc. '
|
|
26
|
+
"Consider well formatted output, e.g. for sentences use punctuation, spaces etc. or for code use "
|
|
27
|
+
"indentation, etc. Never add meta instructions information to your output!\n\n"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ResponsesTokenizer:
|
|
32
|
+
def __init__(self, model: str):
|
|
33
|
+
self._model = model
|
|
34
|
+
try:
|
|
35
|
+
self._tiktoken = tiktoken.encoding_for_model(model)
|
|
36
|
+
except Exception:
|
|
37
|
+
self._tiktoken = tiktoken.get_encoding("o200k_base")
|
|
38
|
+
|
|
39
|
+
def encode(self, text: str) -> list[int]:
|
|
40
|
+
return self._tiktoken.encode(text, disallowed_special=())
|
|
41
|
+
|
|
42
|
+
def decode(self, tokens: list[int]) -> str:
|
|
43
|
+
return self._tiktoken.decode(tokens)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OpenAIResponsesEngine(Engine, OpenAIMixin):
|
|
47
|
+
def __init__(self, api_key: str | None = None, model: str | None = None):
|
|
48
|
+
super().__init__()
|
|
49
|
+
self.config = deepcopy(SYMAI_CONFIG)
|
|
50
|
+
if api_key is not None and model is not None:
|
|
51
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] = api_key
|
|
52
|
+
self.config["NEUROSYMBOLIC_ENGINE_MODEL"] = model
|
|
53
|
+
if self.id() != "neurosymbolic":
|
|
54
|
+
return
|
|
55
|
+
openai.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
56
|
+
self._prefixed_model = self.config["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
57
|
+
self.model = self._strip_prefix(self._prefixed_model)
|
|
58
|
+
self.seed = None
|
|
59
|
+
self.name = self.__class__.__name__
|
|
60
|
+
self.tokenizer = ResponsesTokenizer(model=self.model)
|
|
61
|
+
self.max_context_tokens = self.api_max_context_tokens()
|
|
62
|
+
self.max_response_tokens = self.api_max_response_tokens()
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
self.client = openai.Client(api_key=openai.api_key)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
UserMessage(
|
|
68
|
+
f"Failed to initialize OpenAI client. Caused by: {e}",
|
|
69
|
+
raise_with=ValueError,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def _strip_prefix(self, model_name: str) -> str:
|
|
73
|
+
return model_name.replace("responses:", "")
|
|
74
|
+
|
|
75
|
+
def id(self) -> str:
|
|
76
|
+
model = self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
|
|
77
|
+
if model and model.startswith("responses:"):
|
|
78
|
+
return "neurosymbolic"
|
|
79
|
+
return super().id()
|
|
80
|
+
|
|
81
|
+
def command(self, *args, **kwargs):
|
|
82
|
+
super().command(*args, **kwargs)
|
|
83
|
+
if "NEUROSYMBOLIC_ENGINE_API_KEY" in kwargs:
|
|
84
|
+
openai.api_key = kwargs["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
85
|
+
if "NEUROSYMBOLIC_ENGINE_MODEL" in kwargs:
|
|
86
|
+
self._prefixed_model = kwargs["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
87
|
+
self.model = self._strip_prefix(self._prefixed_model)
|
|
88
|
+
if "seed" in kwargs:
|
|
89
|
+
self.seed = kwargs["seed"]
|
|
90
|
+
|
|
91
|
+
def compute_required_tokens(self, messages: list[dict]) -> int:
|
|
92
|
+
tokens_per_message = 3
|
|
93
|
+
tokens_per_name = 1
|
|
94
|
+
num_tokens = 0
|
|
95
|
+
for message in messages:
|
|
96
|
+
num_tokens += tokens_per_message
|
|
97
|
+
for key, value in message.items():
|
|
98
|
+
if isinstance(value, str):
|
|
99
|
+
num_tokens += len(self.tokenizer.encode(value))
|
|
100
|
+
elif isinstance(value, list):
|
|
101
|
+
for v in value:
|
|
102
|
+
if isinstance(v, dict) and v.get("type") in ("text", "input_text"):
|
|
103
|
+
num_tokens += len(self.tokenizer.encode(v.get("text", "")))
|
|
104
|
+
if key == "name":
|
|
105
|
+
num_tokens += tokens_per_name
|
|
106
|
+
if self._is_reasoning_model():
|
|
107
|
+
num_tokens += 6
|
|
108
|
+
else:
|
|
109
|
+
num_tokens += 3
|
|
110
|
+
return num_tokens
|
|
111
|
+
|
|
112
|
+
def compute_remaining_tokens(self, prompts: list) -> int:
|
|
113
|
+
val = self.compute_required_tokens(prompts)
|
|
114
|
+
return min(self.max_context_tokens - val, self.max_response_tokens)
|
|
115
|
+
|
|
116
|
+
def _is_reasoning_model(self) -> bool:
|
|
117
|
+
return self.model in SUPPORTED_REASONING_MODELS or self.model in {
|
|
118
|
+
"gpt-5.1-chat-latest",
|
|
119
|
+
"gpt-5-pro",
|
|
120
|
+
"o3-pro",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def _handle_image_content(self, content: str) -> list[str]:
|
|
124
|
+
def _extract_pattern(text):
|
|
125
|
+
# This regular expression matches <<vision:...:>> patterns to extract embedded image references.
|
|
126
|
+
pattern = r"<<vision:(.*?):>>"
|
|
127
|
+
return re.findall(pattern, text)
|
|
128
|
+
|
|
129
|
+
image_files: list[str] = []
|
|
130
|
+
if "<<vision:" not in content:
|
|
131
|
+
return image_files
|
|
132
|
+
|
|
133
|
+
parts = _extract_pattern(content)
|
|
134
|
+
for p in parts:
|
|
135
|
+
img_ = p.strip()
|
|
136
|
+
if img_.startswith("http") or img_.startswith("data:image"):
|
|
137
|
+
image_files.append(img_)
|
|
138
|
+
else:
|
|
139
|
+
max_frames_spacing = 50
|
|
140
|
+
max_used_frames = 10
|
|
141
|
+
if img_.startswith("frames:"):
|
|
142
|
+
img_ = img_.replace("frames:", "")
|
|
143
|
+
max_used_frames, img_ = img_.split(":")
|
|
144
|
+
max_used_frames = int(max_used_frames)
|
|
145
|
+
if max_used_frames < 1 or max_used_frames > max_frames_spacing:
|
|
146
|
+
UserMessage(
|
|
147
|
+
f"Invalid max_used_frames value: {max_used_frames}. Expected 1-{max_frames_spacing}",
|
|
148
|
+
raise_with=ValueError,
|
|
149
|
+
)
|
|
150
|
+
buffer, ext = encode_media_frames(img_)
|
|
151
|
+
if len(buffer) > 1:
|
|
152
|
+
step = len(buffer) // max_frames_spacing
|
|
153
|
+
indices = list(range(0, len(buffer), step))[:max_used_frames]
|
|
154
|
+
for i in indices:
|
|
155
|
+
image_files.append(f"data:image/{ext};base64,{buffer[i]}")
|
|
156
|
+
elif len(buffer) == 1:
|
|
157
|
+
image_files.append(f"data:image/{ext};base64,{buffer[0]}")
|
|
158
|
+
else:
|
|
159
|
+
UserMessage("No frames found or error in encoding frames")
|
|
160
|
+
return image_files
|
|
161
|
+
|
|
162
|
+
def _remove_vision_pattern(self, text: str) -> str:
|
|
163
|
+
# This regular expression matches <<vision:...:>> patterns to strip them from output text.
|
|
164
|
+
pattern = r"<<vision:(.*?):>>"
|
|
165
|
+
return re.sub(pattern, "", text)
|
|
166
|
+
|
|
167
|
+
def _build_system_content(self, argument, image_files: list[str]) -> str:
|
|
168
|
+
sections: list[str] = []
|
|
169
|
+
sections.extend(self._verbose_section(argument))
|
|
170
|
+
sections.extend(self._response_format_section(argument))
|
|
171
|
+
sections.extend(self._context_sections(argument))
|
|
172
|
+
sections.extend(self._payload_section(argument))
|
|
173
|
+
sections.extend(self._examples_section(argument))
|
|
174
|
+
sections.extend(self._instruction_section(argument, image_files))
|
|
175
|
+
sections.extend(self._template_suffix_section(argument))
|
|
176
|
+
return "".join(sections)
|
|
177
|
+
|
|
178
|
+
def _verbose_section(self, argument) -> list[str]:
|
|
179
|
+
if argument.prop.suppress_verbose_output:
|
|
180
|
+
return [_NON_VERBOSE_OUTPUT]
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
def _response_format_section(self, argument) -> list[str]:
|
|
184
|
+
if (
|
|
185
|
+
argument.prop.response_format
|
|
186
|
+
and argument.prop.response_format.get("type") == "json_object"
|
|
187
|
+
):
|
|
188
|
+
return ["<RESPONSE_FORMAT/>\nYou are a helpful assistant designed to output JSON.\n\n"]
|
|
189
|
+
return []
|
|
190
|
+
|
|
191
|
+
def _context_sections(self, argument) -> list[str]:
|
|
192
|
+
sections: list[str] = []
|
|
193
|
+
static_ctxt, dyn_ctxt = argument.prop.instance.global_context
|
|
194
|
+
if len(static_ctxt) > 0:
|
|
195
|
+
sections.append(f"<STATIC CONTEXT/>\n{static_ctxt}\n\n")
|
|
196
|
+
if len(dyn_ctxt) > 0:
|
|
197
|
+
sections.append(f"<DYNAMIC CONTEXT/>\n{dyn_ctxt}\n\n")
|
|
198
|
+
return sections
|
|
199
|
+
|
|
200
|
+
def _payload_section(self, argument) -> list[str]:
|
|
201
|
+
if argument.prop.payload:
|
|
202
|
+
return [f"<ADDITIONAL CONTEXT/>\n{argument.prop.payload!s}\n\n"]
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
def _examples_section(self, argument) -> list[str]:
|
|
206
|
+
examples = argument.prop.examples
|
|
207
|
+
if examples and len(examples) > 0:
|
|
208
|
+
return [f"<EXAMPLES/>\n{examples!s}\n\n"]
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
def _instruction_section(self, argument, image_files: list[str]) -> list[str]:
|
|
212
|
+
if argument.prop.prompt is None or len(argument.prop.prompt) == 0:
|
|
213
|
+
return []
|
|
214
|
+
val = str(argument.prop.prompt)
|
|
215
|
+
if len(image_files) > 0:
|
|
216
|
+
val = self._remove_vision_pattern(val)
|
|
217
|
+
return [f"<INSTRUCTION/>\n{val}\n\n"]
|
|
218
|
+
|
|
219
|
+
def _template_suffix_section(self, argument) -> list[str]:
|
|
220
|
+
if argument.prop.template_suffix:
|
|
221
|
+
return [
|
|
222
|
+
f" You will only generate content for the placeholder `{argument.prop.template_suffix!s}` "
|
|
223
|
+
"following the instructions and the provided context information.\n\n"
|
|
224
|
+
]
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
def _build_user_text(self, argument, image_files: list[str]) -> str:
|
|
228
|
+
suffix = str(argument.prop.processed_input)
|
|
229
|
+
if len(image_files) > 0:
|
|
230
|
+
suffix = self._remove_vision_pattern(suffix)
|
|
231
|
+
return suffix
|
|
232
|
+
|
|
233
|
+
def _create_user_message(self, user_text: str, image_files: list[str]) -> dict:
|
|
234
|
+
if image_files:
|
|
235
|
+
images = [{"type": "input_image", "image_url": f} for f in image_files]
|
|
236
|
+
return {"role": "user", "content": [*images, {"type": "input_text", "text": user_text}]}
|
|
237
|
+
return {"role": "user", "content": user_text}
|
|
238
|
+
|
|
239
|
+
def _apply_self_prompt_if_needed(
|
|
240
|
+
self, argument, system: str, user_msg: dict, user_text: str, image_files: list[str]
|
|
241
|
+
) -> tuple[str, dict]:
|
|
242
|
+
if not (
|
|
243
|
+
argument.prop.instance._kwargs.get("self_prompt", False) or argument.prop.self_prompt
|
|
244
|
+
):
|
|
245
|
+
return system, user_msg
|
|
246
|
+
self_prompter = SelfPrompt()
|
|
247
|
+
key = "developer" if self._is_reasoning_model() else "system"
|
|
248
|
+
res = self_prompter({"user": user_text, key: system})
|
|
249
|
+
if res is None:
|
|
250
|
+
UserMessage("Self-prompting failed!", raise_with=ValueError)
|
|
251
|
+
new_user_msg = self._create_user_message(res["user"], image_files)
|
|
252
|
+
return res[key], new_user_msg
|
|
253
|
+
|
|
254
|
+
def _prepare_raw_input(self, argument):
|
|
255
|
+
if not argument.prop.processed_input:
|
|
256
|
+
UserMessage(
|
|
257
|
+
"Need to provide a prompt instruction to the engine if raw_input is enabled.",
|
|
258
|
+
raise_with=ValueError,
|
|
259
|
+
)
|
|
260
|
+
value = argument.prop.processed_input
|
|
261
|
+
if not isinstance(value, list):
|
|
262
|
+
if not isinstance(value, dict):
|
|
263
|
+
value = {"role": "user", "content": str(value)}
|
|
264
|
+
value = [value]
|
|
265
|
+
return value
|
|
266
|
+
|
|
267
|
+
def prepare(self, argument):
|
|
268
|
+
if argument.prop.raw_input:
|
|
269
|
+
argument.prop.prepared_input = self._prepare_raw_input(argument)
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
image_files = self._handle_image_content(str(argument.prop.processed_input))
|
|
273
|
+
system_content = self._build_system_content(argument, image_files)
|
|
274
|
+
user_text = self._build_user_text(argument, image_files)
|
|
275
|
+
user_msg = self._create_user_message(user_text, image_files)
|
|
276
|
+
system_content, user_msg = self._apply_self_prompt_if_needed(
|
|
277
|
+
argument, system_content, user_msg, user_text, image_files
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
role = "developer" if self._is_reasoning_model() else "system"
|
|
281
|
+
argument.prop.prepared_input = [
|
|
282
|
+
{"role": role, "content": system_content},
|
|
283
|
+
user_msg,
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
def _prepare_request_payload(self, messages, argument) -> dict:
|
|
287
|
+
kwargs = argument.kwargs
|
|
288
|
+
max_tokens = kwargs.get("max_tokens")
|
|
289
|
+
max_output_tokens = kwargs.get("max_output_tokens")
|
|
290
|
+
remaining_tokens = self.compute_remaining_tokens(messages)
|
|
291
|
+
|
|
292
|
+
if max_tokens is not None:
|
|
293
|
+
UserMessage(
|
|
294
|
+
"'max_tokens' is deprecated in favor of 'max_output_tokens' for Responses API."
|
|
295
|
+
)
|
|
296
|
+
if max_tokens > self.max_response_tokens:
|
|
297
|
+
max_output_tokens = remaining_tokens
|
|
298
|
+
else:
|
|
299
|
+
max_output_tokens = max_tokens
|
|
300
|
+
|
|
301
|
+
if max_output_tokens is not None and max_output_tokens > self.max_response_tokens:
|
|
302
|
+
UserMessage(
|
|
303
|
+
f"Provided 'max_output_tokens' ({max_output_tokens}) exceeds max ({self.max_response_tokens}). "
|
|
304
|
+
f"Truncating to {remaining_tokens}."
|
|
305
|
+
)
|
|
306
|
+
max_output_tokens = remaining_tokens
|
|
307
|
+
|
|
308
|
+
payload: dict = {
|
|
309
|
+
"model": kwargs.get("model", self.model),
|
|
310
|
+
"input": messages,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if max_output_tokens is not None:
|
|
314
|
+
payload["max_output_tokens"] = max_output_tokens
|
|
315
|
+
|
|
316
|
+
if kwargs.get("temperature") is not None and not self._is_reasoning_model():
|
|
317
|
+
payload["temperature"] = kwargs["temperature"]
|
|
318
|
+
if kwargs.get("top_p") is not None and not self._is_reasoning_model():
|
|
319
|
+
payload["top_p"] = kwargs["top_p"]
|
|
320
|
+
|
|
321
|
+
if self._is_reasoning_model():
|
|
322
|
+
if self.model == "gpt-5-pro":
|
|
323
|
+
reasoning = {"effort": "high"}
|
|
324
|
+
else:
|
|
325
|
+
reasoning = kwargs.get("reasoning", {"effort": "medium"})
|
|
326
|
+
payload["reasoning"] = reasoning
|
|
327
|
+
|
|
328
|
+
tools = kwargs.get("tools")
|
|
329
|
+
if tools:
|
|
330
|
+
payload["tools"] = self._convert_tools(tools)
|
|
331
|
+
tool_choice = kwargs.get("tool_choice", "auto")
|
|
332
|
+
payload["tool_choice"] = tool_choice
|
|
333
|
+
|
|
334
|
+
if kwargs.get("response_format"):
|
|
335
|
+
payload["text"] = {"format": kwargs["response_format"]}
|
|
336
|
+
|
|
337
|
+
return payload
|
|
338
|
+
|
|
339
|
+
def _convert_tools(self, tools: list) -> list:
|
|
340
|
+
converted = []
|
|
341
|
+
for tool in tools:
|
|
342
|
+
if tool.get("type") == "function":
|
|
343
|
+
converted.append(
|
|
344
|
+
{
|
|
345
|
+
"type": "function",
|
|
346
|
+
"name": tool.get("name") or tool.get("function", {}).get("name"),
|
|
347
|
+
"description": tool.get("description")
|
|
348
|
+
or tool.get("function", {}).get("description"),
|
|
349
|
+
"parameters": tool.get("parameters")
|
|
350
|
+
or tool.get("function", {}).get("parameters"),
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
else:
|
|
354
|
+
converted.append(tool)
|
|
355
|
+
return converted
|
|
356
|
+
|
|
357
|
+
def _extract_output_text(self, response) -> list[str]:
|
|
358
|
+
outputs: list[str] = []
|
|
359
|
+
for output in response.output or []:
|
|
360
|
+
if output.type == "message" and output.content:
|
|
361
|
+
for content in output.content:
|
|
362
|
+
if hasattr(content, "text"):
|
|
363
|
+
outputs.append(content.text)
|
|
364
|
+
if not outputs and hasattr(response, "output_text") and response.output_text:
|
|
365
|
+
outputs.append(response.output_text)
|
|
366
|
+
return outputs
|
|
367
|
+
|
|
368
|
+
def _process_function_calls(self, response, metadata: dict) -> dict:
|
|
369
|
+
for output in response.output or []:
|
|
370
|
+
if output.type == "function_call":
|
|
371
|
+
try:
|
|
372
|
+
args_dict = json.loads(output.arguments)
|
|
373
|
+
except json.JSONDecodeError:
|
|
374
|
+
args_dict = {}
|
|
375
|
+
metadata["function_call"] = {
|
|
376
|
+
"name": output.name,
|
|
377
|
+
"arguments": args_dict,
|
|
378
|
+
"call_id": output.call_id,
|
|
379
|
+
}
|
|
380
|
+
break
|
|
381
|
+
return metadata
|
|
382
|
+
|
|
383
|
+
def _extract_thinking(self, response) -> str | None:
|
|
384
|
+
if not self._is_reasoning_model():
|
|
385
|
+
return None
|
|
386
|
+
for output in response.output or []:
|
|
387
|
+
if output.type == "reasoning" and hasattr(output, "summary") and output.summary:
|
|
388
|
+
texts = [s.text for s in output.summary if hasattr(s, "text") and s.text]
|
|
389
|
+
if texts:
|
|
390
|
+
return "\n".join(texts)
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
def forward(self, argument):
|
|
394
|
+
kwargs = argument.kwargs
|
|
395
|
+
messages = argument.prop.prepared_input
|
|
396
|
+
payload = self._prepare_request_payload(messages, argument)
|
|
397
|
+
except_remedy = kwargs.get("except_remedy")
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
res = self.client.responses.create(**payload)
|
|
401
|
+
except Exception as e:
|
|
402
|
+
if openai.api_key is None or openai.api_key == "":
|
|
403
|
+
msg = "OpenAI API key is not set."
|
|
404
|
+
UserMessage(msg)
|
|
405
|
+
if (
|
|
406
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] is None
|
|
407
|
+
or self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] == ""
|
|
408
|
+
):
|
|
409
|
+
UserMessage(msg, raise_with=ValueError)
|
|
410
|
+
openai.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
411
|
+
|
|
412
|
+
callback = self.client.responses.create
|
|
413
|
+
if except_remedy is not None:
|
|
414
|
+
res = except_remedy(self, e, callback, argument)
|
|
415
|
+
else:
|
|
416
|
+
UserMessage(f"Error during generation. Caused by: {e}", raise_with=ValueError)
|
|
417
|
+
|
|
418
|
+
metadata = {"raw_output": res}
|
|
419
|
+
if payload.get("tools"):
|
|
420
|
+
metadata = self._process_function_calls(res, metadata)
|
|
421
|
+
|
|
422
|
+
thinking = self._extract_thinking(res)
|
|
423
|
+
if thinking:
|
|
424
|
+
metadata["thinking"] = thinking
|
|
425
|
+
|
|
426
|
+
output = self._extract_output_text(res)
|
|
427
|
+
if not output and "function_call" in metadata:
|
|
428
|
+
output = [""]
|
|
429
|
+
return output, metadata
|
|
@@ -298,6 +298,7 @@ class ExtractResult(Result):
|
|
|
298
298
|
|
|
299
299
|
class ParallelEngine(Engine):
|
|
300
300
|
MAX_INCLUDE_DOMAINS = 10
|
|
301
|
+
MAX_EXCLUDE_DOMAINS = 10
|
|
301
302
|
|
|
302
303
|
def __init__(self, api_key: str | None = None):
|
|
303
304
|
super().__init__()
|
|
@@ -350,7 +351,6 @@ class ParallelEngine(Engine):
|
|
|
350
351
|
if not netloc or netloc in seen:
|
|
351
352
|
continue
|
|
352
353
|
if not self._is_valid_domain(netloc):
|
|
353
|
-
# Skip strings that are not apex domains or bare TLD patterns
|
|
354
354
|
continue
|
|
355
355
|
seen.add(netloc)
|
|
356
356
|
out.append(netloc)
|
|
@@ -358,6 +358,23 @@ class ParallelEngine(Engine):
|
|
|
358
358
|
break
|
|
359
359
|
return out
|
|
360
360
|
|
|
361
|
+
def _normalize_exclude_domains(self, domains: list[str] | None) -> list[str]:
|
|
362
|
+
if not isinstance(domains, list):
|
|
363
|
+
return []
|
|
364
|
+
seen: set[str] = set()
|
|
365
|
+
out: list[str] = []
|
|
366
|
+
for d in domains:
|
|
367
|
+
netloc = self._extract_netloc(d)
|
|
368
|
+
if not netloc or netloc in seen:
|
|
369
|
+
continue
|
|
370
|
+
if not self._is_valid_domain(netloc):
|
|
371
|
+
continue
|
|
372
|
+
seen.add(netloc)
|
|
373
|
+
out.append(netloc)
|
|
374
|
+
if len(out) >= self.MAX_EXCLUDE_DOMAINS:
|
|
375
|
+
break
|
|
376
|
+
return out
|
|
377
|
+
|
|
361
378
|
def _coerce_search_queries(self, value: Any) -> list[str]:
|
|
362
379
|
if value is None:
|
|
363
380
|
return []
|
|
@@ -411,7 +428,14 @@ class ParallelEngine(Engine):
|
|
|
411
428
|
max_chars_per_result = kwargs.get("max_chars_per_result", 15000)
|
|
412
429
|
excerpts = {"max_chars_per_result": max_chars_per_result}
|
|
413
430
|
include = self._normalize_include_domains(kwargs.get("allowed_domains"))
|
|
414
|
-
|
|
431
|
+
exclude = self._normalize_exclude_domains(kwargs.get("excluded_domains"))
|
|
432
|
+
source_policy: dict[str, Any] | None = None
|
|
433
|
+
if include or exclude:
|
|
434
|
+
source_policy = {}
|
|
435
|
+
if include:
|
|
436
|
+
source_policy["include_domains"] = include
|
|
437
|
+
if exclude:
|
|
438
|
+
source_policy["exclude_domains"] = exclude
|
|
415
439
|
objective = kwargs.get("objective")
|
|
416
440
|
|
|
417
441
|
try:
|
|
@@ -432,7 +456,14 @@ class ParallelEngine(Engine):
|
|
|
432
456
|
task_input = self._compose_task_input(queries)
|
|
433
457
|
|
|
434
458
|
include = self._normalize_include_domains(kwargs.get("allowed_domains"))
|
|
435
|
-
|
|
459
|
+
exclude = self._normalize_exclude_domains(kwargs.get("excluded_domains"))
|
|
460
|
+
source_policy: dict[str, Any] | None = None
|
|
461
|
+
if include or exclude:
|
|
462
|
+
source_policy = {}
|
|
463
|
+
if include:
|
|
464
|
+
source_policy["include_domains"] = include
|
|
465
|
+
if exclude:
|
|
466
|
+
source_policy["exclude_domains"] = exclude
|
|
436
467
|
metadata = self._coerce_metadata(kwargs.get("metadata"))
|
|
437
468
|
|
|
438
469
|
output_schema = (
|
symai/backend/mixin/__init__.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from .anthropic import SUPPORTED_CHAT_MODELS as ANTHROPIC_CHAT_MODELS
|
|
2
2
|
from .anthropic import SUPPORTED_REASONING_MODELS as ANTHROPIC_REASONING_MODELS
|
|
3
|
+
from .cerebras import SUPPORTED_CHAT_MODELS as CEREBRAS_CHAT_MODELS
|
|
4
|
+
from .cerebras import SUPPORTED_REASONING_MODELS as CEREBRAS_REASONING_MODELS
|
|
3
5
|
from .deepseek import SUPPORTED_CHAT_MODELS as DEEPSEEK_CHAT_MODELS
|
|
4
6
|
from .deepseek import SUPPORTED_REASONING_MODELS as DEEPSEEK_REASONING_MODELS
|
|
5
7
|
from .google import SUPPORTED_CHAT_MODELS as GOOGLE_CHAT_MODELS
|
|
@@ -8,10 +10,13 @@ from .groq import SUPPORTED_CHAT_MODELS as GROQ_CHAT_MODELS
|
|
|
8
10
|
from .groq import SUPPORTED_REASONING_MODELS as GROQ_REASONING_MODELS
|
|
9
11
|
from .openai import SUPPORTED_CHAT_MODELS as OPENAI_CHAT_MODELS
|
|
10
12
|
from .openai import SUPPORTED_REASONING_MODELS as OPENAI_REASONING_MODELS
|
|
13
|
+
from .openai import SUPPORTED_RESPONSES_MODELS as OPENAI_RESPONSES_MODELS
|
|
11
14
|
|
|
12
15
|
__all__ = [
|
|
13
16
|
"ANTHROPIC_CHAT_MODELS",
|
|
14
17
|
"ANTHROPIC_REASONING_MODELS",
|
|
18
|
+
"CEREBRAS_CHAT_MODELS",
|
|
19
|
+
"CEREBRAS_REASONING_MODELS",
|
|
15
20
|
"DEEPSEEK_CHAT_MODELS",
|
|
16
21
|
"DEEPSEEK_REASONING_MODELS",
|
|
17
22
|
"GOOGLE_CHAT_MODELS",
|
|
@@ -20,4 +25,5 @@ __all__ = [
|
|
|
20
25
|
"GROQ_REASONING_MODELS",
|
|
21
26
|
"OPENAI_CHAT_MODELS",
|
|
22
27
|
"OPENAI_REASONING_MODELS",
|
|
28
|
+
"OPENAI_RESPONSES_MODELS",
|
|
23
29
|
]
|
symai/backend/mixin/anthropic.py
CHANGED
|
@@ -10,6 +10,7 @@ SUPPORTED_CHAT_MODELS = [
|
|
|
10
10
|
"claude-3-haiku-20240307",
|
|
11
11
|
]
|
|
12
12
|
SUPPORTED_REASONING_MODELS = [
|
|
13
|
+
"claude-opus-4-5",
|
|
13
14
|
"claude-opus-4-1",
|
|
14
15
|
"claude-opus-4-0",
|
|
15
16
|
"claude-sonnet-4-0",
|
|
@@ -22,7 +23,8 @@ SUPPORTED_REASONING_MODELS = [
|
|
|
22
23
|
class AnthropicMixin:
|
|
23
24
|
def api_max_context_tokens(self):
|
|
24
25
|
if (
|
|
25
|
-
self.model == "claude-opus-4-
|
|
26
|
+
self.model == "claude-opus-4-5"
|
|
27
|
+
or self.model == "claude-opus-4-1"
|
|
26
28
|
or self.model == "claude-opus-4-0"
|
|
27
29
|
or self.model == "claude-sonnet-4-0"
|
|
28
30
|
or self.model == "claude-3-7-sonnet-latest"
|
|
@@ -41,7 +43,8 @@ class AnthropicMixin:
|
|
|
41
43
|
|
|
42
44
|
def api_max_response_tokens(self):
|
|
43
45
|
if (
|
|
44
|
-
self.model == "claude-
|
|
46
|
+
self.model == "claude-opus-4-5"
|
|
47
|
+
or self.model == "claude-sonnet-4-0"
|
|
45
48
|
or self.model == "claude-3-7-sonnet-latest"
|
|
46
49
|
or self.model == "claude-haiku-4-5"
|
|
47
50
|
or self.model == "claude-sonnet-4-5"
|
symai/backend/mixin/openai.py
CHANGED
|
@@ -38,6 +38,9 @@ SUPPORTED_EMBEDDING_MODELS = [
|
|
|
38
38
|
"text-embedding-3-small",
|
|
39
39
|
"text-embedding-3-large",
|
|
40
40
|
]
|
|
41
|
+
SUPPORTED_RESPONSES_MODELS = [
|
|
42
|
+
f"responses:{m}" for m in SUPPORTED_CHAT_MODELS + SUPPORTED_REASONING_MODELS
|
|
43
|
+
] + ["responses:gpt-5-pro", "responses:o3-pro"]
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
class OpenAIMixin:
|
|
@@ -89,6 +92,7 @@ class OpenAIMixin:
|
|
|
89
92
|
self.model == "o1"
|
|
90
93
|
or self.model == "o3"
|
|
91
94
|
or self.model == "o3-mini"
|
|
95
|
+
or self.model == "o3-pro"
|
|
92
96
|
or self.model == "o4-mini"
|
|
93
97
|
or self.model == "gpt-5-chat-latest"
|
|
94
98
|
or self.model == "gpt-5.1-chat-latest"
|
|
@@ -99,6 +103,7 @@ class OpenAIMixin:
|
|
|
99
103
|
or self.model == "gpt-5.1"
|
|
100
104
|
or self.model == "gpt-5-mini"
|
|
101
105
|
or self.model == "gpt-5-nano"
|
|
106
|
+
or self.model == "gpt-5-pro"
|
|
102
107
|
):
|
|
103
108
|
return 400_000
|
|
104
109
|
if self.model == "gpt-4.1" or self.model == "gpt-4.1-mini" or self.model == "gpt-4.1-nano":
|
|
@@ -138,6 +143,7 @@ class OpenAIMixin:
|
|
|
138
143
|
self.model == "o1"
|
|
139
144
|
or self.model == "o3"
|
|
140
145
|
or self.model == "o3-mini"
|
|
146
|
+
or self.model == "o3-pro"
|
|
141
147
|
or self.model == "o4-mini"
|
|
142
148
|
):
|
|
143
149
|
return 100_000
|
|
@@ -148,6 +154,8 @@ class OpenAIMixin:
|
|
|
148
154
|
or self.model == "gpt-5-nano"
|
|
149
155
|
):
|
|
150
156
|
return 128_000
|
|
157
|
+
if self.model == "gpt-5-pro":
|
|
158
|
+
return 272_000
|
|
151
159
|
msg = f"Unsupported model: {self.model}"
|
|
152
160
|
UserMessage(msg)
|
|
153
161
|
raise ValueError(msg)
|
symai/components.py
CHANGED
|
@@ -1282,6 +1282,7 @@ class MetadataTracker(Expression):
|
|
|
1282
1282
|
token_details[(engine_name, None)]["completion_breakdown"][
|
|
1283
1283
|
"reasoning_tokens"
|
|
1284
1284
|
] += 0
|
|
1285
|
+
self._track_parallel_usage_items(token_details, engine_name, metadata)
|
|
1285
1286
|
elif engine_name in ("GPTXChatEngine", "GPTXReasoningEngine"):
|
|
1286
1287
|
usage = metadata["raw_output"].usage
|
|
1287
1288
|
token_details[(engine_name, model_name)]["usage"]["completion_tokens"] += (
|
|
@@ -1312,7 +1313,7 @@ class MetadataTracker(Expression):
|
|
|
1312
1313
|
token_details[(engine_name, model_name)]["prompt_breakdown"][
|
|
1313
1314
|
"cached_tokens"
|
|
1314
1315
|
] += usage.prompt_tokens_details.cached_tokens
|
|
1315
|
-
elif engine_name
|
|
1316
|
+
elif engine_name in ("GPTXSearchEngine", "OpenAIResponsesEngine"):
|
|
1316
1317
|
usage = metadata["raw_output"].usage
|
|
1317
1318
|
token_details[(engine_name, model_name)]["usage"]["prompt_tokens"] += (
|
|
1318
1319
|
usage.input_tokens
|
|
@@ -1330,6 +1331,25 @@ class MetadataTracker(Expression):
|
|
|
1330
1331
|
token_details[(engine_name, model_name)]["completion_breakdown"][
|
|
1331
1332
|
"reasoning_tokens"
|
|
1332
1333
|
] += usage.output_tokens_details.reasoning_tokens
|
|
1334
|
+
elif engine_name == "CerebrasEngine":
|
|
1335
|
+
usage = metadata["raw_output"].usage
|
|
1336
|
+
token_details[(engine_name, model_name)]["usage"]["completion_tokens"] += (
|
|
1337
|
+
usage.completion_tokens
|
|
1338
|
+
)
|
|
1339
|
+
token_details[(engine_name, model_name)]["usage"]["prompt_tokens"] += (
|
|
1340
|
+
usage.prompt_tokens
|
|
1341
|
+
)
|
|
1342
|
+
token_details[(engine_name, model_name)]["usage"]["total_tokens"] += (
|
|
1343
|
+
usage.total_tokens
|
|
1344
|
+
)
|
|
1345
|
+
token_details[(engine_name, model_name)]["usage"]["total_calls"] += 1
|
|
1346
|
+
#!: Backward compatibility for components like `RuntimeInfo`
|
|
1347
|
+
token_details[(engine_name, model_name)]["prompt_breakdown"][
|
|
1348
|
+
"cached_tokens"
|
|
1349
|
+
] += 0 # Assignment not allowed with defualtdict
|
|
1350
|
+
token_details[(engine_name, model_name)]["completion_breakdown"][
|
|
1351
|
+
"reasoning_tokens"
|
|
1352
|
+
] += 0
|
|
1333
1353
|
else:
|
|
1334
1354
|
logger.warning(f"Tracking {engine_name} is not supported.")
|
|
1335
1355
|
continue
|
|
@@ -1345,6 +1365,19 @@ class MetadataTracker(Expression):
|
|
|
1345
1365
|
supported_engines = ("GPTXChatEngine", "GPTXReasoningEngine", "GPTXSearchEngine")
|
|
1346
1366
|
return engine_name in supported_engines
|
|
1347
1367
|
|
|
1368
|
+
def _track_parallel_usage_items(self, token_details, engine_name, metadata):
|
|
1369
|
+
usage_items = getattr(metadata.get("raw_output", None), "usage", None)
|
|
1370
|
+
if not usage_items:
|
|
1371
|
+
return
|
|
1372
|
+
if isinstance(usage_items, dict):
|
|
1373
|
+
usage_items = usage_items.values()
|
|
1374
|
+
extras = token_details[(engine_name, None)].setdefault("extras", {})
|
|
1375
|
+
for item in usage_items:
|
|
1376
|
+
name = getattr(item, "name", None)
|
|
1377
|
+
count = getattr(item, "count", None)
|
|
1378
|
+
if name in ("sku_search", "sku_extract_excerpts") and isinstance(count, (int, float)):
|
|
1379
|
+
extras[name] = extras.get(name, 0) + count
|
|
1380
|
+
|
|
1348
1381
|
def _accumulate_time_field(self, accumulated: dict, metadata: dict) -> None:
|
|
1349
1382
|
if "time" in metadata and "time" in accumulated:
|
|
1350
1383
|
accumulated["time"] += metadata["time"]
|
|
@@ -1475,9 +1508,12 @@ class DynamicEngine(Expression):
|
|
|
1475
1508
|
"""Create an engine instance based on the model name."""
|
|
1476
1509
|
# Deferred to avoid components <-> neurosymbolic engine circular imports.
|
|
1477
1510
|
from .backend.engines.neurosymbolic import ENGINE_MAPPING # noqa
|
|
1511
|
+
from .backend.engines.neurosymbolic.engine_cerebras import CerebrasEngine # noqa
|
|
1478
1512
|
|
|
1479
1513
|
try:
|
|
1480
1514
|
engine_class = ENGINE_MAPPING.get(self.model)
|
|
1515
|
+
if engine_class is None and self.model.startswith("cerebras:"):
|
|
1516
|
+
engine_class = CerebrasEngine
|
|
1481
1517
|
if engine_class is None:
|
|
1482
1518
|
UserMessage(f"Unsupported model '{self.model}'", raise_with=ValueError)
|
|
1483
1519
|
return engine_class(api_key=self.api_key, model=self.model)
|
symai/server/qdrant_server.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import os
|
|
2
3
|
import subprocess
|
|
3
4
|
import sys
|
|
4
5
|
from pathlib import Path
|
|
@@ -41,6 +42,13 @@ def qdrant_server(): # noqa
|
|
|
41
42
|
default="./qdrant_storage",
|
|
42
43
|
help="Path to Qdrant storage directory (default: ./qdrant_storage)",
|
|
43
44
|
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--use-env-storage",
|
|
47
|
+
action="store_true",
|
|
48
|
+
default=False,
|
|
49
|
+
help="Use QDRANT__STORAGE__STORAGE_PATH environment variable instead of passing --storage-path. "
|
|
50
|
+
"If set, storage path argument/volume mount will be skipped, allowing Qdrant to use its own defaults or env vars.",
|
|
51
|
+
)
|
|
44
52
|
parser.add_argument(
|
|
45
53
|
"--config-path", type=str, default=None, help="Path to Qdrant configuration file"
|
|
46
54
|
)
|
|
@@ -62,6 +70,12 @@ def qdrant_server(): # noqa
|
|
|
62
70
|
default=False,
|
|
63
71
|
help="Run Docker container in detached mode (default: False)",
|
|
64
72
|
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--no-cache",
|
|
75
|
+
action="store_true",
|
|
76
|
+
default=False,
|
|
77
|
+
help="Disable caching in Qdrant server (default: False)",
|
|
78
|
+
)
|
|
65
79
|
|
|
66
80
|
main_args, qdrant_args = parser.parse_known_args()
|
|
67
81
|
|
|
@@ -92,15 +106,19 @@ def qdrant_server(): # noqa
|
|
|
92
106
|
# Build command for binary execution
|
|
93
107
|
command = [main_args.binary_path]
|
|
94
108
|
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
# Add storage path argument unless --use-env-storage is set
|
|
110
|
+
if not main_args.use_env_storage:
|
|
111
|
+
# Ensure storage directory exists
|
|
112
|
+
storage_path = Path(main_args.storage_path)
|
|
113
|
+
storage_path.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
abs_storage_path = str(storage_path.resolve())
|
|
115
|
+
# Qdrant binary accepts --storage-path argument
|
|
116
|
+
command.extend(["--storage-path", abs_storage_path])
|
|
117
|
+
elif os.getenv("QDRANT__STORAGE__STORAGE_PATH"):
|
|
118
|
+
# If using env storage and env var is set, pass it through
|
|
119
|
+
# Note: Qdrant binary may read this from env, but we can also pass it explicitly
|
|
120
|
+
abs_storage_path = os.getenv("QDRANT__STORAGE__STORAGE_PATH")
|
|
121
|
+
command.extend(["--storage-path", abs_storage_path])
|
|
104
122
|
|
|
105
123
|
# Add host, port, and grpc-port arguments
|
|
106
124
|
command.extend(["--host", main_args.host])
|
|
@@ -110,15 +128,16 @@ def qdrant_server(): # noqa
|
|
|
110
128
|
if main_args.config_path:
|
|
111
129
|
command.extend(["--config-path", main_args.config_path])
|
|
112
130
|
|
|
131
|
+
# Add no-cache environment variable if flag is set
|
|
132
|
+
if main_args.no_cache:
|
|
133
|
+
# Set environment variable to disable caching
|
|
134
|
+
# Qdrant uses environment variables with QDRANT__ prefix
|
|
135
|
+
os.environ["QDRANT__SERVICE__ENABLE_STATIC_CONTENT_CACHE"] = "false"
|
|
136
|
+
|
|
113
137
|
# Add any additional Qdrant-specific arguments
|
|
114
138
|
command.extend(qdrant_args)
|
|
115
139
|
|
|
116
140
|
else: # docker
|
|
117
|
-
# Ensure storage directory exists
|
|
118
|
-
storage_path = Path(main_args.storage_path)
|
|
119
|
-
storage_path.mkdir(parents=True, exist_ok=True)
|
|
120
|
-
abs_storage_path = str(storage_path.resolve())
|
|
121
|
-
|
|
122
141
|
# Build Docker command
|
|
123
142
|
command = ["docker", "run"]
|
|
124
143
|
|
|
@@ -138,8 +157,20 @@ def qdrant_server(): # noqa
|
|
|
138
157
|
command.extend(["-p", f"{main_args.port}:6333"])
|
|
139
158
|
command.extend(["-p", f"{main_args.grpc_port}:6334"])
|
|
140
159
|
|
|
141
|
-
# Volume mount for storage
|
|
142
|
-
|
|
160
|
+
# Volume mount for storage (skip if --use-env-storage is set)
|
|
161
|
+
if not main_args.use_env_storage:
|
|
162
|
+
# Ensure storage directory exists
|
|
163
|
+
storage_path = Path(main_args.storage_path)
|
|
164
|
+
storage_path.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
abs_storage_path = str(storage_path.resolve())
|
|
166
|
+
# Volume mount for storage
|
|
167
|
+
command.extend(["-v", f"{abs_storage_path}:/qdrant/storage:z"])
|
|
168
|
+
# Set storage path environment variable to use the mounted volume
|
|
169
|
+
command.extend(["-e", "QDRANT__STORAGE__STORAGE_PATH=/qdrant/storage"])
|
|
170
|
+
elif os.getenv("QDRANT__STORAGE__STORAGE_PATH"):
|
|
171
|
+
# If using env storage and env var is set, pass it through to container
|
|
172
|
+
env_storage_path = os.getenv("QDRANT__STORAGE__STORAGE_PATH")
|
|
173
|
+
command.extend(["-e", f"QDRANT__STORAGE__STORAGE_PATH={env_storage_path}"])
|
|
143
174
|
|
|
144
175
|
# Volume mount for config (if provided)
|
|
145
176
|
# Note: Qdrant Docker image accepts environment variables and config files
|
|
@@ -151,8 +182,10 @@ def qdrant_server(): # noqa
|
|
|
151
182
|
command.extend(["-v", f"{config_dir}:/qdrant/config:z"])
|
|
152
183
|
# Qdrant looks for config.yaml in /qdrant/config by default
|
|
153
184
|
|
|
154
|
-
#
|
|
155
|
-
|
|
185
|
+
# Add no-cache environment variable if flag is set
|
|
186
|
+
if main_args.no_cache:
|
|
187
|
+
# Set environment variable to disable caching in Docker container
|
|
188
|
+
command.extend(["-e", "QDRANT__SERVICE__ENABLE_STATIC_CONTENT_CACHE=false"])
|
|
156
189
|
|
|
157
190
|
# Docker image
|
|
158
191
|
command.append(main_args.docker_image)
|
|
@@ -176,15 +209,20 @@ def qdrant_server(): # noqa
|
|
|
176
209
|
str(main_args.port),
|
|
177
210
|
"--grpc-port",
|
|
178
211
|
str(main_args.grpc_port),
|
|
179
|
-
"--storage-path",
|
|
180
|
-
main_args.storage_path,
|
|
181
212
|
"--docker-image",
|
|
182
213
|
main_args.docker_image,
|
|
183
214
|
"--docker-container-name",
|
|
184
215
|
main_args.docker_container_name,
|
|
185
216
|
]
|
|
217
|
+
# Only include storage-path in config if not using env storage
|
|
218
|
+
if not main_args.use_env_storage:
|
|
219
|
+
config_args.extend(["--storage-path", main_args.storage_path])
|
|
220
|
+
else:
|
|
221
|
+
config_args.append("--use-env-storage")
|
|
186
222
|
if main_args.config_path:
|
|
187
223
|
config_args.extend(["--config-path", main_args.config_path])
|
|
224
|
+
if main_args.no_cache:
|
|
225
|
+
config_args.append("--no-cache")
|
|
188
226
|
else:
|
|
189
227
|
config_args = [
|
|
190
228
|
"--env",
|
|
@@ -197,10 +235,15 @@ def qdrant_server(): # noqa
|
|
|
197
235
|
str(main_args.port),
|
|
198
236
|
"--grpc-port",
|
|
199
237
|
str(main_args.grpc_port),
|
|
200
|
-
"--storage-path",
|
|
201
|
-
main_args.storage_path,
|
|
202
238
|
]
|
|
239
|
+
# Only include storage-path in config if not using env storage
|
|
240
|
+
if not main_args.use_env_storage:
|
|
241
|
+
config_args.extend(["--storage-path", main_args.storage_path])
|
|
242
|
+
else:
|
|
243
|
+
config_args.append("--use-env-storage")
|
|
203
244
|
if main_args.config_path:
|
|
204
245
|
config_args.extend(["--config-path", main_args.config_path])
|
|
246
|
+
if main_args.no_cache:
|
|
247
|
+
config_args.append("--no-cache")
|
|
205
248
|
|
|
206
249
|
return command, config_args
|
symai/utils.py
CHANGED
|
@@ -4,9 +4,9 @@ import base64
|
|
|
4
4
|
import inspect
|
|
5
5
|
import os
|
|
6
6
|
import warnings
|
|
7
|
-
from dataclasses import dataclass
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
10
|
|
|
11
11
|
import cv2
|
|
12
12
|
import httpx
|
|
@@ -217,6 +217,7 @@ class RuntimeInfo:
|
|
|
217
217
|
total_calls: int
|
|
218
218
|
total_tokens: int
|
|
219
219
|
cost_estimate: float
|
|
220
|
+
extras: dict[str, Any] = field(default_factory=dict)
|
|
220
221
|
|
|
221
222
|
def __add__(self, other):
|
|
222
223
|
add_elapsed_time = other.total_elapsed_time if hasattr(other, "total_elapsed_time") else 0
|
|
@@ -229,6 +230,17 @@ class RuntimeInfo:
|
|
|
229
230
|
add_cached_tokens = other.cached_tokens if hasattr(other, "cached_tokens") else 0
|
|
230
231
|
add_reasoning_tokens = other.reasoning_tokens if hasattr(other, "reasoning_tokens") else 0
|
|
231
232
|
add_total_calls = other.total_calls if hasattr(other, "total_calls") else 0
|
|
233
|
+
extras = other.extras if hasattr(other, "extras") else {}
|
|
234
|
+
merged_extras = {**(self.extras or {})}
|
|
235
|
+
for key, value in (extras or {}).items():
|
|
236
|
+
if (
|
|
237
|
+
key in merged_extras
|
|
238
|
+
and isinstance(merged_extras[key], (int, float))
|
|
239
|
+
and isinstance(value, (int, float))
|
|
240
|
+
):
|
|
241
|
+
merged_extras[key] += value
|
|
242
|
+
else:
|
|
243
|
+
merged_extras[key] = value
|
|
232
244
|
|
|
233
245
|
return RuntimeInfo(
|
|
234
246
|
total_elapsed_time=self.total_elapsed_time + add_elapsed_time,
|
|
@@ -239,6 +251,7 @@ class RuntimeInfo:
|
|
|
239
251
|
total_calls=self.total_calls + add_total_calls,
|
|
240
252
|
total_tokens=self.total_tokens + add_total_tokens,
|
|
241
253
|
cost_estimate=self.cost_estimate + add_cost_estimate,
|
|
254
|
+
extras=merged_extras,
|
|
242
255
|
)
|
|
243
256
|
|
|
244
257
|
@staticmethod
|
|
@@ -248,7 +261,7 @@ class RuntimeInfo:
|
|
|
248
261
|
return RuntimeInfo.from_usage_stats(tracker.usage, total_elapsed_time)
|
|
249
262
|
except Exception as e:
|
|
250
263
|
UserMessage(f"Failed to parse metadata: {e}", raise_with=ValueError)
|
|
251
|
-
return RuntimeInfo(0, 0, 0, 0, 0, 0, 0, 0)
|
|
264
|
+
return RuntimeInfo(0, 0, 0, 0, 0, 0, 0, 0, {})
|
|
252
265
|
|
|
253
266
|
@staticmethod
|
|
254
267
|
def from_usage_stats(usage_stats: dict | None, total_elapsed_time: float = 0):
|
|
@@ -266,9 +279,10 @@ class RuntimeInfo:
|
|
|
266
279
|
total_calls=data_box.usage.total_calls,
|
|
267
280
|
total_tokens=data_box.usage.total_tokens,
|
|
268
281
|
cost_estimate=0, # Placeholder for cost estimate
|
|
282
|
+
extras=data.get("extras", {}),
|
|
269
283
|
)
|
|
270
284
|
return usage_per_engine
|
|
271
|
-
return RuntimeInfo(0, 0, 0, 0, 0, 0, 0, 0)
|
|
285
|
+
return RuntimeInfo(0, 0, 0, 0, 0, 0, 0, 0, {})
|
|
272
286
|
|
|
273
287
|
@staticmethod
|
|
274
288
|
def estimate_cost(info: RuntimeInfo, f_pricing: callable, **kwargs) -> RuntimeInfo:
|
|
@@ -281,4 +295,5 @@ class RuntimeInfo:
|
|
|
281
295
|
total_calls=info.total_calls,
|
|
282
296
|
total_tokens=info.total_tokens,
|
|
283
297
|
cost_estimate=f_pricing(info, **kwargs),
|
|
298
|
+
extras=info.extras,
|
|
284
299
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: symbolicai
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A Neurosymbolic Perspective on Large Language Models
|
|
5
5
|
Author-email: Marius-Constantin Dinu <marius@extensity.ai>, Leoveanu-Condrei Claudiu <leo@extensity.ai>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
symai/TERMS_OF_SERVICE.md,sha256=HN42UXVI_wAVDHjMShzy_k7xAsbjXaATNeMKcIte_eg,91409
|
|
2
|
-
symai/__init__.py,sha256=
|
|
2
|
+
symai/__init__.py,sha256=irjwVGnXl5w5mBwBTT3Z6HyXKjZNdrjEVYiDUxggVfo,18530
|
|
3
3
|
symai/chat.py,sha256=DCEbmZ96wv-eitAVt6-oF6PT3JM3cT59Iy3r2Hucd_M,14100
|
|
4
|
-
symai/components.py,sha256=
|
|
4
|
+
symai/components.py,sha256=s10kLvwAOjSBQQohoHGtAIKs0UHHCd_HhiRvMbNtIH0,64685
|
|
5
5
|
symai/constraints.py,sha256=ljjB9p0qK4DrDl_u5G_Y-Y6WAH5ZHANIqLLxRtwcORs,1980
|
|
6
6
|
symai/context.py,sha256=4M69MJOeWSdPTr2Y9teoNTs-nEvpzcAcr7900UgORXA,189
|
|
7
7
|
symai/core.py,sha256=gI9qvTT0Skq2D0izdhAoN3RdwBtWei59KO52mKN1Sos,70420
|
|
@@ -20,7 +20,7 @@ symai/shellsv.py,sha256=rwTUcgaNdUm4_SRM7u4aMndMaEAaM6jBvWbEQzWoI0c,39831
|
|
|
20
20
|
symai/strategy.py,sha256=BQTXRnBv57fYO47A--WA6KK1oqGmf9Aijm0p4a_vvqY,45004
|
|
21
21
|
symai/symbol.py,sha256=s5CYwP5SGcRUzZ7TlakZFpKBX_Q0mwPQKRbv4pC3sxM,40443
|
|
22
22
|
symai/symsh.md,sha256=QwY_-fX0Ge7Aazul0xde2DuF2FZLw_elxrkXR3kuKDQ,1245
|
|
23
|
-
symai/utils.py,sha256=
|
|
23
|
+
symai/utils.py,sha256=m4iQzxclkPAUSDderTO_OK2fKznJ69pLfbBcTYq4p70,10824
|
|
24
24
|
symai/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
symai/backend/base.py,sha256=28kIR0NrTL-BgmjpP31JXNlRH4u1TF9qarOXqlCFbVI,7296
|
|
26
26
|
symai/backend/settings.py,sha256=T5iUAV8aGLnQy9iRTvUDJq49LGobiSHPGV1HqBHjkEg,6181
|
|
@@ -41,10 +41,10 @@ symai/backend/engines/files/engine_io.py,sha256=4eYBz44rQYWD7VO6Pn7hVF_cOnqNuolo
|
|
|
41
41
|
symai/backend/engines/imagecaptioning/engine_blip2.py,sha256=8lTzc8sQpuNY4AUb_ZweRKr95v-sFtTykT5ennVf6g0,2915
|
|
42
42
|
symai/backend/engines/imagecaptioning/engine_llavacpp_client.py,sha256=jBsLZv0Laa4tuPyX0VQ7uwyldyO3aYIbbj73WjTbceM,6793
|
|
43
43
|
symai/backend/engines/index/engine_pinecone.py,sha256=fxCew1ldUdjd9UtqnMuWFDiVz5X5BUIKZtq1iSDhj28,9132
|
|
44
|
-
symai/backend/engines/index/engine_qdrant.py,sha256=
|
|
44
|
+
symai/backend/engines/index/engine_qdrant.py,sha256=GtWVbgaqJuATfGus0A0h7EgM_8hKlbw3fnorNJmbC_Q,43300
|
|
45
45
|
symai/backend/engines/index/engine_vectordb.py,sha256=xXU8QaC2BX9O4dDjDCVYgWO4PxQMpmNlhtal6UVtV0o,8541
|
|
46
46
|
symai/backend/engines/lean/engine_lean4.py,sha256=ln5nbQn5szq8nRulbREPLCPQ5bwjM_A5XAGMkfzPdT8,10102
|
|
47
|
-
symai/backend/engines/neurosymbolic/__init__.py,sha256=
|
|
47
|
+
symai/backend/engines/neurosymbolic/__init__.py,sha256=o7HUmxcYSrIkutGYB-6_Qur3adHyrkVeWroDtqEK-YE,2279
|
|
48
48
|
symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py,sha256=WyuskobMjf9ynxRWUnXk55DUMUN7qv7jT1nbZP3Bx9o,21054
|
|
49
49
|
symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py,sha256=thEPDh8H-5XrsADF0mVTWB6m_dJgKeTT49HvyCGJcQM,21291
|
|
50
50
|
symai/backend/engines/neurosymbolic/engine_cerebras.py,sha256=ki84Qh7hdxaKn--UgMMUvAEoqJos7VeKtkka6XpHI3g,13336
|
|
@@ -56,11 +56,12 @@ symai/backend/engines/neurosymbolic/engine_llama_cpp.py,sha256=CWy1aqqV-NFey0b9s
|
|
|
56
56
|
symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py,sha256=FfNkesV64d3gf7MWKf2PoK5nUjetS2MndTbWhE1KieE,28267
|
|
57
57
|
symai/backend/engines/neurosymbolic/engine_openai_gptX_completion.py,sha256=YgxRoitmDz2de_W7rkhVXYEkDqTJQlgxK4f8tWlt88Q,13840
|
|
58
58
|
symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py,sha256=yWiCT_jHHQGs2_YqRhSRvVFxQFXGb1TwvXf2zZWoStU,27171
|
|
59
|
+
symai/backend/engines/neurosymbolic/engine_openai_responses.py,sha256=OSqXJIMUWa4BANUhfxZg9lGmO7FE6xM0G_w0aRqODSw,17751
|
|
59
60
|
symai/backend/engines/ocr/engine_apilayer.py,sha256=UpC3oHBdSM6wlPVqxwMkemBd-Y0ReVwc270O_EVbRD0,2267
|
|
60
61
|
symai/backend/engines/output/engine_stdout.py,sha256=BWNXACl5U-WYIJnT1pZNwZsTRMzP1XzA0A7o693mmyQ,899
|
|
61
62
|
symai/backend/engines/scrape/engine_requests.py,sha256=yyVFT9JrZ4S6v5U_cykef-tn5iWGl1MAdpqnDaQ70TA,13821
|
|
62
63
|
symai/backend/engines/search/engine_openai.py,sha256=hAEu3vPZzLTvgmNc4BSZDTcNb4ek4xYeOf8xgti2zRs,14248
|
|
63
|
-
symai/backend/engines/search/engine_parallel.py,sha256=
|
|
64
|
+
symai/backend/engines/search/engine_parallel.py,sha256=PybgfkpJ_rA5FkVebZisfXwWIcki2AJPxqZfnWPl5To,26422
|
|
64
65
|
symai/backend/engines/search/engine_perplexity.py,sha256=rXnZjMCSiIRuJcNSchE58-f9zWJmYpkKMHONF_XwGnk,4100
|
|
65
66
|
symai/backend/engines/search/engine_serpapi.py,sha256=ZJJBnEDoLjkpxWt_o4vFZanwqojH8ZFBWmWNnEaIbww,3618
|
|
66
67
|
symai/backend/engines/speech_to_text/engine_local_whisper.py,sha256=EOUh2GCeEhZ2Av72i_AZ4NSj9e46Pl7Ft6sIErFy6FI,8387
|
|
@@ -68,12 +69,13 @@ symai/backend/engines/symbolic/engine_wolframalpha.py,sha256=mTH0N4rA0gMffSBLjf2
|
|
|
68
69
|
symai/backend/engines/text_to_speech/engine_openai.py,sha256=AtY0mDvIM_yZQ6AgYNXuyinZr_OaMK7XiPLQ6fe6RBo,2013
|
|
69
70
|
symai/backend/engines/text_vision/engine_clip.py,sha256=hU9vsHtKPpQYEoESyjuGXOzMhUNhvspYMCNkCAqn2x8,3648
|
|
70
71
|
symai/backend/engines/userinput/engine_console.py,sha256=fDO6PRQI3NYZ_nHVXDFIsS9cFDRv3aTOfv8h5a360jc,743
|
|
71
|
-
symai/backend/mixin/__init__.py,sha256=
|
|
72
|
-
symai/backend/mixin/anthropic.py,sha256=
|
|
72
|
+
symai/backend/mixin/__init__.py,sha256=rJjz7OSR2Qp_gl9KCL6ILuUh1BduKRPLSiWYIQuBIv4,1320
|
|
73
|
+
symai/backend/mixin/anthropic.py,sha256=GdHimGqiJcA21Jo797ZEeFzotRpCOJdBJQIChl_6NJI,2403
|
|
74
|
+
symai/backend/mixin/cerebras.py,sha256=MEc9vQ6G4KWWrt0NFjdt2y0rojhtBidwa_n4M8Z5EKI,215
|
|
73
75
|
symai/backend/mixin/deepseek.py,sha256=7TnyqXQb2t6r6-hzOClPzxfO2d7TShYC989Lmn_YTzM,414
|
|
74
76
|
symai/backend/mixin/google.py,sha256=N1xxrrTcQkcKJtdPbRorev6dfJ1F65I5XavrGR06GN4,494
|
|
75
77
|
symai/backend/mixin/groq.py,sha256=at6yFLa35Js8o7D8p_-Y4NjOPJI-lH8yx6tsCDrEy6M,227
|
|
76
|
-
symai/backend/mixin/openai.py,sha256=
|
|
78
|
+
symai/backend/mixin/openai.py,sha256=Skwn3JnXtrH0TWSJbojkMBpSkCEvtD4FesmPY6KCD70,5477
|
|
77
79
|
symai/collect/__init__.py,sha256=YD1UQoD4Z-_AodqTp48Vv-3UHYUa1g4lZnhm2AsjCd0,202
|
|
78
80
|
symai/collect/dynamic.py,sha256=72oEdshjue3t_Zs_3D08bhHPKN5mKAw0HEucWAFlqVI,3833
|
|
79
81
|
symai/collect/pipeline.py,sha256=eyxqqNpa1P5xEL50WgUZT6Z-MRocuLRBqexkVIqWqv8,5360
|
|
@@ -159,10 +161,10 @@ symai/ops/primitives.py,sha256=c0GT8rGL2p7dIL-yNoAydpwSZWx__8Ep8T2jj9Q5Eqw,11636
|
|
|
159
161
|
symai/server/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
160
162
|
symai/server/huggingface_server.py,sha256=wSAVqFiKQsCu5UB2YYVpxJBhJ7GgQBBfePxNi265yP8,9039
|
|
161
163
|
symai/server/llama_cpp_server.py,sha256=-WPTNB2cbnwtnpES4AtPM__MCasDKl83jr94JGS9tmI,2144
|
|
162
|
-
symai/server/qdrant_server.py,sha256=
|
|
163
|
-
symbolicai-1.
|
|
164
|
-
symbolicai-1.
|
|
165
|
-
symbolicai-1.
|
|
166
|
-
symbolicai-1.
|
|
167
|
-
symbolicai-1.
|
|
168
|
-
symbolicai-1.
|
|
164
|
+
symai/server/qdrant_server.py,sha256=l4r4rz29c7cO1dapXO0LQ4sHW4WF44keuz7j8v5azMc,9854
|
|
165
|
+
symbolicai-1.2.0.dist-info/licenses/LICENSE,sha256=9vRFudlJ1ghVfra5lcCUIYQCqnZSYcBLjLHbGRsrQCs,1505
|
|
166
|
+
symbolicai-1.2.0.dist-info/METADATA,sha256=hm-h6TAae8Otfn9oKVPRMjNyRGrQHSVs99j2Sq_QWik,23603
|
|
167
|
+
symbolicai-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
168
|
+
symbolicai-1.2.0.dist-info/entry_points.txt,sha256=JV5sdydIfUZdDF6QBEQHiZHod6XNPjCjpWQrXh7gTAw,261
|
|
169
|
+
symbolicai-1.2.0.dist-info/top_level.txt,sha256=bOoIDfpDIvCQtQgXcwVKJvxAKwsxpxo2IL4z92rNJjw,6
|
|
170
|
+
symbolicai-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|