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
@@ -0,0 +1,117 @@
1
+ """GitHub connector implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+ import asyncio
7
+ import logging
8
+
9
+ import httpx
10
+
11
+ from genxai.connectors.base import Connector
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class GitHubConnector(Connector):
17
+ """GitHub connector using REST API v3.
18
+
19
+ Notes:
20
+ - Provide a personal access token with required scopes.
21
+ - Incoming webhook events can be forwarded to `handle_event`.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ connector_id: str,
27
+ token: str,
28
+ name: Optional[str] = None,
29
+ base_url: str = "https://api.github.com",
30
+ timeout: float = 10.0,
31
+ ) -> None:
32
+ super().__init__(connector_id=connector_id, name=name)
33
+ self.token = token
34
+ self.base_url = base_url.rstrip("/")
35
+ self.timeout = timeout
36
+ self._client: Optional[httpx.AsyncClient] = None
37
+ self._lock = asyncio.Lock()
38
+
39
+ async def _start(self) -> None:
40
+ if not self._client:
41
+ self._client = httpx.AsyncClient(
42
+ base_url=self.base_url,
43
+ headers={
44
+ "Authorization": f"Bearer {self.token}",
45
+ "Accept": "application/vnd.github+json",
46
+ "X-GitHub-Api-Version": "2022-11-28",
47
+ },
48
+ timeout=self.timeout,
49
+ )
50
+
51
+ async def _stop(self) -> None:
52
+ if self._client:
53
+ await self._client.aclose()
54
+ self._client = None
55
+
56
+ async def validate_config(self) -> None:
57
+ if not self.token:
58
+ raise ValueError("GitHub token must be provided")
59
+
60
+ async def get_repo(self, owner: str, repo: str) -> Dict[str, Any]:
61
+ """Fetch repository metadata."""
62
+ return await self._get(f"/repos/{owner}/{repo}")
63
+
64
+ async def list_issues(
65
+ self,
66
+ owner: str,
67
+ repo: str,
68
+ state: str = "open",
69
+ per_page: int = 30,
70
+ ) -> Any:
71
+ """List issues for a repository."""
72
+ return await self._get(
73
+ f"/repos/{owner}/{repo}/issues",
74
+ params={"state": state, "per_page": per_page},
75
+ )
76
+
77
+ async def create_issue(
78
+ self,
79
+ owner: str,
80
+ repo: str,
81
+ title: str,
82
+ body: Optional[str] = None,
83
+ ) -> Dict[str, Any]:
84
+ """Create a new GitHub issue."""
85
+ payload: Dict[str, Any] = {"title": title}
86
+ if body:
87
+ payload["body"] = body
88
+ return await self._post(f"/repos/{owner}/{repo}/issues", payload)
89
+
90
+ async def handle_event(
91
+ self,
92
+ payload: Dict[str, Any],
93
+ headers: Optional[Dict[str, str]] = None,
94
+ ) -> None:
95
+ """Handle an inbound GitHub webhook event and emit it downstream."""
96
+ await self.emit(payload=payload, metadata={"headers": headers or {}})
97
+
98
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
99
+ await self._ensure_client()
100
+ assert self._client is not None
101
+ response = await self._client.get(path, params=params or {})
102
+ response.raise_for_status()
103
+ return response.json()
104
+
105
+ async def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
106
+ await self._ensure_client()
107
+ assert self._client is not None
108
+ response = await self._client.post(path, json=payload)
109
+ response.raise_for_status()
110
+ return response.json()
111
+
112
+ async def _ensure_client(self) -> None:
113
+ if self._client is not None:
114
+ return
115
+ async with self._lock:
116
+ if self._client is None:
117
+ await self._start()
@@ -0,0 +1,124 @@
1
+ """Google Workspace connector implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+ import asyncio
7
+ import logging
8
+
9
+ import httpx
10
+
11
+ from genxai.connectors.base import Connector
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class GoogleWorkspaceConnector(Connector):
17
+ """Google Workspace connector using Google REST APIs.
18
+
19
+ Notes:
20
+ - Provide an OAuth access token (Bearer) with required scopes.
21
+ - Supports basic operations for Sheets, Drive, and Calendar.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ connector_id: str,
27
+ access_token: str,
28
+ name: Optional[str] = None,
29
+ base_url: str = "https://www.googleapis.com",
30
+ timeout: float = 10.0,
31
+ ) -> None:
32
+ super().__init__(connector_id=connector_id, name=name)
33
+ self.access_token = access_token
34
+ self.base_url = base_url.rstrip("/")
35
+ self.timeout = timeout
36
+ self._client: Optional[httpx.AsyncClient] = None
37
+ self._lock = asyncio.Lock()
38
+
39
+ async def _start(self) -> None:
40
+ if not self._client:
41
+ self._client = httpx.AsyncClient(
42
+ base_url=self.base_url,
43
+ headers={
44
+ "Authorization": f"Bearer {self.access_token}",
45
+ "Accept": "application/json",
46
+ "Content-Type": "application/json",
47
+ },
48
+ timeout=self.timeout,
49
+ )
50
+
51
+ async def _stop(self) -> None:
52
+ if self._client:
53
+ await self._client.aclose()
54
+ self._client = None
55
+
56
+ async def validate_config(self) -> None:
57
+ if not self.access_token:
58
+ raise ValueError("Google Workspace access_token must be provided")
59
+
60
+ async def get_sheet(self, spreadsheet_id: str) -> Dict[str, Any]:
61
+ """Fetch spreadsheet metadata."""
62
+ return await self._get(f"/sheets/v4/spreadsheets/{spreadsheet_id}")
63
+
64
+ async def append_sheet_values(
65
+ self,
66
+ spreadsheet_id: str,
67
+ range_: str,
68
+ values: list[list[Any]],
69
+ value_input_option: str = "USER_ENTERED",
70
+ ) -> Dict[str, Any]:
71
+ """Append values to a Google Sheet."""
72
+ params = {"valueInputOption": value_input_option}
73
+ payload = {"values": values}
74
+ return await self._post(
75
+ f"/sheets/v4/spreadsheets/{spreadsheet_id}/values/{range_}:append",
76
+ payload,
77
+ params=params,
78
+ )
79
+
80
+ async def list_drive_files(self, page_size: int = 10, query: Optional[str] = None) -> Dict[str, Any]:
81
+ """List Drive files."""
82
+ params: Dict[str, Any] = {"pageSize": page_size}
83
+ if query:
84
+ params["q"] = query
85
+ return await self._get("/drive/v3/files", params=params)
86
+
87
+ async def get_calendar_events(
88
+ self,
89
+ calendar_id: str = "primary",
90
+ max_results: int = 10,
91
+ ) -> Dict[str, Any]:
92
+ """List calendar events."""
93
+ params = {"maxResults": max_results, "singleEvents": True, "orderBy": "startTime"}
94
+ return await self._get(f"/calendar/v3/calendars/{calendar_id}/events", params=params)
95
+
96
+ async def handle_event(self, payload: Dict[str, Any], headers: Optional[Dict[str, str]] = None) -> None:
97
+ """Handle an inbound event payload and emit it downstream."""
98
+ await self.emit(payload=payload, metadata={"headers": headers or {}})
99
+
100
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
101
+ await self._ensure_client()
102
+ assert self._client is not None
103
+ response = await self._client.get(path, params=params or {})
104
+ response.raise_for_status()
105
+ return response.json()
106
+
107
+ async def _post(
108
+ self,
109
+ path: str,
110
+ payload: Dict[str, Any],
111
+ params: Optional[Dict[str, Any]] = None,
112
+ ) -> Dict[str, Any]:
113
+ await self._ensure_client()
114
+ assert self._client is not None
115
+ response = await self._client.post(path, params=params or {}, json=payload)
116
+ response.raise_for_status()
117
+ return response.json()
118
+
119
+ async def _ensure_client(self) -> None:
120
+ if self._client is not None:
121
+ return
122
+ async with self._lock:
123
+ if self._client is None:
124
+ await self._start()
@@ -0,0 +1,108 @@
1
+ """Jira connector implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+ import asyncio
7
+ import base64
8
+ import logging
9
+
10
+ import httpx
11
+
12
+ from genxai.connectors.base import Connector
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class JiraConnector(Connector):
18
+ """Jira connector using Jira Cloud REST API v3.
19
+
20
+ Notes:
21
+ - Use email + API token for basic auth.
22
+ - Incoming webhook events can be forwarded to `handle_event`.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ connector_id: str,
28
+ email: str,
29
+ api_token: str,
30
+ base_url: str,
31
+ name: Optional[str] = None,
32
+ timeout: float = 10.0,
33
+ ) -> None:
34
+ super().__init__(connector_id=connector_id, name=name)
35
+ self.email = email
36
+ self.api_token = api_token
37
+ self.base_url = base_url.rstrip("/")
38
+ self.timeout = timeout
39
+ self._client: Optional[httpx.AsyncClient] = None
40
+ self._lock = asyncio.Lock()
41
+
42
+ async def _start(self) -> None:
43
+ if not self._client:
44
+ token = f"{self.email}:{self.api_token}".encode("utf-8")
45
+ auth_header = base64.b64encode(token).decode("utf-8")
46
+ self._client = httpx.AsyncClient(
47
+ base_url=self.base_url,
48
+ headers={
49
+ "Authorization": f"Basic {auth_header}",
50
+ "Accept": "application/json",
51
+ "Content-Type": "application/json",
52
+ },
53
+ timeout=self.timeout,
54
+ )
55
+
56
+ async def _stop(self) -> None:
57
+ if self._client:
58
+ await self._client.aclose()
59
+ self._client = None
60
+
61
+ async def validate_config(self) -> None:
62
+ if not self.email:
63
+ raise ValueError("Jira email must be provided")
64
+ if not self.api_token:
65
+ raise ValueError("Jira api_token must be provided")
66
+ if not self.base_url:
67
+ raise ValueError("Jira base_url must be provided")
68
+
69
+ async def get_project(self, project_key: str) -> Dict[str, Any]:
70
+ """Fetch Jira project metadata."""
71
+ return await self._get(f"/rest/api/3/project/{project_key}")
72
+
73
+ async def search_issues(self, jql: str, max_results: int = 50) -> Dict[str, Any]:
74
+ """Search issues with JQL."""
75
+ payload = {"jql": jql, "maxResults": max_results}
76
+ return await self._post("/rest/api/3/search", payload)
77
+
78
+ async def create_issue(self, payload: Dict[str, Any]) -> Dict[str, Any]:
79
+ """Create a Jira issue using the provided payload."""
80
+ return await self._post("/rest/api/3/issue", payload)
81
+
82
+ async def handle_event(
83
+ self,
84
+ payload: Dict[str, Any],
85
+ headers: Optional[Dict[str, str]] = None,
86
+ ) -> None:
87
+ await self.emit(payload=payload, metadata={"headers": headers or {}})
88
+
89
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
90
+ await self._ensure_client()
91
+ assert self._client is not None
92
+ response = await self._client.get(path, params=params or {})
93
+ response.raise_for_status()
94
+ return response.json()
95
+
96
+ async def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
97
+ await self._ensure_client()
98
+ assert self._client is not None
99
+ response = await self._client.post(path, json=payload)
100
+ response.raise_for_status()
101
+ return response.json()
102
+
103
+ async def _ensure_client(self) -> None:
104
+ if self._client is not None:
105
+ return
106
+ async with self._lock:
107
+ if self._client is None:
108
+ await self._start()
@@ -0,0 +1,97 @@
1
+ """Notion connector implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+ import asyncio
7
+ import logging
8
+
9
+ import httpx
10
+
11
+ from genxai.connectors.base import Connector
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class NotionConnector(Connector):
17
+ """Notion connector using the Notion API.
18
+
19
+ Notes:
20
+ - Provide a Notion integration token.
21
+ - Incoming webhook-like events can be forwarded to `handle_event`.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ connector_id: str,
27
+ token: str,
28
+ name: Optional[str] = None,
29
+ base_url: str = "https://api.notion.com/v1",
30
+ notion_version: str = "2022-06-28",
31
+ timeout: float = 10.0,
32
+ ) -> None:
33
+ super().__init__(connector_id=connector_id, name=name)
34
+ self.token = token
35
+ self.base_url = base_url.rstrip("/")
36
+ self.notion_version = notion_version
37
+ self.timeout = timeout
38
+ self._client: Optional[httpx.AsyncClient] = None
39
+ self._lock = asyncio.Lock()
40
+
41
+ async def _start(self) -> None:
42
+ if not self._client:
43
+ self._client = httpx.AsyncClient(
44
+ base_url=self.base_url,
45
+ headers={
46
+ "Authorization": f"Bearer {self.token}",
47
+ "Notion-Version": self.notion_version,
48
+ "Content-Type": "application/json",
49
+ },
50
+ timeout=self.timeout,
51
+ )
52
+
53
+ async def _stop(self) -> None:
54
+ if self._client:
55
+ await self._client.aclose()
56
+ self._client = None
57
+
58
+ async def validate_config(self) -> None:
59
+ if not self.token:
60
+ raise ValueError("Notion token must be provided")
61
+
62
+ async def get_page(self, page_id: str) -> Dict[str, Any]:
63
+ """Fetch a Notion page by ID."""
64
+ return await self._get(f"/pages/{page_id}")
65
+
66
+ async def query_database(self, database_id: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
67
+ """Query a Notion database."""
68
+ return await self._post(f"/databases/{database_id}/query", payload or {})
69
+
70
+ async def create_page(self, payload: Dict[str, Any]) -> Dict[str, Any]:
71
+ """Create a Notion page using the provided payload."""
72
+ return await self._post("/pages", payload)
73
+
74
+ async def handle_event(self, payload: Dict[str, Any], headers: Optional[Dict[str, str]] = None) -> None:
75
+ """Handle an inbound Notion event payload and emit it downstream."""
76
+ await self.emit(payload=payload, metadata={"headers": headers or {}})
77
+
78
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
79
+ await self._ensure_client()
80
+ assert self._client is not None
81
+ response = await self._client.get(path, params=params or {})
82
+ response.raise_for_status()
83
+ return response.json()
84
+
85
+ async def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
86
+ await self._ensure_client()
87
+ assert self._client is not None
88
+ response = await self._client.post(path, json=payload)
89
+ response.raise_for_status()
90
+ return response.json()
91
+
92
+ async def _ensure_client(self) -> None:
93
+ if self._client is not None:
94
+ return
95
+ async with self._lock:
96
+ if self._client is None:
97
+ await self._start()
@@ -0,0 +1,121 @@
1
+ """Slack connector implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+ import asyncio
7
+ import logging
8
+
9
+ import httpx
10
+
11
+ from genxai.connectors.base import Connector
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SlackConnector(Connector):
17
+ """Slack connector using Slack Web API + Events API payloads.
18
+
19
+ Notes:
20
+ - This connector handles outgoing API calls and can emit inbound events
21
+ when `handle_event` is called by your webhook route.
22
+ - You must provide a Slack Bot token for Web API calls.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ connector_id: str,
28
+ bot_token: str,
29
+ name: Optional[str] = None,
30
+ base_url: str = "https://slack.com/api",
31
+ timeout: float = 10.0,
32
+ ) -> None:
33
+ super().__init__(connector_id=connector_id, name=name)
34
+ self.bot_token = bot_token
35
+ self.base_url = base_url.rstrip("/")
36
+ self.timeout = timeout
37
+ self._client: Optional[httpx.AsyncClient] = None
38
+ self._lock = asyncio.Lock()
39
+
40
+ async def _start(self) -> None:
41
+ if not self._client:
42
+ self._client = httpx.AsyncClient(
43
+ base_url=self.base_url,
44
+ headers={
45
+ "Authorization": f"Bearer {self.bot_token}",
46
+ "Content-Type": "application/json; charset=utf-8",
47
+ },
48
+ timeout=self.timeout,
49
+ )
50
+
51
+ async def _stop(self) -> None:
52
+ if self._client:
53
+ await self._client.aclose()
54
+ self._client = None
55
+
56
+ async def validate_config(self) -> None:
57
+ if not self.bot_token:
58
+ raise ValueError("Slack bot_token must be provided")
59
+
60
+ async def send_message(
61
+ self,
62
+ channel: str,
63
+ text: str,
64
+ blocks: Optional[List[Dict[str, Any]]] = None,
65
+ attachments: Optional[List[Dict[str, Any]]] = None,
66
+ ) -> Dict[str, Any]:
67
+ """Send a message to a Slack channel."""
68
+ payload: Dict[str, Any] = {"channel": channel, "text": text}
69
+ if blocks:
70
+ payload["blocks"] = blocks
71
+ if attachments:
72
+ payload["attachments"] = attachments
73
+ return await self._post("/chat.postMessage", payload)
74
+
75
+ async def post_ephemeral(
76
+ self,
77
+ channel: str,
78
+ user: str,
79
+ text: str,
80
+ blocks: Optional[List[Dict[str, Any]]] = None,
81
+ ) -> Dict[str, Any]:
82
+ """Send an ephemeral message to a user in a channel."""
83
+ payload: Dict[str, Any] = {"channel": channel, "user": user, "text": text}
84
+ if blocks:
85
+ payload["blocks"] = blocks
86
+ return await self._post("/chat.postEphemeral", payload)
87
+
88
+ async def list_channels(self, types: str = "public_channel,private_channel") -> Dict[str, Any]:
89
+ """List available channels."""
90
+ return await self._get("/conversations.list", {"types": types})
91
+
92
+ async def handle_event(self, payload: Dict[str, Any], headers: Optional[Dict[str, str]] = None) -> None:
93
+ """Handle an inbound Slack event payload and emit it downstream."""
94
+ await self.emit(payload=payload, metadata={"headers": headers or {}})
95
+
96
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
97
+ await self._ensure_client()
98
+ assert self._client is not None
99
+ response = await self._client.get(path, params=params or {})
100
+ response.raise_for_status()
101
+ data = response.json()
102
+ if not data.get("ok", False):
103
+ raise ValueError(f"Slack API error: {data}")
104
+ return data
105
+
106
+ async def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
107
+ await self._ensure_client()
108
+ assert self._client is not None
109
+ response = await self._client.post(path, json=payload)
110
+ response.raise_for_status()
111
+ data = response.json()
112
+ if not data.get("ok", False):
113
+ raise ValueError(f"Slack API error: {data}")
114
+ return data
115
+
116
+ async def _ensure_client(self) -> None:
117
+ if self._client is not None:
118
+ return
119
+ async with self._lock:
120
+ if self._client is None:
121
+ await self._start()
@@ -26,7 +26,38 @@ def agent_to_dict(agent: Agent) -> Dict[str, Any]:
26
26
 
27
27
  def agent_from_dict(data: Dict[str, Any]) -> Agent:
28
28
  """Load Agent from a dictionary."""
29
- return Agent(id=data["id"], config=agent_config_from_dict(data["config"]))
29
+ if "config" in data and isinstance(data.get("config"), dict):
30
+ config = agent_config_from_dict(data["config"])
31
+ return Agent(id=data["id"], config=config)
32
+
33
+ # Support flat agent definitions (no config wrapper).
34
+ llm_model = data.get("llm_model") or data.get("llm") or "gpt-4"
35
+ config = AgentConfig(
36
+ role=data.get("role", "Agent"),
37
+ goal=data.get("goal", "Process tasks"),
38
+ backstory=data.get("backstory", ""),
39
+ llm_provider=data.get("llm_provider", "openai"),
40
+ llm_model=llm_model,
41
+ llm_temperature=data.get("llm_temperature", 0.7),
42
+ tools=data.get("tools", []),
43
+ enable_memory=data.get("memory", {}).get("enabled", True)
44
+ if isinstance(data.get("memory"), dict)
45
+ else data.get("enable_memory", True),
46
+ memory_type=data.get("memory", {}).get("type", "short_term")
47
+ if isinstance(data.get("memory"), dict)
48
+ else data.get("memory_type", "short_term"),
49
+ agent_type=data.get("behavior", {}).get("agent_type", "reactive")
50
+ if isinstance(data.get("behavior"), dict)
51
+ else data.get("agent_type", "reactive"),
52
+ max_iterations=data.get("behavior", {}).get("max_iterations", 10)
53
+ if isinstance(data.get("behavior"), dict)
54
+ else data.get("max_iterations", 10),
55
+ verbose=data.get("behavior", {}).get("verbose", False)
56
+ if isinstance(data.get("behavior"), dict)
57
+ else data.get("verbose", False),
58
+ metadata=data.get("metadata", {}),
59
+ )
60
+ return Agent(id=data["id"], config=config)
30
61
 
31
62
 
32
63
  def export_agent_config_yaml(agent: Agent, path: Path) -> None: