openai-http-proxy 3.0.2__tar.gz → 3.2.0__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.
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/PKG-INFO +5 -2
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/README.md +3 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/bootstrap.py +11 -6
- openai_http_proxy-3.2.0/lm_proxy/strategies/__init__.py +4 -0
- openai_http_proxy-3.2.0/lm_proxy/strategies/fallback.py +69 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/utils.py +32 -6
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/pyproject.toml +2 -2
- openai_http_proxy-3.0.2/lm_proxy/_app.py +0 -82
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/LICENSE +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/__init__.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/__main__.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/api_key_check/__init__.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/api_key_check/allow_all.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/api_key_check/in_config.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/api_key_check/with_request.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/app.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/base_types.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/config.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/config_loaders/__init__.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/config_loaders/json.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/config_loaders/python.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/config_loaders/toml.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/config_loaders/yaml.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/core.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/errors.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/handlers/__init__.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/handlers/forward_http_headers.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/handlers/rate_limiter.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/loggers.py +0 -0
- {openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/models_endpoint.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: openai-http-proxy
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: OpenAI HTTP Proxy is an OpenAI-compatible http proxy server for inferencing various LLMs capable of working with Google, Anthropic, OpenAI APIs, local PyTorch inference, etc.
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ Provides-Extra: all
|
|
|
45
45
|
Provides-Extra: anthropic
|
|
46
46
|
Provides-Extra: google
|
|
47
47
|
Provides-Extra: test
|
|
48
|
-
Requires-Dist: ai-microcore (>=5.1.2,<
|
|
48
|
+
Requires-Dist: ai-microcore (>=5.1.2,<7)
|
|
49
49
|
Requires-Dist: anthropic (>=0.77,<1) ; extra == "all"
|
|
50
50
|
Requires-Dist: anthropic (>=0.77,<1) ; extra == "anthropic"
|
|
51
51
|
Requires-Dist: fastapi (>=0.121.3,<1)
|
|
@@ -73,6 +73,8 @@ Description-Content-Type: text/markdown
|
|
|
73
73
|
<a href="https://github.com/Nayjest/lm-proxy/actions/workflows/code-style.yml"><img src="https://github.com/Nayjest/lm-proxy/actions/workflows/code-style.yml/badge.svg" alt="Code Style"></a>
|
|
74
74
|
<img src="https://raw.githubusercontent.com/Nayjest/lm-proxy/main/coverage.svg" alt="Code Coverage">
|
|
75
75
|
<a href="https://www.bestpractices.dev/projects/11364"><img src="https://www.bestpractices.dev/projects/11364/badge"></a>
|
|
76
|
+
<br>
|
|
77
|
+
<a href="https://github.com/vshymanskyy/StandWithUkraine/blob/main/README.md"><img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/refs/heads/main/badges/StandWithUkraine.svg" alt="Stand With Ukraine"></a>
|
|
76
78
|
<a href="https://github.com/Nayjest/lm-proxy/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Nayjest/lm-proxy?color=d08aff" alt="License"></a>
|
|
77
79
|
</p>
|
|
78
80
|
|
|
@@ -711,6 +713,7 @@ prefix = "SECURITY_AUDIT"
|
|
|
711
713
|
|
|
712
714
|
For more detailed information, check out these articles:
|
|
713
715
|
- [HTTP Header Management](https://github.com/Nayjest/lm-proxy/blob/main/doc/http_headers.md)
|
|
716
|
+
- [Configuring fallbacks](https://github.com/Nayjest/lm-proxy/blob/main/doc/fallback.md)
|
|
714
717
|
|
|
715
718
|
|
|
716
719
|
## 🚧 Known Limitations<a id="-known-limitations"></a>
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<a href="https://github.com/Nayjest/lm-proxy/actions/workflows/code-style.yml"><img src="https://github.com/Nayjest/lm-proxy/actions/workflows/code-style.yml/badge.svg" alt="Code Style"></a>
|
|
9
9
|
<img src="https://raw.githubusercontent.com/Nayjest/lm-proxy/main/coverage.svg" alt="Code Coverage">
|
|
10
10
|
<a href="https://www.bestpractices.dev/projects/11364"><img src="https://www.bestpractices.dev/projects/11364/badge"></a>
|
|
11
|
+
<br>
|
|
12
|
+
<a href="https://github.com/vshymanskyy/StandWithUkraine/blob/main/README.md"><img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/refs/heads/main/badges/StandWithUkraine.svg" alt="Stand With Ukraine"></a>
|
|
11
13
|
<a href="https://github.com/Nayjest/lm-proxy/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Nayjest/lm-proxy?color=d08aff" alt="License"></a>
|
|
12
14
|
</p>
|
|
13
15
|
|
|
@@ -646,6 +648,7 @@ prefix = "SECURITY_AUDIT"
|
|
|
646
648
|
|
|
647
649
|
For more detailed information, check out these articles:
|
|
648
650
|
- [HTTP Header Management](https://github.com/Nayjest/lm-proxy/blob/main/doc/http_headers.md)
|
|
651
|
+
- [Configuring fallbacks](https://github.com/Nayjest/lm-proxy/blob/main/doc/fallback.md)
|
|
649
652
|
|
|
650
653
|
|
|
651
654
|
## 🚧 Known Limitations<a id="-known-limitations"></a>
|
|
@@ -61,6 +61,12 @@ class Env:
|
|
|
61
61
|
@staticmethod
|
|
62
62
|
def init(config: Config | str | PathLike, debug: bool = False):
|
|
63
63
|
"""Initializes the LM-Proxy runtime environment singleton."""
|
|
64
|
+
|
|
65
|
+
def _is_async_callable(obj) -> bool:
|
|
66
|
+
return inspect.iscoroutinefunction(obj) or inspect.iscoroutinefunction(
|
|
67
|
+
getattr(obj, "__call__", None)
|
|
68
|
+
)
|
|
69
|
+
|
|
64
70
|
env.debug = debug
|
|
65
71
|
|
|
66
72
|
if not isinstance(config, Config):
|
|
@@ -78,14 +84,13 @@ class Env:
|
|
|
78
84
|
# initialize connections
|
|
79
85
|
env.connections = {}
|
|
80
86
|
for conn_name, conn_config in env.config.connections.items():
|
|
81
|
-
logging.info("Initializing
|
|
87
|
+
logging.info("Initializing \"%s\" LLM proxy connection...", ui.green(conn_name))
|
|
82
88
|
try:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
env.connections[conn_name] = resolve_instance_or_callable(conn_config)
|
|
89
|
+
fn_or_config = resolve_instance_or_callable(conn_config, allow_types=[dict])
|
|
90
|
+
if _is_async_callable(fn_or_config):
|
|
91
|
+
env.connections[conn_name] = fn_or_config
|
|
87
92
|
else:
|
|
88
|
-
mc.configure(**
|
|
93
|
+
mc.configure(**fn_or_config, EMBEDDING_DB_TYPE=mc.EmbeddingDbType.NONE)
|
|
89
94
|
env.connections[conn_name] = mc.env().llm_async_function
|
|
90
95
|
except mc.LLMConfigError as e:
|
|
91
96
|
raise ValueError(f"Error in configuration for connection '{conn_name}': {e}") from e
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Fallback strategy: tries connections in order until one succeeds."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import microcore as mc
|
|
6
|
+
from microcore import ui
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, field_validator
|
|
9
|
+
|
|
10
|
+
from ..bootstrap import env
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Fallback(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
Tries each connection in sequence, returning the first successful response.
|
|
16
|
+
|
|
17
|
+
If a connection fails, the error is logged and the next one is attempted.
|
|
18
|
+
If all connections fail, the last exception is re-raised.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
connections: dict[str, dict] | list[str]
|
|
22
|
+
|
|
23
|
+
@field_validator("connections")
|
|
24
|
+
@classmethod
|
|
25
|
+
def validate_connections(cls, v: list[str] | dict[str]) -> dict[str, dict]:
|
|
26
|
+
if len(v) < 2:
|
|
27
|
+
raise ValueError("Fallback requires at least 2 connections")
|
|
28
|
+
if isinstance(v, list):
|
|
29
|
+
v_dict = {}
|
|
30
|
+
for conn_name_and_model in v:
|
|
31
|
+
if "." in conn_name_and_model:
|
|
32
|
+
conn_name, model = conn_name_and_model.split(".", 1)
|
|
33
|
+
v_dict[conn_name] = {"model": model}
|
|
34
|
+
else:
|
|
35
|
+
v_dict[conn_name_and_model] = {}
|
|
36
|
+
return v_dict
|
|
37
|
+
return v
|
|
38
|
+
|
|
39
|
+
async def __call__(self, *args, **kwargs):
|
|
40
|
+
for conn_name, override_params in self.connections.items():
|
|
41
|
+
logging.info(
|
|
42
|
+
f'Fallback strategy: using "{ui.green(conn_name)}" connection'
|
|
43
|
+
+ (
|
|
44
|
+
(", overridden params: " + ui.yellow(override_params))
|
|
45
|
+
if override_params
|
|
46
|
+
else ""
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
if conn_name not in env.connections:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Fallback connection '{conn_name}' not found. "
|
|
52
|
+
f"Available: {list(env.connections.keys())}"
|
|
53
|
+
)
|
|
54
|
+
kw_args = dict(kwargs)
|
|
55
|
+
kw_args.update(override_params or {})
|
|
56
|
+
fn: mc.types.LLMAsyncFunctionType = env.connections[conn_name]
|
|
57
|
+
try:
|
|
58
|
+
return await fn(*args, **kw_args)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
is_last = conn_name == list(self.connections)[-1]
|
|
61
|
+
if is_last:
|
|
62
|
+
logging.error("All fallback connections failed, last error: %s", e)
|
|
63
|
+
raise
|
|
64
|
+
logging.warning(
|
|
65
|
+
"Connection '%s' failed (%s: %s), trying next one...",
|
|
66
|
+
conn_name,
|
|
67
|
+
type(e).__name__,
|
|
68
|
+
e,
|
|
69
|
+
)
|
|
@@ -37,19 +37,45 @@ def resolve_instance_or_callable(
|
|
|
37
37
|
allow_types: list[type] = None,
|
|
38
38
|
) -> Callable | object | None:
|
|
39
39
|
"""
|
|
40
|
-
Resolves a
|
|
40
|
+
Resolves a configuration value into a callable or an object instance.
|
|
41
|
+
|
|
42
|
+
Supports multiple input formats commonly used in configuration files:
|
|
43
|
+
|
|
44
|
+
- ``None`` or ``""``: Returns ``None``.
|
|
45
|
+
- ``dict`` with a class key: Instantiates the class with remaining dict entries as kwargs.
|
|
46
|
+
Example: ``{"class": "my_module.MyClass", "param": 42}`` → ``MyClass(param=42)``
|
|
47
|
+
- ``str``: Imports the dotted path. Classes are instantiated; functions are returned as-is.
|
|
48
|
+
Example: ``"my_module.my_func"`` → ``my_func``
|
|
49
|
+
Example: ``"my_module.MyClass"`` → ``MyClass()``
|
|
50
|
+
- ``class``: Instantiated with no arguments.
|
|
51
|
+
- ``callable``: Returned as-is (lambdas, function objects, callable instances).
|
|
52
|
+
- Other types: Accepted only if their type is listed in ``allow_types``.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
item: The configuration value to resolve.
|
|
56
|
+
class_key: Key used to identify the class in dict configs (default: ``"class"``).
|
|
57
|
+
debug_name: Human-readable label for error messages
|
|
58
|
+
(e.g., ``"logger"``, ``"api_key_check"``).
|
|
59
|
+
allow_types: Additional types to accept as valid values without transformation.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
A callable, an object instance, or ``None``.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If the input cannot be resolved to a valid callable or instance.
|
|
41
66
|
"""
|
|
42
67
|
if item is None or item == "":
|
|
43
68
|
return None
|
|
44
69
|
if isinstance(item, dict):
|
|
45
|
-
if class_key
|
|
70
|
+
if class_key in item:
|
|
71
|
+
args = dict(item)
|
|
72
|
+
class_name = args.pop(class_key)
|
|
73
|
+
constructor = resolve_callable(class_name)
|
|
74
|
+
return constructor(**args)
|
|
75
|
+
elif dict not in (allow_types or []):
|
|
46
76
|
raise ValueError(
|
|
47
77
|
f"'{class_key}' key is missing in {debug_name or 'item'} config: {item}"
|
|
48
78
|
)
|
|
49
|
-
args = dict(item)
|
|
50
|
-
class_name = args.pop(class_key)
|
|
51
|
-
constructor = resolve_callable(class_name)
|
|
52
|
-
return constructor(**args)
|
|
53
79
|
if isinstance(item, str):
|
|
54
80
|
fn = resolve_callable(item)
|
|
55
81
|
return fn() if inspect.isclass(fn) else fn
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "openai-http-proxy"
|
|
3
|
-
version = "3.0
|
|
3
|
+
version = "3.2.0"
|
|
4
4
|
description = "OpenAI HTTP Proxy is an OpenAI-compatible http proxy server for inferencing various LLMs capable of working with Google, Anthropic, OpenAI APIs, local PyTorch inference, etc."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = ["llm", "large language models", "ai", "gpt", "openai", "proxy", "http", "proxy-server", "llm gateway", "openai", "anthropic", "google genai"]
|
|
@@ -35,7 +35,7 @@ documentation = "https://github.com/Nayjest/lm-proxy#readme"
|
|
|
35
35
|
license = { file = "LICENSE" }
|
|
36
36
|
|
|
37
37
|
dependencies = [
|
|
38
|
-
"ai-microcore>=5.1.2,<
|
|
38
|
+
"ai-microcore>=5.1.2,<7",
|
|
39
39
|
"fastapi>=0.121.3,<1",
|
|
40
40
|
"uvicorn>=0.41.0",
|
|
41
41
|
"typer>=0.24.0",
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
LM-Proxy Application Entrypoint
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from typing import Optional
|
|
7
|
-
from fastapi import FastAPI
|
|
8
|
-
import typer
|
|
9
|
-
import uvicorn
|
|
10
|
-
|
|
11
|
-
from .bootstrap import env, bootstrap
|
|
12
|
-
from .core import chat_completions
|
|
13
|
-
from .models_endpoint import models
|
|
14
|
-
|
|
15
|
-
cli_app = typer.Typer()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@cli_app.callback(invoke_without_command=True)
|
|
19
|
-
def run_server(
|
|
20
|
-
config: Optional[str] = typer.Option(None, help="Path to the configuration file"),
|
|
21
|
-
debug: Optional[bool] = typer.Option(None, help="Enable debug mode (more verbose logging)"),
|
|
22
|
-
env_file: Optional[str] = typer.Option(
|
|
23
|
-
".env",
|
|
24
|
-
"--env",
|
|
25
|
-
"--env-file",
|
|
26
|
-
"--env_file",
|
|
27
|
-
help="Set the .env file to load ENV vars from",
|
|
28
|
-
),
|
|
29
|
-
):
|
|
30
|
-
"""
|
|
31
|
-
Default command for CLI application: Run LM-Proxy web server
|
|
32
|
-
"""
|
|
33
|
-
try:
|
|
34
|
-
bootstrap(config=config or "config.toml", env_file=env_file, debug=debug)
|
|
35
|
-
uvicorn.run(
|
|
36
|
-
"lm_proxy.app:web_app",
|
|
37
|
-
host=env.config.host,
|
|
38
|
-
port=env.config.port,
|
|
39
|
-
ssl_keyfile=getattr(env.config, "ssl_keyfile", None),
|
|
40
|
-
ssl_certfile=getattr(env.config, "ssl_certfile", None),
|
|
41
|
-
reload=env.config.dev_autoreload,
|
|
42
|
-
factory=True,
|
|
43
|
-
)
|
|
44
|
-
except Exception as e:
|
|
45
|
-
if env.debug:
|
|
46
|
-
raise
|
|
47
|
-
logging.error(e)
|
|
48
|
-
raise typer.Exit(code=1)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def web_app():
|
|
52
|
-
"""
|
|
53
|
-
Entrypoint for ASGI server
|
|
54
|
-
"""
|
|
55
|
-
app = FastAPI(title="LM-Proxy", description="OpenAI-compatible proxy server for LLM inference")
|
|
56
|
-
app.add_api_route(
|
|
57
|
-
path=f"{env.config.api_prefix}/chat/completions",
|
|
58
|
-
endpoint=chat_completions,
|
|
59
|
-
methods=["POST"],
|
|
60
|
-
)
|
|
61
|
-
app.add_api_route(
|
|
62
|
-
path=f"{env.config.api_prefix}/models",
|
|
63
|
-
endpoint=models,
|
|
64
|
-
methods=["GET"],
|
|
65
|
-
)
|
|
66
|
-
# app.add_api_route(path="", endpoint=lambda: {"status": "ok"}, methods=["GET"])
|
|
67
|
-
|
|
68
|
-
# @app.middleware("http")
|
|
69
|
-
# async def log_requests(request, call_next):
|
|
70
|
-
# body = await request.body()
|
|
71
|
-
# logging.info(f"Request URL: {request.url}")
|
|
72
|
-
# logging.info(f"Request Headers: {dict(request.headers)}")
|
|
73
|
-
# logging.info(f"Request Body: {body.decode()}")
|
|
74
|
-
#
|
|
75
|
-
# response = await call_next(request)
|
|
76
|
-
# return response
|
|
77
|
-
|
|
78
|
-
return app
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if __name__ == "__main__":
|
|
82
|
-
cli_app()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openai_http_proxy-3.0.2 → openai_http_proxy-3.2.0}/lm_proxy/handlers/forward_http_headers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|