Spaces:
Sleeping
Sleeping
| import json | |
| import os | |
| import sys | |
| from typing import Any, Dict, List | |
| import httpx | |
| from openai import OpenAI | |
| from dotenv import load_dotenv | |
| # Load variables from .env if present | |
| load_dotenv() | |
| # ------------------------------------------------------------------ # | |
| # Configuration Required for Hackathon Submission | |
| # ------------------------------------------------------------------ # | |
| # Required variables exactly as strictly specified in the submission prompt | |
| API_BASE_URL = os.getenv("API_BASE_URL", "https://api.openai.com/v1") | |
| MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini") | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| # Optional - if you use from_docker_image(): | |
| LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME") | |
| # Environment Endpoint config | |
| ENV_API_URL = os.getenv("ENV_BASE_URL", "http://localhost:7860") | |
| EPISODES_PER_TASK = 5 | |
| # ------------------------------------------------------------------ # | |
| # OpenAI client configured via the required variables | |
| # ------------------------------------------------------------------ # | |
| # Using HF_TOKEN or another key. We pass it through easily. | |
| # If testing locally, you can export OPENAI_API_KEY. | |
| api_key = os.getenv("OPENAI_API_KEY", HF_TOKEN) if not HF_TOKEN else HF_TOKEN | |
| ai_client = OpenAI( | |
| base_url=API_BASE_URL, | |
| api_key=api_key or "DUMMY_KEY", # Fallback for environments that don't enforce keys | |
| ) | |
| # ------------------------------------------------------------------ # | |
| # System prompt | |
| # ------------------------------------------------------------------ # | |
| SYSTEM_PROMPT = """\ | |
| You are a professional customer support agent. Your job is to help customers \ | |
| resolve their issues efficiently and politely. | |
| For the EASY task: Read the customer message and reply with ONLY the category label. | |
| Valid categories are: refund, technical, shipping, billing, account | |
| For the MEDIUM task: Write a single, complete, empathetic reply that addresses the \ | |
| customer's issue in one message. | |
| For the HARD task (multi-turn): | |
| - Turn 1: Ask ONE clarifying question to better understand the issue. | |
| - Turn 2: Provide a concrete solution based on what the customer told you. | |
| - Turn 3: Close the conversation politely. | |
| """ | |
| def get_agent_reply(conversation: List[str], task_name: str, turn: int) -> str: | |
| messages = [{"role": "system", "content": SYSTEM_PROMPT}] | |
| for i, msg in enumerate(conversation): | |
| role = "user" if i % 2 == 0 else "assistant" | |
| messages.append({"role": role, "content": msg}) | |
| if task_name == "hard": | |
| hints = { | |
| 1: "This is turn 1. Ask ONE clarifying question only.", | |
| 2: "This is turn 2. Provide a concrete, actionable solution.", | |
| 3: "This is turn 3. Close the conversation politely.", | |
| } | |
| hint = hints.get(turn, "Continue the support conversation appropriately.") | |
| messages.append({"role": "system", "content": f"[HINT FOR THIS TURN: {hint}]"}) | |
| try: | |
| response = ai_client.chat.completions.create( | |
| model=MODEL_NAME, | |
| messages=messages, | |
| temperature=0.3, | |
| max_tokens=300, | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| print(f" [OpenAI error] {e}") | |
| return "I apologize for the inconvenience. Let me help you with that." | |
| # ------------------------------------------------------------------ # | |
| # Environment API helpers | |
| # ------------------------------------------------------------------ # | |
| def env_reset(client: httpx.Client, task_name: str, seed: int) -> Dict[str, Any]: | |
| response = client.post( | |
| f"{ENV_API_URL}/reset", | |
| json={"task_name": task_name, "seed": seed}, | |
| ) | |
| response.raise_for_status() | |
| return response.json() | |
| def env_step( | |
| client: httpx.Client, session_id: str, message: str, intent: str = None | |
| ) -> Dict[str, Any]: | |
| payload = {"session_id": session_id, "message": message} | |
| if intent: | |
| payload["intent"] = intent | |
| response = client.post(f"{ENV_API_URL}/step", json=payload) | |
| response.raise_for_status() | |
| return response.json() | |
| # ------------------------------------------------------------------ # | |
| # Run episodes strictly logging START/STEP/END | |
| # ------------------------------------------------------------------ # | |
| def run_task(client: httpx.Client, task_name: str) -> List[float]: | |
| rewards = [] | |
| for ep in range(EPISODES_PER_TASK): | |
| print(f"[START] task={task_name}", flush=True) # REQUIRED STRUCTURED LOGGING | |
| try: | |
| reset_data = env_reset(client, task_name, seed=ep) | |
| session_id = reset_data["session_id"] | |
| obs = reset_data.get("observation", {}) | |
| done = obs.get("done", False) | |
| cumulative = obs.get("cumulative_reward", 0.0) | |
| turn = 0 | |
| while not done: | |
| turn += 1 | |
| conversation = obs.get("conversation", []) | |
| agent_reply = get_agent_reply(conversation, task_name, turn) | |
| if task_name == "easy": | |
| intent = "classify" | |
| elif task_name == "medium": | |
| intent = "respond" | |
| else: | |
| intent_map = {1: "clarify", 2: "respond", 3: "close"} | |
| intent = intent_map.get(turn, "close") | |
| step_data = env_step(client, session_id, agent_reply, intent) | |
| obs = step_data.get("observation", {}) | |
| done = obs.get("done", False) | |
| cumulative = obs.get("cumulative_reward", 0.0) | |
| print(f"[STEP] step={turn} reward={round(cumulative, 4)}", flush=True) # REQUIRED STRUCTURED LOGGING | |
| if turn >= 15: | |
| print(f"[Warning] Episode exceeded turn limit, breaking.", file=sys.stderr, flush=True) | |
| break | |
| episode_reward = cumulative if cumulative is not None else 0.0 | |
| rewards.append(episode_reward) | |
| print(f"[END] task={task_name} score={round(episode_reward, 4)} steps={turn}", flush=True) # REQUIRED STRUCTURED LOGGING | |
| except Exception as e: | |
| print(f"[ERROR] Episode {ep} failed: {e}", file=sys.stderr, flush=True) | |
| print(f"[END] task={task_name} score=0.0 steps=0", flush=True) | |
| rewards.append(0.0) | |
| return rewards | |
| # ------------------------------------------------------------------ # | |
| # Main | |
| # ------------------------------------------------------------------ # | |
| def main(): | |
| results = {} | |
| with httpx.Client(timeout=90.0) as client: | |
| for task_name in ["easy", "medium", "hard"]: | |
| rewards = run_task(client, task_name) | |
| avg_reward = sum(rewards) / len(rewards) if rewards else 0.0 | |
| results[task_name] = { | |
| "average_score": round(avg_reward, 4), | |
| "scores": [round(r, 4) for r in rewards], | |
| "episodes": len(rewards), | |
| "model": MODEL_NAME, | |
| } | |
| # Write summary to stderr so it does NOT pollute the structured stdout stream | |
| print(json.dumps(results, indent=2), file=sys.stderr, flush=True) | |
| if __name__ == "__main__": | |
| main() | |