promptreview-proxy 0.1.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.
- promptreview_proxy-0.1.0/PKG-INFO +53 -0
- promptreview_proxy-0.1.0/README.md +28 -0
- promptreview_proxy-0.1.0/promptreview_proxy/__init__.py +3 -0
- promptreview_proxy-0.1.0/promptreview_proxy/server.py +198 -0
- promptreview_proxy-0.1.0/promptreview_proxy.egg-info/PKG-INFO +53 -0
- promptreview_proxy-0.1.0/promptreview_proxy.egg-info/SOURCES.txt +10 -0
- promptreview_proxy-0.1.0/promptreview_proxy.egg-info/dependency_links.txt +1 -0
- promptreview_proxy-0.1.0/promptreview_proxy.egg-info/entry_points.txt +2 -0
- promptreview_proxy-0.1.0/promptreview_proxy.egg-info/requires.txt +4 -0
- promptreview_proxy-0.1.0/promptreview_proxy.egg-info/top_level.txt +1 -0
- promptreview_proxy-0.1.0/pyproject.toml +39 -0
- promptreview_proxy-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: promptreview-proxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local HTTP proxy forwarding engine for PromptReview VS Code extension with API subdomain analytics.
|
|
5
|
+
Author-email: PromptReview Team <support@prompt-guard-ai.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: llm,proxy,fastapi,vscode,promptreview,ai
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Framework :: FastAPI
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: fastapi>=0.110.0
|
|
22
|
+
Requires-Dist: uvicorn>=0.28.0
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: pydantic>=2.6.0
|
|
25
|
+
|
|
26
|
+
# PromptReview Proxy (`promptreview-proxy`)
|
|
27
|
+
|
|
28
|
+
`promptreview-proxy` is a lightweight, local HTTP proxy forwarding server for the PromptReview VS Code Extension.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
- **Local HTTP Proxying**: Intercepts outbound LLM traffic from VS Code (`http.proxy`).
|
|
32
|
+
- **Cloud Analytics Relay**: Asynchronously logs prompt metadata to your API subdomain (`https://api.prompt-guard-ai.com/v1/prompts`).
|
|
33
|
+
- **Dynamic Provider Routing**: Forwards requests to target LLM providers (OpenAI, Anthropic, Groq, Ollama, etc.) and streams responses back to VS Code.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
```bash
|
|
39
|
+
pip install promptreview-proxy
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Usage
|
|
43
|
+
Run the proxy server from command line:
|
|
44
|
+
```bash
|
|
45
|
+
promptreview-proxy
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or configure environment variables:
|
|
49
|
+
```bash
|
|
50
|
+
export PROXY_PORT=6767
|
|
51
|
+
export API_BASE_URL="https://api.prompt-guard-ai.com"
|
|
52
|
+
promptreview-proxy
|
|
53
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# PromptReview Proxy (`promptreview-proxy`)
|
|
2
|
+
|
|
3
|
+
`promptreview-proxy` is a lightweight, local HTTP proxy forwarding server for the PromptReview VS Code Extension.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Local HTTP Proxying**: Intercepts outbound LLM traffic from VS Code (`http.proxy`).
|
|
7
|
+
- **Cloud Analytics Relay**: Asynchronously logs prompt metadata to your API subdomain (`https://api.prompt-guard-ai.com/v1/prompts`).
|
|
8
|
+
- **Dynamic Provider Routing**: Forwards requests to target LLM providers (OpenAI, Anthropic, Groq, Ollama, etc.) and streams responses back to VS Code.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
### Installation
|
|
13
|
+
```bash
|
|
14
|
+
pip install promptreview-proxy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Usage
|
|
18
|
+
Run the proxy server from command line:
|
|
19
|
+
```bash
|
|
20
|
+
promptreview-proxy
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or configure environment variables:
|
|
24
|
+
```bash
|
|
25
|
+
export PROXY_PORT=6767
|
|
26
|
+
export API_BASE_URL="https://api.prompt-guard-ai.com"
|
|
27
|
+
promptreview-proxy
|
|
28
|
+
```
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, AsyncGenerator
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from fastapi import FastAPI, Header, HTTPException, Request, Response
|
|
9
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ProxyConfig:
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self.host = os.getenv("PROXY_HOST", "127.0.0.1")
|
|
16
|
+
self.port = int(os.getenv("PROXY_PORT", "6767"))
|
|
17
|
+
|
|
18
|
+
self.api_base_url = os.getenv("API_BASE_URL", "https://api.prompt-guard-ai.com").rstrip("/")
|
|
19
|
+
self.api_token = os.getenv("API_TOKEN", "")
|
|
20
|
+
|
|
21
|
+
self.llm_provider_url = os.getenv("LLM_PROVIDER_URL", "https://api.openai.com/v1/chat/completions")
|
|
22
|
+
self.llm_provider_api_key = os.getenv("LLM_PROVIDER_API_KEY", "")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
config = ProxyConfig()
|
|
26
|
+
app = FastAPI(title="PromptReview Local Proxy", version="0.1.0")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProxyChatRequest(BaseModel):
|
|
30
|
+
company: str = Field(..., description="Organization name or email (C1)")
|
|
31
|
+
project: str = Field(default="default-project", description="Project name (P1)")
|
|
32
|
+
user: str = Field(..., description="User identity or email (U1)")
|
|
33
|
+
prompt: str = Field(..., min_length=1, description="Prompt string")
|
|
34
|
+
model: str | None = Field(default="gpt-4o", description="Target LLM model")
|
|
35
|
+
messages: list[dict[str, Any]] | None = Field(default=None, description="Optional messages payload")
|
|
36
|
+
metadata: dict[str, Any] | None = Field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def send_event_to_api_subdomain(payload: dict[str, Any]) -> None:
|
|
40
|
+
"""Asynchronously dispatch prompt event to API subdomain for cloud storage."""
|
|
41
|
+
target_url = f"{config.api_base_url}/v1/prompts"
|
|
42
|
+
headers = {"Content-Type": "application/json"}
|
|
43
|
+
if config.api_token:
|
|
44
|
+
headers["Authorization"] = f"Bearer {config.api_token}"
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
48
|
+
res = await client.post(target_url, json=payload, headers=headers)
|
|
49
|
+
if res.status_code >= 400:
|
|
50
|
+
print(f"[promptreview-proxy] API subdomain warning ({res.status_code}): {res.text}", file=sys.stderr)
|
|
51
|
+
except Exception as exc:
|
|
52
|
+
print(f"[promptreview-proxy] Failed sending event to API subdomain: {exc}", file=sys.stderr)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.get("/health")
|
|
56
|
+
async def health() -> dict[str, str]:
|
|
57
|
+
return {"status": "ok", "service": "promptreview-proxy"}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.post("/v1/proxy/chat")
|
|
61
|
+
async def proxy_chat(
|
|
62
|
+
request: ProxyChatRequest,
|
|
63
|
+
x_company: str | None = Header(default=None),
|
|
64
|
+
x_project: str | None = Header(default=None),
|
|
65
|
+
x_user: str | None = Header(default=None),
|
|
66
|
+
authorization: str | None = Header(default=None),
|
|
67
|
+
) -> dict[str, Any]:
|
|
68
|
+
company = request.company or x_company or "UnknownCompany"
|
|
69
|
+
project = request.project or x_project or "default-project"
|
|
70
|
+
user = request.user or x_user or "UnknownUser"
|
|
71
|
+
|
|
72
|
+
api_payload = {
|
|
73
|
+
"company": company,
|
|
74
|
+
"project": project,
|
|
75
|
+
"user": user,
|
|
76
|
+
"prompt": request.prompt,
|
|
77
|
+
"model": request.model,
|
|
78
|
+
"messages": request.messages or [{"role": "user", "content": request.prompt}],
|
|
79
|
+
"metadata": request.metadata or {},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
asyncio.create_task(send_event_to_api_subdomain(api_payload))
|
|
83
|
+
|
|
84
|
+
llm_payload = {
|
|
85
|
+
"model": request.model,
|
|
86
|
+
"messages": request.messages or [{"role": "user", "content": request.prompt}],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
headers = {"Content-Type": "application/json"}
|
|
90
|
+
if authorization:
|
|
91
|
+
headers["Authorization"] = authorization
|
|
92
|
+
elif config.llm_provider_api_key:
|
|
93
|
+
headers["Authorization"] = f"Bearer {config.llm_provider_api_key}"
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
97
|
+
llm_res = await client.post(config.llm_provider_url, json=llm_payload, headers=headers)
|
|
98
|
+
llm_res.raise_for_status()
|
|
99
|
+
return {
|
|
100
|
+
"ok": True,
|
|
101
|
+
"company": company,
|
|
102
|
+
"project": project,
|
|
103
|
+
"user": user,
|
|
104
|
+
"provider_response": llm_res.json(),
|
|
105
|
+
}
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
raise HTTPException(status_code=502, detail=f"LLM Provider error: {str(exc)}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.api_route("/v1/chat/completions", methods=["POST", "OPTIONS"])
|
|
111
|
+
@app.api_route("/{full_path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"])
|
|
112
|
+
async def catch_all_proxy(request: Request, full_path: str = "") -> Response:
|
|
113
|
+
if request.method == "OPTIONS":
|
|
114
|
+
return Response(status_code=204)
|
|
115
|
+
|
|
116
|
+
body_bytes = await request.body()
|
|
117
|
+
body_json: dict[str, Any] = {}
|
|
118
|
+
if body_bytes:
|
|
119
|
+
try:
|
|
120
|
+
body_json = json.loads(body_bytes.decode("utf-8"))
|
|
121
|
+
except Exception:
|
|
122
|
+
body_json = {}
|
|
123
|
+
|
|
124
|
+
headers = dict(request.headers)
|
|
125
|
+
company = headers.get("x-company") or headers.get("x-organization-name") or "C1"
|
|
126
|
+
project = headers.get("x-project") or "P1"
|
|
127
|
+
user = headers.get("x-user") or headers.get("x-user-email") or "U1"
|
|
128
|
+
|
|
129
|
+
messages = body_json.get("messages", [])
|
|
130
|
+
prompt = ""
|
|
131
|
+
if messages and isinstance(messages, list):
|
|
132
|
+
last_msg = messages[-1]
|
|
133
|
+
if isinstance(last_msg, dict):
|
|
134
|
+
prompt = str(last_msg.get("content", ""))
|
|
135
|
+
elif "prompt" in body_json:
|
|
136
|
+
prompt = str(body_json.get("prompt", ""))
|
|
137
|
+
|
|
138
|
+
if prompt:
|
|
139
|
+
api_payload = {
|
|
140
|
+
"company": company,
|
|
141
|
+
"project": project,
|
|
142
|
+
"user": user,
|
|
143
|
+
"prompt": prompt,
|
|
144
|
+
"model": body_json.get("model"),
|
|
145
|
+
"messages": messages,
|
|
146
|
+
"metadata": {"path": full_path},
|
|
147
|
+
}
|
|
148
|
+
asyncio.create_task(send_event_to_api_subdomain(api_payload))
|
|
149
|
+
|
|
150
|
+
target_url = headers.get("x-target-llm-url") or config.llm_provider_url
|
|
151
|
+
if full_path and not target_url.endswith(full_path):
|
|
152
|
+
if not target_url.endswith("/"):
|
|
153
|
+
target_url = f"{target_url}/{full_path}"
|
|
154
|
+
else:
|
|
155
|
+
target_url = f"{target_url}{full_path}"
|
|
156
|
+
|
|
157
|
+
forward_headers = {
|
|
158
|
+
k: v for k, v in headers.items() if k.lower() not in ("host", "content-length", "x-target-llm-url", "x-company", "x-project", "x-user")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
client = httpx.AsyncClient(timeout=120.0)
|
|
163
|
+
req = client.build_request(
|
|
164
|
+
method=request.method,
|
|
165
|
+
url=target_url,
|
|
166
|
+
headers=forward_headers,
|
|
167
|
+
content=body_bytes,
|
|
168
|
+
)
|
|
169
|
+
res = await client.send(req, stream=True)
|
|
170
|
+
|
|
171
|
+
async def stream_response() -> AsyncGenerator[bytes, None]:
|
|
172
|
+
try:
|
|
173
|
+
async for chunk in res.aiter_bytes():
|
|
174
|
+
yield chunk
|
|
175
|
+
finally:
|
|
176
|
+
await res.aclose()
|
|
177
|
+
await client.aclose()
|
|
178
|
+
|
|
179
|
+
return StreamingResponse(
|
|
180
|
+
stream_response(),
|
|
181
|
+
status_code=res.status_code,
|
|
182
|
+
headers={k: v for k, v in res.headers.items() if k.lower() not in ("content-length", "transfer-encoding")},
|
|
183
|
+
)
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
return JSONResponse(
|
|
186
|
+
status_code=502,
|
|
187
|
+
content={"error": f"Local proxy forwarding failed: {str(exc)}"},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def main() -> None:
|
|
192
|
+
import uvicorn
|
|
193
|
+
|
|
194
|
+
uvicorn.run(app, host=config.host, port=config.port)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: promptreview-proxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local HTTP proxy forwarding engine for PromptReview VS Code extension with API subdomain analytics.
|
|
5
|
+
Author-email: PromptReview Team <support@prompt-guard-ai.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: llm,proxy,fastapi,vscode,promptreview,ai
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Framework :: FastAPI
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: fastapi>=0.110.0
|
|
22
|
+
Requires-Dist: uvicorn>=0.28.0
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: pydantic>=2.6.0
|
|
25
|
+
|
|
26
|
+
# PromptReview Proxy (`promptreview-proxy`)
|
|
27
|
+
|
|
28
|
+
`promptreview-proxy` is a lightweight, local HTTP proxy forwarding server for the PromptReview VS Code Extension.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
- **Local HTTP Proxying**: Intercepts outbound LLM traffic from VS Code (`http.proxy`).
|
|
32
|
+
- **Cloud Analytics Relay**: Asynchronously logs prompt metadata to your API subdomain (`https://api.prompt-guard-ai.com/v1/prompts`).
|
|
33
|
+
- **Dynamic Provider Routing**: Forwards requests to target LLM providers (OpenAI, Anthropic, Groq, Ollama, etc.) and streams responses back to VS Code.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
```bash
|
|
39
|
+
pip install promptreview-proxy
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Usage
|
|
43
|
+
Run the proxy server from command line:
|
|
44
|
+
```bash
|
|
45
|
+
promptreview-proxy
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or configure environment variables:
|
|
49
|
+
```bash
|
|
50
|
+
export PROXY_PORT=6767
|
|
51
|
+
export API_BASE_URL="https://api.prompt-guard-ai.com"
|
|
52
|
+
promptreview-proxy
|
|
53
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
promptreview_proxy/__init__.py
|
|
4
|
+
promptreview_proxy/server.py
|
|
5
|
+
promptreview_proxy.egg-info/PKG-INFO
|
|
6
|
+
promptreview_proxy.egg-info/SOURCES.txt
|
|
7
|
+
promptreview_proxy.egg-info/dependency_links.txt
|
|
8
|
+
promptreview_proxy.egg-info/entry_points.txt
|
|
9
|
+
promptreview_proxy.egg-info/requires.txt
|
|
10
|
+
promptreview_proxy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
promptreview_proxy
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "promptreview-proxy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local HTTP proxy forwarding engine for PromptReview VS Code extension with API subdomain analytics."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [{ name = "PromptReview Team", email = "support@prompt-guard-ai.com" }]
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
keywords = ["llm", "proxy", "fastapi", "vscode", "promptreview", "ai"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Framework :: FastAPI",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13"
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"fastapi>=0.110.0",
|
|
29
|
+
"uvicorn>=0.28.0",
|
|
30
|
+
"httpx>=0.27.0",
|
|
31
|
+
"pydantic>=2.6.0"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
promptreview-proxy = "promptreview_proxy.server:main"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["."]
|
|
39
|
+
include = ["promptreview_proxy*"]
|