printf-sourav commited on
Commit
4e45b0f
·
1 Parent(s): ebde6bf

LLM openai fixed

Browse files
Files changed (3) hide show
  1. Dockerfile +6 -7
  2. inference.py +75 -82
  3. test_remote.py +29 -0
Dockerfile CHANGED
@@ -5,16 +5,14 @@ FROM ghcr.io/meta-pytorch/openenv-base:latest AS builder
5
  WORKDIR /app
6
 
7
  ARG BUILD_MODE=in-repo
8
-
9
  COPY . /app/env
10
-
11
  WORKDIR /app/env
12
 
13
  # Ensure uv is available
14
  RUN if ! command -v uv >/dev/null 2>&1; then \
15
- curl -LsSf https://astral.sh/uv/install.sh | sh && \
16
- mv /root/.local/bin/uv /usr/local/bin/uv && \
17
- mv /root/.local/bin/uvx /usr/local/bin/uvx; \
18
  fi
19
 
20
  # Install git for git-based deps
@@ -38,7 +36,7 @@ FROM ghcr.io/meta-pytorch/openenv-base:latest
38
  WORKDIR /app
39
 
40
  COPY --from=builder /app/env/.venv /app/.venv
41
- COPY --from=builder /app/env /app/env
42
 
43
  ENV PATH="/app/.venv/bin:$PATH"
44
  ENV PYTHONPATH="/app/env:$PYTHONPATH"
@@ -49,4 +47,5 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
49
 
50
  EXPOSE 8000
51
 
52
- CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
 
 
5
  WORKDIR /app
6
 
7
  ARG BUILD_MODE=in-repo
 
8
  COPY . /app/env
 
9
  WORKDIR /app/env
10
 
11
  # Ensure uv is available
12
  RUN if ! command -v uv >/dev/null 2>&1; then \
13
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
14
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
15
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
16
  fi
17
 
18
  # Install git for git-based deps
 
36
  WORKDIR /app
37
 
38
  COPY --from=builder /app/env/.venv /app/.venv
39
+ COPY --from=builder /app/env /app/env
40
 
41
  ENV PATH="/app/.venv/bin:$PATH"
42
  ENV PYTHONPATH="/app/env:$PYTHONPATH"
 
47
 
48
  EXPOSE 8000
49
 
50
+ # Start the MCP server in background, wait for it to be ready, then run inference
51
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000 & sleep 5 && python inference.py"]
inference.py CHANGED
@@ -4,15 +4,15 @@ Inference Script — CSV Cleaner Environment
4
  Baseline agent using OpenAI client to clean CSV datasets across 3 tasks.
5
 
6
  MANDATORY ENV VARS:
7
- API_BASE_URL The API endpoint for the LLM.
8
- MODEL_NAME The model identifier to use for inference.
9
- HF_TOKEN Your Hugging Face / API key.
10
- IMAGE_NAME Docker image name (if using from_docker_image)
11
 
12
  STDOUT FORMAT:
13
- [START] task=<task_name> env=<benchmark> model=<model_name>
14
- [STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
15
- [END] success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>
16
  """
17
 
18
  import asyncio
@@ -25,40 +25,44 @@ from openai import OpenAI
25
 
26
  from csv_cleaner_env import CsvCleanerEnv
27
 
28
- IMAGE_NAME = os.environ.get("IMAGE_NAME", os.environ.get("LOCAL_IMAGE_NAME"))
29
- API_KEY = os.environ.get("API_KEY", os.environ.get("HF_TOKEN"))
30
- API_BASE_URL = os.environ.get("API_BASE_URL", "https://router.huggingface.co/v1")
31
- MODEL_NAME = os.environ.get("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
32
- BENCHMARK = os.getenv("CSV_CLEANER_BENCHMARK", "csv_cleaner_env")
 
33
  TEMPERATURE = 0.3
34
- MAX_TOKENS = 300
 
 
 
35
 
36
  # Task configurations
37
  TASKS = [
38
- {"name": "fix_column_types", "max_steps": 10},
39
- {"name": "clean_missing_duplicates", "max_steps": 15},
40
- {"name": "full_pipeline", "max_steps": 20},
41
  ]
42
 
43
  SYSTEM_PROMPT = textwrap.dedent("""
44
- You are a data cleaning agent. You interact with a CSV dataset through structured tool calls.
45
-
46
- Available tools:
47
- - get_dataset_info(): See current columns, types, null counts, samples
48
- - rename_column(old_name, new_name): Rename a column
49
- - cast_column(column, dtype): Cast column to int/float/str/datetime
50
- - fill_missing(column, strategy, value): Fill nulls. strategy: mean/median/mode/constant/zero
51
- - drop_missing(column): Drop rows with nulls (empty string for all columns)
52
- - drop_duplicates(columns): Remove duplicates (empty string for all columns)
53
- - filter_rows(column, operator, value): Filter rows. operator: ==/!=/>/</contains
54
- - strip_whitespace(column): Strip whitespace from string column
55
- - replace_values(column, old_value, new_value): Replace values in column
56
-
57
- You must respond with EXACTLY ONE tool call per turn as a JSON object:
58
- {"tool": "<tool_name>", "args": {"param1": "value1", ...}}
59
-
60
- Read the task description carefully and execute the cleaning steps one at a time.
61
- Start by calling get_dataset_info to understand the current state, then fix issues.
62
  """).strip()
63
 
64
 
@@ -68,7 +72,7 @@ def log_start(task: str, env: str, model: str) -> None:
68
 
69
  def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
70
  error_val = error if error else "null"
71
- done_val = str(done).lower()
72
  print(
73
  f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",
74
  flush=True,
@@ -86,23 +90,21 @@ def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> No
86
  def parse_tool_call(text: str) -> Optional[Dict[str, Any]]:
87
  """Extract JSON tool call from model response."""
88
  text = text.strip()
89
- # Try to find JSON in the response
90
- for start_char, end_char in [("{", "}"), ]:
91
  start = text.find(start_char)
92
  if start == -1:
93
  continue
94
- # Find matching closing brace
95
  depth = 0
96
  for i in range(start, len(text)):
97
  if text[i] == "{":
98
  depth += 1
99
  elif text[i] == "}":
100
  depth -= 1
101
- if depth == 0:
102
- try:
103
- return json.loads(text[start : i + 1])
104
- except json.JSONDecodeError:
105
- continue
106
  return None
107
 
108
 
@@ -116,19 +118,20 @@ def get_model_response(
116
  ) -> Optional[Dict[str, Any]]:
117
  """Get next tool call from the model."""
118
  history_block = "\n".join(history[-6:]) if history else "None"
 
119
  user_prompt = textwrap.dedent(f"""
120
- Task: {task_desc}
121
 
122
- Current Step: {step}
123
- Last Action Result: {last_result}
124
 
125
- Current Dataset State:
126
- {dataset_info}
127
 
128
- Previous Actions:
129
- {history_block}
130
 
131
- Respond with your next tool call as JSON: {{"tool": "tool_name", "args": {{...}}}}
132
  """).strip()
133
 
134
  try:
@@ -136,7 +139,7 @@ Respond with your next tool call as JSON: {{"tool": "tool_name", "args": {{...}}
136
  model=MODEL_NAME,
137
  messages=[
138
  {"role": "system", "content": SYSTEM_PROMPT},
139
- {"role": "user", "content": user_prompt},
140
  ],
141
  temperature=TEMPERATURE,
142
  max_tokens=MAX_TOKENS,
@@ -156,18 +159,17 @@ async def run_task(client: OpenAI, env: CsvCleanerEnv, task_config: Dict) -> Non
156
 
157
  log_start(task=task_name, env=BENCHMARK, model=MODEL_NAME)
158
 
159
- rewards: List[float] = []
160
- steps_taken = 0
161
- score = 0.0
162
- success = False
163
 
164
  try:
165
- result = await env.reset(task=task_name)
166
- metadata = result.metadata or {}
167
- task_desc = metadata.get("task_description", task_name)
168
  dataset_info = json.dumps(metadata.get("columns", []), indent=2)
169
- last_result = metadata.get("last_action_result", "Ready")
170
-
171
  history: List[str] = []
172
 
173
  for step in range(1, max_steps + 1):
@@ -181,48 +183,39 @@ async def run_task(client: OpenAI, env: CsvCleanerEnv, task_config: Dict) -> Non
181
  tool_call = get_model_response(
182
  client, task_desc, dataset_info, last_result, step, history
183
  )
184
-
185
- if tool_call is None:
186
- tool_call = {"tool": "get_dataset_info", "args": {}}
187
 
188
  tool_name = tool_call.get("tool", "get_dataset_info")
189
  tool_args = tool_call.get("args", {})
190
 
191
- # Execute via MCP call_tool
192
  try:
193
  call_result = await env.call_tool(tool_name, **tool_args)
194
- result_str = str(call_result) if call_result else ""
195
  except Exception as e:
196
  result_str = f"Error: {e}"
197
 
198
- # Get updated observation via step
199
- # The call_tool already executed the step internally via MCP
200
- # We need to read the reward from the observation
201
  reward = result.reward if hasattr(result, "reward") and result.reward else 0.0
202
- done = result.done if hasattr(result, "done") else False
203
 
204
- # If the tool call was successful, try to extract progress
205
  if result.metadata:
206
- progress = result.metadata.get("progress", 0.0)
207
- score = progress
208
  dataset_info = json.dumps(result.metadata.get("columns", []), indent=2)
209
- last_result = result.metadata.get("last_action_result", result_str)
210
  else:
211
  last_result = result_str
212
 
213
  rewards.append(reward)
214
  steps_taken = step
215
-
216
- action_str = f"{tool_name}({json.dumps(tool_args)})"
217
  log_step(step=step, action=action_str, reward=reward, done=done, error=None)
218
-
219
  history.append(f"Step {step}: {action_str} -> {last_result[:100]}")
220
 
221
  if done:
222
  break
223
 
224
- # Final score = last progress value
225
- score = min(max(score, 0.0), 1.0)
226
  success = score >= 0.5
227
 
228
  except Exception as e:
@@ -232,10 +225,10 @@ async def run_task(client: OpenAI, env: CsvCleanerEnv, task_config: Dict) -> Non
232
 
233
 
234
  async def main() -> None:
235
- client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
 
236
 
237
  env = await CsvCleanerEnv.from_docker_image(IMAGE_NAME)
238
-
239
  try:
240
  for task_config in TASKS:
241
  await run_task(client, env, task_config)
@@ -247,4 +240,4 @@ async def main() -> None:
247
 
248
 
249
  if __name__ == "__main__":
250
- asyncio.run(main())
 
4
  Baseline agent using OpenAI client to clean CSV datasets across 3 tasks.
5
 
6
  MANDATORY ENV VARS:
7
+ API_BASE_URL The API endpoint for the LLM.
8
+ MODEL_NAME The model identifier to use for inference.
9
+ HF_TOKEN Your Hugging Face / API key.
10
+ IMAGE_NAME Docker image name (if using from_docker_image)
11
 
12
  STDOUT FORMAT:
13
+ [START] task=<task_name> env=<benchmark> model=<model_name>
14
+ [STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
15
+ [END] success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>
16
  """
17
 
18
  import asyncio
 
25
 
26
  from csv_cleaner_env import CsvCleanerEnv
27
 
28
+ IMAGE_NAME = os.getenv("IMAGE_NAME")
29
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1") # default allowed
30
+ MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct") # default allowed
31
+ HF_TOKEN = os.getenv("HF_TOKEN") # NO default — injected by validator
32
+
33
+ BENCHMARK = os.getenv("CSV_CLEANER_BENCHMARK", "csv_cleaner_env")
34
  TEMPERATURE = 0.3
35
+ MAX_TOKENS = 300
36
+
37
+ # Debug print to confirm env vars are loaded
38
+ print(f"[CONFIG] API_BASE_URL={API_BASE_URL} MODEL={MODEL_NAME} HF_TOKEN={'SET' if HF_TOKEN else 'NOT SET'}", flush=True)
39
 
40
  # Task configurations
41
  TASKS = [
42
+ {"name": "fix_column_types", "max_steps": 10},
43
+ {"name": "clean_missing_duplicates", "max_steps": 15},
44
+ {"name": "full_pipeline", "max_steps": 20},
45
  ]
46
 
47
  SYSTEM_PROMPT = textwrap.dedent("""
48
+ You are a data cleaning agent. You interact with a CSV dataset through structured tool calls.
49
+
50
+ Available tools:
51
+ - get_dataset_info(): See current columns, types, null counts, samples
52
+ - rename_column(old_name, new_name): Rename a column
53
+ - cast_column(column, dtype): Cast column to int/float/str/datetime
54
+ - fill_missing(column, strategy, value): Fill nulls. strategy: mean/median/mode/constant/zero
55
+ - drop_missing(column): Drop rows with nulls (empty string for all columns)
56
+ - drop_duplicates(columns): Remove duplicates (empty string for all columns)
57
+ - filter_rows(column, operator, value): Filter rows. operator: ==/!=/>/</contains
58
+ - strip_whitespace(column): Strip whitespace from string column
59
+ - replace_values(column, old_value, new_value): Replace values in column
60
+
61
+ You must respond with EXACTLY ONE tool call per turn as a JSON object:
62
+ {"tool": "<tool_name>", "args": {"param1": "value1", ...}}
63
+
64
+ Read the task description carefully and execute the cleaning steps one at a time.
65
+ Start by calling get_dataset_info to understand the current state, then fix issues.
66
  """).strip()
67
 
68
 
 
72
 
73
  def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
74
  error_val = error if error else "null"
75
+ done_val = str(done).lower()
76
  print(
77
  f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",
78
  flush=True,
 
90
  def parse_tool_call(text: str) -> Optional[Dict[str, Any]]:
91
  """Extract JSON tool call from model response."""
92
  text = text.strip()
93
+ for start_char in ["{"]:
 
94
  start = text.find(start_char)
95
  if start == -1:
96
  continue
 
97
  depth = 0
98
  for i in range(start, len(text)):
99
  if text[i] == "{":
100
  depth += 1
101
  elif text[i] == "}":
102
  depth -= 1
103
+ if depth == 0:
104
+ try:
105
+ return json.loads(text[start : i + 1])
106
+ except json.JSONDecodeError:
107
+ continue
108
  return None
109
 
110
 
 
118
  ) -> Optional[Dict[str, Any]]:
119
  """Get next tool call from the model."""
120
  history_block = "\n".join(history[-6:]) if history else "None"
121
+
122
  user_prompt = textwrap.dedent(f"""
123
+ Task: {task_desc}
124
 
125
+ Current Step: {step}
126
+ Last Action Result: {last_result}
127
 
128
+ Current Dataset State:
129
+ {dataset_info}
130
 
131
+ Previous Actions:
132
+ {history_block}
133
 
134
+ Respond with your next tool call as JSON: {{"tool": "tool_name", "args": {{...}}}}
135
  """).strip()
136
 
137
  try:
 
139
  model=MODEL_NAME,
140
  messages=[
141
  {"role": "system", "content": SYSTEM_PROMPT},
142
+ {"role": "user", "content": user_prompt},
143
  ],
144
  temperature=TEMPERATURE,
145
  max_tokens=MAX_TOKENS,
 
159
 
160
  log_start(task=task_name, env=BENCHMARK, model=MODEL_NAME)
161
 
162
+ rewards: List[float] = []
163
+ steps_taken: int = 0
164
+ score: float = 0.0
165
+ success: bool = False
166
 
167
  try:
168
+ result = await env.reset(task=task_name)
169
+ metadata = result.metadata or {}
170
+ task_desc = metadata.get("task_description", task_name)
171
  dataset_info = json.dumps(metadata.get("columns", []), indent=2)
172
+ last_result = metadata.get("last_action_result", "Ready")
 
173
  history: List[str] = []
174
 
175
  for step in range(1, max_steps + 1):
 
183
  tool_call = get_model_response(
184
  client, task_desc, dataset_info, last_result, step, history
185
  )
186
+ if tool_call is None:
187
+ tool_call = {"tool": "get_dataset_info", "args": {}}
 
188
 
189
  tool_name = tool_call.get("tool", "get_dataset_info")
190
  tool_args = tool_call.get("args", {})
191
 
 
192
  try:
193
  call_result = await env.call_tool(tool_name, **tool_args)
194
+ result_str = str(call_result) if call_result else ""
195
  except Exception as e:
196
  result_str = f"Error: {e}"
197
 
 
 
 
198
  reward = result.reward if hasattr(result, "reward") and result.reward else 0.0
199
+ done = result.done if hasattr(result, "done") else False
200
 
 
201
  if result.metadata:
202
+ progress = result.metadata.get("progress", 0.0)
203
+ score = progress
204
  dataset_info = json.dumps(result.metadata.get("columns", []), indent=2)
205
+ last_result = result.metadata.get("last_action_result", result_str)
206
  else:
207
  last_result = result_str
208
 
209
  rewards.append(reward)
210
  steps_taken = step
211
+ action_str = f"{tool_name}({json.dumps(tool_args)})"
 
212
  log_step(step=step, action=action_str, reward=reward, done=done, error=None)
 
213
  history.append(f"Step {step}: {action_str} -> {last_result[:100]}")
214
 
215
  if done:
216
  break
217
 
218
+ score = min(max(score, 0.0), 1.0)
 
219
  success = score >= 0.5
220
 
221
  except Exception as e:
 
225
 
226
 
227
  async def main() -> None:
228
+ # Use HF_TOKEN as the API key — injected by the hackathon validator
229
+ client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
230
 
231
  env = await CsvCleanerEnv.from_docker_image(IMAGE_NAME)
 
232
  try:
233
  for task_config in TASKS:
234
  await run_task(client, env, task_config)
 
240
 
241
 
242
  if __name__ == "__main__":
243
+ asyncio.run(main())
test_remote.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from client import CsvCleanerEnv
3
+ try:
4
+ from server.tasks import TASKS
5
+ except ImportError:
6
+ TASKS = {"fix_column_types": None, "clean_missing_duplicates": None, "full_pipeline": None}
7
+
8
+ async def test_space():
9
+ url = "https://printf-sourav-csv-dc-env.hf.space"
10
+ print(f"Connecting to {url}...")
11
+ try:
12
+ env = CsvCleanerEnv(base_url=url)
13
+ for task_name in TASKS.keys():
14
+ print(f"\\nTesting task: {task_name}")
15
+ result = await env.reset(task=task_name)
16
+ print(f"✅ Reset successful. Result: {result}")
17
+
18
+ # test one simple MCP call
19
+ tools = await env.list_tools()
20
+ if "get_dataset_info" in tools:
21
+ print("✅ Toolkit active.")
22
+
23
+ await env.close()
24
+ print("\\nAll tasks reachable and responsive in HF Space!")
25
+ except Exception as e:
26
+ print(f"Failed to connect or test: {e}")
27
+
28
+ if __name__ == "__main__":
29
+ asyncio.run(test_space())