aishani-s20 commited on
Commit
760fb86
·
1 Parent(s): 7d10441

changed app and inference

Browse files
Files changed (2) hide show
  1. inference.py +82 -52
  2. server/app.py +17 -51
inference.py CHANGED
@@ -1,13 +1,15 @@
1
  """
2
- Inference Script Example
3
- ===================================
4
- MANDATORY
5
- - Before submitting, ensure the following variables are defined in your environment configuration:
6
- API_BASE_URL The API endpoint for the LLM.
7
- MODEL_NAME The model identifier to use for inference.
8
- HF_TOKEN Your Hugging Face / API key.
9
- LOCAL_IMAGE_NAME The name of the local image to use for the environment if you are using from_docker_image()
10
- method
 
 
11
  """
12
 
13
  import asyncio
@@ -15,6 +17,7 @@ import json
15
  import os
16
  import textwrap
17
  from typing import List, Optional
 
18
  from dotenv import load_dotenv
19
 
20
  load_dotenv()
@@ -26,24 +29,25 @@ from quantum_openenv_env.client import QuantumOpenenvEnv
26
  from quantum_openenv_env.models import QuantumAction
27
 
28
  API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
29
- IMAGE_NAME = os.getenv("IMAGE_NAME", "quantum_env")
30
-
31
-
32
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
33
  MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct"
34
- TASK_NAME = os.getenv("QUANTUM_TASK", "random")
35
  BENCHMARK = os.getenv("QUANTUM_BENCHMARK", "quantum_optimization")
36
- MAX_STEPS = 50
 
37
  TEMPERATURE = 0.7
38
  MAX_TOKENS = 150
39
- SUCCESS_SCORE_THRESHOLD = 0.1
 
 
 
40
 
41
 
42
  SYSTEM_PROMPT = textwrap.dedent(
43
  """
44
  You are an AI agent tasked with optimizing a multi-qubit quantum circuit.
45
  You will be given the current circuit as a list of gates with their index, name, and target_qubits.
46
-
47
  You have 4 possible actions you can take at any index.
48
  Action 1: Cancel identical self-inverse gates (H, X, Y, Z, CNOT, SWAP). They must be on the same qubits and not blocked by intermediate gates sharing those qubits.
49
  Action 2: Swap adjacent commuting gates (gates that operate on entirely different qubits and do not overlap).
@@ -72,11 +76,18 @@ def log_step(step: int, action: str, reward: float, done: bool, error: Optional[
72
 
73
  def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
74
  rewards_str = ",".join(f"{r:.2f}" for r in rewards)
75
- print(f"[END] success={str(success).lower()} steps={steps} score={score:.2f} rewards={rewards_str}", flush=True)
 
 
 
76
 
77
- def build_user_prompt(step: int, circuit: List[object], last_reward: float, history: List[str]) -> str:
 
78
  if circuit:
79
- circuit_lines = [f"Index {i}: {gate.name} on qubits {gate.target_qubits}" for i, gate in enumerate(circuit)]
 
 
 
80
  circuit_block = "\n".join(circuit_lines)
81
  else:
82
  circuit_block = "Empty circuit"
@@ -95,7 +106,7 @@ def build_user_prompt(step: int, circuit: List[object], last_reward: float, hist
95
  ).strip()
96
 
97
 
98
- def get_model_message(client: OpenAI, step: int, circuit: List[object], last_reward: float, history: List[str]) -> str:
99
  user_prompt = build_user_prompt(step, circuit, last_reward, history)
100
  try:
101
  completion = client.chat.completions.create(
@@ -109,16 +120,17 @@ def get_model_message(client: OpenAI, step: int, circuit: List[object], last_rew
109
  stream=False,
110
  )
111
  text = (completion.choices[0].message.content or "").strip()
112
- return text if text else "hello"
113
  except Exception as exc:
114
  print(f"[DEBUG] Model request failed: {exc}", flush=True)
115
- return "hello"
116
-
117
 
118
- async def main() -> None:
119
- client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
120
- env = await QuantumOpenenvEnv.from_docker_image(IMAGE_NAME)
121
 
 
 
 
 
 
122
  history: List[str] = []
123
  rewards: List[float] = []
124
  steps_taken = 0
@@ -126,26 +138,17 @@ async def main() -> None:
126
  success = False
127
 
128
  try:
 
129
  result = await env.reset()
130
  circuit = result.observation.circuit
131
  last_reward = 0.0
132
-
133
- # --- BULLETPROOF FIX START ---
134
- # 1. Track initial gate count locally so the grader never fails
135
  initial_gate_count = len(circuit)
136
-
137
- # 2. Infer the exact task from the circuit topology if metadata is missing
138
- actual_task = result.observation.metadata.get("task") if result.observation.metadata else None
139
-
140
- if not actual_task or actual_task == "random":
141
- num_qubits = result.observation.num_qubits
142
- if num_qubits <= 2:
143
- actual_task = "easy"
144
- elif num_qubits <= 4:
145
- actual_task = "medium"
146
- else:
147
- actual_task = "hard"
148
- # --- BULLETPROOF FIX END ---
149
 
150
  log_start(task=actual_task, env=BENCHMARK, model=MODEL_NAME)
151
 
@@ -153,11 +156,11 @@ async def main() -> None:
153
  if result.done:
154
  break
155
 
156
- message = get_model_message(client, step, circuit, last_reward, history)
157
 
158
  try:
159
- clean_message = message.replace("```json", "").replace("```", "").strip()
160
- parsed = json.loads(clean_message)
161
  target_index = int(parsed["target_index"])
162
  action_type = int(parsed.get("action_type", 1))
163
  error = None
@@ -167,7 +170,6 @@ async def main() -> None:
167
  action_type = 1
168
 
169
  result = await env.step(QuantumAction(target_index=target_index, action_type=action_type))
170
-
171
  reward = result.reward or 0.0
172
  done = result.done
173
 
@@ -182,23 +184,51 @@ async def main() -> None:
182
  if done:
183
  break
184
 
185
- # Inject the saved initial count back into metadata for the grader
186
  if not result.observation.metadata:
187
  result.observation.metadata = {}
188
  result.observation.metadata["initial_count"] = initial_gate_count
189
 
190
- # Fetch the correct grader safely, falling back to the hard grader if the task name is missing
191
  grader = GRADERS.get(actual_task, GRADERS["hard"])
192
  score = grader(result.observation)
193
  success = score >= SUCCESS_SCORE_THRESHOLD
194
 
 
 
 
195
  finally:
196
- try:
197
- await env.close()
198
- except Exception as e:
199
- print(f"[DEBUG] env.close() error (container cleanup): {e}", flush=True)
200
  log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
201
 
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  if __name__ == "__main__":
204
  asyncio.run(main())
 
1
  """
2
+ Inference Script
3
+ ================
4
+ Runs the LLM agent against all 3 tasks (easy, medium, hard) and emits
5
+ a [START] / [END] log line for each, which the hackathon platform requires
6
+ to validate that all 3 tasks have graders.
7
+
8
+ Required environment variables:
9
+ API_BASE_URL The API endpoint for the LLM.
10
+ MODEL_NAME The model identifier.
11
+ HF_TOKEN Your Hugging Face / API key.
12
+ IMAGE_NAME Docker image name (default: quantum_env).
13
  """
14
 
15
  import asyncio
 
17
  import os
18
  import textwrap
19
  from typing import List, Optional
20
+
21
  from dotenv import load_dotenv
22
 
23
  load_dotenv()
 
29
  from quantum_openenv_env.models import QuantumAction
30
 
31
  API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
32
+ IMAGE_NAME = os.getenv("IMAGE_NAME", "quantum_env")
 
 
33
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
34
  MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct"
 
35
  BENCHMARK = os.getenv("QUANTUM_BENCHMARK", "quantum_optimization")
36
+
37
+ MAX_STEPS = 50
38
  TEMPERATURE = 0.7
39
  MAX_TOKENS = 150
40
+ SUCCESS_SCORE_THRESHOLD = 0.1
41
+
42
+ # All 3 tasks are always evaluated — this is what the platform requires
43
+ ALL_TASKS = ["easy", "medium", "hard"]
44
 
45
 
46
  SYSTEM_PROMPT = textwrap.dedent(
47
  """
48
  You are an AI agent tasked with optimizing a multi-qubit quantum circuit.
49
  You will be given the current circuit as a list of gates with their index, name, and target_qubits.
50
+
51
  You have 4 possible actions you can take at any index.
52
  Action 1: Cancel identical self-inverse gates (H, X, Y, Z, CNOT, SWAP). They must be on the same qubits and not blocked by intermediate gates sharing those qubits.
53
  Action 2: Swap adjacent commuting gates (gates that operate on entirely different qubits and do not overlap).
 
76
 
77
  def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
78
  rewards_str = ",".join(f"{r:.2f}" for r in rewards)
79
+ print(
80
+ f"[END] success={str(success).lower()} steps={steps} score={score:.2f} rewards={rewards_str}",
81
+ flush=True,
82
+ )
83
 
84
+
85
+ def build_user_prompt(step: int, circuit: list, last_reward: float, history: List[str]) -> str:
86
  if circuit:
87
+ circuit_lines = [
88
+ f"Index {i}: {gate.name} on qubits {gate.target_qubits}"
89
+ for i, gate in enumerate(circuit)
90
+ ]
91
  circuit_block = "\n".join(circuit_lines)
92
  else:
93
  circuit_block = "Empty circuit"
 
106
  ).strip()
107
 
108
 
109
+ def get_model_action(client: OpenAI, step: int, circuit: list, last_reward: float, history: List[str]) -> str:
110
  user_prompt = build_user_prompt(step, circuit, last_reward, history)
111
  try:
112
  completion = client.chat.completions.create(
 
120
  stream=False,
121
  )
122
  text = (completion.choices[0].message.content or "").strip()
123
+ return text if text else "{}"
124
  except Exception as exc:
125
  print(f"[DEBUG] Model request failed: {exc}", flush=True)
126
+ return "{}"
 
127
 
 
 
 
128
 
129
+ async def run_single_task(task_name: str, env: QuantumOpenenvEnv, client: OpenAI) -> None:
130
+ """
131
+ Run one full episode for a given task and emit [START] / [END] log lines.
132
+ The platform validates that all 3 tasks appear in these logs.
133
+ """
134
  history: List[str] = []
135
  rewards: List[float] = []
136
  steps_taken = 0
 
138
  success = False
139
 
140
  try:
141
+ # Reset with the specific task seed for reproducibility
142
  result = await env.reset()
143
  circuit = result.observation.circuit
144
  last_reward = 0.0
145
+
 
 
146
  initial_gate_count = len(circuit)
147
+
148
+ # Infer actual task name from metadata (env may be running in random mode)
149
+ actual_task = (result.observation.metadata or {}).get("task", task_name)
150
+ if actual_task not in ALL_TASKS:
151
+ actual_task = task_name
 
 
 
 
 
 
 
 
152
 
153
  log_start(task=actual_task, env=BENCHMARK, model=MODEL_NAME)
154
 
 
156
  if result.done:
157
  break
158
 
159
+ message = get_model_action(client, step, circuit, last_reward, history)
160
 
161
  try:
162
+ clean = message.replace("```json", "").replace("```", "").strip()
163
+ parsed = json.loads(clean)
164
  target_index = int(parsed["target_index"])
165
  action_type = int(parsed.get("action_type", 1))
166
  error = None
 
170
  action_type = 1
171
 
172
  result = await env.step(QuantumAction(target_index=target_index, action_type=action_type))
 
173
  reward = result.reward or 0.0
174
  done = result.done
175
 
 
184
  if done:
185
  break
186
 
187
+ # Inject initial count for grader
188
  if not result.observation.metadata:
189
  result.observation.metadata = {}
190
  result.observation.metadata["initial_count"] = initial_gate_count
191
 
 
192
  grader = GRADERS.get(actual_task, GRADERS["hard"])
193
  score = grader(result.observation)
194
  success = score >= SUCCESS_SCORE_THRESHOLD
195
 
196
+ except Exception as exc:
197
+ print(f"[DEBUG] Task {task_name} episode error: {exc}", flush=True)
198
+
199
  finally:
 
 
 
 
200
  log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
201
 
202
 
203
+ async def main() -> None:
204
+ """
205
+ Run all 3 tasks sequentially.
206
+
207
+ The hackathon platform requires inference.py to produce a [START] / [END]
208
+ log pair for EACH of the 3 tasks (easy, medium, hard). Running only one
209
+ task causes "Not enough tasks with graders" in Phase 2 Task Validation.
210
+ """
211
+ client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
212
+
213
+ for task_name in ALL_TASKS:
214
+ print(f"\n{'='*60}", flush=True)
215
+ print(f"Running task: {task_name}", flush=True)
216
+ print(f"{'='*60}", flush=True)
217
+
218
+ # Start a fresh Docker environment instance for each task
219
+ # Pass task name so the env generates the right circuit type
220
+ env = await QuantumOpenenvEnv.from_docker_image(
221
+ IMAGE_NAME,
222
+ env_vars={"QUANTUM_TASK": task_name},
223
+ )
224
+ try:
225
+ await run_single_task(task_name, env, client)
226
+ finally:
227
+ try:
228
+ await env.close()
229
+ except Exception as e:
230
+ print(f"[DEBUG] env.close() error for task {task_name}: {e}", flush=True)
231
+
232
+
233
  if __name__ == "__main__":
234
  asyncio.run(main())
server/app.py CHANGED
@@ -5,78 +5,44 @@
5
  # LICENSE file in the root directory of this source tree.
6
 
7
  """
8
- FastAPI application for the Quantum Openenv Env Environment.
9
-
10
- This module creates an HTTP server that exposes the QuantumOpenenvEnvironment
11
- over HTTP and WebSocket endpoints, compatible with EnvClient.
12
-
13
- Endpoints:
14
- - POST /reset: Reset the environment
15
- - POST /step: Execute an action
16
- - GET /state: Get current environment state
17
- - GET /schema: Get action/observation schemas
18
- - WS /ws: WebSocket endpoint for persistent sessions
19
-
20
- Usage:
21
- # Development (with auto-reload):
22
- uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
23
-
24
- # Production:
25
- uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
26
-
27
- # Or run directly:
28
- python -m server.app
29
  """
30
 
 
 
 
31
  try:
32
  from openenv.core.env_server.http_server import create_app
33
- except Exception as e: # pragma: no cover
34
  raise ImportError(
35
- "openenv is required for the web interface. Install dependencies with '\n uv sync\n'"
36
  ) from e
37
 
38
  from quantum_openenv_env.models import QuantumAction, QuantumObservation
39
  from quantum_openenv_env.server.quantum_openenv_env_environment import QuantumCircuitOptimizationEnvironment
40
 
 
 
 
 
 
 
 
 
41
 
42
- # Create the app with web interface and README integration
43
  app = create_app(
44
- QuantumCircuitOptimizationEnvironment,
45
  QuantumAction,
46
  QuantumObservation,
47
  env_name="quantum_openenv_env",
48
- max_concurrent_envs=100, # increase this number to allow more concurrent WebSocket sessions
49
  )
50
 
51
 
52
  def main():
53
- """
54
- Entry point for direct execution via uv run or python -m.
55
-
56
- This function enables running the server without Docker:
57
- uv run --project . server
58
- uv run --project . server --port 8001
59
- python -m server.app
60
-
61
- Args:
62
- host: Host address to bind to (default: "0.0.0.0")
63
- port: Port number to listen on (default: 8000)
64
-
65
- For production deployments, consider using uvicorn directly with
66
- multiple workers:
67
- uvicorn server.app:app --workers 4
68
- """
69
  import uvicorn
70
  uvicorn.run(app, host="0.0.0.0", port=8000)
71
 
72
 
73
- # if __name__ == "__main__":
74
- # import argparse
75
-
76
- # parser = argparse.ArgumentParser()
77
- # parser.add_argument("--port", type=int, default=8000)
78
- # args = parser.parse_args()
79
- # main(port=args.port)
80
-
81
  if __name__ == "__main__":
82
- main()
 
5
  # LICENSE file in the root directory of this source tree.
6
 
7
  """
8
+ FastAPI application for the Quantum Circuit Optimization Environment.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  """
10
 
11
+ import os
12
+ import functools
13
+
14
  try:
15
  from openenv.core.env_server.http_server import create_app
16
+ except Exception as e:
17
  raise ImportError(
18
+ "openenv is required. Install dependencies with 'uv sync'"
19
  ) from e
20
 
21
  from quantum_openenv_env.models import QuantumAction, QuantumObservation
22
  from quantum_openenv_env.server.quantum_openenv_env_environment import QuantumCircuitOptimizationEnvironment
23
 
24
+ # Read QUANTUM_TASK from environment variable (default: "random")
25
+ # When inference.py starts a container with env_vars={"QUANTUM_TASK": "easy"},
26
+ # this ensures the environment is instantiated with the correct task.
27
+ _task = os.getenv("QUANTUM_TASK", "random")
28
+
29
+ # Create a factory function (not a bare class) so we can pass task= argument
30
+ def _env_factory() -> QuantumCircuitOptimizationEnvironment:
31
+ return QuantumCircuitOptimizationEnvironment(task=_task)
32
 
 
33
  app = create_app(
34
+ _env_factory,
35
  QuantumAction,
36
  QuantumObservation,
37
  env_name="quantum_openenv_env",
38
+ max_concurrent_envs=100,
39
  )
40
 
41
 
42
  def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  import uvicorn
44
  uvicorn.run(app, host="0.0.0.0", port=8000)
45
 
46
 
 
 
 
 
 
 
 
 
47
  if __name__ == "__main__":
48
+ main()