aether-taskflow / inference.py
Nithin1026's picture
Initial submit (#1)
9a28110
Raw
History Blame Contribute Delete
7.72 kB
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional
from openai import OpenAI
_REPO_ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(_REPO_ROOT))
from env.aether_env import AetherTaskFlowEnvironment
API_KEY = os.getenv("API_KEY", os.getenv("OPENAI_API_KEY", os.getenv("HF_TOKEN", "")))
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.1"))
MAX_LLM_TOKENS = int(os.getenv("MAX_LLM_TOKENS", "128"))
USE_LLM = bool(API_KEY)
SYSTEM_PROMPT = """You are an expert workflow orchestration agent inside the AETHER-TaskFlow RL environment.
Each step you receive an observation and must output a single JSON action.
RULES:
- Output ONLY valid JSON with keys: action_type, task_id, reasoning
- action_type must be one of: execute, defer, delegate, optimize
- task_id must be an integer matching a pending task id
- reasoning should be brief and may be empty
- No explanation, no markdown, no extra text - raw JSON only
STRATEGY:
- execute: high-priority, low-uncertainty tasks with sufficient resources
- optimize: before executing high-uncertainty tasks (reduces failure risk)
- delegate: when resources are too low (free action, 35% reward)
- defer: tasks you can handle later when resources recover
Example: {"action_type": "execute", "task_id": 2, "reasoning": "highest value low risk"}"""
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,
) -> None:
error_val = error if error else "null"
done_val = str(done).lower()
action_safe = action.replace("\n", " ").replace("\r", "")[:120]
print(
f"[STEP] step={step} action={action_safe} reward={reward:.2f} "
f"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} "
f"score={score:.2f} rewards={rewards_str}",
flush=True,
)
def _obs_to_prompt(obs_dict: Dict[str, Any]) -> str:
tasks = obs_dict.get("tasks", [])
task_lines = []
for t in tasks:
task_lines.append(
f" id={t['task_id']} name='{t['name']}' priority={t['priority']:.2f} "
f"deadline={t['deadline']} uncertainty={t['uncertainty']:.2f} "
f"value={t['value']:.1f} energy_cost={t['required_energy']:.1f} "
f"budget_cost={t['required_budget']:.1f} status={t.get('status', 'pending')}"
)
return (
f"OBSERVATION:\n"
f" time_remaining={obs_dict.get('time_remaining')} "
f"energy={obs_dict.get('energy_remaining', 0):.1f} "
f"budget={obs_dict.get('budget_remaining', 0):.1f} "
f"system_health={obs_dict.get('system_health', 1):.2f}\n"
f"PENDING TASKS:\n"
+ "\n".join(task_lines)
+ "\n\nOutput your action JSON:"
)
def _call_llm(client: OpenAI, obs_text: str, history: List[dict]) -> Optional[Dict[str, Any]]:
"""
Call the LLM using proper OpenAI client (required by judges).
Falls back to heuristic if it fails.
"""
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(history[-4:])
messages.append({"role": "user", "content": obs_text})
try:
completion = client.chat.completions.create(
model=MODEL_NAME,
messages=messages,
temperature=TEMPERATURE,
max_tokens=MAX_LLM_TOKENS,
)
raw = (completion.choices[0].message.content or "").strip()
if raw.startswith("```"):
raw = raw.split("```")[1]
if raw.startswith("json"):
raw = raw[4:].strip()
parsed = json.loads(raw)
return {
"task_id": int(parsed["task_id"]),
"action_type": str(parsed["action_type"]),
"reasoning": str(parsed.get("reasoning", "")),
}
except Exception as exc:
print(f"[DEBUG] LLM call/parse failed: {exc}", flush=True)
return None
def get_llm_action(
client: OpenAI,
obs_dict: Dict[str, Any],
history: List[dict],
) -> Optional[Dict[str, Any]]:
"""Call the configured OpenAI-compatible endpoint for an LLM action."""
obs_text = _obs_to_prompt(obs_dict)
result = _call_llm(client, obs_text, history)
if result is not None:
history.append({"role": "user", "content": obs_text})
history.append({"role": "assistant", "content": json.dumps(result)})
return result
def get_heuristic_action(env: AetherTaskFlowEnvironment) -> Dict[str, Any]:
"""Built-in AETHER + RAPTOR heuristic - no API required."""
action = env.message_to_action("")
if hasattr(action, "model_dump"):
return action.model_dump(exclude={"reasoning"})
return {
"action_type": getattr(action, "action_type", "execute").value
if hasattr(action, "action_type")
else "execute",
"task_id": getattr(action, "task_id", 0),
}
def get_action(
env: AetherTaskFlowEnvironment,
client: OpenAI,
obs_dict: Dict[str, Any],
history: List[dict],
) -> Dict[str, Any]:
"""Return LLM action if an API key is set, otherwise heuristic."""
if USE_LLM:
result = get_llm_action(client, obs_dict, history)
if result is not None:
return result
heuristic = get_heuristic_action(env)
obs_text = _obs_to_prompt(obs_dict)
history.append({"role": "user", "content": obs_text})
history.append({"role": "assistant", "content": json.dumps(heuristic)})
return heuristic
def run_episode(difficulty: str, client: OpenAI) -> None:
os.environ["AETHER_DIFFICULTY"] = difficulty
env = AetherTaskFlowEnvironment(difficulty=difficulty)
model_label = MODEL_NAME if USE_LLM else "HEURISTIC-AETHER-RAPTOR"
log_start(task=difficulty, env="aether_taskflow", model=model_label)
obs = env.reset()
obs_dict = obs.model_dump() if hasattr(obs, "model_dump") else obs
rewards: List[float] = []
history: List[dict] = []
step = 0
while True:
step += 1
action_dict = get_action(env, client, obs_dict, history)
next_obs = env.step(action_dict)
next_obs_dict = next_obs.model_dump() if hasattr(next_obs, "model_dump") else next_obs
reward = next_obs_dict.get("reward", 0.0)
done = next_obs_dict.get("done", False)
rewards.append(reward)
action_str = (
f"{action_dict.get('action_type', 'execute')}"
f"(task_id={action_dict.get('task_id', 0)})"
)
log_step(step, action_str, reward, done)
obs_dict = next_obs_dict
if done:
break
score = env.compute_final_score()
log_end(success=True, steps=step, score=score, rewards=rewards)
def main() -> None:
client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
parser = argparse.ArgumentParser(description="AETHER-TaskFlow Inference")
parser.add_argument(
"--single",
choices=["easy", "medium", "hard"],
default=None,
help="Run a single difficulty (default: all three)",
)
args = parser.parse_args()
difficulties = [args.single] if args.single else ["easy", "medium", "hard"]
for diff in difficulties:
run_episode(diff, client)
if __name__ == "__main__":
main()