Spaces:
Sleeping
Sleeping
Download inference.py from V1ShAl1613/project_meta: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/V1ShAl1613/project_meta/resolve/main/inference.py
- Command line
-
hf download hf://spaces/V1ShAl1613/project_meta/inference.py
-
curl -L -o inference.py https://huggingface.co/spaces/V1ShAl1613/project_meta/resolve/main/inference.py
10.4 kB
| """Baseline inference script for OpenEnv Customer Support environment. | |
| Uses the OpenAI client to interact with an LLM and solve tasks. | |
| Follows the STRICT stdout format required by OpenEnv validation. | |
| Environment variables: | |
| API_BASE_URL β Base URL for the OpenAI-compatible API | |
| MODEL_NAME β Model identifier | |
| HF_TOKEN β Hugging Face token (used as API key) | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import traceback | |
| import requests | |
| from openai import OpenAI | |
| # ββ Configuration βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.openai.com/v1") | |
| MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini") | |
| HF_TOKEN = os.environ.get("HF_TOKEN", "") | |
| ENV_BASE_URL = os.environ.get("ENV_BASE_URL", "http://localhost:7860") | |
| # ββ OpenAI client βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| client = OpenAI( | |
| base_url=API_BASE_URL, | |
| api_key=HF_TOKEN or "sk-placeholder", | |
| ) | |
| # ββ Task configs ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| TASK_CONFIGS = { | |
| "easy_classification": { | |
| "difficulty": "easy", | |
| "max_steps": 3, | |
| "ticket_count": 5, | |
| }, | |
| "medium_response": { | |
| "difficulty": "medium", | |
| "max_steps": 5, | |
| "ticket_count": 3, | |
| }, | |
| "hard_resolution": { | |
| "difficulty": "hard", | |
| "max_steps": 10, | |
| "ticket_count": 3, | |
| }, | |
| } | |
| # ββ Helper: call LLM βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def call_llm(system_prompt: str, user_prompt: str) -> str: | |
| """Call the LLM and return the response text.""" | |
| try: | |
| response = client.chat.completions.create( | |
| model=MODEL_NAME, | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| temperature=0.0, | |
| max_tokens=1024, | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| return f"ERROR: {e}" | |
| # ββ Helper: env API calls ββββββββββββββββββββββββββββββββββββββββββββ | |
| def env_reset(task_id: str, ticket_index: int = 0) -> dict: | |
| """Reset the environment.""" | |
| resp = requests.post( | |
| f"{ENV_BASE_URL}/reset", | |
| json={"task_id": task_id, "ticket_index": ticket_index}, | |
| timeout=30, | |
| ) | |
| resp.raise_for_status() | |
| return resp.json() | |
| def env_step(action: dict) -> dict: | |
| """Send an action to the environment.""" | |
| resp = requests.post( | |
| f"{ENV_BASE_URL}/step", | |
| json=action, | |
| timeout=30, | |
| ) | |
| resp.raise_for_status() | |
| return resp.json() | |
| # ββ Prompts βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CLASSIFY_SYSTEM = """You are a customer support ticket classifier. | |
| Classify the ticket into exactly ONE of these categories: | |
| - delivery_delay | |
| - damaged_product | |
| - refund_request | |
| - billing_error | |
| - product_inquiry | |
| - account_issue | |
| Reply with ONLY the category label, nothing else.""" | |
| RESPOND_SYSTEM = """You are a professional customer support agent. | |
| Write a helpful, empathetic response to the customer's issue. | |
| Guidelines: | |
| - Address the customer by their first name | |
| - Acknowledge their frustration if they are upset | |
| - Reference their order if applicable | |
| - Outline clear next steps | |
| - Be professional and warm | |
| - Keep it concise (2-4 sentences) | |
| Reply with ONLY the response text.""" | |
| ACTION_SYSTEM = """You are deciding the resolution action for a customer support ticket. | |
| Based on the ticket details, choose ONE action and respond in JSON format: | |
| Rules: | |
| - For damaged products with high value (>$200) or angry customers: {"action": "escalate", "reason": "..."} | |
| - For billing errors or refund requests: {"action": "refund", "amount": <order_value>} | |
| - For delivery delays with critical priority: {"action": "escalate", "reason": "..."} | |
| - For simple inquiries or low-severity issues: {"action": "resolve"} | |
| - For any critical priority tickets: {"action": "escalate", "reason": "..."} | |
| Reply with ONLY the JSON object.""" | |
| # ββ Run a single task βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_task(task_id: str, ticket_index: int = 0) -> dict: | |
| """Run one episode of a task and return results.""" | |
| config = TASK_CONFIGS[task_id] | |
| difficulty = config["difficulty"] | |
| # Reset | |
| reset_data = env_reset(task_id, ticket_index) | |
| obs = reset_data["observation"] | |
| ticket = obs["ticket"] | |
| ticket_text = ( | |
| f"Ticket ID: {ticket['ticket_id']}\n" | |
| f"Customer: {ticket['customer_name']}\n" | |
| f"Issue: {ticket['issue_text']}\n" | |
| f"Order Value: ${ticket['order_value']}\n" | |
| f"Priority: {ticket['priority']}\n" | |
| f"Sentiment: {ticket['sentiment']}" | |
| ) | |
| rewards = [] | |
| step_num = 0 | |
| done = False | |
| score = 0.0 | |
| last_error = None | |
| print(f"[START] task={task_id} env=customer-support model={MODEL_NAME}") | |
| try: | |
| # ββ Step 1: Classify ββ | |
| if not done: | |
| classification = call_llm(CLASSIFY_SYSTEM, ticket_text) | |
| classification = classification.strip().lower().replace(" ", "_").replace("-", "_") | |
| step_result = env_step({ | |
| "action_type": "classify", | |
| "classification": classification, | |
| }) | |
| step_num += 1 | |
| reward_val = step_result["reward"]["total"] | |
| done = step_result["done"] | |
| score = step_result["observation"]["score"] | |
| rewards.append(reward_val) | |
| print( | |
| f"[STEP] step={step_num} action=classify " | |
| f"reward={reward_val:.2f} done={str(done).lower()} error=null" | |
| ) | |
| # ββ Step 2: Respond (medium + hard) ββ | |
| if not done and difficulty in ("medium", "hard"): | |
| response = call_llm( | |
| RESPOND_SYSTEM, | |
| f"{ticket_text}\n\nClassification: {classification}", | |
| ) | |
| step_result = env_step({ | |
| "action_type": "respond", | |
| "response_text": response, | |
| }) | |
| step_num += 1 | |
| reward_val = step_result["reward"]["total"] | |
| done = step_result["done"] | |
| score = step_result["observation"]["score"] | |
| rewards.append(reward_val) | |
| print( | |
| f"[STEP] step={step_num} action=respond " | |
| f"reward={reward_val:.2f} done={str(done).lower()} error=null" | |
| ) | |
| # ββ Step 3: Take action (hard only) ββ | |
| if not done and difficulty == "hard": | |
| action_response = call_llm( | |
| ACTION_SYSTEM, | |
| f"{ticket_text}\n\nClassification: {classification}", | |
| ) | |
| # Parse the JSON action | |
| try: | |
| action_json = json.loads(action_response) | |
| except json.JSONDecodeError: | |
| # Fallback: try to extract action from text | |
| action_json = {"action": "escalate", "reason": "Unable to parse, escalating"} | |
| action_type = action_json.get("action", "resolve") | |
| step_action: dict = {"action_type": action_type} | |
| if action_type == "refund": | |
| step_action["refund_amount"] = action_json.get("amount", ticket["order_value"]) | |
| elif action_type == "escalate": | |
| step_action["escalation_reason"] = action_json.get( | |
| "reason", "Customer issue requires management attention" | |
| ) | |
| step_result = env_step(step_action) | |
| step_num += 1 | |
| reward_val = step_result["reward"]["total"] | |
| done = step_result["done"] | |
| score = step_result["observation"]["score"] | |
| rewards.append(reward_val) | |
| print( | |
| f"[STEP] step={step_num} action={action_type} " | |
| f"reward={reward_val:.2f} done={str(done).lower()} error=null" | |
| ) | |
| except Exception as exc: | |
| last_error = str(exc) | |
| print( | |
| f"[STEP] step={step_num} action=error " | |
| f"reward=0.05 done=true error={last_error}" | |
| ) | |
| rewards.append(0.05) | |
| # ββ Summary ββ | |
| success = done and score > 0.3 | |
| rewards_str = ",".join(f"{r:.2f}" for r in rewards) | |
| print( | |
| f"[END] success={str(success).lower()} steps={step_num} " | |
| f"score={score:.2f} rewards={rewards_str}" | |
| ) | |
| return { | |
| "task_id": task_id, | |
| "ticket_index": ticket_index, | |
| "success": success, | |
| "steps": step_num, | |
| "score": score, | |
| "rewards": rewards, | |
| } | |
| # ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def main(): | |
| """Run inference across all tasks and ticket pools.""" | |
| all_results = [] | |
| for task_id, config in TASK_CONFIGS.items(): | |
| ticket_count = config["ticket_count"] | |
| for idx in range(ticket_count): | |
| try: | |
| result = run_task(task_id, ticket_index=idx) | |
| all_results.append(result) | |
| except Exception as exc: | |
| print( | |
| f"[END] success=false steps=0 score=0.05 " | |
| f"rewards=0.05" | |
| ) | |
| traceback.print_exc(file=sys.stderr) | |
| # Summary | |
| print("\n--- Summary ---") | |
| for r in all_results: | |
| print( | |
| f" {r['task_id']}[{r['ticket_index']}]: " | |
| f"score={r['score']:.2f} steps={r['steps']} " | |
| f"success={r['success']}" | |
| ) | |
| if __name__ == "__main__": | |
| main() | |