wu981526092 commited on
Commit
91c2a08
·
1 Parent(s): f0fc928
agentgraph/methods/production/openai_structured_extractor.py CHANGED
@@ -28,8 +28,8 @@ class KnowledgeGraphReasoning(BaseModel):
28
  steps: List[Step]
29
  final_answer: KnowledgeGraph
30
 
31
- # Load environment variables from root directory
32
- load_dotenv('/Users/zekunwu/Desktop/agent_monitoring/.env')
33
 
34
  # Configure logging
35
  logger = logging.getLogger(__name__)
@@ -40,18 +40,14 @@ class OpenAIStructuredExtractor:
40
  Simple knowledge graph extractor using OpenAI's structured outputs.
41
  """
42
 
43
- def __init__(self, model: str = "gpt-5-mini", *, compatibility_mode: bool = False, allow_required_by: bool = False):
44
  """
45
  Initialize the extractor.
46
 
47
  Args:
48
  model: OpenAI model to use (must support structured outputs)
49
- compatibility_mode: Enable light post-processing to better align with sample/reference KGs
50
- allow_required_by: When in compatibility mode, also add REQUIRED_BY Tool→Task relation when appropriate
51
  """
52
  self.model = model
53
- self.compatibility_mode = compatibility_mode
54
- self.allow_required_by = allow_required_by
55
  self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
56
  logger.info(f"OpenAI Structured Extractor initialized with model: {model}")
57
 
@@ -83,8 +79,10 @@ ENTITY TYPES & PRIORITIES:
83
  RELATION PRIORITIES (ULTRA-SIMPLIFIED):
84
  - PERFORMS (Agent→Task): ONLY agent-task relation needed
85
  - Input→Agent→Task→Output→Human: Essential workflow chain
86
- - NO COMPLEX RELATIONS: Avoid ASSIGNED_TO, INTERVENES, REQUIRED_BY
87
- - TARGET: 6-8 total relations maximum (keep it simple!)
 
 
88
 
89
  WORKFLOW PATTERNS:
90
  - Simple (1-2 agents): Single consolidated task, basic relations
@@ -101,6 +99,13 @@ OUTPUT REQUIREMENTS:
101
  - Empty raw_prompt/interaction_prompt fields
102
  - Include 1-2 failures and optimizations
103
 
 
 
 
 
 
 
 
104
  == FEW-SHOT EXAMPLE (COMPLEX DISCOVERY WORKFLOW) ==
105
 
106
  Input Query: "What is the closest eatery to Harkness Memorial State Park open at 11pm on Wednesdays?"
@@ -195,7 +200,7 @@ ANALYSIS STEPS:
195
  * Task sharing = IMMEDIATE FAILURE - completely unacceptable
196
  * ALWAYS decompose into independent subtasks for each agent
197
 
198
- 4. RELATION MAPPING (FORCED 1:1 MAPPING):
199
  - PERFORMS: Each agent performs EXACTLY ONE UNIQUE task
200
  * 3 agents = 3 different tasks = 3 PERFORMS relations
201
  * agent_001 → task_001, agent_002 → task_002, agent_003 → task_003
@@ -207,10 +212,11 @@ ANALYSIS STEPS:
207
  * Task→Task (NEXT): For sequential workflows only
208
  * Last Task→Output (PRODUCES): 1 relation only
209
  * Output→Human (DELIVERS_TO): 1 relation only
210
- * Agent→Tool (USES): Only if tools exist
 
211
 
212
- - FORBIDDEN: ASSIGNED_TO, INTERVENES, REQUIRED_BY, complex multi-connections
213
- - TARGET: Maximum 9 total relations for 3-agent workflows
214
 
215
  5. QUALITY CHECK (Contextual Graph Enhanced):
216
  - Verify all relation IDs reference existing entities
@@ -219,6 +225,11 @@ ANALYSIS STEPS:
219
  - CONTEXT VALIDATION: Ensure relationships align with trace context and domain semantics
220
  - EDGE CASE HANDLING: Apply simplification for simple workflows, complexity for multi-domain scenarios
221
 
 
 
 
 
 
222
  FORMATTING:
223
  - IDs: agent_001, task_001, tool_001, etc.
224
  - Empty raw_prompt and interaction_prompt fields
@@ -243,17 +254,6 @@ TRACE DATA:
243
  logger.info(f"Response parsed successfully. Type: {type(reasoning_result)}")
244
 
245
  knowledge_graph = reasoning_result.final_answer
246
-
247
- # Optional post-alignment for reference/sample compatibility
248
- if self.compatibility_mode:
249
- try:
250
- knowledge_graph = self._align_for_reference_compatibility(
251
- knowledge_graph, input_data
252
- )
253
- # Ensure saved result contains aligned KG
254
- reasoning_result.final_answer = knowledge_graph
255
- except Exception as align_err:
256
- logger.warning(f"Compatibility alignment skipped due to error: {align_err}")
257
  logger.info(f"Knowledge graph extracted. Type: {type(knowledge_graph)}")
258
 
259
  # Save reasoning steps and knowledge graph for analysis
@@ -273,87 +273,7 @@ TRACE DATA:
273
  logger.error(f"Extraction failed: {e}")
274
  raise
275
 
276
- def _align_for_reference_compatibility(self, kg: KnowledgeGraph, trace_text: str) -> KnowledgeGraph:
277
- """Apply light, deterministic adjustments to better match sample/reference KGs.
278
-
279
- Adjustments:
280
- - Ensure exactly one Agent→Tool USES relation if a Tool exists (choose the agent performing task_001 or the first Agent)
281
- - Optionally add Tool→Task REQUIRED_BY for the first Task when allow_required_by is enabled
282
- - Normalize common display names (e.g., "Computer_terminal" → "Computer Terminal")
283
- - For discovery workflows, normalize Output name to "Restaurant Recommendations" when semantically identical
284
- """
285
- # Build indices
286
- entities_by_id = {e.id: e for e in kg.entities}
287
- agents = [e for e in kg.entities if e.type == "Agent"]
288
- tools = [e for e in kg.entities if e.type == "Tool"]
289
- tasks = [e for e in kg.entities if e.type == "Task"]
290
- outputs = [e for e in kg.entities if e.type == "Output"]
291
-
292
- # Normalize tool naming
293
- for tool in tools:
294
- if tool.name == "Computer_terminal":
295
- tool.name = "Computer Terminal"
296
-
297
- # Detect if a USES relation already exists
298
- def has_uses():
299
- return any(r.type == "USES" for r in kg.relations)
300
-
301
- # Pick primary agent (PERFORMS task_001 or first Agent)
302
- def pick_primary_agent_id() -> str:
303
- # Find task_001
304
- task1 = next((t for t in tasks if t.id == "task_001"), None)
305
- if task1 is not None:
306
- for r in kg.relations:
307
- if r.type == "PERFORMS" and r.target == task1.id:
308
- return r.source
309
- return agents[0].id if agents else ""
310
-
311
- # Add USES if missing and a Tool exists
312
- if tools and agents and not has_uses():
313
- try:
314
- primary_agent_id = pick_primary_agent_id()
315
- tool_id = tools[0].id
316
- from agentgraph.shared.models.reference_based import Relation, ContentReference
317
- uses_rel = Relation(
318
- source=primary_agent_id,
319
- target=tool_id,
320
- type="USES",
321
- importance="MEDIUM",
322
- interaction_prompt="",
323
- interaction_prompt_ref=[ContentReference(line_start=None, line_end=None)]
324
- )
325
- kg.relations.append(uses_rel)
326
- except Exception as e:
327
- logger.debug(f"Failed to add USES relation: {e}")
328
-
329
- # Optionally add REQUIRED_BY Tool→first Task
330
- if self.allow_required_by and tools and tasks:
331
- try:
332
- has_required_by = any(r.type == "REQUIRED_BY" for r in kg.relations)
333
- if not has_required_by:
334
- from agentgraph.shared.models.reference_based import Relation, ContentReference
335
- req_rel = Relation(
336
- source=tools[0].id,
337
- target=tasks[0].id,
338
- type="REQUIRED_BY",
339
- importance="MEDIUM",
340
- interaction_prompt="",
341
- interaction_prompt_ref=[ContentReference(line_start=None, line_end=None)]
342
- )
343
- kg.relations.append(req_rel)
344
- except Exception as e:
345
- logger.debug(f"Failed to add REQUIRED_BY relation: {e}")
346
-
347
- # Normalize Output naming for discovery workflows
348
- system_name = (kg.system_name or "").lower()
349
- is_discovery = "restaurant" in system_name or "location" in system_name
350
- if is_discovery and outputs:
351
- out = outputs[0]
352
- # If name already contains Restaurant Recommendations, standardize to concise form
353
- if "restaurant recommendations" in (out.name or "").lower() and out.name.strip() != "Restaurant Recommendations":
354
- out.name = "Restaurant Recommendations"
355
-
356
- return kg
357
 
358
  def _save_extraction_results(self, reasoning_result, trace_text: str):
359
  """Save reasoning steps and knowledge graph for analysis"""
 
28
  steps: List[Step]
29
  final_answer: KnowledgeGraph
30
 
31
+ # Load environment variables (searches current directory and parents)
32
+ load_dotenv()
33
 
34
  # Configure logging
35
  logger = logging.getLogger(__name__)
 
40
  Simple knowledge graph extractor using OpenAI's structured outputs.
41
  """
42
 
43
+ def __init__(self, model: str = "gpt-5-mini"):
44
  """
45
  Initialize the extractor.
46
 
47
  Args:
48
  model: OpenAI model to use (must support structured outputs)
 
 
49
  """
50
  self.model = model
 
 
51
  self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
52
  logger.info(f"OpenAI Structured Extractor initialized with model: {model}")
53
 
 
79
  RELATION PRIORITIES (ULTRA-SIMPLIFIED):
80
  - PERFORMS (Agent→Task): ONLY agent-task relation needed
81
  - Input→Agent→Task→Output→Human: Essential workflow chain
82
+ - USES (Agent→Tool): Include exactly one if a Tool exists and is used
83
+ - REQUIRED_BY (Tool→Task): Conditionally allowed when the task explicitly depends on a Tool (e.g., execution on Computer Terminal). Use at most one.
84
+ - FORBIDDEN: ASSIGNED_TO, INTERVENES
85
+ - TARGET: 6-10 total relations maximum (simple but complete)
86
 
87
  WORKFLOW PATTERNS:
88
  - Simple (1-2 agents): Single consolidated task, basic relations
 
99
  - Empty raw_prompt/interaction_prompt fields
100
  - Include 1-2 failures and optimizations
101
 
102
+ NAMING STYLE & CANONICALIZATION:
103
+ - Title Case with spaces (avoid underscores in names)
104
+ - Prefer canonical names where applicable: "Computer Terminal" (not Computer_terminal)
105
+ - Domain templates (use when content clearly matches):
106
+ * Location-based discovery: Tasks → Geographic Proximity Analysis → Restaurant Data Collection → Operating Hours Validation; Output → Restaurant Recommendations; System → Location-Based Restaurant Discovery System
107
+ * Wildlife data analysis: Tasks → Government Dataset Processing → Population Statistics Calculation → Ecological Data Validation; Human → Wildlife Researcher; System → Wildlife Data Analysis and Ecological Monitoring System
108
+
109
  == FEW-SHOT EXAMPLE (COMPLEX DISCOVERY WORKFLOW) ==
110
 
111
  Input Query: "What is the closest eatery to Harkness Memorial State Park open at 11pm on Wednesdays?"
 
200
  * Task sharing = IMMEDIATE FAILURE - completely unacceptable
201
  * ALWAYS decompose into independent subtasks for each agent
202
 
203
+ 4. RELATION MAPPING (STRICT BUT COMPLETE):
204
  - PERFORMS: Each agent performs EXACTLY ONE UNIQUE task
205
  * 3 agents = 3 different tasks = 3 PERFORMS relations
206
  * agent_001 → task_001, agent_002 → task_002, agent_003 → task_003
 
212
  * Task→Task (NEXT): For sequential workflows only
213
  * Last Task→Output (PRODUCES): 1 relation only
214
  * Output→Human (DELIVERS_TO): 1 relation only
215
+ * Agent→Tool (USES): Include exactly one when a Tool exists
216
+ * Tool→Task (REQUIRED_BY): Include one only when the task explicitly depends on a Tool (execution/platform dependency). Otherwise omit.
217
 
218
+ - FORBIDDEN: ASSIGNED_TO, INTERVENES
219
+ - TARGET: Maximum 9–10 total relations for 3-agent workflows
220
 
221
  5. QUALITY CHECK (Contextual Graph Enhanced):
222
  - Verify all relation IDs reference existing entities
 
225
  - CONTEXT VALIDATION: Ensure relationships align with trace context and domain semantics
226
  - EDGE CASE HANDLING: Apply simplification for simple workflows, complexity for multi-domain scenarios
227
 
228
+ NAMING & CANONICALIZATION:
229
+ - Title Case + spaces only (no underscores in names)
230
+ - Prefer canonical domain templates (see system rules) when content/tags match
231
+ - Prefer concise outputs like "Restaurant Recommendations" for discovery workflows
232
+
233
  FORMATTING:
234
  - IDs: agent_001, task_001, tool_001, etc.
235
  - Empty raw_prompt and interaction_prompt fields
 
254
  logger.info(f"Response parsed successfully. Type: {type(reasoning_result)}")
255
 
256
  knowledge_graph = reasoning_result.final_answer
 
 
 
 
 
 
 
 
 
 
 
257
  logger.info(f"Knowledge graph extracted. Type: {type(knowledge_graph)}")
258
 
259
  # Save reasoning steps and knowledge graph for analysis
 
273
  logger.error(f"Extraction failed: {e}")
274
  raise
275
 
276
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  def _save_extraction_results(self, reasoning_result, trace_text: str):
279
  """Save reasoning steps and knowledge graph for analysis"""