Deadman922 commited on
Commit
1e71db5
·
verified ·
1 Parent(s): 0997498

Delete inference.py

Browse files
Files changed (1) hide show
  1. inference.py +0 -356
inference.py DELETED
@@ -1,356 +0,0 @@
1
- """Inference module for SQL Quality Environment.
2
-
3
- This module provides an LLM-based AI agent interface for solving SQL data quality tasks.
4
- It uses OpenAI API to generate SQL queries and fixes dynamically.
5
- """
6
-
7
- import json
8
- import os
9
- import sys
10
- from typing import Any, Dict, List, Optional
11
-
12
- import requests
13
- from openai import OpenAI
14
-
15
- # Configuration from environment
16
- MAX_STEPS = int(os.getenv("MAX_STEPS", "10"))
17
- API_BASE_URL = os.getenv("API_BASE_URL", "https://api.openai.com/v1")
18
- MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini")
19
- HF_TOKEN = os.getenv("HF_TOKEN", "")
20
-
21
-
22
- class SQLQualityAgent:
23
- """LLM-based agent for solving SQL data quality tasks."""
24
-
25
- def __init__(self, base_url: str = "http://localhost:7860"):
26
- self.base_url = base_url.rstrip("/")
27
- self.task_id: Optional[str] = None
28
- self.episode_id: Optional[str] = None
29
- self.step_count = 0
30
- self.trajectory: List[Dict[str, Any]] = []
31
-
32
- # Initialize OpenAI client
33
- api_key = HF_TOKEN or os.getenv("OPENAI_API_KEY", "")
34
- self.client = OpenAI(
35
- api_key=api_key,
36
- base_url=API_BASE_URL if "openai" not in API_BASE_URL else None
37
- )
38
-
39
- def health_check(self) -> bool:
40
- """Check if the environment is running."""
41
- try:
42
- r = requests.get(f"{self.base_url}/health", timeout=5)
43
- return r.status_code == 200 and r.json().get("status") == "ok"
44
- except Exception:
45
- return False
46
-
47
- def reset(self, task_id: str, seed: Optional[int] = None) -> Dict[str, Any]:
48
- """Initialize a new episode for the given task."""
49
- self.task_id = task_id
50
- payload = {"task_id": task_id}
51
- if seed is not None:
52
- payload["seed"] = seed
53
-
54
- r = requests.post(f"{self.base_url}/reset", json=payload, timeout=30)
55
- r.raise_for_status()
56
- data = r.json()
57
- self.episode_id = data.get("episode_id")
58
- return data
59
-
60
- def query(self, sql: str) -> Dict[str, Any]:
61
- """Execute a SELECT query to investigate the data."""
62
- if not self.task_id:
63
- raise ValueError("No active task. Call reset() first.")
64
-
65
- self.step_count += 1
66
- if self.step_count > MAX_STEPS:
67
- raise RuntimeError(f"Exceeded MAX_STEPS ({MAX_STEPS})")
68
-
69
- r = requests.post(
70
- f"{self.base_url}/step",
71
- json={
72
- "task_id": self.task_id,
73
- "action_type": "query",
74
- "sql": sql,
75
- },
76
- timeout=30,
77
- )
78
- r.raise_for_status()
79
- return r.json()
80
-
81
- def profile(self, column: str) -> Dict[str, Any]:
82
- """Profile a column to get statistics."""
83
- if not self.task_id:
84
- raise ValueError("No active task. Call reset() first.")
85
-
86
- self.step_count += 1
87
- if self.step_count > MAX_STEPS:
88
- raise RuntimeError(f"Exceeded MAX_STEPS ({MAX_STEPS})")
89
-
90
- r = requests.post(
91
- f"{self.base_url}/step",
92
- json={
93
- "task_id": self.task_id,
94
- "action_type": "profile",
95
- "column_name": column,
96
- },
97
- timeout=30,
98
- )
99
- r.raise_for_status()
100
- return r.json()
101
-
102
- def fix(self, fix_sql: str) -> Dict[str, Any]:
103
- """Apply a fix SQL (UPDATE/INSERT/DELETE)."""
104
- if not self.task_id:
105
- raise ValueError("No active task. Call reset() first.")
106
-
107
- self.step_count += 1
108
- if self.step_count > MAX_STEPS:
109
- raise RuntimeError(f"Exceeded MAX_STEPS ({MAX_STEPS})")
110
-
111
- r = requests.post(
112
- f"{self.base_url}/step",
113
- json={
114
- "task_id": self.task_id,
115
- "action_type": "fix",
116
- "fix_query": fix_sql,
117
- },
118
- timeout=30,
119
- )
120
- r.raise_for_status()
121
- return r.json()
122
-
123
- def submit(
124
- self,
125
- answer: Optional[str] = None,
126
- fix_query: Optional[str] = None,
127
- root_cause_table: Optional[str] = None,
128
- offending_key: Optional[str] = None,
129
- ) -> Dict[str, Any]:
130
- """Submit final answer to complete the episode."""
131
- if not self.task_id:
132
- raise ValueError("No active task. Call reset() first.")
133
-
134
- payload: Dict[str, Any] = {
135
- "task_id": self.task_id,
136
- "action_type": "submit",
137
- }
138
- if answer:
139
- payload["answer"] = answer
140
- if fix_query:
141
- payload["fix_query"] = fix_query
142
- if root_cause_table:
143
- payload["root_cause_table"] = root_cause_table
144
- if offending_key:
145
- payload["offending_key"] = offending_key
146
-
147
- r = requests.post(f"{self.base_url}/step", json=payload, timeout=30)
148
- r.raise_for_status()
149
- return r.json()
150
-
151
-
152
- def llm_generate_sql(self, prompt: str, system_context: str = "") -> str:
153
- """Generate SQL using LLM."""
154
- messages = [
155
- {"role": "system", "content": system_context or "You are a SQL data quality expert. Generate valid SQLite SQL queries."},
156
- {"role": "user", "content": prompt}
157
- ]
158
-
159
- try:
160
- response = self.client.chat.completions.create(
161
- model=MODEL_NAME,
162
- messages=messages,
163
- temperature=0.1,
164
- max_tokens=500
165
- )
166
- sql = response.choices[0].message.content.strip()
167
- # Extract SQL from markdown if present
168
- if "```sql" in sql:
169
- sql = sql.split("```sql")[1].split("```")[0].strip()
170
- elif "```" in sql:
171
- sql = sql.split("```")[1].split("```")[0].strip()
172
- return sql
173
- except Exception as e:
174
- print(f"LLM generation failed: {e}")
175
- return ""
176
-
177
-
178
- def run_agent(task_id: str, base_url: str = "http://localhost:7860") -> Dict[str, Any]:
179
- """Run LLM-based agent on a task."""
180
- agent = SQLQualityAgent(base_url)
181
-
182
- if not agent.health_check():
183
- print("Error: Environment not available")
184
- sys.exit(1)
185
-
186
- # Initialize episode
187
- print(f"Initializing task: {task_id}")
188
- obs = agent.reset(task_id)
189
- print(f"Episode ID: {agent.episode_id}")
190
-
191
- # Get schema and task info
192
- schema = obs.get("observation", {}).get("schema_info", [])
193
- task_desc = obs.get("observation", {}).get("task_description", "")
194
-
195
- print(f"Task: {task_desc}")
196
- print(f"Schema: {json.dumps(schema, indent=2)}")
197
-
198
- # Build context for LLM
199
- schema_text = json.dumps(schema, indent=2)
200
-
201
- try:
202
- if task_id == "null_audit":
203
- # Use LLM to find null percentages
204
- prompt = f"""Given this schema: {schema_text}
205
-
206
- Write a SQL query to find which column has the highest percentage of NULL values.
207
- Return only the SQL query."""
208
-
209
- sql = agent.llm_generate_sql(prompt, "You are a data quality expert.")
210
- if sql:
211
- result = agent.query(sql)
212
- print(f"Query result: {result}")
213
-
214
- # Ask LLM to identify the answer
215
- answer_prompt = f"""Based on the query results above, which column name has null rate > 50%?
216
- Return ONLY the column name (e.g., 'city'), nothing else."""
217
-
218
- answer = agent.llm_generate_sql(answer_prompt, "You identify the problematic column.")
219
- if not answer:
220
- answer = "city" # fallback
221
-
222
- submit_result = agent.submit(answer=answer.strip())
223
- return submit_result
224
-
225
- elif task_id == "type_repair":
226
- # Check for type issues
227
- prompt = f"""Given schema: {schema_text}
228
-
229
- Task: {task_desc}
230
- Write a SQL query to inspect the transactions table and identify type mismatches in the amount column.
231
- Return only the SQL."""
232
-
233
- sql = agent.llm_generate_sql(prompt)
234
- if sql:
235
- result = agent.query(sql)
236
- print(f"Inspection result: {result}")
237
-
238
- # Generate fix
239
- fix_prompt = f"""Given the type mismatch in the amount column (stored as TEXT with '$' prefix),
240
- write a SQLite UPDATE statement to convert it to REAL numeric values.
241
- Return only the SQL UPDATE statement."""
242
-
243
- fix_sql = agent.llm_generate_sql(fix_prompt, "You write data repair SQL.")
244
- if fix_sql:
245
- fix_result = agent.fix(fix_sql)
246
- print(f"Fix result: {fix_result}")
247
- submit_result = agent.submit(fix_query=fix_sql)
248
- else:
249
- submit_result = agent.submit()
250
- return submit_result
251
-
252
- elif task_id == "dedup_bug":
253
- # Find duplicates
254
- prompt = f"""Given schema: {schema_text}
255
-
256
- Write a SQL query to find duplicate event_id values in the events table.
257
- Return only the SQL."""
258
-
259
- sql = agent.llm_generate_sql(prompt)
260
- if sql:
261
- result = agent.query(sql)
262
- print(f"Duplicates found: {result}")
263
-
264
- # Generate dedup fix
265
- fix_prompt = f"""Write a SQLite DELETE statement to remove duplicate rows from the events table,
266
- keeping only the first occurrence (minimum rowid) for each event_id.
267
- Return only the SQL DELETE statement."""
268
-
269
- fix_sql = agent.llm_generate_sql(fix_prompt)
270
- if fix_sql:
271
- fix_result = agent.fix(fix_sql)
272
- print(f"Fix result: {fix_result}")
273
- submit_result = agent.submit(
274
- fix_query=fix_sql,
275
- root_cause_table="events",
276
- offending_key="event_id"
277
- )
278
- else:
279
- submit_result = agent.submit()
280
- return submit_result
281
-
282
- elif task_id == "fk_integrity":
283
- # Find orphans
284
- prompt = f"""Given schema with tables: invoices and invoice_items
285
- Write a SQL query to count orphan invoice_items (items with invoice_id not in invoices table).
286
- Return only the SQL."""
287
-
288
- sql = agent.llm_generate_sql(prompt)
289
- if sql:
290
- result = agent.query(sql)
291
- print(f"Orphan count: {result}")
292
-
293
- # Generate orphan removal
294
- fix_prompt = f"""Write a SQLite DELETE statement to remove orphan records from invoice_items table
295
- where invoice_id does not exist in the invoices table.
296
- Return only the SQL DELETE statement."""
297
-
298
- fix_sql = agent.llm_generate_sql(fix_prompt)
299
- if fix_sql:
300
- fix_result = agent.fix(fix_sql)
301
- print(f"Fix result: {fix_result}")
302
- submit_result = agent.submit(fix_query=fix_sql)
303
- else:
304
- submit_result = agent.submit()
305
- return submit_result
306
-
307
- elif task_id == "schema_drift":
308
- # Check schema
309
- prompt = f"""Write a SQL query: PRAGMA table_info(raw_events)
310
- Return only the SQL."""
311
-
312
- sql = agent.llm_generate_sql(prompt)
313
- if sql:
314
- result = agent.query(sql)
315
- print(f"Schema: {result}")
316
-
317
- # Generate normalization
318
- fix_prompt = f"""Given the raw_events table with columns: event_id, timestamp, user_id, value, event_type
319
- Write a SQLite CREATE TABLE AS statement to create analytics_events with:
320
- - event_id (preserved)
321
- - timestamp cast to INTEGER
322
- - user_id (preserved)
323
- - value cast to REAL
324
- - event_type (preserved)
325
- Return only the SQL CREATE TABLE AS statement."""
326
-
327
- fix_sql = agent.llm_generate_sql(fix_prompt)
328
- if fix_sql:
329
- fix_result = agent.fix(fix_sql)
330
- print(f"Fix result: {fix_result}")
331
- submit_result = agent.submit(fix_query=fix_sql)
332
- else:
333
- submit_result = agent.submit()
334
- return submit_result
335
-
336
- else:
337
- raise ValueError(f"Unknown task: {task_id}")
338
-
339
- except RuntimeError as e:
340
- print(f"Agent failed: {e}")
341
- # Submit empty answer to end episode
342
- return agent.submit()
343
-
344
-
345
- if __name__ == "__main__":
346
- import argparse
347
-
348
- parser = argparse.ArgumentParser(description="Run SQL Quality Agent")
349
- parser.add_argument("--task", required=True, help="Task ID to run")
350
- parser.add_argument("--url", default="http://localhost:7860", help="Base URL")
351
- parser.add_argument("--seed", type=int, help="Random seed")
352
-
353
- args = parser.parse_args()
354
-
355
- result = run_agent(args.task, args.url)
356
- print(f"\nFinal result: {json.dumps(result, indent=2)}")