Shardul Dhekane commited on
Commit ·
edaad73
1
Parent(s): 28ec988
Normalize provider-agnostic API config and add Space server endpoints
Browse files- Dockerfile +10 -34
- environment/tasks.py +2 -2
- inference.py +23 -6
- openenv.yaml +1 -0
- requirements.txt +2 -1
- server/__init__.py +0 -0
- server/app.py +76 -0
- submit.py +9 -3
Dockerfile
CHANGED
|
@@ -12,38 +12,14 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|
| 12 |
|
| 13 |
COPY . .
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
echo " HF_TOKEN - Your Hugging Face / API key"\n\
|
| 22 |
-
echo ""\n\
|
| 23 |
-
echo "Examples:"\n\
|
| 24 |
-
echo " OpenAI: API_BASE_URL=https://api.openai.com/v1 MODEL_NAME=gpt-4"\n\
|
| 25 |
-
echo " Gemini: API_BASE_URL=https://generativelanguage.googleapis.com MODEL_NAME=gemini-1.5-pro"\n\
|
| 26 |
-
echo " Local: API_BASE_URL=http://localhost:11434/v1 MODEL_NAME=llama2"\n\
|
| 27 |
-
exit 1\n\
|
| 28 |
-
fi\n\
|
| 29 |
-
\n\
|
| 30 |
-
if [ -z "$MODEL_NAME" ]; then\n\
|
| 31 |
-
echo "ERROR: MODEL_NAME environment variable is required"\n\
|
| 32 |
-
exit 1\n\
|
| 33 |
-
fi\n\
|
| 34 |
-
\n\
|
| 35 |
-
if [ -z "$HF_TOKEN" ] && [ -z "$OPENAI_API_KEY" ] && [ -z "$API_KEY" ]; then\n\
|
| 36 |
-
echo "ERROR: Missing auth token. Set HF_TOKEN (preferred) or OPENAI_API_KEY"\n\
|
| 37 |
-
exit 1\n\
|
| 38 |
-
fi\n\
|
| 39 |
-
\n\
|
| 40 |
-
echo "Configuration:"\n\
|
| 41 |
-
echo " API_BASE_URL: ${API_BASE_URL}"\n\
|
| 42 |
-
echo " MODEL_NAME: ${MODEL_NAME}"\n\
|
| 43 |
-
echo " TEMPERATURE: ${TEMPERATURE:-0.7}"\n\
|
| 44 |
-
echo " MAX_TOKENS: ${MAX_TOKENS:-2000}"\n\
|
| 45 |
-
echo ""\n\
|
| 46 |
-
\n\
|
| 47 |
-
python inference.py "$@"' > /usr/local/bin/run-agent && chmod +x /usr/local/bin/run-agent
|
| 48 |
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
COPY . .
|
| 14 |
|
| 15 |
+
# Runtime API configuration (OpenAI-compatible):
|
| 16 |
+
# - API_BASE_URL is required
|
| 17 |
+
# - MODEL_NAME is required
|
| 18 |
+
# - API_KEY is canonical auth env var
|
| 19 |
+
# - HF_TOKEN and OPENAI_API_KEY are accepted as backward-compatible aliases
|
| 20 |
+
EXPOSE 7860
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
ENV HOST=0.0.0.0
|
| 23 |
+
ENV PORT=7860
|
| 24 |
+
|
| 25 |
+
CMD ["python", "-m", "server.app"]
|
environment/tasks.py
CHANGED
|
@@ -60,7 +60,7 @@ class TaskDefinitions:
|
|
| 60 |
"line_count": 4,
|
| 61 |
"expected_issues": [
|
| 62 |
{
|
| 63 |
-
"line":
|
| 64 |
"type": "index_error",
|
| 65 |
"severity": "medium",
|
| 66 |
"description": "Index out of bounds when i is the last element"
|
|
@@ -128,7 +128,7 @@ def format_output(data):
|
|
| 128 |
"line_count": 4,
|
| 129 |
"expected_issues": [
|
| 130 |
{
|
| 131 |
-
"line":
|
| 132 |
"type": "performance",
|
| 133 |
"severity": "medium",
|
| 134 |
"description": "Inefficient string concatenation in loop"
|
|
|
|
| 60 |
"line_count": 4,
|
| 61 |
"expected_issues": [
|
| 62 |
{
|
| 63 |
+
"line": 4,
|
| 64 |
"type": "index_error",
|
| 65 |
"severity": "medium",
|
| 66 |
"description": "Index out of bounds when i is the last element"
|
|
|
|
| 128 |
"line_count": 4,
|
| 129 |
"expected_issues": [
|
| 130 |
{
|
| 131 |
+
"line": 4,
|
| 132 |
"type": "performance",
|
| 133 |
"severity": "medium",
|
| 134 |
"description": "Inefficient string concatenation in loop"
|
inference.py
CHANGED
|
@@ -9,9 +9,19 @@ import sys
|
|
| 9 |
from typing import Dict, Any
|
| 10 |
from openai import OpenAI
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
API_BASE_URL = os.environ.get("API_BASE_URL", "")
|
| 13 |
MODEL_NAME = os.environ.get("MODEL_NAME", "")
|
| 14 |
-
API_KEY =
|
| 15 |
TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7"))
|
| 16 |
MAX_TOKENS = int(os.environ.get("MAX_TOKENS", "2000"))
|
| 17 |
REQUEST_TIMEOUT = int(os.environ.get("REQUEST_TIMEOUT", "60"))
|
|
@@ -21,18 +31,25 @@ if not API_BASE_URL:
|
|
| 21 |
print("API Configuration Required")
|
| 22 |
print("=" * 60)
|
| 23 |
print("\nPlease set the following environment variables:\n")
|
| 24 |
-
print(" API_BASE_URL -
|
| 25 |
print(" MODEL_NAME - Model identifier")
|
| 26 |
-
print("
|
|
|
|
|
|
|
|
|
|
| 27 |
print("Examples:\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
print(" Groq:")
|
| 29 |
print(" export API_BASE_URL=https://api.groq.com/openai/v1")
|
| 30 |
print(" export MODEL_NAME=llama-3.3-70b-versatile")
|
| 31 |
-
print(" export
|
| 32 |
print(" Local Ollama:")
|
| 33 |
print(" export API_BASE_URL=http://localhost:11434/v1")
|
| 34 |
print(" export MODEL_NAME=llama3")
|
| 35 |
-
print(" export
|
| 36 |
print("=" * 60)
|
| 37 |
sys.exit(1)
|
| 38 |
|
|
@@ -41,7 +58,7 @@ if not MODEL_NAME:
|
|
| 41 |
sys.exit(1)
|
| 42 |
|
| 43 |
if not API_KEY:
|
| 44 |
-
print("ERROR: Missing auth token. Set
|
| 45 |
sys.exit(1)
|
| 46 |
|
| 47 |
FALLBACK_ACTION = json.dumps({
|
|
|
|
| 9 |
from typing import Dict, Any
|
| 10 |
from openai import OpenAI
|
| 11 |
|
| 12 |
+
|
| 13 |
+
def resolve_api_key() -> str:
|
| 14 |
+
# Canonical env var is API_KEY, aliases are supported for compatibility.
|
| 15 |
+
return (
|
| 16 |
+
(os.environ.get("API_KEY") or "").strip()
|
| 17 |
+
or (os.environ.get("HF_TOKEN") or "").strip()
|
| 18 |
+
or (os.environ.get("OPENAI_API_KEY") or "").strip()
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
API_BASE_URL = os.environ.get("API_BASE_URL", "")
|
| 23 |
MODEL_NAME = os.environ.get("MODEL_NAME", "")
|
| 24 |
+
API_KEY = resolve_api_key()
|
| 25 |
TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7"))
|
| 26 |
MAX_TOKENS = int(os.environ.get("MAX_TOKENS", "2000"))
|
| 27 |
REQUEST_TIMEOUT = int(os.environ.get("REQUEST_TIMEOUT", "60"))
|
|
|
|
| 31 |
print("API Configuration Required")
|
| 32 |
print("=" * 60)
|
| 33 |
print("\nPlease set the following environment variables:\n")
|
| 34 |
+
print(" API_BASE_URL - OpenAI-compatible API endpoint")
|
| 35 |
print(" MODEL_NAME - Model identifier")
|
| 36 |
+
print(" API_KEY - API key (canonical)\n")
|
| 37 |
+
print("Supported auth aliases (backward compatibility):")
|
| 38 |
+
print(" HF_TOKEN")
|
| 39 |
+
print(" OPENAI_API_KEY\n")
|
| 40 |
print("Examples:\n")
|
| 41 |
+
print(" OpenAI:")
|
| 42 |
+
print(" export API_BASE_URL=https://api.openai.com/v1")
|
| 43 |
+
print(" export MODEL_NAME=gpt-4o-mini")
|
| 44 |
+
print(" export API_KEY=sk-xxxxx\n")
|
| 45 |
print(" Groq:")
|
| 46 |
print(" export API_BASE_URL=https://api.groq.com/openai/v1")
|
| 47 |
print(" export MODEL_NAME=llama-3.3-70b-versatile")
|
| 48 |
+
print(" export API_KEY=gsk_xxxxx\n")
|
| 49 |
print(" Local Ollama:")
|
| 50 |
print(" export API_BASE_URL=http://localhost:11434/v1")
|
| 51 |
print(" export MODEL_NAME=llama3")
|
| 52 |
+
print(" export API_KEY=not-needed\n")
|
| 53 |
print("=" * 60)
|
| 54 |
sys.exit(1)
|
| 55 |
|
|
|
|
| 58 |
sys.exit(1)
|
| 59 |
|
| 60 |
if not API_KEY:
|
| 61 |
+
print("ERROR: Missing auth token. Set API_KEY (preferred), or HF_TOKEN/OPENAI_API_KEY.")
|
| 62 |
sys.exit(1)
|
| 63 |
|
| 64 |
FALLBACK_ACTION = json.dumps({
|
openenv.yaml
CHANGED
|
@@ -63,6 +63,7 @@ reward_range:
|
|
| 63 |
max_episode_steps: 50
|
| 64 |
|
| 65 |
requires_api_keys:
|
|
|
|
| 66 |
- HF_TOKEN
|
| 67 |
- OPENAI_API_KEY
|
| 68 |
- API_BASE_URL
|
|
|
|
| 63 |
max_episode_steps: 50
|
| 64 |
|
| 65 |
requires_api_keys:
|
| 66 |
+
- API_KEY
|
| 67 |
- HF_TOKEN
|
| 68 |
- OPENAI_API_KEY
|
| 69 |
- API_BASE_URL
|
requirements.txt
CHANGED
|
@@ -7,4 +7,5 @@ pytest-cov>=4.0.0
|
|
| 7 |
numpy>=1.24.0
|
| 8 |
typing-extensions>=4.5.0
|
| 9 |
requests>=2.31.0
|
| 10 |
-
python-dotenv>=1.0.0
|
|
|
|
|
|
| 7 |
numpy>=1.24.0
|
| 8 |
typing-extensions>=4.5.0
|
| 9 |
requests>=2.31.0
|
| 10 |
+
python-dotenv>=1.0.0
|
| 11 |
+
flask>=3.0.0
|
server/__init__.py
ADDED
|
File without changes
|
server/app.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HTTP server for running the code-review environment in a Space/container."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from threading import Lock
|
| 7 |
+
from typing import Any, Dict
|
| 8 |
+
|
| 9 |
+
from flask import Flask, jsonify, request
|
| 10 |
+
|
| 11 |
+
from environment.env import CodeReviewEnv
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
_env = CodeReviewEnv()
|
| 16 |
+
_lock = Lock()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@app.get("/")
|
| 20 |
+
def root() -> Any:
|
| 21 |
+
return jsonify({
|
| 22 |
+
"status": "ok",
|
| 23 |
+
"service": "code-review-agent-env",
|
| 24 |
+
"endpoints": ["/health", "/reset", "/step", "/state"],
|
| 25 |
+
})
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@app.get("/health")
|
| 29 |
+
def health() -> Any:
|
| 30 |
+
return jsonify({"status": "healthy"})
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@app.route("/reset", methods=["GET", "POST"])
|
| 34 |
+
def reset() -> Any:
|
| 35 |
+
payload: Dict[str, Any] = request.get_json(silent=True) or {}
|
| 36 |
+
task_id = payload.get("task_id") or request.args.get("task_id")
|
| 37 |
+
with _lock:
|
| 38 |
+
obs = _env.reset(task_id=task_id)
|
| 39 |
+
return jsonify({"observation": obs})
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@app.post("/step")
|
| 43 |
+
def step() -> Any:
|
| 44 |
+
payload = request.get_json(silent=True) or {}
|
| 45 |
+
action = payload.get("action")
|
| 46 |
+
if not isinstance(action, dict):
|
| 47 |
+
return jsonify({"error": "Request body must include an 'action' object"}), 400
|
| 48 |
+
|
| 49 |
+
with _lock:
|
| 50 |
+
observation, reward, done, info = _env.step(action)
|
| 51 |
+
|
| 52 |
+
return jsonify(
|
| 53 |
+
{
|
| 54 |
+
"observation": observation,
|
| 55 |
+
"reward": reward,
|
| 56 |
+
"done": done,
|
| 57 |
+
"info": info,
|
| 58 |
+
}
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@app.get("/state")
|
| 63 |
+
def state() -> Any:
|
| 64 |
+
with _lock:
|
| 65 |
+
current_state = _env.state()
|
| 66 |
+
return jsonify(current_state)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> None:
|
| 70 |
+
host = os.getenv("HOST", "0.0.0.0")
|
| 71 |
+
port = int(os.getenv("PORT", "7860"))
|
| 72 |
+
app.run(host=host, port=port)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
main()
|
submit.py
CHANGED
|
@@ -25,7 +25,13 @@ CORE_TASKS = [
|
|
| 25 |
|
| 26 |
def _run_cmd(command: List[str]) -> Tuple[int, str, str]:
|
| 27 |
"""Run a command and return (returncode, stdout, stderr)."""
|
| 28 |
-
result = subprocess.run(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
return result.returncode, result.stdout, result.stderr
|
| 30 |
|
| 31 |
|
|
@@ -118,9 +124,9 @@ def _inference_env_ready() -> Tuple[bool, str]:
|
|
| 118 |
return False, "API_BASE_URL is not set"
|
| 119 |
if not (os.getenv("MODEL_NAME") or "").strip():
|
| 120 |
return False, "MODEL_NAME is not set"
|
| 121 |
-
token = (os.getenv("
|
| 122 |
if not token:
|
| 123 |
-
return False, "
|
| 124 |
return True, ""
|
| 125 |
|
| 126 |
|
|
|
|
| 25 |
|
| 26 |
def _run_cmd(command: List[str]) -> Tuple[int, str, str]:
|
| 27 |
"""Run a command and return (returncode, stdout, stderr)."""
|
| 28 |
+
result = subprocess.run(
|
| 29 |
+
command,
|
| 30 |
+
capture_output=True,
|
| 31 |
+
text=True,
|
| 32 |
+
encoding="utf-8",
|
| 33 |
+
errors="replace",
|
| 34 |
+
)
|
| 35 |
return result.returncode, result.stdout, result.stderr
|
| 36 |
|
| 37 |
|
|
|
|
| 124 |
return False, "API_BASE_URL is not set"
|
| 125 |
if not (os.getenv("MODEL_NAME") or "").strip():
|
| 126 |
return False, "MODEL_NAME is not set"
|
| 127 |
+
token = (os.getenv("API_KEY") or os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY") or "").strip()
|
| 128 |
if not token:
|
| 129 |
+
return False, "API_KEY is not set (supported aliases: HF_TOKEN, OPENAI_API_KEY)"
|
| 130 |
return True, ""
|
| 131 |
|
| 132 |
|