avalan 1.0.3__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 (79) hide show
  1. avalan/__init__.py +32 -0
  2. avalan/agent/__init__.py +54 -0
  3. avalan/agent/blueprint.toml +30 -0
  4. avalan/agent/engine.py +160 -0
  5. avalan/agent/loader.py +303 -0
  6. avalan/agent/orchestrator.py +284 -0
  7. avalan/agent/orchestrators/default.py +60 -0
  8. avalan/agent/orchestrators/json.py +133 -0
  9. avalan/agent/renderer.py +125 -0
  10. avalan/agent/templates/agent.md +35 -0
  11. avalan/agent/templates/agent_json.md +24 -0
  12. avalan/cli/__init__.py +66 -0
  13. avalan/cli/__main__.py +1131 -0
  14. avalan/cli/commands/__init__.py +0 -0
  15. avalan/cli/commands/agent.py +374 -0
  16. avalan/cli/commands/cache.py +73 -0
  17. avalan/cli/commands/memory.py +324 -0
  18. avalan/cli/commands/model.py +424 -0
  19. avalan/cli/commands/tokenizer.py +72 -0
  20. avalan/cli/download.py +61 -0
  21. avalan/cli/theme/__init__.py +402 -0
  22. avalan/cli/theme/fancy.py +1593 -0
  23. avalan/compat.py +14 -0
  24. avalan/event/__init__.py +15 -0
  25. avalan/event/manager.py +27 -0
  26. avalan/flow/__init__.py +0 -0
  27. avalan/flow/connection.py +29 -0
  28. avalan/flow/flow.py +155 -0
  29. avalan/flow/node.py +80 -0
  30. avalan/memory/__init__.py +63 -0
  31. avalan/memory/manager.py +174 -0
  32. avalan/memory/partitioner/__init__.py +8 -0
  33. avalan/memory/partitioner/code.py +333 -0
  34. avalan/memory/partitioner/text.py +102 -0
  35. avalan/memory/permanent/__init__.py +241 -0
  36. avalan/memory/permanent/migrations/pgsql/down/00001-messages-down.sql +29 -0
  37. avalan/memory/permanent/migrations/pgsql/up/00001-messages-up.sql +168 -0
  38. avalan/memory/permanent/pgsql/__init__.py +216 -0
  39. avalan/memory/permanent/pgsql/message.py +284 -0
  40. avalan/memory/permanent/pgsql/raw.py +235 -0
  41. avalan/model/__init__.py +86 -0
  42. avalan/model/audio.py +81 -0
  43. avalan/model/criteria.py +34 -0
  44. avalan/model/engine.py +329 -0
  45. avalan/model/entities.py +402 -0
  46. avalan/model/hubs/__init__.py +2 -0
  47. avalan/model/hubs/huggingface.py +255 -0
  48. avalan/model/manager.py +180 -0
  49. avalan/model/nlp/__init__.py +122 -0
  50. avalan/model/nlp/question.py +76 -0
  51. avalan/model/nlp/sentence.py +76 -0
  52. avalan/model/nlp/sequence.py +183 -0
  53. avalan/model/nlp/text/__init__.py +88 -0
  54. avalan/model/nlp/text/generation.py +424 -0
  55. avalan/model/nlp/text/vendor/__init__.py +111 -0
  56. avalan/model/nlp/text/vendor/anthropic.py +77 -0
  57. avalan/model/nlp/text/vendor/google.py +64 -0
  58. avalan/model/nlp/text/vendor/openai.py +58 -0
  59. avalan/model/nlp/text/vendor/openrouter.py +23 -0
  60. avalan/model/nlp/text/vllm.py +117 -0
  61. avalan/model/nlp/token.py +70 -0
  62. avalan/model/transformer.py +163 -0
  63. avalan/model/vision/__init__.py +20 -0
  64. avalan/model/vision/detection.py +68 -0
  65. avalan/model/vision/image.py +79 -0
  66. avalan/model/vision/segmentation.py +41 -0
  67. avalan/server/__init__.py +91 -0
  68. avalan/server/entities.py +64 -0
  69. avalan/server/routers/__init__.py +0 -0
  70. avalan/server/routers/chat.py +98 -0
  71. avalan/tool/__init__.py +21 -0
  72. avalan/tool/manager.py +87 -0
  73. avalan/tool/parser.py +175 -0
  74. avalan/utils.py +19 -0
  75. avalan-1.0.3.dist-info/LICENSE +21 -0
  76. avalan-1.0.3.dist-info/METADATA +901 -0
  77. avalan-1.0.3.dist-info/RECORD +79 -0
  78. avalan-1.0.3.dist-info/WHEEL +4 -0
  79. avalan-1.0.3.dist-info/entry_points.txt +3 -0
avalan/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ from packaging.version import parse, Version
2
+ from pathlib import Path
3
+ from tomllib import load
4
+ from urllib.parse import urlparse
5
+ from urllib.parse import ParseResult
6
+
7
+ def _config() -> dict:
8
+ project_path = Path(__file__).resolve().parents[2] / 'pyproject.toml'
9
+ with open(project_path, "rb") as file:
10
+ config = load(file)
11
+ return config
12
+
13
+ config = _config()
14
+
15
+ def license() -> str:
16
+ assert "project" in config and "license" in config["project"] and "text" in config["project"]["license"]
17
+ return config["project"]["license"]["text"]
18
+
19
+ def name() -> str:
20
+ assert "project" in config and "name" in config["project"]
21
+ return config["project"]["name"]
22
+
23
+ def version() -> Version:
24
+ assert "project" in config and "version" in config["project"]
25
+ version = config["project"]["version"]
26
+ return parse(version)
27
+
28
+ def site() -> ParseResult:
29
+ assert "project" in config and "urls" in config["project"] and "homepage" in config["project"]["urls"]
30
+ homepage = config["project"]["urls"]["homepage"]
31
+ return urlparse(homepage)
32
+
@@ -0,0 +1,54 @@
1
+ from avalan.model.entities import (
2
+ EngineSettings,
3
+ EngineUri,
4
+ GenerationSettings,
5
+ TransformerEngineSettings
6
+ )
7
+ from dataclasses import dataclass, field
8
+ from enum import StrEnum
9
+ from typing import Optional, Union
10
+
11
+ class NoOperationAvailableException(Exception):
12
+ pass
13
+
14
+ class EngineType(StrEnum):
15
+ TEXT_GENERATION = "text_generation"
16
+
17
+ class InputType(StrEnum):
18
+ TEXT = "text"
19
+
20
+ class OutputType(StrEnum):
21
+ JSON = "json"
22
+ TEXT = "text"
23
+
24
+ @dataclass(frozen=True, kw_only=True)
25
+ class Goal:
26
+ task: str
27
+ instructions: list[str]
28
+
29
+ @dataclass(frozen=True, kw_only=True)
30
+ class Role:
31
+ persona: list[str]
32
+
33
+ @dataclass(frozen=True, kw_only=True)
34
+ class Specification:
35
+ role: Role
36
+ goal: Optional[Goal]
37
+ rules: list[str]=field(default_factory=list)
38
+ input_type: InputType=InputType.TEXT
39
+ output_type: OutputType=OutputType.TEXT
40
+ settings: Optional[GenerationSettings]=None
41
+ template_id: Optional[str]=None
42
+ template_vars: Optional[dict]=None
43
+
44
+ @dataclass(frozen=True, kw_only=True)
45
+ class EngineEnvironment:
46
+ engine_uri: EngineUri
47
+ settings: Union[EngineSettings, TransformerEngineSettings]
48
+ type: EngineType=EngineType.TEXT_GENERATION
49
+
50
+ @dataclass(frozen=True, kw_only=True)
51
+ class Operation:
52
+ specification: Specification
53
+ environment: EngineEnvironment
54
+
@@ -0,0 +1,30 @@
1
+ [agent]
2
+ {% if name %}name = "{{ name }}"{% endif %}
3
+ role = """
4
+ {{ role | indent(4) }}
5
+ """
6
+ {% if task %}
7
+ task = """
8
+ {{ task | indent(4) }}
9
+ """
10
+ {% endif %}
11
+ {% if instructions %}
12
+ instructions = """
13
+ {{ instructions | indent(4) }}
14
+ """
15
+ {% endif %}
16
+
17
+ [memory]
18
+ recent = {{ memory_recent | lower }}
19
+ {% if memory_permanent %}permanent = "{{ memory_permanent }}"{% endif %}
20
+
21
+ [memory.engine]
22
+ model_id = "{{ memory_engine_model_id }}"
23
+
24
+ [engine]
25
+ uri = "{{ engine_uri }}"
26
+
27
+ [run]
28
+ use_cache = {{ run_use_cache | lower }}
29
+ max_new_tokens = {{ max_new_tokens }}
30
+ skip_special_tokens = {{ run_skip_special_tokens | lower }}
avalan/agent/engine.py ADDED
@@ -0,0 +1,160 @@
1
+ from abc import ABC, abstractmethod
2
+ from avalan.agent import Specification
3
+ from avalan.memory.manager import MemoryManager
4
+ from avalan.model.engine import Engine
5
+ from avalan.model.entities import (
6
+ EngineMessage,
7
+ GenerationSettings,
8
+ Input,
9
+ Message,
10
+ MessageRole,
11
+ )
12
+ from avalan.model.nlp.text import TextGenerationResponse
13
+ from avalan.model.nlp.text.vendor import TextGenerationVendorModel
14
+ from avalan.tool.manager import ToolManager
15
+ from dataclasses import replace
16
+ from typing import Any, Optional, Union, Tuple
17
+ from uuid import UUID, uuid4
18
+
19
+ class EngineAgent(ABC):
20
+ _id: UUID
21
+ _name: Optional[str]
22
+ _model: Engine
23
+ _memory: MemoryManager
24
+ _tool: ToolManager
25
+ _last_output: Optional[TextGenerationResponse]=None
26
+ _last_prompt: Optional[Tuple[Input,Optional[str]]]=None
27
+
28
+ @abstractmethod
29
+ def _prepare_call(
30
+ self,
31
+ specification: Specification,
32
+ input: str,
33
+ **kwargs: Any
34
+ ) -> Any:
35
+ raise NotImplementedError()
36
+
37
+ @property
38
+ def memory(self) -> MemoryManager:
39
+ return self._memory
40
+
41
+ @property
42
+ def engine(self) -> Engine:
43
+ return self._model
44
+
45
+ @property
46
+ def output(self) -> Optional[TextGenerationResponse]:
47
+ return self._last_output
48
+
49
+ @property
50
+ def input_token_count(self) -> Optional[int]:
51
+ return self._model.input_token_count(
52
+ self._last_prompt[0],
53
+ system_prompt=self._last_prompt[1]
54
+ ) if self._last_prompt else None
55
+
56
+ def __init__(
57
+ self,
58
+ model: Engine,
59
+ memory: MemoryManager,
60
+ tool: ToolManager,
61
+ *args,
62
+ name: Optional[str]=None,
63
+ id: Optional[UUID]=None,
64
+ ):
65
+ self._id = id or uuid4()
66
+ self._name = name
67
+ self._model = model
68
+ self._memory = memory
69
+ self._tool = tool
70
+
71
+ async def __call__(
72
+ self,
73
+ specification: Specification,
74
+ input: str,
75
+ **kwargs
76
+ ) -> Union[
77
+ TextGenerationResponse,
78
+ str
79
+ ]:
80
+ run_args = self._prepare_call(specification, input, **kwargs)
81
+ return await self._run(input, **run_args)
82
+
83
+ async def _run(
84
+ self,
85
+ input: str,
86
+ *args,
87
+ settings: Optional[GenerationSettings]=None,
88
+ system_prompt: Optional[str]=None,
89
+ skip_special_tokens=True,
90
+ **kwargs
91
+ ) -> Union[
92
+ TextGenerationResponse
93
+ ]:
94
+ # Process settings
95
+ if settings and kwargs:
96
+ settings = replace(settings, **kwargs)
97
+ elif not settings:
98
+ kwargs.setdefault("temperature", None)
99
+ kwargs.setdefault("do_sample", False)
100
+ settings = GenerationSettings(**kwargs)
101
+ assert settings
102
+
103
+ # Prepare memory
104
+ assert not self._memory.has_recent_message \
105
+ or self._memory.recent_message is not None
106
+
107
+ # Should always be stored, with or without memory
108
+ self._last_prompt = (input, system_prompt)
109
+
110
+ # Transform input (by adding memory, if necessary)
111
+ if (
112
+ self._memory.has_permanent_message or
113
+ self._memory.has_recent_message
114
+ ) and isinstance(input,Message):
115
+ previous_message: Optional[Message]=None
116
+ new_message: Message = input
117
+
118
+ # Handle last message if not already consumed
119
+ previous_output = self._last_output
120
+ if previous_output \
121
+ and isinstance(previous_output, TextGenerationResponse):
122
+ previous_message = Message(
123
+ role=MessageRole.ASSISTANT,
124
+ content=await previous_output.to_str()
125
+ )
126
+
127
+ # Append messages
128
+ if previous_message:
129
+ await self._memory.append_message(EngineMessage(
130
+ agent_id=self._id,
131
+ model_id=self._model.model_id,
132
+ message=previous_message
133
+ ))
134
+ await self._memory.append_message(EngineMessage(
135
+ agent_id=self._id,
136
+ model_id=self._model.model_id,
137
+ message=new_message
138
+ ))
139
+
140
+ # Make recent memory the new model input
141
+ input = [ rm.message for rm in self._memory.recent_messages ]
142
+
143
+ # Have model generate output from input
144
+
145
+ model_settings = dict(
146
+ system_prompt=system_prompt,
147
+ settings=settings,
148
+ tool=self._tool
149
+ )
150
+ if not isinstance(self._model, TextGenerationVendorModel):
151
+ model_settings["skip_special_tokens"] = skip_special_tokens
152
+
153
+ output = await self._model(input, **model_settings)
154
+
155
+ # Update memory
156
+ if self._memory.has_recent_message:
157
+ self._last_output = output
158
+
159
+ return output
160
+
avalan/agent/loader.py ADDED
@@ -0,0 +1,303 @@
1
+ from avalan.agent.orchestrator import Orchestrator
2
+ from avalan.agent.orchestrators.default import DefaultOrchestrator
3
+ from avalan.agent.orchestrators.json import JsonOrchestrator, Property
4
+ from avalan.memory.manager import MemoryManager
5
+ from avalan.memory.partitioner.text import TextPartitioner
6
+ from avalan.model.entities import EngineUri, TransformerEngineSettings
7
+ from avalan.model.hubs.huggingface import HuggingfaceHub
8
+ from avalan.model.manager import ModelManager
9
+ from avalan.model.nlp.sentence import SentenceTransformerModel
10
+ from avalan.tool.manager import ToolManager
11
+ from avalan.event.manager import EventManager
12
+ from contextlib import AsyncExitStack
13
+ from logging import Logger
14
+ from os import access, R_OK
15
+ from os.path import exists
16
+ from tomllib import load
17
+ from typing import Optional
18
+ from uuid import UUID, uuid4
19
+
20
+ class Loader:
21
+ DEFAULT_SENTENCE_MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2"
22
+
23
+ @classmethod
24
+ async def from_file(
25
+ cls,
26
+ path: str,
27
+ *args,
28
+ agent_id: Optional[UUID],
29
+ hub: HuggingfaceHub,
30
+ logger: Logger,
31
+ participant_id: UUID,
32
+ stack: AsyncExitStack,
33
+ disable_memory: bool=False
34
+ ) -> Orchestrator:
35
+ if not exists(path):
36
+ raise FileNotFoundError(path)
37
+ elif not access(path, R_OK):
38
+ raise PermissionError(path)
39
+
40
+ logger.debug(f"Loading agent from {path}")
41
+
42
+ with open(path, "rb") as file:
43
+ config = load(file)
44
+
45
+ # Validate settings
46
+
47
+ assert "agent" in config, "No agent section in configuration"
48
+ assert "engine" in config, \
49
+ "No engine section defined in configuration"
50
+ assert "uri" in config["engine"], \
51
+ "No uri defined in engine section of configuration"
52
+
53
+ agent_config = config["agent"]
54
+ for setting in ["role"]:
55
+ assert setting in agent_config, \
56
+ f"No {setting} defined in agent section of configuration"
57
+
58
+ assert "engine" in config, \
59
+ "No engine section defined in configuration"
60
+ assert "uri" in config["engine"], \
61
+ "No uri defined in engine section of configuration"
62
+
63
+ uri = config["engine"]["uri"]
64
+ engine_config = config["engine"]
65
+ enable_tools = (
66
+ engine_config["tools"]
67
+ if "tools" in engine_config
68
+ else None
69
+ )
70
+ engine_config.pop("uri", None)
71
+ engine_config.pop("tools", None)
72
+ orchestrator_type = (
73
+ config["agent"]["type"] if "type" in config["agent"]
74
+ else None
75
+ )
76
+ agent_id = (
77
+ agent_id if agent_id
78
+ else config["agent"]["id"] if "id" in config["agent"]
79
+ else uuid4()
80
+ )
81
+
82
+ assert orchestrator_type is None or orchestrator_type in ["json"], \
83
+ f"Unknown type {config['agent']['type']} in agent section " + \
84
+ "of configuration"
85
+ assert "role" in agent_config, \
86
+ "No role defined in agent section of configuration"
87
+
88
+ call_options = config["run"] if "run" in config else None
89
+ template_vars = config["template"] \
90
+ if "template" in config else None
91
+
92
+ # Tool configuration
93
+
94
+ tool = ToolManager.create_instance(
95
+ enable_tools=enable_tools
96
+ )
97
+
98
+ # Memory configuration
99
+
100
+ memory_options = (
101
+ config["memory"]
102
+ if "memory" in config and not disable_memory
103
+ else None
104
+ )
105
+
106
+ memory_permanent = (
107
+ memory_options["permanent"]
108
+ if memory_options and "permanent" in memory_options
109
+ else None
110
+ )
111
+ assert not memory_permanent or isinstance(memory_permanent,str), \
112
+ "Permanent message memory should be a string"
113
+ memory_recent = (
114
+ memory_options["recent"]
115
+ if memory_options and "recent" in memory_options
116
+ else False
117
+ )
118
+ assert isinstance(memory_recent,bool), \
119
+ "Recent message memory can only be set or unset"
120
+
121
+ sentence_model_id = (
122
+ config["memory.engine"]["model_id"]
123
+ if "memory.engine" in config and
124
+ "model_id" in config["memory.engine"]
125
+ else Loader.DEFAULT_SENTENCE_MODEL_ID
126
+ )
127
+ sentence_model_engine_config = (
128
+ config["memory.engine"]
129
+ if "memory.engine" in config
130
+ else None
131
+ )
132
+ sentence_model_max_tokens = (
133
+ config["memory.engine"]["max_tokens"]
134
+ if sentence_model_engine_config
135
+ and "max_tokens" in sentence_model_engine_config
136
+ else 500
137
+ )
138
+ sentence_model_overlap_size = (
139
+ config["memory.engine"]["overlap_size"]
140
+ if sentence_model_engine_config
141
+ and "overlap_size" in sentence_model_engine_config
142
+ else 125
143
+ )
144
+ sentence_model_window_size = (
145
+ config["memory.engine"]["window_size"]
146
+ if sentence_model_engine_config
147
+ and "window_size" in sentence_model_engine_config
148
+ else 250
149
+ )
150
+
151
+ if sentence_model_engine_config:
152
+ sentence_model_engine_config.pop("model_id", None)
153
+ sentence_model_engine_config.pop("max_tokens", None)
154
+ sentence_model_engine_config.pop("overlap_size", None)
155
+ sentence_model_engine_config.pop("window_size", None)
156
+
157
+ sentence_model_engine_settings = (
158
+ TransformerEngineSettings(**sentence_model_engine_config)
159
+ if sentence_model_engine_config
160
+ else TransformerEngineSettings()
161
+ )
162
+
163
+ logger.debug("Loading sentence transformer "
164
+ f"model {sentence_model_id} for agent {agent_id}")
165
+
166
+ sentence_model = SentenceTransformerModel(
167
+ model_id=sentence_model_id,
168
+ settings=sentence_model_engine_settings,
169
+ logger=logger
170
+ )
171
+ sentence_model = stack.enter_context(sentence_model)
172
+
173
+ logger.debug("Loading text partitioner for "
174
+ f"model {sentence_model_id} for agent {agent_id} "
175
+ f"with settings ({sentence_model_max_tokens}, "
176
+ f"{sentence_model_overlap_size}, "
177
+ f"{sentence_model_window_size})")
178
+
179
+ text_partitioner = TextPartitioner(
180
+ model=sentence_model,
181
+ logger=logger,
182
+ max_tokens=sentence_model_max_tokens,
183
+ overlap_size=sentence_model_overlap_size,
184
+ window_size=sentence_model_window_size
185
+ )
186
+
187
+ logger.debug(f"Loading memory manager for agent {agent_id}")
188
+
189
+ memory = await MemoryManager.create_instance(
190
+ agent_id=agent_id,
191
+ participant_id=participant_id,
192
+ text_partitioner=text_partitioner,
193
+ with_permanent_message_memory=memory_permanent,
194
+ with_recent_message_memory=memory_recent,
195
+ )
196
+
197
+ event_manager = EventManager()
198
+ event_manager.add_listener(
199
+ lambda e: logger.debug(f"Event {e.type}: {e.payload}")
200
+ )
201
+
202
+ # Agent creation
203
+
204
+ logger.debug(f"Creating agent {orchestrator_type} #{agent_id}")
205
+
206
+ model_manager = ModelManager(hub, logger)
207
+ model_manager = stack.enter_context(model_manager)
208
+
209
+ engine_uri = model_manager.parse_uri(uri)
210
+ engine_settings = model_manager.get_engine_settings(
211
+ engine_uri,
212
+ settings=engine_config
213
+ )
214
+
215
+ agent = \
216
+ cls._load_json_orchestrator(
217
+ engine_uri=engine_uri,
218
+ engine_settings=engine_settings,
219
+ logger=logger,
220
+ model_manager=model_manager,
221
+ memory=memory,
222
+ tool=tool,
223
+ event_manager=event_manager,
224
+ config=config,
225
+ agent_config=agent_config,
226
+ call_options=call_options,
227
+ template_vars=template_vars
228
+ ) if orchestrator_type == "json" else \
229
+ DefaultOrchestrator(
230
+ engine_uri,
231
+ logger,
232
+ model_manager,
233
+ memory,
234
+ tool,
235
+ event_manager,
236
+ name=agent_config["name"] \
237
+ if "name" in agent_config else None,
238
+ role=agent_config["role"],
239
+ task=agent_config["task"]
240
+ if "task" in agent_config else None,
241
+ instructions=agent_config["instructions"]
242
+ if "instructions" in agent_config else None,
243
+ rules=agent_config["rules"]
244
+ if "rules" in agent_config else None,
245
+ settings=engine_settings,
246
+ call_options=call_options,
247
+ template_vars=template_vars
248
+ )
249
+
250
+ return agent
251
+
252
+ @staticmethod
253
+ def _load_json_orchestrator(
254
+ engine_uri: EngineUri,
255
+ engine_settings: TransformerEngineSettings,
256
+ logger: Logger,
257
+ model_manager: ModelManager,
258
+ memory: MemoryManager,
259
+ tool: ToolManager,
260
+ event_manager: EventManager,
261
+ config: dict,
262
+ agent_config: dict,
263
+ call_options: Optional[dict],
264
+ template_vars: Optional[dict]
265
+ ) -> JsonOrchestrator:
266
+ assert "json" in config, "No json section in configuration"
267
+ assert "instructions" in agent_config, \
268
+ "No instructions defined in agent section of configuration"
269
+ assert "task" in agent_config, \
270
+ "No task defined in agent section of configuration"
271
+ assert "role" in agent_config, \
272
+ "No role defined in agent section of configuration"
273
+
274
+ properties : list[Property] = []
275
+ for property_name in config.get("json", []):
276
+ output_property = config["json"][property_name]
277
+ properties.append(Property(
278
+ name=property_name,
279
+ data_type=output_property["type"],
280
+ description=output_property["description"]
281
+ ))
282
+
283
+ assert properties, "No properties defined in configuration"
284
+
285
+ agent = JsonOrchestrator(
286
+ engine_uri,
287
+ logger,
288
+ model_manager,
289
+ memory,
290
+ tool,
291
+ event_manager,
292
+ properties,
293
+ name=agent_config["name"] if "name" in agent_config else None,
294
+ role=agent_config["role"],
295
+ task=agent_config["task"],
296
+ instructions=agent_config["instructions"],
297
+ rules=agent_config["rules"] if "rules" in agent_config else None,
298
+ settings=engine_settings,
299
+ call_options=call_options,
300
+ template_vars=template_vars
301
+ )
302
+ return agent
303
+