genxai-framework 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. cli/commands/__init__.py +3 -1
  2. cli/commands/connector.py +309 -0
  3. cli/commands/workflow.py +80 -0
  4. cli/main.py +3 -1
  5. genxai/__init__.py +33 -0
  6. genxai/agents/__init__.py +8 -0
  7. genxai/agents/presets.py +53 -0
  8. genxai/connectors/__init__.py +10 -0
  9. genxai/connectors/base.py +3 -3
  10. genxai/connectors/config_store.py +106 -0
  11. genxai/connectors/github.py +117 -0
  12. genxai/connectors/google_workspace.py +124 -0
  13. genxai/connectors/jira.py +108 -0
  14. genxai/connectors/notion.py +97 -0
  15. genxai/connectors/slack.py +121 -0
  16. genxai/core/agent/config_io.py +32 -1
  17. genxai/core/agent/runtime.py +41 -4
  18. genxai/core/graph/__init__.py +3 -0
  19. genxai/core/graph/engine.py +218 -11
  20. genxai/core/graph/executor.py +103 -10
  21. genxai/core/graph/nodes.py +28 -0
  22. genxai/core/graph/workflow_io.py +199 -0
  23. genxai/flows/__init__.py +33 -0
  24. genxai/flows/auction.py +66 -0
  25. genxai/flows/base.py +134 -0
  26. genxai/flows/conditional.py +45 -0
  27. genxai/flows/coordinator_worker.py +62 -0
  28. genxai/flows/critic_review.py +62 -0
  29. genxai/flows/ensemble_voting.py +49 -0
  30. genxai/flows/loop.py +42 -0
  31. genxai/flows/map_reduce.py +61 -0
  32. genxai/flows/p2p.py +146 -0
  33. genxai/flows/parallel.py +27 -0
  34. genxai/flows/round_robin.py +24 -0
  35. genxai/flows/router.py +45 -0
  36. genxai/flows/selector.py +63 -0
  37. genxai/flows/subworkflow.py +35 -0
  38. genxai/llm/factory.py +17 -10
  39. genxai/llm/providers/anthropic.py +116 -1
  40. genxai/observability/logging.py +2 -2
  41. genxai/security/auth.py +10 -6
  42. genxai/security/cost_control.py +6 -6
  43. genxai/security/jwt.py +2 -2
  44. genxai/security/pii.py +2 -2
  45. genxai/tools/builtin/__init__.py +3 -0
  46. genxai/tools/builtin/communication/human_input.py +32 -0
  47. genxai/tools/custom/test-2.py +19 -0
  48. genxai/tools/custom/test_tool_ui.py +9 -0
  49. genxai/tools/persistence/service.py +3 -3
  50. genxai/triggers/schedule.py +2 -2
  51. genxai/utils/tokens.py +6 -0
  52. {genxai_framework-0.1.0.dist-info → genxai_framework-0.1.2.dist-info}/METADATA +63 -12
  53. {genxai_framework-0.1.0.dist-info → genxai_framework-0.1.2.dist-info}/RECORD +57 -28
  54. {genxai_framework-0.1.0.dist-info → genxai_framework-0.1.2.dist-info}/WHEEL +0 -0
  55. {genxai_framework-0.1.0.dist-info → genxai_framework-0.1.2.dist-info}/entry_points.txt +0 -0
  56. {genxai_framework-0.1.0.dist-info → genxai_framework-0.1.2.dist-info}/licenses/LICENSE +0 -0
  57. {genxai_framework-0.1.0.dist-info → genxai_framework-0.1.2.dist-info}/top_level.txt +0 -0
@@ -2,12 +2,24 @@
2
2
 
3
3
  import asyncio
4
4
  import copy
5
- from typing import Any, Dict, List, Optional
5
+ from typing import Any, Callable, Dict, List, Optional
6
6
  import logging
7
7
  from pathlib import Path
8
8
 
9
9
  from genxai.core.graph.engine import Graph
10
- from genxai.core.graph.nodes import InputNode, OutputNode, AgentNode, NodeType
10
+ from genxai.core.memory.shared import SharedMemoryBus
11
+ from genxai.core.graph.nodes import (
12
+ InputNode,
13
+ OutputNode,
14
+ AgentNode,
15
+ ConditionNode,
16
+ ToolNode,
17
+ SubgraphNode,
18
+ LoopNode,
19
+ Node,
20
+ NodeConfig,
21
+ NodeType,
22
+ )
11
23
  from genxai.core.graph.edges import Edge, ConditionalEdge
12
24
  from genxai.core.agent.base import Agent, AgentFactory
13
25
  from genxai.core.agent.registry import AgentRegistry
@@ -30,7 +42,9 @@ class EnhancedGraph(Graph):
30
42
  in GenXAI.
31
43
  """
32
44
 
33
- async def _execute_node_logic(self, node: Any, state: Dict[str, Any]) -> Any:
45
+ async def _execute_node_logic(
46
+ self, node: Any, state: Dict[str, Any], max_iterations: int = 100
47
+ ) -> Any:
34
48
  """Execute node logic with actual agent execution.
35
49
 
36
50
  Args:
@@ -74,8 +88,7 @@ class EnhancedGraph(Graph):
74
88
  return result
75
89
 
76
90
  else:
77
- # Default behavior
78
- return {"node_id": node.id, "type": node.type.value}
91
+ return await super()._execute_node_logic(node, state, max_iterations)
79
92
 
80
93
  async def _execute_agent_with_tools(
81
94
  self, agent: Agent, task: str, state: Dict[str, Any]
@@ -95,7 +108,15 @@ class EnhancedGraph(Graph):
95
108
  # Use AgentRuntime for full integration
96
109
  from genxai.core.agent.runtime import AgentRuntime
97
110
 
98
- runtime = AgentRuntime(agent=agent, enable_memory=True)
111
+ # Pass both API keys to runtime so it can select the correct one based on model
112
+ runtime = AgentRuntime(
113
+ agent=agent,
114
+ llm_provider=getattr(self, "llm_provider", None),
115
+ openai_api_key=getattr(self, "openai_api_key", None),
116
+ anthropic_api_key=getattr(self, "anthropic_api_key", None),
117
+ enable_memory=True,
118
+ shared_memory=getattr(self, "shared_memory", None),
119
+ )
99
120
 
100
121
  # Load tools from registry
101
122
  if agent.config.tools:
@@ -108,7 +129,10 @@ class EnhancedGraph(Graph):
108
129
  logger.debug(f"Loaded {len(tools)} tools for agent")
109
130
 
110
131
  # Execute agent with full runtime support
111
- result = await runtime.execute(task, context=state)
132
+ context = dict(state)
133
+ if getattr(self, "shared_memory", None) is not None:
134
+ context["shared_memory"] = getattr(self, "shared_memory")
135
+ result = await runtime.execute(task, context=context)
112
136
 
113
137
  return result
114
138
 
@@ -256,11 +280,14 @@ class WorkflowExecutor:
256
280
  Constructed graph
257
281
  """
258
282
  graph = EnhancedGraph(name="workflow")
283
+ graph.openai_api_key = self.openai_api_key
284
+ graph.anthropic_api_key = self.anthropic_api_key
259
285
 
260
286
  # Add nodes
261
287
  for node in nodes:
262
288
  node_id = node.get("id")
263
289
  node_type = node.get("type")
290
+ config = node.get("config", {})
264
291
 
265
292
  # Support some common aliases used by the Studio UI
266
293
  # - "start" behaves like an input node
@@ -271,6 +298,29 @@ class WorkflowExecutor:
271
298
  graph.add_node(OutputNode(id=node_id))
272
299
  elif node_type == "agent":
273
300
  graph.add_node(AgentNode(id=node_id, agent_id=node_id))
301
+ elif node_type == "tool":
302
+ tool_name = config.get("tool_name") or config.get("name") or "tool"
303
+ graph.add_node(ToolNode(id=node_id, tool_name=tool_name))
304
+ elif node_type == "decision":
305
+ condition = config.get("condition", "")
306
+ graph.add_node(ConditionNode(id=node_id, condition=condition))
307
+ elif node_type == "subgraph":
308
+ workflow_id = config.get("workflow_id") or config.get("subgraph_id") or config.get("workflow")
309
+ if workflow_id:
310
+ graph.add_node(SubgraphNode(id=node_id, workflow_id=workflow_id))
311
+ else:
312
+ graph.add_node(
313
+ Node(
314
+ id=node_id,
315
+ type=NodeType.SUBGRAPH,
316
+ config=NodeConfig(type=NodeType.SUBGRAPH, data={"workflow_id": ""}),
317
+ )
318
+ )
319
+ logger.warning(f"Subgraph node '{node_id}' missing workflow_id")
320
+ elif node_type == "loop":
321
+ condition = config.get("condition", "")
322
+ max_iterations = int(config.get("max_iterations", 5))
323
+ graph.add_node(LoopNode(id=node_id, condition=condition, max_iterations=max_iterations))
274
324
  else:
275
325
  logger.warning(f"Unknown node type: {node_type}")
276
326
 
@@ -321,6 +371,10 @@ class WorkflowExecutor:
321
371
  run_id: Optional[str] = None,
322
372
  checkpoint_dir: Optional[str] = None,
323
373
  resume_from: Optional[str] = None,
374
+ model_override: Optional[str] = None,
375
+ event_callback: Optional[Callable[[Dict[str, Any]], Any]] = None,
376
+ shared_memory: bool = False,
377
+ llm_provider: Optional[Any] = None,
324
378
  ) -> Dict[str, Any]:
325
379
  """Execute a workflow.
326
380
 
@@ -338,11 +392,23 @@ class WorkflowExecutor:
338
392
  try:
339
393
  logger.info("Starting workflow execution")
340
394
 
395
+ # Apply model override if provided
396
+ if model_override:
397
+ for node in nodes:
398
+ if node.get("type") == "agent":
399
+ config = node.setdefault("config", {})
400
+ config["llm_model"] = model_override
401
+
341
402
  # Create agents from nodes
342
403
  self._create_agents_from_nodes(nodes)
343
404
 
344
405
  # Build graph
345
406
  graph = self._build_graph(nodes, edges)
407
+ graph.llm_provider = llm_provider
408
+ if shared_memory:
409
+ graph.set_shared_memory(SharedMemoryBus())
410
+ graph.shared_memory = graph.shared_memory
411
+ graph.shared_memory_enabled = True
346
412
 
347
413
  # Validate graph
348
414
  graph.validate()
@@ -364,7 +430,11 @@ class WorkflowExecutor:
364
430
  status="allowed",
365
431
  )
366
432
  )
367
- result = await graph.run(input_data=input_data, resume_from=checkpoint)
433
+ result = await graph.run(
434
+ input_data=input_data,
435
+ resume_from=checkpoint,
436
+ event_callback=event_callback,
437
+ )
368
438
 
369
439
  logger.info("Workflow execution completed successfully")
370
440
 
@@ -378,6 +448,7 @@ class WorkflowExecutor:
378
448
  "status": "success",
379
449
  "run_id": run_id,
380
450
  "result": result,
451
+ "node_events": result.get("node_events", []),
381
452
  "nodes_executed": len(graph.nodes),
382
453
  "message": "Workflow executed successfully"
383
454
  }
@@ -410,6 +481,7 @@ class WorkflowExecutor:
410
481
  run_id: Optional[str] = None,
411
482
  checkpoint_dir: Optional[str] = None,
412
483
  resume_from: Optional[str] = None,
484
+ model_override: Optional[str] = None,
413
485
  ) -> str:
414
486
  """Enqueue workflow execution using a worker queue engine."""
415
487
  if not self.queue_engine:
@@ -430,6 +502,7 @@ class WorkflowExecutor:
430
502
  run_id=payload["run_id"],
431
503
  checkpoint_dir=payload.get("checkpoint_dir"),
432
504
  resume_from=payload.get("resume_from"),
505
+ model_override=payload.get("model_override"),
433
506
  )
434
507
 
435
508
  await self.queue_engine.start()
@@ -441,6 +514,7 @@ class WorkflowExecutor:
441
514
  "run_id": run_id,
442
515
  "checkpoint_dir": checkpoint_dir,
443
516
  "resume_from": resume_from,
517
+ "model_override": model_override,
444
518
  },
445
519
  _handler,
446
520
  metadata={"workflow": "queued"},
@@ -454,6 +528,8 @@ def execute_workflow_sync(
454
528
  input_data: Dict[str, Any],
455
529
  openai_api_key: Optional[str] = None,
456
530
  anthropic_api_key: Optional[str] = None,
531
+ model_override: Optional[str] = None,
532
+ shared_memory: bool = False,
457
533
  ) -> Dict[str, Any]:
458
534
  """Synchronous wrapper for workflow execution.
459
535
 
@@ -480,7 +556,13 @@ def execute_workflow_sync(
480
556
  asyncio.set_event_loop(loop)
481
557
  try:
482
558
  result = loop.run_until_complete(
483
- executor.execute(nodes, edges, input_data)
559
+ executor.execute(
560
+ nodes,
561
+ edges,
562
+ input_data,
563
+ model_override=model_override,
564
+ shared_memory=shared_memory,
565
+ )
484
566
  )
485
567
  return result
486
568
  finally:
@@ -493,6 +575,9 @@ async def execute_workflow_async(
493
575
  input_data: Dict[str, Any],
494
576
  openai_api_key: Optional[str] = None,
495
577
  anthropic_api_key: Optional[str] = None,
578
+ model_override: Optional[str] = None,
579
+ event_callback: Optional[Callable[[Dict[str, Any]], Any]] = None,
580
+ shared_memory: bool = False,
496
581
  ) -> Dict[str, Any]:
497
582
  """Async convenience function for workflow execution.
498
583
 
@@ -513,4 +598,12 @@ async def execute_workflow_async(
513
598
  openai_api_key=openai_api_key,
514
599
  anthropic_api_key=anthropic_api_key,
515
600
  )
516
- return await executor.execute(nodes, edges, input_data)
601
+ return await executor.execute(
602
+ nodes,
603
+ edges,
604
+ input_data,
605
+ model_override=model_override,
606
+ event_callback=event_callback,
607
+ shared_memory=shared_memory,
608
+ )
609
+
@@ -15,6 +15,7 @@ class NodeType(str, Enum):
15
15
  HUMAN = "human"
16
16
  INPUT = "input"
17
17
  OUTPUT = "output"
18
+ LOOP = "loop"
18
19
 
19
20
 
20
21
  class NodeConfig(BaseModel):
@@ -159,3 +160,30 @@ class OutputNode(Node):
159
160
  super().__init__(
160
161
  id=id, type=NodeType.OUTPUT, config=NodeConfig(type=NodeType.OUTPUT), **kwargs
161
162
  )
163
+
164
+
165
+ class SubgraphNode(Node):
166
+ """Node that executes a nested workflow."""
167
+
168
+ def __init__(self, id: str, workflow_id: str, **kwargs: Any) -> None:
169
+ super().__init__(
170
+ id=id,
171
+ type=NodeType.SUBGRAPH,
172
+ config=NodeConfig(type=NodeType.SUBGRAPH, data={"workflow_id": workflow_id}),
173
+ **kwargs,
174
+ )
175
+
176
+
177
+ class LoopNode(Node):
178
+ """Node that represents a loop with a termination condition."""
179
+
180
+ def __init__(self, id: str, condition: str, max_iterations: int = 5, **kwargs: Any) -> None:
181
+ super().__init__(
182
+ id=id,
183
+ type=NodeType.LOOP,
184
+ config=NodeConfig(
185
+ type=NodeType.LOOP,
186
+ data={"condition": condition, "max_iterations": max_iterations},
187
+ ),
188
+ **kwargs,
189
+ )
@@ -0,0 +1,199 @@
1
+ """Workflow YAML loading utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List
7
+
8
+ import yaml
9
+
10
+ from genxai.core.agent.base import Agent
11
+ from genxai.core.agent.registry import AgentRegistry
12
+ from genxai.core.agent.config_io import import_agents_yaml
13
+
14
+
15
+ def load_workflow_yaml(path: Path) -> Dict[str, Any]:
16
+ """Load a workflow YAML file.
17
+
18
+ Supports `agents_ref` to pull reusable agent definitions from another YAML file.
19
+ Inline agents remain supported and will be merged with referenced agents.
20
+ """
21
+ payload = yaml.safe_load(path.read_text())
22
+ if not isinstance(payload, dict) or "workflow" not in payload:
23
+ raise ValueError("Workflow YAML must contain a top-level 'workflow' mapping")
24
+
25
+ workflow = payload["workflow"]
26
+ if not isinstance(workflow, dict):
27
+ raise ValueError("workflow must be a mapping")
28
+
29
+ _merge_agents_ref(workflow, base_path=path.parent)
30
+ _validate_workflow_schema(workflow)
31
+ return workflow
32
+
33
+
34
+ def register_workflow_agents(workflow: Dict[str, Any]) -> List[Agent]:
35
+ """Register agents defined in a workflow dict and return them.
36
+
37
+ Accepts agent dictionaries with the `agents_ref` already resolved.
38
+ """
39
+ agents_payload = workflow.get("agents", [])
40
+ if not agents_payload:
41
+ return []
42
+
43
+ workflow_memory = workflow.get("memory") if isinstance(workflow.get("memory"), dict) else {}
44
+
45
+ agents: List[Agent] = []
46
+ for agent_data in agents_payload:
47
+ if not isinstance(agent_data, dict):
48
+ raise ValueError("Invalid agent definition in workflow")
49
+ merged_agent = _apply_workflow_memory_defaults(agent_data, workflow_memory)
50
+ agent = _agent_from_workflow_dict(merged_agent)
51
+ AgentRegistry.register(agent)
52
+ agents.append(agent)
53
+ return agents
54
+
55
+
56
+ def _apply_workflow_memory_defaults(
57
+ agent_data: Dict[str, Any], workflow_memory: Dict[str, Any]
58
+ ) -> Dict[str, Any]:
59
+ if not workflow_memory:
60
+ return agent_data
61
+
62
+ defaults: Dict[str, Any] = {}
63
+ if "enabled" in workflow_memory:
64
+ defaults["enabled"] = workflow_memory.get("enabled")
65
+ if "type" in workflow_memory:
66
+ defaults["type"] = workflow_memory.get("type")
67
+
68
+ if not defaults:
69
+ return agent_data
70
+
71
+ if isinstance(agent_data.get("memory"), dict):
72
+ memory_block = dict(agent_data.get("memory") or {})
73
+ memory_block.setdefault("enabled", defaults.get("enabled"))
74
+ memory_block.setdefault("type", defaults.get("type"))
75
+ agent_data = {**agent_data, "memory": memory_block}
76
+ return agent_data
77
+
78
+ if "enable_memory" in agent_data or "memory_type" in agent_data:
79
+ return agent_data
80
+
81
+ return {**agent_data, "memory": {k: v for k, v in defaults.items() if v is not None}}
82
+
83
+
84
+ def _merge_agents_ref(workflow: Dict[str, Any], base_path: Path) -> None:
85
+ agents_ref = workflow.get("agents_ref")
86
+ if not agents_ref:
87
+ return
88
+
89
+ agents_ref_path = (base_path / agents_ref).resolve()
90
+ referenced = import_agents_yaml(agents_ref_path)
91
+ referenced_dicts = [
92
+ {"id": agent.id, **agent.config.model_dump(mode="json")}
93
+ if isinstance(agent, Agent)
94
+ else dict(agent)
95
+ for agent in referenced
96
+ ]
97
+
98
+ inline_agents = workflow.get("agents", [])
99
+ if inline_agents is None:
100
+ inline_agents = []
101
+ if not isinstance(inline_agents, list):
102
+ raise ValueError("workflow.agents must be a list")
103
+
104
+ # Merge with inline agents taking precedence by id.
105
+ merged = {agent["id"]: agent for agent in referenced_dicts if isinstance(agent, dict)}
106
+ for agent in inline_agents:
107
+ if not isinstance(agent, dict) or "id" not in agent:
108
+ raise ValueError("Invalid inline agent definition")
109
+ merged[agent["id"]] = agent
110
+
111
+ workflow["agents"] = list(merged.values())
112
+
113
+
114
+ def _validate_workflow_schema(workflow: Dict[str, Any]) -> None:
115
+ if not workflow.get("name"):
116
+ raise ValueError("workflow.name is required")
117
+
118
+ if "memory" in workflow and not isinstance(workflow.get("memory"), dict):
119
+ raise ValueError("workflow.memory must be a mapping when provided")
120
+
121
+ graph = workflow.get("graph")
122
+ if not isinstance(graph, dict):
123
+ raise ValueError("workflow.graph must be a mapping")
124
+
125
+ nodes = graph.get("nodes")
126
+ if not isinstance(nodes, list) or not nodes:
127
+ raise ValueError("workflow.graph.nodes must be a non-empty list")
128
+
129
+ edges = graph.get("edges")
130
+ if not isinstance(edges, list):
131
+ raise ValueError("workflow.graph.edges must be a list")
132
+
133
+ node_ids = set()
134
+ for node in nodes:
135
+ if not isinstance(node, dict):
136
+ raise ValueError("workflow.graph.nodes entries must be mappings")
137
+ if "id" not in node or "type" not in node:
138
+ raise ValueError("Each node requires 'id' and 'type'")
139
+ node_ids.add(node["id"])
140
+ if node["type"] not in {"input", "start", "output", "end", "agent", "tool", "condition"}:
141
+ raise ValueError(f"Unsupported node type: {node['type']}")
142
+
143
+ for edge in edges:
144
+ if not isinstance(edge, dict):
145
+ raise ValueError("workflow.graph.edges entries must be mappings")
146
+ if "from" not in edge or "to" not in edge:
147
+ raise ValueError("Each edge requires 'from' and 'to'")
148
+ if edge["from"] not in node_ids or edge["to"] not in node_ids:
149
+ raise ValueError("Edges must reference existing node ids")
150
+
151
+ agent_ids = {agent.get("id") for agent in workflow.get("agents", []) if isinstance(agent, dict)}
152
+ for node in nodes:
153
+ if node.get("type") == "agent" and node.get("id") not in agent_ids:
154
+ raise ValueError(f"Agent node '{node['id']}' has no matching agent definition")
155
+
156
+
157
+ def _agent_from_workflow_dict(data: Dict[str, Any]) -> Agent:
158
+ config_payload = data.get("config") if isinstance(data.get("config"), dict) else {}
159
+ merged = {
160
+ **config_payload,
161
+ **{k: v for k, v in data.items() if k not in {"config"}},
162
+ }
163
+ return Agent(
164
+ id=data["id"],
165
+ config=_agent_config_from_workflow_dict(merged),
166
+ )
167
+
168
+
169
+ def _agent_config_from_workflow_dict(data: Dict[str, Any]):
170
+ from genxai.core.agent.base import AgentConfig
171
+
172
+ # Allow workflow agents to specify either `llm` (legacy) or `llm_model`.
173
+ llm_model = data.get("llm_model") or data.get("llm") or "gpt-4"
174
+
175
+ return AgentConfig(
176
+ role=data.get("role", "Agent"),
177
+ goal=data.get("goal", "Process tasks"),
178
+ backstory=data.get("backstory", ""),
179
+ llm_provider=data.get("llm_provider", "openai"),
180
+ llm_model=llm_model,
181
+ llm_temperature=data.get("llm_temperature", 0.7),
182
+ tools=data.get("tools", []),
183
+ enable_memory=data.get("memory", {}).get("enabled", True)
184
+ if isinstance(data.get("memory"), dict)
185
+ else data.get("enable_memory", True),
186
+ memory_type=data.get("memory", {}).get("type", "short_term")
187
+ if isinstance(data.get("memory"), dict)
188
+ else data.get("memory_type", "short_term"),
189
+ agent_type=data.get("behavior", {}).get("agent_type", "reactive")
190
+ if isinstance(data.get("behavior"), dict)
191
+ else data.get("agent_type", "reactive"),
192
+ max_iterations=data.get("behavior", {}).get("max_iterations", 10)
193
+ if isinstance(data.get("behavior"), dict)
194
+ else data.get("max_iterations", 10),
195
+ verbose=data.get("behavior", {}).get("verbose", False)
196
+ if isinstance(data.get("behavior"), dict)
197
+ else data.get("verbose", False),
198
+ metadata=data.get("metadata", {}),
199
+ )
@@ -0,0 +1,33 @@
1
+ """Flow orchestrators for common agent coordination patterns."""
2
+
3
+ from genxai.flows.base import FlowOrchestrator
4
+ from genxai.flows.round_robin import RoundRobinFlow
5
+ from genxai.flows.selector import SelectorFlow
6
+ from genxai.flows.p2p import P2PFlow
7
+ from genxai.flows.parallel import ParallelFlow
8
+ from genxai.flows.conditional import ConditionalFlow
9
+ from genxai.flows.loop import LoopFlow
10
+ from genxai.flows.router import RouterFlow
11
+ from genxai.flows.ensemble_voting import EnsembleVotingFlow
12
+ from genxai.flows.critic_review import CriticReviewFlow
13
+ from genxai.flows.coordinator_worker import CoordinatorWorkerFlow
14
+ from genxai.flows.map_reduce import MapReduceFlow
15
+ from genxai.flows.subworkflow import SubworkflowFlow
16
+ from genxai.flows.auction import AuctionFlow
17
+
18
+ __all__ = [
19
+ "FlowOrchestrator",
20
+ "RoundRobinFlow",
21
+ "SelectorFlow",
22
+ "P2PFlow",
23
+ "ParallelFlow",
24
+ "ConditionalFlow",
25
+ "LoopFlow",
26
+ "RouterFlow",
27
+ "EnsembleVotingFlow",
28
+ "CriticReviewFlow",
29
+ "CoordinatorWorkerFlow",
30
+ "MapReduceFlow",
31
+ "SubworkflowFlow",
32
+ "AuctionFlow",
33
+ ]
@@ -0,0 +1,66 @@
1
+ """Auction flow orchestrator."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from genxai.core.agent.runtime import AgentRuntime
6
+ from genxai.flows.base import FlowOrchestrator
7
+
8
+
9
+ class AuctionFlow(FlowOrchestrator):
10
+ """Agents bid to handle a task; highest bid executes."""
11
+
12
+ def __init__(
13
+ self,
14
+ agents: List[Any],
15
+ name: str = "auction_flow",
16
+ llm_provider: Any = None,
17
+ ) -> None:
18
+ super().__init__(agents=agents, name=name, llm_provider=llm_provider)
19
+
20
+ async def run(
21
+ self,
22
+ input_data: Any,
23
+ state: Optional[Dict[str, Any]] = None,
24
+ max_iterations: int = 100,
25
+ ) -> Dict[str, Any]:
26
+ if state is None:
27
+ state = {}
28
+ state["input"] = input_data
29
+ state.setdefault("bids", {})
30
+
31
+ runtimes = {
32
+ agent.id: AgentRuntime(agent=agent, llm_provider=self.llm_provider)
33
+ for agent in self.agents
34
+ }
35
+
36
+ bid_task = state.get("bid_task", "Provide a numeric bid between 0 and 1")
37
+ tasks = [
38
+ self._execute_with_retry(
39
+ runtimes[agent.id],
40
+ task=bid_task,
41
+ context=state,
42
+ )
43
+ for agent in self.agents
44
+ ]
45
+ results = await self._gather_tasks(tasks)
46
+ for agent, bid_result in zip(self.agents, results):
47
+ bid_value = 0.0
48
+ try:
49
+ bid_value = float(bid_result.get("output", 0))
50
+ except (TypeError, ValueError, AttributeError):
51
+ bid_value = 0.0
52
+ state["bids"][agent.id] = bid_value
53
+
54
+ if not state["bids"]:
55
+ raise ValueError("AuctionFlow requires at least one bid")
56
+
57
+ winner_id = max(state["bids"], key=state["bids"].get)
58
+ winner_runtime = runtimes[winner_id]
59
+ execution = await self._execute_with_retry(
60
+ winner_runtime,
61
+ task=state.get("task", "Execute the task"),
62
+ context={**state, "winner_id": winner_id},
63
+ )
64
+ state["winner_id"] = winner_id
65
+ state["winner_result"] = execution
66
+ return state