argo-proxy 3.2.1__tar.gz → 3.2.3__tar.gz
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.
- {argo_proxy-3.2.1/src/argo_proxy.egg-info → argo_proxy-3.2.3}/PKG-INFO +2 -2
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/pyproject.toml +1 -1
- {argo_proxy-3.2.1 → argo_proxy-3.2.3/src/argo_proxy.egg-info}/PKG-INFO +2 -2
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argo_proxy.egg-info/SOURCES.txt +5 -2
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argo_proxy.egg-info/requires.txt +1 -1
- argo_proxy-3.2.3/src/argoproxy/__init__.py +1 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/app.py +5 -1
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/cli/handlers.py +3 -3
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/endpoints/dispatch.py +2 -4
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/endpoints/passthrough.py +4 -0
- argo_proxy-3.2.3/src/argoproxy/models/__init__.py +45 -0
- argo_proxy-3.2.3/src/argoproxy/models/constants.py +153 -0
- argo_proxy-3.2.3/src/argoproxy/models/registry.py +432 -0
- argo_proxy-3.2.3/src/argoproxy/models/upstream.py +343 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/utils/attack_logger.py +157 -2
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/utils/misc.py +10 -0
- argo_proxy-3.2.3/tests/test_security_middleware.py +161 -0
- argo_proxy-3.2.1/src/argoproxy/__init__.py +0 -1
- argo_proxy-3.2.1/src/argoproxy/models.py +0 -1011
- argo_proxy-3.2.1/src/argoproxy/utils/models.py +0 -23
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/LICENSE +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/README.md +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/setup.cfg +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argo_proxy.egg-info/dependency_links.txt +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argo_proxy.egg-info/entry_points.txt +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argo_proxy.egg-info/top_level.txt +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/_vendor/__init__.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/_vendor/semver.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/_vendor/yaml.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/cli/__init__.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/cli/display.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/cli/parser.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/config/__init__.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/config/interactive.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/config/io.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/config/model.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/config/validation.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/endpoints/dev_proxy.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/endpoints/extras.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/performance.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/py.typed +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/utils/image_processing.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/utils/logging.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/src/argoproxy/utils/transports.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/test/test_chat_completions.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/test/test_embeddings.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/test/test_function_calling_multiple.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/test/test_function_calling_single.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/test/test_leaked_tool_parser.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/test/test_model_resolution.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/tests/test_config_migrate.py +0 -0
- {argo_proxy-3.2.1 → argo_proxy-3.2.3}/tests/test_unix_socket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argo-proxy
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.3
|
|
4
4
|
Summary: Proxy server to Argo API, OpenAI format compatible
|
|
5
5
|
Author-email: Peng Ding <oaklight@gmx.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,7 @@ Requires-Python: >=3.10
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: aiohttp>=3.12.2
|
|
18
|
-
Requires-Dist: llm-rosetta>=0.7.
|
|
18
|
+
Requires-Dist: llm-rosetta>=0.7.1
|
|
19
19
|
Requires-Dist: pydantic>=2.11.7
|
|
20
20
|
Requires-Dist: tiktoken>=0.9.0
|
|
21
21
|
Requires-Dist: tqdm>=4.67.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argo-proxy
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.3
|
|
4
4
|
Summary: Proxy server to Argo API, OpenAI format compatible
|
|
5
5
|
Author-email: Peng Ding <oaklight@gmx.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,7 @@ Requires-Python: >=3.10
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: aiohttp>=3.12.2
|
|
18
|
-
Requires-Dist: llm-rosetta>=0.7.
|
|
18
|
+
Requires-Dist: llm-rosetta>=0.7.1
|
|
19
19
|
Requires-Dist: pydantic>=2.11.7
|
|
20
20
|
Requires-Dist: tiktoken>=0.9.0
|
|
21
21
|
Requires-Dist: tqdm>=4.67.1
|
|
@@ -9,7 +9,6 @@ src/argo_proxy.egg-info/requires.txt
|
|
|
9
9
|
src/argo_proxy.egg-info/top_level.txt
|
|
10
10
|
src/argoproxy/__init__.py
|
|
11
11
|
src/argoproxy/app.py
|
|
12
|
-
src/argoproxy/models.py
|
|
13
12
|
src/argoproxy/performance.py
|
|
14
13
|
src/argoproxy/py.typed
|
|
15
14
|
src/argoproxy/_vendor/__init__.py
|
|
@@ -28,11 +27,14 @@ src/argoproxy/endpoints/dev_proxy.py
|
|
|
28
27
|
src/argoproxy/endpoints/dispatch.py
|
|
29
28
|
src/argoproxy/endpoints/extras.py
|
|
30
29
|
src/argoproxy/endpoints/passthrough.py
|
|
30
|
+
src/argoproxy/models/__init__.py
|
|
31
|
+
src/argoproxy/models/constants.py
|
|
32
|
+
src/argoproxy/models/registry.py
|
|
33
|
+
src/argoproxy/models/upstream.py
|
|
31
34
|
src/argoproxy/utils/attack_logger.py
|
|
32
35
|
src/argoproxy/utils/image_processing.py
|
|
33
36
|
src/argoproxy/utils/logging.py
|
|
34
37
|
src/argoproxy/utils/misc.py
|
|
35
|
-
src/argoproxy/utils/models.py
|
|
36
38
|
src/argoproxy/utils/transports.py
|
|
37
39
|
test/test_chat_completions.py
|
|
38
40
|
test/test_embeddings.py
|
|
@@ -41,4 +43,5 @@ test/test_function_calling_single.py
|
|
|
41
43
|
test/test_leaked_tool_parser.py
|
|
42
44
|
test/test_model_resolution.py
|
|
43
45
|
tests/test_config_migrate.py
|
|
46
|
+
tests/test_security_middleware.py
|
|
44
47
|
tests/test_unix_socket.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2.3"
|
|
@@ -19,6 +19,7 @@ from .performance import (
|
|
|
19
19
|
OptimizedHTTPSession,
|
|
20
20
|
get_performance_config,
|
|
21
21
|
)
|
|
22
|
+
from .utils.attack_logger import security_middleware
|
|
22
23
|
from .utils.logging import log_debug, log_error, log_info, log_warning
|
|
23
24
|
|
|
24
25
|
|
|
@@ -320,7 +321,10 @@ def create_app():
|
|
|
320
321
|
# Set client_max_size to 100MB to handle large image payloads from remote clients
|
|
321
322
|
# Users may send images larger than the gateway's 20MB limit; argo-proxy will
|
|
322
323
|
# compress them before forwarding. Default aiohttp limit is 1MB which is too small.
|
|
323
|
-
app = web.Application(
|
|
324
|
+
app = web.Application(
|
|
325
|
+
client_max_size=100 * 1024 * 1024,
|
|
326
|
+
middlewares=[security_middleware],
|
|
327
|
+
)
|
|
324
328
|
app.on_startup.append(prepare_app)
|
|
325
329
|
app.on_shutdown.append(cleanup_app)
|
|
326
330
|
|
|
@@ -700,7 +700,7 @@ def handle_models(args: argparse.Namespace):
|
|
|
700
700
|
from collections import defaultdict
|
|
701
701
|
|
|
702
702
|
from ..config import load_config
|
|
703
|
-
from ..models import ModelRegistry
|
|
703
|
+
from ..models import ModelRegistry, classify_model_family
|
|
704
704
|
|
|
705
705
|
config_data, _ = load_config(args.config, verbose=False)
|
|
706
706
|
if not config_data:
|
|
@@ -762,7 +762,7 @@ def handle_models(args: argparse.Namespace):
|
|
|
762
762
|
if args.json:
|
|
763
763
|
output = []
|
|
764
764
|
for internal_id, aliases in sorted(chat_id_to_aliases.items()):
|
|
765
|
-
family =
|
|
765
|
+
family = classify_model_family(internal_id)
|
|
766
766
|
output.append(
|
|
767
767
|
{
|
|
768
768
|
"upstream_id": internal_id,
|
|
@@ -807,7 +807,7 @@ def handle_models(args: argparse.Namespace):
|
|
|
807
807
|
# Classify chat models by family
|
|
808
808
|
chat_families: dict[str, list[tuple[str, list[str]]]] = defaultdict(list)
|
|
809
809
|
for internal_id, aliases in sorted(chat_id_to_aliases.items()):
|
|
810
|
-
family =
|
|
810
|
+
family = classify_model_family(internal_id)
|
|
811
811
|
chat_families[family].append((internal_id, aliases))
|
|
812
812
|
|
|
813
813
|
for family in family_order:
|
|
@@ -27,6 +27,7 @@ from llm_rosetta.shims.providers import load_providers
|
|
|
27
27
|
from ..config import ArgoConfig
|
|
28
28
|
from ..models import ModelRegistry
|
|
29
29
|
from ..utils.image_processing import process_anthropic_images, process_openai_images
|
|
30
|
+
from ..utils.misc import build_user_agent
|
|
30
31
|
from ..utils.logging import (
|
|
31
32
|
clear_request_user,
|
|
32
33
|
log_converted_request,
|
|
@@ -288,10 +289,7 @@ def _build_upstream_headers(
|
|
|
288
289
|
"""Build HTTP headers for the upstream API request."""
|
|
289
290
|
headers: dict[str, str] = {"Content-Type": "application/json"}
|
|
290
291
|
|
|
291
|
-
|
|
292
|
-
client_ua = request.headers.get("User-Agent")
|
|
293
|
-
if client_ua:
|
|
294
|
-
headers["User-Agent"] = client_ua
|
|
292
|
+
headers["User-Agent"] = build_user_agent(request.headers.get("User-Agent"))
|
|
295
293
|
|
|
296
294
|
if should_use_username_passthrough():
|
|
297
295
|
api_key = _extract_client_credential(request, target_provider) or fallback_user
|
|
@@ -13,6 +13,7 @@ from aiohttp import web
|
|
|
13
13
|
|
|
14
14
|
from ..config import ArgoConfig
|
|
15
15
|
from ..models import ModelRegistry
|
|
16
|
+
from ..utils.misc import build_user_agent
|
|
16
17
|
from ..utils.logging import (
|
|
17
18
|
log_converted_request,
|
|
18
19
|
log_debug,
|
|
@@ -67,6 +68,9 @@ async def proxy_embeddings_request(
|
|
|
67
68
|
upstream_url = f"{config.native_openai_base_url}/embeddings"
|
|
68
69
|
|
|
69
70
|
headers = {"Content-Type": "application/json"}
|
|
71
|
+
|
|
72
|
+
headers["User-Agent"] = build_user_agent(request.headers.get("User-Agent"))
|
|
73
|
+
|
|
70
74
|
if "Authorization" in request.headers:
|
|
71
75
|
headers["Authorization"] = request.headers["Authorization"]
|
|
72
76
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Model registry, classification, and upstream communication."""
|
|
2
|
+
|
|
3
|
+
from .constants import (
|
|
4
|
+
ANTHROPIC_PATTERNS,
|
|
5
|
+
GEMINI_PATTERN,
|
|
6
|
+
GPT_O_PATTERN,
|
|
7
|
+
NATIVE_TOOL_CALL_MODELS,
|
|
8
|
+
NATIVE_TOOL_CALL_PATTERNS,
|
|
9
|
+
NO_SYS_MSG_MODELS,
|
|
10
|
+
NO_SYS_MSG_PATTERNS,
|
|
11
|
+
OPTION_2_INPUT_MODELS,
|
|
12
|
+
OPTION_2_INPUT_PATTERNS,
|
|
13
|
+
TIKTOKEN_ENCODING_PREFIX_MAPPING,
|
|
14
|
+
_DEFAULT_CHAT_MODELS,
|
|
15
|
+
_EMBED_MODELS,
|
|
16
|
+
classify_model_family,
|
|
17
|
+
filter_model_by_patterns,
|
|
18
|
+
flatten_mapping,
|
|
19
|
+
is_anthropic_model,
|
|
20
|
+
)
|
|
21
|
+
from .registry import ModelRegistry, OpenAIModel
|
|
22
|
+
from .upstream import Model, produce_argo_model_list
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ANTHROPIC_PATTERNS",
|
|
26
|
+
"GEMINI_PATTERN",
|
|
27
|
+
"GPT_O_PATTERN",
|
|
28
|
+
"NATIVE_TOOL_CALL_MODELS",
|
|
29
|
+
"NATIVE_TOOL_CALL_PATTERNS",
|
|
30
|
+
"NO_SYS_MSG_MODELS",
|
|
31
|
+
"NO_SYS_MSG_PATTERNS",
|
|
32
|
+
"OPTION_2_INPUT_MODELS",
|
|
33
|
+
"OPTION_2_INPUT_PATTERNS",
|
|
34
|
+
"TIKTOKEN_ENCODING_PREFIX_MAPPING",
|
|
35
|
+
"Model",
|
|
36
|
+
"ModelRegistry",
|
|
37
|
+
"OpenAIModel",
|
|
38
|
+
"_DEFAULT_CHAT_MODELS",
|
|
39
|
+
"_EMBED_MODELS",
|
|
40
|
+
"classify_model_family",
|
|
41
|
+
"filter_model_by_patterns",
|
|
42
|
+
"flatten_mapping",
|
|
43
|
+
"is_anthropic_model",
|
|
44
|
+
"produce_argo_model_list",
|
|
45
|
+
]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Model constants, default lists, patterns, and classification helpers."""
|
|
2
|
+
|
|
3
|
+
import fnmatch
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def flatten_mapping(mapping: dict[str, Any]) -> dict[str, str]:
|
|
8
|
+
flat = {}
|
|
9
|
+
for model, aliases in mapping.items():
|
|
10
|
+
if isinstance(aliases, str):
|
|
11
|
+
flat[aliases] = model
|
|
12
|
+
else:
|
|
13
|
+
for alias in aliases:
|
|
14
|
+
flat[alias] = model
|
|
15
|
+
return flat
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Default models fallback — kept in sync with the live /v1/models endpoint.
|
|
19
|
+
_DEFAULT_CHAT_MODELS = flatten_mapping(
|
|
20
|
+
{
|
|
21
|
+
# openai – gpt-4o (legacy, still served)
|
|
22
|
+
"gpt4o": "argo:gpt-4o",
|
|
23
|
+
# openai – o-series reasoning
|
|
24
|
+
"gpto1": ["argo:gpt-o1", "argo:o1"],
|
|
25
|
+
"gpto3mini": ["argo:gpt-o3-mini", "argo:o3-mini"],
|
|
26
|
+
"gpto3": ["argo:gpt-o3", "argo:o3"],
|
|
27
|
+
"gpto4mini": ["argo:gpt-o4-mini", "argo:o4-mini"],
|
|
28
|
+
# openai – gpt-4.1 family
|
|
29
|
+
"gpt41": "argo:gpt-4.1",
|
|
30
|
+
"gpt41mini": "argo:gpt-4.1-mini",
|
|
31
|
+
"gpt41nano": "argo:gpt-4.1-nano",
|
|
32
|
+
# openai – gpt-5 family
|
|
33
|
+
"gpt5": "argo:gpt-5",
|
|
34
|
+
"gpt5mini": "argo:gpt-5-mini",
|
|
35
|
+
"gpt5nano": "argo:gpt-5-nano",
|
|
36
|
+
"gpt51": "argo:gpt-5.1",
|
|
37
|
+
"gpt52": "argo:gpt-5.2",
|
|
38
|
+
"gpt54": "argo:gpt-5.4",
|
|
39
|
+
"gpt54mini": "argo:gpt-5.4-mini",
|
|
40
|
+
"gpt54nano": "argo:gpt-5.4-nano",
|
|
41
|
+
"gpt55": "argo:gpt-5.5",
|
|
42
|
+
# gemini
|
|
43
|
+
"gemini25pro": "argo:gemini-2.5-pro",
|
|
44
|
+
"gemini25flash": "argo:gemini-2.5-flash",
|
|
45
|
+
"gemini35flash": "argo:gemini-3.5-flash",
|
|
46
|
+
"gemini31flashlite": "argo:gemini-3.1-flash-lite",
|
|
47
|
+
# claude – opus
|
|
48
|
+
"claudeopus48": ["argo:claude-4.8-opus", "argo:claude-opus-4.8"],
|
|
49
|
+
"claudeopus47": ["argo:claude-4.7-opus", "argo:claude-opus-4.7"],
|
|
50
|
+
"claudeopus46": ["argo:claude-4.6-opus", "argo:claude-opus-4.6"],
|
|
51
|
+
"claudeopus45": ["argo:claude-4.5-opus", "argo:claude-opus-4.5"],
|
|
52
|
+
"claudeopus41": ["argo:claude-4.1-opus", "argo:claude-opus-4.1"],
|
|
53
|
+
# claude – sonnet
|
|
54
|
+
"claudesonnet5": ["argo:claude-5-sonnet", "argo:claude-sonnet-5"],
|
|
55
|
+
"claudesonnet46": ["argo:claude-4.6-sonnet", "argo:claude-sonnet-4.6"],
|
|
56
|
+
"claudesonnet45": ["argo:claude-4.5-sonnet", "argo:claude-sonnet-4.5"],
|
|
57
|
+
# claude – haiku
|
|
58
|
+
"claudehaiku45": ["argo:claude-4.5-haiku", "argo:claude-haiku-4.5"],
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
_EMBED_MODELS = flatten_mapping(
|
|
63
|
+
{
|
|
64
|
+
"ada002": "argo:text-embedding-ada-002",
|
|
65
|
+
"v3small": "argo:text-embedding-3-small",
|
|
66
|
+
"v3large": "argo:text-embedding-3-large",
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def filter_model_by_patterns(
|
|
72
|
+
model_dict: dict[str, str], patterns: set[str]
|
|
73
|
+
) -> list[str]:
|
|
74
|
+
"""Filter model_dict values (model_id) by given fnmatch patterns,
|
|
75
|
+
returning both the model_name (key) and model_id (value) for matches."""
|
|
76
|
+
matching = set()
|
|
77
|
+
for model_name, model_id in model_dict.items():
|
|
78
|
+
if any(fnmatch.fnmatch(model_id, pattern) for pattern in patterns):
|
|
79
|
+
matching.add(model_name)
|
|
80
|
+
matching.add(model_id)
|
|
81
|
+
return sorted(matching)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# any models that unable to handle system prompt (o1-mini / o1-preview retired)
|
|
85
|
+
NO_SYS_MSG_PATTERNS: set[str] = set()
|
|
86
|
+
|
|
87
|
+
NO_SYS_MSG_MODELS = filter_model_by_patterns(
|
|
88
|
+
_DEFAULT_CHAT_MODELS,
|
|
89
|
+
NO_SYS_MSG_PATTERNS,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# any models that only able to handle single system prompt and no system prompt at all
|
|
94
|
+
OPTION_2_INPUT_PATTERNS: set[str] = set()
|
|
95
|
+
|
|
96
|
+
OPTION_2_INPUT_MODELS = filter_model_by_patterns(
|
|
97
|
+
_DEFAULT_CHAT_MODELS,
|
|
98
|
+
OPTION_2_INPUT_PATTERNS,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# any models that supports native tool call
|
|
102
|
+
NATIVE_TOOL_CALL_PATTERNS: set[str] = {
|
|
103
|
+
"*o1",
|
|
104
|
+
"*o3*",
|
|
105
|
+
"*o4*",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
NATIVE_TOOL_CALL_MODELS = filter_model_by_patterns(
|
|
109
|
+
_DEFAULT_CHAT_MODELS,
|
|
110
|
+
NATIVE_TOOL_CALL_PATTERNS,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
TIKTOKEN_ENCODING_PREFIX_MAPPING = {
|
|
114
|
+
"gpt5": "o200k_base", # gpt-5 family
|
|
115
|
+
"gpto": "o200k_base", # o-series
|
|
116
|
+
"gpt4o": "o200k_base", # gpt-4o
|
|
117
|
+
"gpt41": "o200k_base", # gpt-4.1 family
|
|
118
|
+
# this order need to be preserved to correctly parse mapping
|
|
119
|
+
"gpt4": "cl100k_base", # gpt-4 series (legacy fallback)
|
|
120
|
+
"ada002": "cl100k_base", # embedding
|
|
121
|
+
"v3": "cl100k_base", # embedding
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# --- Provider detection patterns ---
|
|
125
|
+
|
|
126
|
+
GPT_O_PATTERN = "gpto*"
|
|
127
|
+
GEMINI_PATTERN = "gemini*"
|
|
128
|
+
|
|
129
|
+
ANTHROPIC_PATTERNS = ("claude*", "sonnet*", "opus*", "haiku*", "fable*")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def is_anthropic_model(model_id: str) -> bool:
|
|
133
|
+
return any(fnmatch.fnmatch(model_id, p) for p in ANTHROPIC_PATTERNS)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def classify_model_family(model_id: str) -> str:
|
|
137
|
+
"""Classify a model by its provider family based on model ID patterns."""
|
|
138
|
+
if (
|
|
139
|
+
fnmatch.fnmatch(model_id, "gpt*")
|
|
140
|
+
or fnmatch.fnmatch(model_id, GPT_O_PATTERN)
|
|
141
|
+
or fnmatch.fnmatch(model_id, "ada*")
|
|
142
|
+
or fnmatch.fnmatch(model_id, "v3*")
|
|
143
|
+
or fnmatch.fnmatch(model_id, "*embedding*")
|
|
144
|
+
):
|
|
145
|
+
return "openai"
|
|
146
|
+
|
|
147
|
+
if is_anthropic_model(model_id):
|
|
148
|
+
return "anthropic"
|
|
149
|
+
|
|
150
|
+
if fnmatch.fnmatch(model_id, GEMINI_PATTERN):
|
|
151
|
+
return "google"
|
|
152
|
+
|
|
153
|
+
return "unknown"
|