meta-com-openenv-agent / inference.py
Monike123's picture
Fix Phase 2: 3 tasks with graders, clamped scores to (0,1)
320bac3
Raw
History Blame Contribute Delete
9.39 kB
"""
Inference Script — Meta_com OpenEnv Agent
=========================================
MANDATORY
- Before submitting, ensure the following variables are defined in your environment configuration:
API_BASE_URL The API endpoint for the LLM.
MODEL_NAME The model identifier to use for inference.
HF_TOKEN Your Hugging Face / API key.
LOCAL_IMAGE_NAME The name of the local image to use for the environment if you are using from_docker_image()
method
- Defaults are set only for API_BASE_URL and MODEL_NAME
(and should reflect your active inference setup):
API_BASE_URL = os.getenv("API_BASE_URL", "<your-active-endpoint>")
MODEL_NAME = os.getenv("MODEL_NAME", "<your-active-model>")
- The inference script must be named `inference.py` and placed in the root directory of the project
- Participants must use OpenAI Client for all LLM calls using above variables
STDOUT FORMAT
- The script must emit exactly three line types to stdout, in this order:
[START] task=<task_name> env=<benchmark> model=<model_name>
[STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
[END] success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>
TASK REQUIREMENTS
- Must run at least 3 tasks with graders.
- Each task score must be strictly between 0 and 1 (not 0.0 and not 1.0).
"""
import asyncio
import os
import textwrap
from typing import Dict, List, Optional
from openai import OpenAI
from my_env_v4 import MyEnvV4Action, MyEnvV4Env
# ---------------------------------------------------------------------------
# Environment configuration (OpenEnv-compliant variable names)
# ---------------------------------------------------------------------------
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
HF_TOKEN = os.getenv("HF_TOKEN")
# Optional — if you use from_docker_image():
LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
BENCHMARK = os.getenv("MY_ENV_V4_BENCHMARK", "my_env_v4")
# ---------------------------------------------------------------------------
# Task registry — at least 3 tasks required by OpenEnv Phase 2 validation
# ---------------------------------------------------------------------------
TASKS: List[Dict] = [
{
"id": "git_conflict_trivial",
"max_steps": 4,
"temperature": 0.5,
"system_prompt": (
"You are resolving a trivial Git merge conflict. "
"The conflict involves simple whitespace or formatting differences. "
"Reply with exactly one corrected code block — no quotes, no prefixes."
),
},
{
"id": "git_conflict_multifile",
"max_steps": 6,
"temperature": 0.7,
"system_prompt": (
"You are resolving a multi-file Git merge conflict. "
"Two branches modified different parts of an API. Reconcile both changes. "
"Reply with exactly one corrected code block — no quotes, no prefixes."
),
},
{
"id": "git_conflict_semantic",
"max_steps": 8,
"temperature": 0.8,
"system_prompt": (
"You are resolving a deep semantic Git merge conflict. "
"Two branches implement competing logic for the same feature. "
"Synthesize both intents into a single coherent implementation. "
"Reply with exactly one corrected code block — no quotes, no prefixes."
),
},
]
MAX_TOKENS = 150
# Score boundary constants — OpenEnv requires scores strictly in (0, 1)
SCORE_FLOOR = 0.001
SCORE_CEIL = 0.999
# ---------------------------------------------------------------------------
# Structured logging helpers (stdout format required by OpenEnv)
# ---------------------------------------------------------------------------
def log_start(task: str, env: str, model: str) -> None:
print(f"[START] task={task} env={env} model={model}", flush=True)
def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
error_val = error if error else "null"
done_val = str(done).lower()
print(
f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",
flush=True,
)
def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
print(
f"[END] success={str(success).lower()} steps={steps} score={score:.4f} rewards={rewards_str}",
flush=True,
)
# ---------------------------------------------------------------------------
# Agent interaction helpers
# ---------------------------------------------------------------------------
def build_user_prompt(step: int, last_echoed: str, last_reward: float, history: List[str]) -> str:
history_block = "\n".join(history[-4:]) if history else "None"
return textwrap.dedent(
f"""
Step: {step}
Last echoed message: {last_echoed!r}
Last reward: {last_reward:.2f}
Previous steps:
{history_block}
Send your next message.
"""
).strip()
def get_model_message(
client: OpenAI,
system_prompt: str,
step: int,
last_echoed: str,
last_reward: float,
history: List[str],
temperature: float,
) -> str:
user_prompt = build_user_prompt(step, last_echoed, last_reward, history)
# Fallback heuristic when no real API key is available (build/test phase)
if HF_TOKEN is None:
return f"Conflict resolution patch for step {step}"
try:
completion = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=temperature,
max_tokens=MAX_TOKENS,
stream=False,
)
text = (completion.choices[0].message.content or "").strip()
return text if text else "hello"
except Exception as exc:
print(f"[DEBUG] Model request failed: {exc}", flush=True)
return "hello"
def clamp_score(raw: float) -> float:
"""Clamp a raw score to the open interval (0, 1) as required by OpenEnv."""
return min(max(raw, SCORE_FLOOR), SCORE_CEIL)
# ---------------------------------------------------------------------------
# Single-task episode runner
# ---------------------------------------------------------------------------
async def run_task(client: OpenAI, env: MyEnvV4Env, task: Dict) -> None:
"""Run one complete agent episode for the given task definition."""
task_id = task["id"]
max_steps = task["max_steps"]
temperature = task["temperature"]
system_prompt = task["system_prompt"]
# Max possible reward for normalization
max_reward_per_step = MAX_TOKENS * 0.1
max_total_reward = max_steps * max_reward_per_step
history: List[str] = []
rewards: List[float] = []
steps_taken = 0
score = 0.0
success = False
log_start(task=task_id, env=BENCHMARK, model=MODEL_NAME)
try:
result = await env.reset(task_id=task_id)
last_echoed = result.observation.echoed_message
last_reward = 0.0
for step in range(1, max_steps + 1):
if result.done:
break
message = get_model_message(
client, system_prompt, step, last_echoed, last_reward, history, temperature
)
result = await env.step(MyEnvV4Action(message=message))
obs = result.observation
reward = result.reward or 0.0
done = result.done
error = None
rewards.append(reward)
steps_taken = step
last_echoed = obs.echoed_message
last_reward = reward
log_step(step=step, action=message, reward=reward, done=done, error=error)
history.append(f"Step {step}: {message!r} -> reward {reward:+.2f}")
if done:
break
raw_score = sum(rewards) / max_total_reward if max_total_reward > 0 else 0.5
score = clamp_score(raw_score)
success = score >= 0.1
except Exception as exc:
print(f"[DEBUG] Task {task_id} error: {exc}", flush=True)
# Even on error, emit a valid clamped score so the grader accepts it
score = clamp_score(0.0)
success = False
finally:
log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
# ---------------------------------------------------------------------------
# Main entrypoint — runs ALL registered tasks sequentially
# ---------------------------------------------------------------------------
async def main() -> None:
api_key_to_use = HF_TOKEN if HF_TOKEN else "fake-key"
client = OpenAI(base_url=API_BASE_URL, api_key=api_key_to_use)
env = await MyEnvV4Env.from_docker_image(LOCAL_IMAGE_NAME)
try:
for task in TASKS:
await run_task(client, env, task)
finally:
try:
await env.close()
except Exception as e:
print(f"[DEBUG] env.close() error (container cleanup): {e}", flush=True)
if __name__ == "__main__":
asyncio.run(main())