argo-proxy 3.1.1__tar.gz → 3.2.0a0__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.1.1/src/argo_proxy.egg-info → argo_proxy-3.2.0a0}/PKG-INFO +5 -5
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/pyproject.toml +4 -4
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0/src/argo_proxy.egg-info}/PKG-INFO +5 -5
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/SOURCES.txt +2 -2
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/requires.txt +4 -4
- argo_proxy-3.2.0a0/src/argoproxy/__init__.py +1 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/app.py +65 -3
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/handlers.py +21 -2
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/parser.py +11 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/io.py +15 -1
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/model.py +5 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/dispatch.py +136 -428
- argo_proxy-3.2.0a0/tests/test_unix_socket.py +179 -0
- argo_proxy-3.1.1/src/argoproxy/__init__.py +0 -1
- argo_proxy-3.1.1/src/argoproxy/utils/tool_calls.py +0 -263
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/LICENSE +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/README.md +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/setup.cfg +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/dependency_links.txt +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/entry_points.txt +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/top_level.txt +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/_vendor/__init__.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/_vendor/semver.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/_vendor/yaml.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/__init__.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/display.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/__init__.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/interactive.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/validation.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/dev_proxy.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/extras.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/passthrough.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/models.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/performance.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/py.typed +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/attack_logger.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/image_processing.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/logging.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/misc.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/models.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/transports.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_chat_completions.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_embeddings.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_function_calling_multiple.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_function_calling_single.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_leaked_tool_parser.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_model_resolution.py +0 -0
- {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/tests/test_config_migrate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argo-proxy
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0a0
|
|
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
|
|
18
|
+
Requires-Dist: llm-rosetta>=0.7.0a0
|
|
19
19
|
Requires-Dist: pydantic>=2.11.7
|
|
20
20
|
Requires-Dist: tiktoken>=0.9.0
|
|
21
21
|
Requires-Dist: tqdm>=4.67.1
|
|
@@ -23,9 +23,9 @@ Requires-Dist: Pillow>=12.0.0
|
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9; extra == "dev"
|
|
25
25
|
Requires-Dist: openai>=1.79.0; extra == "dev"
|
|
26
|
-
Requires-Dist: ruff
|
|
27
|
-
Requires-Dist: ty
|
|
28
|
-
Requires-Dist: complexipy
|
|
26
|
+
Requires-Dist: ruff==0.15.20; extra == "dev"
|
|
27
|
+
Requires-Dist: ty==0.0.54; extra == "dev"
|
|
28
|
+
Requires-Dist: complexipy==5.6.1; extra == "dev"
|
|
29
29
|
Requires-Dist: build>=1.2.2.post1; extra == "dev"
|
|
30
30
|
Requires-Dist: twine>=6.1.0; extra == "dev"
|
|
31
31
|
Requires-Dist: httpx>=0.28.1; extra == "dev"
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
|
|
21
21
|
dependencies = [
|
|
22
22
|
"aiohttp>=3.12.2",
|
|
23
|
-
"llm-rosetta>=0.
|
|
23
|
+
"llm-rosetta>=0.7.0a0",
|
|
24
24
|
"pydantic>=2.11.7",
|
|
25
25
|
"tiktoken>=0.9.0",
|
|
26
26
|
"tqdm>=4.67.1",
|
|
@@ -31,9 +31,9 @@ dependencies = [
|
|
|
31
31
|
dev = [
|
|
32
32
|
"dotenv>=0.9.9",
|
|
33
33
|
"openai>=1.79.0",
|
|
34
|
-
"ruff
|
|
35
|
-
"ty
|
|
36
|
-
"complexipy
|
|
34
|
+
"ruff==0.15.20",
|
|
35
|
+
"ty==0.0.54",
|
|
36
|
+
"complexipy==5.6.1",
|
|
37
37
|
"build>=1.2.2.post1",
|
|
38
38
|
"twine>=6.1.0",
|
|
39
39
|
"httpx>=0.28.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argo-proxy
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0a0
|
|
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
|
|
18
|
+
Requires-Dist: llm-rosetta>=0.7.0a0
|
|
19
19
|
Requires-Dist: pydantic>=2.11.7
|
|
20
20
|
Requires-Dist: tiktoken>=0.9.0
|
|
21
21
|
Requires-Dist: tqdm>=4.67.1
|
|
@@ -23,9 +23,9 @@ Requires-Dist: Pillow>=12.0.0
|
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9; extra == "dev"
|
|
25
25
|
Requires-Dist: openai>=1.79.0; extra == "dev"
|
|
26
|
-
Requires-Dist: ruff
|
|
27
|
-
Requires-Dist: ty
|
|
28
|
-
Requires-Dist: complexipy
|
|
26
|
+
Requires-Dist: ruff==0.15.20; extra == "dev"
|
|
27
|
+
Requires-Dist: ty==0.0.54; extra == "dev"
|
|
28
|
+
Requires-Dist: complexipy==5.6.1; extra == "dev"
|
|
29
29
|
Requires-Dist: build>=1.2.2.post1; extra == "dev"
|
|
30
30
|
Requires-Dist: twine>=6.1.0; extra == "dev"
|
|
31
31
|
Requires-Dist: httpx>=0.28.1; extra == "dev"
|
|
@@ -33,7 +33,6 @@ src/argoproxy/utils/image_processing.py
|
|
|
33
33
|
src/argoproxy/utils/logging.py
|
|
34
34
|
src/argoproxy/utils/misc.py
|
|
35
35
|
src/argoproxy/utils/models.py
|
|
36
|
-
src/argoproxy/utils/tool_calls.py
|
|
37
36
|
src/argoproxy/utils/transports.py
|
|
38
37
|
test/test_chat_completions.py
|
|
39
38
|
test/test_embeddings.py
|
|
@@ -41,4 +40,5 @@ test/test_function_calling_multiple.py
|
|
|
41
40
|
test/test_function_calling_single.py
|
|
42
41
|
test/test_leaked_tool_parser.py
|
|
43
42
|
test/test_model_resolution.py
|
|
44
|
-
tests/test_config_migrate.py
|
|
43
|
+
tests/test_config_migrate.py
|
|
44
|
+
tests/test_unix_socket.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
aiohttp>=3.12.2
|
|
2
|
-
llm-rosetta
|
|
2
|
+
llm-rosetta>=0.7.0a0
|
|
3
3
|
pydantic>=2.11.7
|
|
4
4
|
tiktoken>=0.9.0
|
|
5
5
|
tqdm>=4.67.1
|
|
@@ -11,9 +11,9 @@ argo-proxy[dev,test]
|
|
|
11
11
|
[dev]
|
|
12
12
|
dotenv>=0.9.9
|
|
13
13
|
openai>=1.79.0
|
|
14
|
-
ruff
|
|
15
|
-
ty
|
|
16
|
-
complexipy
|
|
14
|
+
ruff==0.15.20
|
|
15
|
+
ty==0.0.54
|
|
16
|
+
complexipy==5.6.1
|
|
17
17
|
build>=1.2.2.post1
|
|
18
18
|
twine>=6.1.0
|
|
19
19
|
httpx>=0.28.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2.0a0"
|
|
@@ -331,9 +331,10 @@ def create_app():
|
|
|
331
331
|
context="app",
|
|
332
332
|
)
|
|
333
333
|
|
|
334
|
-
#
|
|
334
|
+
# Register basic utility routes available in dev mode
|
|
335
335
|
app.router.add_get("/health", health_check)
|
|
336
336
|
app.router.add_get("/version", get_version)
|
|
337
|
+
app.router.add_post("/refresh", refresh_models)
|
|
337
338
|
|
|
338
339
|
# Register dev proxy routes using a temporary config for URL resolution
|
|
339
340
|
dev_config = ArgoConfig()
|
|
@@ -366,7 +367,7 @@ def create_app():
|
|
|
366
367
|
return app
|
|
367
368
|
|
|
368
369
|
|
|
369
|
-
def run(*, host: str = "0.0.0.0", port: int = 8080):
|
|
370
|
+
def run(*, host: str = "0.0.0.0", port: int = 8080, socket: str = ""):
|
|
370
371
|
app = create_app()
|
|
371
372
|
|
|
372
373
|
# Add this to ensure signal handlers trigger a full shutdown
|
|
@@ -378,7 +379,68 @@ def run(*, host: str = "0.0.0.0", port: int = 8080):
|
|
|
378
379
|
signal.signal(sig, _force_exit)
|
|
379
380
|
|
|
380
381
|
try:
|
|
381
|
-
|
|
382
|
+
if socket:
|
|
383
|
+
_run_unix_socket(app, socket)
|
|
384
|
+
else:
|
|
385
|
+
web.run_app(app, host=host, port=port)
|
|
382
386
|
except Exception as e:
|
|
383
387
|
log_error(f"An error occurred while starting the server: {e}", context="app")
|
|
384
388
|
sys.exit(1)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _run_unix_socket(app: web.Application, socket_path: str):
|
|
392
|
+
"""Start the server listening on a Unix domain socket.
|
|
393
|
+
|
|
394
|
+
Removes any stale socket file, starts the app, then restricts
|
|
395
|
+
permissions to owner-only (0o600) so other users on a shared
|
|
396
|
+
host cannot connect.
|
|
397
|
+
"""
|
|
398
|
+
import stat
|
|
399
|
+
|
|
400
|
+
path = os.path.realpath(socket_path)
|
|
401
|
+
|
|
402
|
+
# Remove stale socket if present
|
|
403
|
+
if os.path.exists(path):
|
|
404
|
+
try:
|
|
405
|
+
st = os.stat(path)
|
|
406
|
+
if stat.S_ISSOCK(st.st_mode):
|
|
407
|
+
os.unlink(path)
|
|
408
|
+
log_info(f"Removed stale socket: {path}", context="app")
|
|
409
|
+
else:
|
|
410
|
+
log_error(
|
|
411
|
+
f"Socket path exists and is not a socket: {path}", context="app"
|
|
412
|
+
)
|
|
413
|
+
sys.exit(1)
|
|
414
|
+
except OSError as e:
|
|
415
|
+
log_error(f"Cannot remove stale socket {path}: {e}", context="app")
|
|
416
|
+
sys.exit(1)
|
|
417
|
+
|
|
418
|
+
# Ensure parent directory exists
|
|
419
|
+
parent = os.path.dirname(path)
|
|
420
|
+
if not os.path.isdir(parent):
|
|
421
|
+
log_error(f"Socket parent directory does not exist: {parent}", context="app")
|
|
422
|
+
sys.exit(1)
|
|
423
|
+
|
|
424
|
+
async def _set_socket_permissions(app_inner: web.Application):
|
|
425
|
+
"""Set restrictive permissions on the socket after it is created."""
|
|
426
|
+
if os.path.exists(path):
|
|
427
|
+
os.chmod(path, 0o600)
|
|
428
|
+
log_info(
|
|
429
|
+
f"Socket permissions set to 0700 (owner-only): {path}",
|
|
430
|
+
context="app",
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
async def _cleanup_socket(app_inner: web.Application):
|
|
434
|
+
"""Remove socket file on shutdown."""
|
|
435
|
+
if os.path.exists(path):
|
|
436
|
+
try:
|
|
437
|
+
os.unlink(path)
|
|
438
|
+
log_info(f"Removed socket: {path}", context="app")
|
|
439
|
+
except OSError:
|
|
440
|
+
pass
|
|
441
|
+
|
|
442
|
+
app.on_startup.append(_set_socket_permissions)
|
|
443
|
+
app.on_shutdown.append(_cleanup_socket)
|
|
444
|
+
|
|
445
|
+
log_info(f"Starting server on unix socket: {path}", context="app")
|
|
446
|
+
web.run_app(app, path=path)
|
|
@@ -12,7 +12,7 @@ from .._vendor.semver import version_parse
|
|
|
12
12
|
from ..__init__ import __version__
|
|
13
13
|
from ..config import PATHS_TO_TRY, validate_config
|
|
14
14
|
from ..utils.attack_logger import get_attack_logger
|
|
15
|
-
from ..utils.logging import log_error
|
|
15
|
+
from ..utils.logging import log_error, log_info
|
|
16
16
|
from .display import CHANGELOG_URL, display_startup_banner
|
|
17
17
|
|
|
18
18
|
|
|
@@ -25,8 +25,12 @@ def set_config_envs(args: argparse.Namespace):
|
|
|
25
25
|
"""Set environment variables from serve CLI arguments."""
|
|
26
26
|
if args.config:
|
|
27
27
|
os.environ["CONFIG_PATH"] = args.config
|
|
28
|
+
if args.host:
|
|
29
|
+
os.environ["HOST"] = args.host
|
|
28
30
|
if args.port:
|
|
29
31
|
os.environ["PORT"] = str(args.port)
|
|
32
|
+
if getattr(args, "socket", None):
|
|
33
|
+
os.environ["SOCKET"] = args.socket
|
|
30
34
|
if args.verbose:
|
|
31
35
|
os.environ["VERBOSE"] = str(True)
|
|
32
36
|
if args.quiet:
|
|
@@ -73,7 +77,22 @@ def handle_serve(args: argparse.Namespace):
|
|
|
73
77
|
if config_path is not None:
|
|
74
78
|
get_attack_logger().set_config_path(config_path)
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
if config_instance.socket:
|
|
81
|
+
log_info(
|
|
82
|
+
f"🌐 Listening on unix://{config_instance.socket}",
|
|
83
|
+
context="cli",
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
log_info(
|
|
87
|
+
f"🌐 Listening on http://{config_instance.host}:{config_instance.port}",
|
|
88
|
+
context="cli",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
run(
|
|
92
|
+
host=config_instance.host,
|
|
93
|
+
port=config_instance.port,
|
|
94
|
+
socket=config_instance.socket,
|
|
95
|
+
)
|
|
77
96
|
except KeyError:
|
|
78
97
|
log_error("Port not specified in configuration file.", context="cli")
|
|
79
98
|
sys.exit(1)
|
|
@@ -40,6 +40,17 @@ def _add_serve_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
40
40
|
type=int,
|
|
41
41
|
help="Port number to bind the server to",
|
|
42
42
|
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--socket",
|
|
45
|
+
"-S",
|
|
46
|
+
type=str,
|
|
47
|
+
default=None,
|
|
48
|
+
help=(
|
|
49
|
+
"Unix socket path to listen on (overrides --host/--port).\n"
|
|
50
|
+
"Permissions are set to 0700 (owner-only) for security on\n"
|
|
51
|
+
"shared hosts. Example: /run/user/$(id -u)/argo-proxy.sock"
|
|
52
|
+
),
|
|
53
|
+
)
|
|
43
54
|
|
|
44
55
|
verbosity = parser.add_mutually_exclusive_group()
|
|
45
56
|
verbosity.add_argument(
|
|
@@ -34,7 +34,15 @@ def _format_config_yaml(data: dict) -> str:
|
|
|
34
34
|
groups: list[tuple[str, list[str]]] = [
|
|
35
35
|
(
|
|
36
36
|
"# Core settings",
|
|
37
|
-
[
|
|
37
|
+
[
|
|
38
|
+
"config_version",
|
|
39
|
+
"user",
|
|
40
|
+
"host",
|
|
41
|
+
"port",
|
|
42
|
+
"socket",
|
|
43
|
+
"verbose",
|
|
44
|
+
"log_to_file",
|
|
45
|
+
],
|
|
38
46
|
),
|
|
39
47
|
(
|
|
40
48
|
"# Upstream",
|
|
@@ -118,6 +126,12 @@ def save_config(
|
|
|
118
126
|
|
|
119
127
|
def _apply_env_overrides(config_data: ArgoConfig) -> ArgoConfig:
|
|
120
128
|
"""Apply environment variable overrides to the config."""
|
|
129
|
+
if env_host := os.getenv("HOST"):
|
|
130
|
+
config_data.host = env_host
|
|
131
|
+
|
|
132
|
+
if env_socket := os.getenv("SOCKET"):
|
|
133
|
+
config_data.socket = env_socket
|
|
134
|
+
|
|
121
135
|
if env_port := os.getenv("PORT"):
|
|
122
136
|
config_data.port = int(env_port)
|
|
123
137
|
|
|
@@ -30,6 +30,7 @@ class ArgoConfig:
|
|
|
30
30
|
# Configuration fields with default values
|
|
31
31
|
host: str = "0.0.0.0" # Default to 0.0.0.0
|
|
32
32
|
port: int = 44497
|
|
33
|
+
socket: str = "" # Unix socket path; overrides host:port when set
|
|
33
34
|
user: str = ""
|
|
34
35
|
verbose: bool = True
|
|
35
36
|
|
|
@@ -248,6 +249,10 @@ class ArgoConfig:
|
|
|
248
249
|
# Drop private fields
|
|
249
250
|
serialized = {k: v for k, v in serialized.items() if not k.startswith("_")}
|
|
250
251
|
|
|
252
|
+
# Only persist socket when explicitly set
|
|
253
|
+
if not self.socket:
|
|
254
|
+
serialized.pop("socket", None)
|
|
255
|
+
|
|
251
256
|
# Add the user-configurable base URL (stored as private field)
|
|
252
257
|
if self._argo_base_url:
|
|
253
258
|
serialized["argo_base_url"] = self.argo_base_url
|