idun-agent-engine 0.1.0__py3-none-any.whl → 0.2.1__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 (37) hide show
  1. idun_agent_engine/__init__.py +2 -25
  2. idun_agent_engine/_version.py +1 -1
  3. idun_agent_engine/agent/__init__.py +10 -0
  4. idun_agent_engine/agent/base.py +97 -0
  5. idun_agent_engine/agent/haystack/__init__.py +9 -0
  6. idun_agent_engine/agent/haystack/haystack.py +261 -0
  7. idun_agent_engine/agent/haystack/haystack_model.py +13 -0
  8. idun_agent_engine/agent/haystack/utils.py +13 -0
  9. idun_agent_engine/agent/langgraph/__init__.py +7 -0
  10. idun_agent_engine/agent/langgraph/langgraph.py +429 -0
  11. idun_agent_engine/cli/__init__.py +16 -0
  12. idun_agent_engine/core/__init__.py +11 -0
  13. idun_agent_engine/core/app_factory.py +63 -0
  14. idun_agent_engine/core/config_builder.py +456 -0
  15. idun_agent_engine/core/engine_config.py +22 -0
  16. idun_agent_engine/core/server_runner.py +146 -0
  17. idun_agent_engine/observability/__init__.py +13 -0
  18. idun_agent_engine/observability/base.py +111 -0
  19. idun_agent_engine/observability/langfuse/__init__.py +5 -0
  20. idun_agent_engine/observability/langfuse/langfuse_handler.py +72 -0
  21. idun_agent_engine/observability/phoenix/__init__.py +5 -0
  22. idun_agent_engine/observability/phoenix/phoenix_handler.py +65 -0
  23. idun_agent_engine/observability/phoenix_local/__init__.py +5 -0
  24. idun_agent_engine/observability/phoenix_local/phoenix_local_handler.py +123 -0
  25. idun_agent_engine/py.typed +0 -1
  26. idun_agent_engine/server/__init__.py +5 -0
  27. idun_agent_engine/server/dependencies.py +23 -0
  28. idun_agent_engine/server/lifespan.py +42 -0
  29. idun_agent_engine/server/routers/__init__.py +5 -0
  30. idun_agent_engine/server/routers/agent.py +68 -0
  31. idun_agent_engine/server/routers/base.py +60 -0
  32. idun_agent_engine/server/server_config.py +8 -0
  33. idun_agent_engine-0.2.1.dist-info/METADATA +278 -0
  34. idun_agent_engine-0.2.1.dist-info/RECORD +35 -0
  35. {idun_agent_engine-0.1.0.dist-info → idun_agent_engine-0.2.1.dist-info}/WHEEL +1 -1
  36. idun_agent_engine-0.1.0.dist-info/METADATA +0 -317
  37. idun_agent_engine-0.1.0.dist-info/RECORD +0 -6
@@ -0,0 +1,456 @@
1
+ """Configuration Builder for Idun Agent Engine.
2
+
3
+ This module provides a fluent API for building configuration objects using Pydantic models.
4
+ This approach ensures type safety, validation, and consistency with the rest of the codebase.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import yaml
11
+ from idun_agent_schema.engine.agent_framework import AgentFramework
12
+ from idun_agent_schema.engine.haystack import HaystackAgentConfig
13
+ from idun_agent_schema.engine.langgraph import (
14
+ LangGraphAgentConfig,
15
+ SqliteCheckpointConfig,
16
+ )
17
+
18
+ from idun_agent_engine.server.server_config import ServerAPIConfig
19
+
20
+ from ..agent.base import BaseAgent
21
+ from .engine_config import AgentConfig, EngineConfig, ServerConfig
22
+
23
+
24
+ class ConfigBuilder:
25
+ """A fluent builder for creating Idun Agent Engine configurations using Pydantic models.
26
+
27
+ This class provides a convenient way to build strongly-typed configuration objects
28
+ that are validated at creation time, ensuring consistency and catching errors early.
29
+ It also handles agent initialization and management.
30
+
31
+ Example:
32
+ config = (ConfigBuilder()
33
+ .with_api_port(8080)
34
+ .with_langgraph_agent(
35
+ name="My Agent",
36
+ graph_definition="my_agent.py:graph",
37
+ sqlite_checkpointer="agent.db")
38
+ .build())
39
+
40
+ app = create_app(config_dict=config.model_dump())
41
+ """
42
+
43
+ def __init__(self):
44
+ """Initialize a new configuration builder with default values."""
45
+ self._server_config = ServerConfig()
46
+ self._agent_config: AgentConfig | None = None
47
+
48
+ def with_api_port(self, port: int) -> "ConfigBuilder":
49
+ """Set the API port for the server.
50
+
51
+ Args:
52
+ port: The port number to bind the server to
53
+
54
+ Returns:
55
+ ConfigBuilder: This builder instance for method chaining
56
+ """
57
+ # Create new API config with updated port
58
+ api_config = ServerAPIConfig(port=port)
59
+ self._server_config = ServerConfig(
60
+ api=api_config,
61
+ )
62
+ return self
63
+
64
+ def with_server_config(
65
+ self, api_port: int | None = None, telemetry_provider: str | None = None
66
+ ) -> "ConfigBuilder":
67
+ """Set server configuration options directly.
68
+
69
+ Args:
70
+ api_port: Optional API port
71
+ telemetry_provider: Optional telemetry provider
72
+
73
+ Returns:
74
+ ConfigBuilder: This builder instance for method chaining
75
+ """
76
+ api_config = (
77
+ ServerAPIConfig(port=api_port) if api_port else self._server_config.api
78
+ )
79
+
80
+ self._server_config = ServerConfig(api=api_config)
81
+ return self
82
+
83
+ def with_config_from_api(self, agent_api_key: str, url: str) -> "ConfigBuilder":
84
+ """Fetches the yaml config file, from idun agent manager api.
85
+
86
+ Requires the agent api key to pass in the headers.
87
+ """
88
+ import requests
89
+ import yaml
90
+
91
+ headers = {"auth": f"Bearer {agent_api_key}"}
92
+ try:
93
+ response = requests.get(url=url, headers=headers)
94
+ if response.status_code != 200:
95
+ raise ValueError(
96
+ f"Error sending retrieving config from url. response : {response.json()}"
97
+ )
98
+ yaml_config = yaml.safe_load(response.text)
99
+ self._server_config = yaml_config["engine_config"]["server"]
100
+ self._agent_config = yaml_config["engine_config"]["agent"]
101
+ return self
102
+
103
+ except Exception as e:
104
+ raise ValueError(f"Error occured while getting config from api: {e}") from e
105
+
106
+ def with_langgraph_agent(
107
+ self,
108
+ name: str,
109
+ graph_definition: str,
110
+ sqlite_checkpointer: str | None = None,
111
+ **additional_config,
112
+ ) -> "ConfigBuilder":
113
+ """Configure a LangGraph agent using the LangGraphAgentConfig model.
114
+
115
+ Args:
116
+ name: Human-readable name for the agent
117
+ graph_definition: Path to the graph in format "module.py:variable_name"
118
+ sqlite_checkpointer: Optional path to SQLite database for checkpointing
119
+ **additional_config: Additional configuration parameters
120
+
121
+ Returns:
122
+ ConfigBuilder: This builder instance for method chaining
123
+ """
124
+ # Build the agent config dictionary
125
+ agent_config_dict = {
126
+ "name": name,
127
+ "graph_definition": graph_definition,
128
+ **additional_config,
129
+ }
130
+
131
+ # Add checkpointer if specified
132
+ if sqlite_checkpointer:
133
+ checkpointer = SqliteCheckpointConfig(
134
+ type="sqlite", db_url=f"sqlite:///{sqlite_checkpointer}"
135
+ )
136
+ agent_config_dict["checkpointer"] = checkpointer
137
+
138
+ # Create and validate the LangGraph config
139
+ langgraph_config = LangGraphAgentConfig.model_validate(agent_config_dict)
140
+
141
+ # Create the agent config (store as strongly-typed model, not dict)
142
+ self._agent_config = AgentConfig(type="langgraph", config=langgraph_config)
143
+ return self
144
+
145
+ def with_custom_agent(
146
+ self, agent_type: str, config: dict[str, Any]
147
+ ) -> "ConfigBuilder":
148
+ """Configure a custom agent type.
149
+
150
+ This method allows for configuring agent types that don't have
151
+ dedicated builder methods yet. The config will be validated
152
+ when the AgentConfig is created.
153
+
154
+ Args:
155
+ agent_type: The type of agent (e.g., "crewai", "autogen")
156
+ config: Configuration dictionary specific to the agent type
157
+
158
+ Returns:
159
+ ConfigBuilder: This builder instance for method chaining
160
+ """
161
+ if agent_type == AgentFramework.LANGGRAPH:
162
+ self._agent_config = AgentConfig(
163
+ type="langgraph", config=LangGraphAgentConfig.model_validate(config)
164
+ )
165
+
166
+ elif agent_type == AgentFramework.HAYSTACK:
167
+ self._agent_config = AgentConfig(
168
+ type="haystack", config=HaystackAgentConfig.model_validate(config)
169
+ )
170
+ else:
171
+ raise ValueError(f"Unsupported agent type: {agent_type}")
172
+ return self
173
+
174
+ def build(self) -> EngineConfig:
175
+ """Build and return the complete configuration as a validated Pydantic model.
176
+
177
+ Returns:
178
+ EngineConfig: The complete, validated configuration object
179
+
180
+ Raises:
181
+ ValueError: If the configuration is incomplete or invalid
182
+ """
183
+ if not self._agent_config:
184
+ raise ValueError(
185
+ "Agent configuration is required. Use with_langgraph_agent() or with_custom_agent()"
186
+ )
187
+
188
+ # Create and validate the complete configuration
189
+ return EngineConfig(server=self._server_config, agent=self._agent_config)
190
+
191
+ def build_dict(self) -> dict[str, Any]:
192
+ """Build and return the configuration as a dictionary.
193
+
194
+ This is a convenience method for backward compatibility.
195
+
196
+ Returns:
197
+ Dict[str, Any]: The complete configuration dictionary
198
+ """
199
+ engine_config = self.build()
200
+ return engine_config.model_dump()
201
+
202
+ def save_to_file(self, file_path: str) -> None:
203
+ """Save the configuration to a YAML file.
204
+
205
+ Args:
206
+ file_path: Path where to save the configuration file
207
+ """
208
+ config = self.build_dict()
209
+ with open(file_path, "w") as f:
210
+ yaml.dump(config, f, default_flow_style=False, indent=2)
211
+
212
+ async def build_and_initialize_agent(self) -> BaseAgent:
213
+ """Build configuration and initialize the agent in one step.
214
+
215
+ Returns:
216
+ BaseAgent: Initialized agent instance
217
+
218
+ Raises:
219
+ ValueError: If agent type is unsupported or configuration is invalid
220
+ """
221
+ engine_config = self.build()
222
+ return await self.initialize_agent_from_config(engine_config)
223
+
224
+ @staticmethod
225
+ async def initialize_agent_from_config(engine_config: EngineConfig) -> BaseAgent:
226
+ """Initialize an agent instance from a validated EngineConfig.
227
+
228
+ Args:
229
+ engine_config: Validated configuration object
230
+
231
+ Returns:
232
+ BaseAgent: Initialized agent instance
233
+
234
+ Raises:
235
+ ValueError: If agent type is unsupported
236
+ """
237
+ agent_config_obj = engine_config.agent.config
238
+ print("CONFIG:", agent_config_obj)
239
+ agent_type = engine_config.agent.type
240
+
241
+ # Initialize the appropriate agent
242
+ agent_instance = None
243
+ if agent_type == AgentFramework.LANGGRAPH:
244
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
245
+
246
+ try:
247
+ validated_config = LangGraphAgentConfig.model_validate(agent_config_obj)
248
+
249
+ except Exception as e:
250
+ raise ValueError(
251
+ f"Cannot validate into a LangGraphAgentConfig model. Got {agent_config_obj}"
252
+ ) from e
253
+
254
+ agent_instance = LanggraphAgent()
255
+
256
+ elif agent_type == AgentFramework.HAYSTACK:
257
+ from idun_agent_engine.agent.haystack.haystack import HaystackAgent
258
+
259
+ try:
260
+ validated_config = HaystackAgentConfig.model_validate(agent_config_obj)
261
+
262
+ except Exception as e:
263
+ raise ValueError(
264
+ f"Cannot validate into a HaystackAgentConfig model. Got {agent_config_obj}"
265
+ ) from e
266
+ agent_instance = HaystackAgent()
267
+ else:
268
+ raise ValueError(f"Unsupported agent type: {agent_type}")
269
+
270
+ # Initialize the agent with its configuration
271
+ await agent_instance.initialize(validated_config) # type: ignore[arg-type]
272
+ return agent_instance
273
+
274
+ @staticmethod
275
+ def get_agent_class(agent_type: str) -> type[BaseAgent]:
276
+ """Get the agent class for a given agent type without initializing it.
277
+
278
+ Args:
279
+ agent_type: The type of agent
280
+
281
+ Returns:
282
+ Type[BaseAgent]: The agent class
283
+
284
+ Raises:
285
+ ValueError: If agent type is unsupported
286
+ """
287
+ if agent_type == "langgraph":
288
+ from ..agent.langgraph.langgraph import LanggraphAgent
289
+
290
+ return LanggraphAgent
291
+
292
+ else:
293
+ raise ValueError(f"Unsupported agent type: {agent_type}")
294
+
295
+ @staticmethod
296
+ def validate_agent_config(
297
+ agent_type: str, config: dict[str, Any]
298
+ ) -> dict[str, Any]:
299
+ """Validate agent configuration against the appropriate Pydantic model.
300
+
301
+ Args:
302
+ agent_type: The type of agent
303
+ config: Configuration dictionary to validate
304
+
305
+ Returns:
306
+ Dict[str, Any]: Validated configuration dictionary
307
+
308
+ Raises:
309
+ ValueError: If agent type is unsupported or config is invalid
310
+ """
311
+ if agent_type == "langgraph":
312
+ validated_config = LangGraphAgentConfig.model_validate(config)
313
+ return validated_config.model_dump()
314
+ # Future agent types can be added here:
315
+ # elif agent_type == "crewai":
316
+ # validated_config = CrewAIAgentConfig.model_validate(config)
317
+ # return validated_config.model_dump()
318
+ else:
319
+ raise ValueError(f"Unsupported agent type: {agent_type}")
320
+
321
+ @staticmethod
322
+ def load_from_file(config_path: str = "config.yaml") -> EngineConfig:
323
+ """Load configuration from a YAML file and return a validated EngineConfig.
324
+
325
+ Args:
326
+ config_path: Path to the configuration YAML file
327
+
328
+ Returns:
329
+ EngineConfig: Validated configuration object
330
+
331
+ Raises:
332
+ FileNotFoundError: If the configuration file doesn't exist
333
+ ValidationError: If the configuration is invalid
334
+ """
335
+ path = Path(config_path)
336
+ if not path.is_absolute():
337
+ # Resolve relative to the current working directory
338
+ path = Path.cwd() / path
339
+
340
+ with open(path) as f:
341
+ config_data = yaml.safe_load(f)
342
+
343
+ return EngineConfig.model_validate(config_data)
344
+
345
+ @staticmethod
346
+ async def load_and_initialize_agent(
347
+ config_path: str = "config.yaml",
348
+ ) -> tuple[EngineConfig, BaseAgent]:
349
+ """Load configuration and initialize agent in one step.
350
+
351
+ Args:
352
+ config_path: Path to the configuration YAML file
353
+
354
+ Returns:
355
+ tuple[EngineConfig, BaseAgent]: Configuration and initialized agent
356
+ """
357
+ engine_config = ConfigBuilder.load_from_file(config_path)
358
+ agent = await ConfigBuilder.initialize_agent_from_config(engine_config)
359
+ return engine_config, agent
360
+
361
+ @staticmethod
362
+ def resolve_config(
363
+ config_path: str | None = None,
364
+ config_dict: dict[str, Any] | None = None,
365
+ engine_config: EngineConfig | None = None,
366
+ ) -> EngineConfig:
367
+ """Umbrella function to resolve configuration from various sources.
368
+
369
+ This function handles all the different ways configuration can be provided
370
+ and returns a validated EngineConfig. It follows a priority order:
371
+ 1. engine_config (pre-validated EngineConfig from ConfigBuilder)
372
+ 2. config_dict (dictionary to be validated)
373
+ 3. config_path (file path to load and validate)
374
+ 4. default "config.yaml" file
375
+
376
+ Args:
377
+ config_path: Path to a YAML configuration file
378
+ config_dict: Dictionary containing configuration
379
+ engine_config: Pre-validated EngineConfig instance
380
+
381
+ Returns:
382
+ EngineConfig: Validated configuration object
383
+
384
+ Raises:
385
+ FileNotFoundError: If config file doesn't exist
386
+ ValidationError: If configuration is invalid
387
+ """
388
+ if engine_config:
389
+ # Use pre-validated EngineConfig (from ConfigBuilder)
390
+ print("✅ Using pre-validated EngineConfig")
391
+ return engine_config
392
+ elif config_dict:
393
+ # Validate dictionary config
394
+ print("✅ Validated dictionary configuration")
395
+ return EngineConfig.model_validate(config_dict)
396
+ elif config_path:
397
+ # Load from file using ConfigB/uilder
398
+ print(f"✅ Loaded configuration from {config_path}")
399
+ return ConfigBuilder.load_from_file(config_path)
400
+ else:
401
+ # Default to loading config.yaml
402
+ print("✅ Loaded default configuration from config.yaml")
403
+ return ConfigBuilder.load_from_file("config.yaml")
404
+
405
+ @classmethod
406
+ def from_dict(cls, config_dict: dict[str, Any]) -> "ConfigBuilder":
407
+ """Create a ConfigBuilder from an existing configuration dictionary.
408
+
409
+ This method validates the input dictionary against the Pydantic models.
410
+
411
+ Args:
412
+ config_dict: Existing configuration dictionary
413
+
414
+ Returns:
415
+ ConfigBuilder: A new builder instance with the provided configuration
416
+
417
+ Raises:
418
+ ValidationError: If the configuration dictionary is invalid
419
+ """
420
+ # Validate the entire config first
421
+ engine_config = EngineConfig.model_validate(config_dict)
422
+
423
+ # Create a new builder
424
+ builder = cls()
425
+ builder._server_config = engine_config.server
426
+ builder._agent_config = engine_config.agent
427
+
428
+ return builder
429
+
430
+ @classmethod
431
+ def from_file(cls, config_path: str = "config.yaml") -> "ConfigBuilder":
432
+ """Create a ConfigBuilder from a YAML configuration file.
433
+
434
+ Args:
435
+ config_path: Path to the configuration YAML file
436
+
437
+ Returns:
438
+ ConfigBuilder: A new builder instance with the loaded configuration
439
+ """
440
+ engine_config = cls.load_from_file(config_path)
441
+ return cls.from_engine_config(engine_config)
442
+
443
+ @classmethod
444
+ def from_engine_config(cls, engine_config: EngineConfig) -> "ConfigBuilder":
445
+ """Create a ConfigBuilder from an existing EngineConfig instance.
446
+
447
+ Args:
448
+ engine_config: Existing EngineConfig instance
449
+
450
+ Returns:
451
+ ConfigBuilder: A new builder instance with the provided configuration
452
+ """
453
+ builder = cls()
454
+ builder._server_config = engine_config.server
455
+ builder._agent_config = engine_config.agent
456
+ return builder
@@ -0,0 +1,22 @@
1
+ """Compatibility re-exports for Engine configuration models."""
2
+
3
+ from idun_agent_schema.engine.agent import BaseAgentConfig # noqa: F401
4
+ from idun_agent_schema.engine.agent import ( # noqa: F401
5
+ AgentConfig,
6
+ )
7
+
8
+ from idun_agent_schema.engine.engine import ( # noqa: F401
9
+ EngineConfig,
10
+ )
11
+ from idun_agent_schema.engine.langgraph import ( # noqa: F401
12
+ LangGraphAgentConfig,
13
+ )
14
+ from idun_agent_schema.engine.server import ServerConfig # noqa: F401
15
+
16
+ __all__ = [
17
+ "AgentConfig",
18
+ "EngineConfig",
19
+ "LangGraphAgentConfig",
20
+ "BaseAgentConfig",
21
+ "ServerConfig",
22
+ ]
@@ -0,0 +1,146 @@
1
+ """Server Runner for Idun Agent Engine.
2
+
3
+ This module provides convenient functions to run FastAPI applications created with
4
+ the Idun Agent Engine. It handles common deployment scenarios and provides sensible defaults.
5
+ """
6
+
7
+ import uvicorn
8
+ from fastapi import FastAPI
9
+
10
+
11
+ def run_server(
12
+ app: FastAPI,
13
+ host: str = "0.0.0.0",
14
+ port: int = 8000,
15
+ reload: bool = False,
16
+ log_level: str = "info",
17
+ workers: int | None = None,
18
+ ) -> None:
19
+ """Run a FastAPI application created with Idun Agent Engine.
20
+
21
+ This is a convenience function that wraps uvicorn.run() with sensible defaults
22
+ for serving agent applications. It automatically handles common deployment scenarios.
23
+
24
+ Args:
25
+ app: The FastAPI application created with create_app()
26
+ host: Host to bind the server to. Defaults to "0.0.0.0" (all interfaces)
27
+ port: Port to bind the server to. Defaults to 8000
28
+ reload: Enable auto-reload for development. Defaults to False
29
+ log_level: Logging level. Defaults to "info"
30
+ workers: Number of worker processes. If None, uses single process
31
+
32
+ Example:
33
+ from idun_agent_engine import create_app, run_server
34
+
35
+ # Create your app
36
+ app = create_app("config.yaml")
37
+
38
+ # Run in development mode
39
+ run_server(app, reload=True)
40
+
41
+ # Run in production mode
42
+ run_server(app, workers=4)
43
+ """
44
+ print(f"🌐 Starting Idun Agent Engine server on http://{host}:{port}")
45
+ print(f"📚 API documentation available at http://{host}:{port}/docs")
46
+
47
+ if reload and workers:
48
+ print(
49
+ "⚠️ Warning: reload=True is incompatible with workers > 1. Disabling reload."
50
+ )
51
+ reload = False
52
+
53
+ uvicorn.run(
54
+ app,
55
+ host=host,
56
+ port=port,
57
+ # reload=reload,
58
+ log_level=log_level,
59
+ # workers=workers
60
+ )
61
+
62
+
63
+ def run_server_from_config(config_path: str = "config.yaml", **kwargs) -> None:
64
+ """Create and run a server directly from a configuration file.
65
+
66
+ This is the most convenient way to start a server - it combines create_app()
67
+ and run_server() in a single function call using ConfigBuilder.
68
+
69
+ Args:
70
+ config_path: Path to the configuration YAML file
71
+ **kwargs: Additional arguments passed to run_server()
72
+
73
+ Example:
74
+ # Run server directly from config
75
+ run_server_from_config("my_agent.yaml", port=8080, reload=True)
76
+ """
77
+ from .app_factory import create_app
78
+ from .config_builder import ConfigBuilder
79
+
80
+ # Load configuration using ConfigBuilder
81
+ engine_config = ConfigBuilder.load_from_file(config_path)
82
+
83
+ # Create app with the loaded config
84
+ app = create_app(engine_config=engine_config)
85
+
86
+ # Extract port from config if not overridden
87
+ if "port" not in kwargs:
88
+ kwargs["port"] = engine_config.server.api.port
89
+
90
+ # Show configuration info
91
+ print(f"🔧 Loaded configuration from {config_path}")
92
+ # Best-effort: handle both dict-like and model access
93
+ agent_name = (
94
+ engine_config.agent.config.get("name") # type: ignore[call-arg, index]
95
+ if hasattr(engine_config.agent.config, "get")
96
+ else getattr(engine_config.agent.config, "name", "Unknown")
97
+ )
98
+ print(f"🤖 Agent: {agent_name} ({engine_config.agent.type})")
99
+
100
+ run_server(app, **kwargs)
101
+
102
+
103
+ def run_server_from_builder(config_builder, **kwargs) -> None:
104
+ """Create and run a server directly from a ConfigBuilder instance.
105
+
106
+ This allows for programmatic configuration with immediate server startup.
107
+
108
+ Args:
109
+ config_builder: ConfigBuilder instance (can be built or unbuilt)
110
+ **kwargs: Additional arguments passed to run_server()
111
+
112
+ Example:
113
+ from idun_agent_engine import ConfigBuilder
114
+
115
+ builder = (ConfigBuilder()
116
+ .with_langgraph_agent(name="My Agent", graph_definition="agent.py:graph")
117
+ .with_api_port(8080))
118
+
119
+ run_server_from_builder(builder, reload=True)
120
+ """
121
+ from .app_factory import create_app
122
+
123
+ # Build the configuration if it's a ConfigBuilder instance
124
+ if hasattr(config_builder, "build"):
125
+ engine_config = config_builder.build()
126
+ else:
127
+ # Assume it's already an EngineConfig
128
+ engine_config = config_builder
129
+
130
+ # Create app with the config
131
+ app = create_app(engine_config=engine_config)
132
+
133
+ # Extract port from config if not overridden
134
+ if "port" not in kwargs:
135
+ kwargs["port"] = engine_config.server.api.port
136
+
137
+ # Show configuration info
138
+ print("🔧 Using programmatic configuration")
139
+ agent_name = (
140
+ engine_config.agent.config.get("name") # type: ignore[call-arg, index]
141
+ if hasattr(engine_config.agent.config, "get")
142
+ else getattr(engine_config.agent.config, "name", "Unknown")
143
+ )
144
+ print(f"🤖 Agent: {agent_name} ({engine_config.agent.type})")
145
+
146
+ run_server(app, **kwargs)
@@ -0,0 +1,13 @@
1
+ """Observability package providing provider-agnostic tracing interfaces."""
2
+
3
+ from .base import (
4
+ ObservabilityConfig,
5
+ ObservabilityHandlerBase,
6
+ create_observability_handler,
7
+ )
8
+
9
+ __all__ = [
10
+ "ObservabilityConfig",
11
+ "ObservabilityHandlerBase",
12
+ "create_observability_handler",
13
+ ]