Skip to content

AgentsManifest

Base classes

Name Children Inherits
ConfigModel
llmling.config.base
Base class for all LLMling configuration models.

⋔ Inheritance diagram

graph TD
  94461874253744["manifest.AgentsManifest"]
  94461848889392["base.ConfigModel"]
  94461844082608["main.BaseModel"]
  139711135027392["builtins.object"]
  94461848889392 --> 94461874253744
  94461844082608 --> 94461848889392
  139711135027392 --> 94461844082608

🛈 DocStrings

Bases: ConfigModel

Complete agent configuration manifest defining all available agents.

This is the root configuration that: - Defines available response types (both inline and imported) - Configures all agent instances and their settings - Sets up custom role definitions and capabilities - Manages environment configurations

A single manifest can define multiple agents that can work independently or collaborate through the orchestrator.

Source code in src/llmling_agent/models/manifest.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
class AgentsManifest(ConfigModel):
    """Complete agent configuration manifest defining all available agents.

    This is the root configuration that:
    - Defines available response types (both inline and imported)
    - Configures all agent instances and their settings
    - Sets up custom role definitions and capabilities
    - Manages environment configurations

    A single manifest can define multiple agents that can work independently
    or collaborate through the orchestrator.
    """

    INHERIT: str | list[str] | None = None
    """Inheritance references."""

    resources: dict[str, ResourceConfig | str] = Field(default_factory=dict)
    """Resource configurations defining available filesystems.

    Supports both full config and URI shorthand:
        resources:
          docs: "file://./docs"  # shorthand
          data:  # full config
            type: "source"
            uri: "s3://bucket/data"
            cached: true
    """

    ui: UIConfig = Field(default_factory=StdlibUIConfig)
    """UI configuration."""

    agents: dict[str, AgentConfig] = Field(default_factory=dict)
    """Mapping of agent IDs to their configurations"""

    teams: dict[str, TeamConfig] = Field(default_factory=dict)
    """Mapping of team IDs to their configurations"""

    storage: StorageConfig = Field(default_factory=StorageConfig)
    """Storage provider configuration."""

    observability: ObservabilityConfig = Field(default_factory=ObservabilityConfig)
    """Observability provider configuration."""

    conversion: ConversionConfig = Field(default_factory=ConversionConfig)
    """Document conversion configuration."""

    responses: dict[str, ResponseDefinition] = Field(default_factory=dict)
    """Mapping of response names to their definitions"""

    jobs: dict[str, Job] = Field(default_factory=dict)
    """Pre-defined jobs, ready to be used by nodes."""

    mcp_servers: list[str | MCPServerConfig] = Field(default_factory=list)
    """List of MCP server configurations:

    These MCP servers are used to provide tools and other resources to the nodes.
    """
    pool_server: PoolServerConfig = Field(default_factory=PoolServerConfig)
    """Pool server configuration.

    This MCP server configuration is used for the pool MCP server,
    which exposes pool functionality to other applications / clients."""

    prompts: PromptConfig = Field(default_factory=PromptConfig)

    model_config = ConfigDict(use_attribute_docstrings=True, extra="forbid")

    @model_validator(mode="before")
    @classmethod
    def normalize_workers(cls, data: dict[str, Any]) -> dict[str, Any]:
        """Convert string workers to appropriate WorkerConfig for all agents."""
        teams = data.get("teams", {})
        agents = data.get("agents", {})

        # Process workers for all agents that have them
        for agent_name, agent_config in agents.items():
            if isinstance(agent_config, dict):
                workers = agent_config.get("workers", [])
            else:
                workers = agent_config.workers

            if workers:
                normalized: list[BaseWorkerConfig] = []

                for worker in workers:
                    match worker:
                        case str() as name:
                            # Determine type based on presence in teams/agents
                            if name in teams:
                                normalized.append(TeamWorkerConfig(name=name))
                            elif name in agents:
                                normalized.append(AgentWorkerConfig(name=name))
                            else:
                                # Default to agent if type can't be determined
                                normalized.append(AgentWorkerConfig(name=name))

                        case dict() as config:
                            # If type is explicitly specified, use it
                            if worker_type := config.get("type"):
                                match worker_type:
                                    case "team":
                                        normalized.append(TeamWorkerConfig(**config))
                                    case "agent":
                                        normalized.append(AgentWorkerConfig(**config))
                                    case _:
                                        msg = f"Invalid worker type: {worker_type}"
                                        raise ValueError(msg)
                            else:
                                # Determine type based on worker name
                                worker_name = config.get("name")
                                if not worker_name:
                                    msg = "Worker config missing name"
                                    raise ValueError(msg)

                                if worker_name in teams:
                                    normalized.append(TeamWorkerConfig(**config))
                                else:
                                    normalized.append(AgentWorkerConfig(**config))

                        case BaseWorkerConfig():  # Already normalized
                            normalized.append(worker)

                        case _:
                            msg = f"Invalid worker configuration: {worker}"
                            raise ValueError(msg)

                if isinstance(agent_config, dict):
                    agent_config["workers"] = normalized
                else:
                    # Need to create a new dict with updated workers
                    agent_dict = agent_config.model_dump()
                    agent_dict["workers"] = normalized
                    agents[agent_name] = agent_dict

        return data

    @cached_property
    def resource_registry(self) -> ResourceRegistry:
        """Get registry with all configured resources."""
        registry = ResourceRegistry()
        for name, config in self.resources.items():
            if isinstance(config, str):
                # Convert URI shorthand to SourceResourceConfig
                config = SourceResourceConfig(uri=config)
            registry.register_from_config(name, config)
        return registry

    def clone_agent_config(
        self,
        name: str,
        new_name: str | None = None,
        *,
        template_context: dict[str, Any] | None = None,
        **overrides: Any,
    ) -> str:
        """Create a copy of an agent configuration.

        Args:
            name: Name of agent to clone
            new_name: Optional new name (auto-generated if None)
            template_context: Variables for template rendering
            **overrides: Configuration overrides for the clone

        Returns:
            Name of the new agent

        Raises:
            KeyError: If original agent not found
            ValueError: If new name already exists or if overrides invalid
        """
        if name not in self.agents:
            msg = f"Agent {name} not found"
            raise KeyError(msg)

        actual_name = new_name or f"{name}_copy_{len(self.agents)}"
        if actual_name in self.agents:
            msg = f"Agent {actual_name} already exists"
            raise ValueError(msg)

        # Deep copy the configuration
        config = self.agents[name].model_copy(deep=True)

        # Apply overrides
        for key, value in overrides.items():
            if not hasattr(config, key):
                msg = f"Invalid override: {key}"
                raise ValueError(msg)
            setattr(config, key, value)

        # Handle template rendering if context provided
        if template_context:
            # Apply name from context if not explicitly overridden
            if "name" in template_context and "name" not in overrides:
                config.name = template_context["name"]

            # Render system prompts
            config.system_prompts = config.render_system_prompts(template_context)

        self.agents[actual_name] = config
        return actual_name

    @model_validator(mode="before")
    @classmethod
    def resolve_inheritance(cls, data: dict) -> dict:
        """Resolve agent inheritance chains."""
        nodes = data.get("agents", {})
        resolved: dict[str, dict] = {}
        seen: set[str] = set()

        def resolve_node(name: str) -> dict:
            if name in resolved:
                return resolved[name]

            if name in seen:
                msg = f"Circular inheritance detected: {name}"
                raise ValueError(msg)

            seen.add(name)
            config = (
                nodes[name].model_copy()
                if hasattr(nodes[name], "model_copy")
                else nodes[name].copy()
            )
            inherit = (
                config.get("inherits") if isinstance(config, dict) else config.inherits
            )
            if inherit:
                if inherit not in nodes:
                    msg = f"Parent agent {inherit} not found"
                    raise ValueError(msg)

                # Get resolved parent config
                parent = resolve_node(inherit)
                # Merge parent with child (child overrides parent)
                merged = parent.copy()
                merged.update(config)
                config = merged

            seen.remove(name)
            resolved[name] = config
            return config

        # Resolve all nodes
        for name in nodes:
            resolved[name] = resolve_node(name)

        # Update nodes with resolved configs
        data["agents"] = resolved
        return data

    @model_validator(mode="after")
    def set_instrument_libraries(self) -> Self:
        """Auto-set libraries to instrument based on used providers."""
        if (
            not self.observability.enabled
            or self.observability.instrument_libraries is not None
        ):
            return self
        self.observability.instrument_libraries = list(self.get_used_providers())
        return self

    @property
    def node_names(self) -> list[str]:
        """Get list of all agent and team names."""
        return list(self.agents.keys()) + list(self.teams.keys())

    @property
    def nodes(self) -> dict[str, Any]:
        """Get all agent and team configurations."""
        return {**self.agents, **self.teams}

    def get_mcp_servers(self) -> list[MCPServerConfig]:
        """Get processed MCP server configurations.

        Converts string entries to StdioMCPServerConfig configs by splitting
        into command and arguments.

        Returns:
            List of MCPServerConfig instances

        Raises:
            ValueError: If string entry is empty
        """
        configs: list[MCPServerConfig] = []

        for server in self.mcp_servers:
            match server:
                case str():
                    parts = server.split()
                    if not parts:
                        msg = "Empty MCP server command"
                        raise ValueError(msg)

                    configs.append(StdioMCPServerConfig(command=parts[0], args=parts[1:]))
                case BaseMCPServerConfig():
                    configs.append(server)

        return configs

    @cached_property
    def prompt_manager(self) -> PromptManager:
        """Get prompt manager for this manifest."""
        from llmling_agent.prompts.manager import PromptManager

        return PromptManager(self.prompts)

    # @model_validator(mode="after")
    # def validate_response_types(self) -> AgentsManifest:
    #     """Ensure all agent result_types exist in responses or are inline."""
    #     for agent_id, agent in self.agents.items():
    #         if (
    #             isinstance(agent.result_type, str)
    #             and agent.result_type not in self.responses
    #         ):
    #             msg = f"'{agent.result_type=}' for '{agent_id=}' not found in responses"
    #             raise ValueError(msg)
    #     return self

    def get_agent[TAgentDeps](
        self, name: str, deps: TAgentDeps | None = None
    ) -> AnyAgent[TAgentDeps, Any]:
        from llmling import RuntimeConfig

        from llmling_agent import Agent, AgentContext

        config = self.agents[name]
        # Create runtime without async context
        cfg = config.get_config()
        runtime = RuntimeConfig.from_config(cfg)

        # Create context with config path and capabilities
        context = AgentContext[TAgentDeps](
            node_name=name,
            data=deps,
            capabilities=config.capabilities,
            definition=self,
            config=config,
            runtime=runtime,
            # pool=self,
            # confirmation_callback=confirmation_callback,
        )

        sys_prompts = config.system_prompts.copy()
        # Library prompts
        if config.library_system_prompts:
            for prompt_ref in config.library_system_prompts:
                try:
                    content = self.prompt_manager.get_sync(prompt_ref)
                    sys_prompts.append(content)
                except Exception as e:
                    msg = f"Failed to load library prompt {prompt_ref!r} for agent {name}"
                    logger.exception(msg)
                    raise ValueError(msg) from e
        # Create agent with runtime and context
        agent = Agent[Any](
            runtime=runtime,
            context=context,
            provider=config.get_provider(),
            system_prompt=sys_prompts,
            name=name,
            description=config.description,
            retries=config.retries,
            session=config.get_session_config(),
            result_retries=config.result_retries,
            end_strategy=config.end_strategy,
            capabilities=config.capabilities,
            debug=config.debug,
            # name=config.name or name,
        )
        if result_type := self.get_result_type(name):
            return agent.to_structured(result_type)
        return agent

    def get_used_providers(self) -> set[str]:
        """Get all providers configured in this manifest."""
        providers = set[str]()

        for agent_config in self.agents.values():
            match agent_config.provider:
                case "pydantic_ai":
                    providers.add("pydantic_ai")
                case "litellm":
                    providers.add("litellm")
                case BaseProviderConfig():
                    providers.add(agent_config.provider.type)
        return providers

    @classmethod
    def from_file(cls, path: StrPath) -> Self:
        """Load agent configuration from YAML file.

        Args:
            path: Path to the configuration file

        Returns:
            Loaded agent definition

        Raises:
            ValueError: If loading fails
        """
        import yamling

        try:
            data = yamling.load_yaml_file(path, resolve_inherit=True)
            agent_def = cls.model_validate(data)
            # Update all agents with the config file path and ensure names
            agents = {
                name: config.model_copy(update={"config_file_path": str(path)})
                for name, config in agent_def.agents.items()
            }
            return agent_def.model_copy(update={"agents": agents})
        except Exception as exc:
            msg = f"Failed to load agent config from {path}"
            raise ValueError(msg) from exc

    @cached_property
    def pool(self) -> AgentPool:
        """Create an agent pool from this manifest.

        Returns:
            Configured agent pool
        """
        from llmling_agent import AgentPool

        return AgentPool(manifest=self)

    def get_result_type(self, agent_name: str) -> type[Any] | None:
        """Get the resolved result type for an agent.

        Returns None if no result type is configured.
        """
        agent_config = self.agents[agent_name]
        if not agent_config.result_type:
            return None
        logger.debug("Building response model for %r", agent_config.result_type)
        if isinstance(agent_config.result_type, str):
            response_def = self.responses[agent_config.result_type]
            return response_def.create_model()  # type: ignore
        return agent_config.result_type.create_model()  # type: ignore

INHERIT class-attribute instance-attribute

INHERIT: str | list[str] | None = None

Inheritance references.

agents class-attribute instance-attribute

agents: dict[str, AgentConfig] = Field(default_factory=dict)

Mapping of agent IDs to their configurations

conversion class-attribute instance-attribute

conversion: ConversionConfig = Field(default_factory=ConversionConfig)

Document conversion configuration.

jobs class-attribute instance-attribute

jobs: dict[str, Job] = Field(default_factory=dict)

Pre-defined jobs, ready to be used by nodes.

mcp_servers class-attribute instance-attribute

mcp_servers: list[str | MCPServerConfig] = Field(default_factory=list)

List of MCP server configurations:

These MCP servers are used to provide tools and other resources to the nodes.

node_names property

node_names: list[str]

Get list of all agent and team names.

nodes property

nodes: dict[str, Any]

Get all agent and team configurations.

observability class-attribute instance-attribute

observability: ObservabilityConfig = Field(default_factory=ObservabilityConfig)

Observability provider configuration.

pool cached property

pool: AgentPool

Create an agent pool from this manifest.

Returns:

Type Description
AgentPool

Configured agent pool

pool_server class-attribute instance-attribute

pool_server: PoolServerConfig = Field(default_factory=PoolServerConfig)

Pool server configuration.

This MCP server configuration is used for the pool MCP server, which exposes pool functionality to other applications / clients.

prompt_manager cached property

prompt_manager: PromptManager

Get prompt manager for this manifest.

resource_registry cached property

resource_registry: ResourceRegistry

Get registry with all configured resources.

resources class-attribute instance-attribute

resources: dict[str, ResourceConfig | str] = Field(default_factory=dict)

Resource configurations defining available filesystems.

Supports both full config and URI shorthand

resources: docs: "file://./docs" # shorthand data: # full config type: "source" uri: "s3://bucket/data" cached: true

responses class-attribute instance-attribute

responses: dict[str, ResponseDefinition] = Field(default_factory=dict)

Mapping of response names to their definitions

storage class-attribute instance-attribute

storage: StorageConfig = Field(default_factory=StorageConfig)

Storage provider configuration.

teams class-attribute instance-attribute

teams: dict[str, TeamConfig] = Field(default_factory=dict)

Mapping of team IDs to their configurations

ui class-attribute instance-attribute

ui: UIConfig = Field(default_factory=StdlibUIConfig)

UI configuration.

clone_agent_config

clone_agent_config(
    name: str,
    new_name: str | None = None,
    *,
    template_context: dict[str, Any] | None = None,
    **overrides: Any,
) -> str

Create a copy of an agent configuration.

Parameters:

Name Type Description Default
name str

Name of agent to clone

required
new_name str | None

Optional new name (auto-generated if None)

None
template_context dict[str, Any] | None

Variables for template rendering

None
**overrides Any

Configuration overrides for the clone

{}

Returns:

Type Description
str

Name of the new agent

Raises:

Type Description
KeyError

If original agent not found

ValueError

If new name already exists or if overrides invalid

Source code in src/llmling_agent/models/manifest.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def clone_agent_config(
    self,
    name: str,
    new_name: str | None = None,
    *,
    template_context: dict[str, Any] | None = None,
    **overrides: Any,
) -> str:
    """Create a copy of an agent configuration.

    Args:
        name: Name of agent to clone
        new_name: Optional new name (auto-generated if None)
        template_context: Variables for template rendering
        **overrides: Configuration overrides for the clone

    Returns:
        Name of the new agent

    Raises:
        KeyError: If original agent not found
        ValueError: If new name already exists or if overrides invalid
    """
    if name not in self.agents:
        msg = f"Agent {name} not found"
        raise KeyError(msg)

    actual_name = new_name or f"{name}_copy_{len(self.agents)}"
    if actual_name in self.agents:
        msg = f"Agent {actual_name} already exists"
        raise ValueError(msg)

    # Deep copy the configuration
    config = self.agents[name].model_copy(deep=True)

    # Apply overrides
    for key, value in overrides.items():
        if not hasattr(config, key):
            msg = f"Invalid override: {key}"
            raise ValueError(msg)
        setattr(config, key, value)

    # Handle template rendering if context provided
    if template_context:
        # Apply name from context if not explicitly overridden
        if "name" in template_context and "name" not in overrides:
            config.name = template_context["name"]

        # Render system prompts
        config.system_prompts = config.render_system_prompts(template_context)

    self.agents[actual_name] = config
    return actual_name

from_file classmethod

from_file(path: StrPath) -> Self

Load agent configuration from YAML file.

Parameters:

Name Type Description Default
path StrPath

Path to the configuration file

required

Returns:

Type Description
Self

Loaded agent definition

Raises:

Type Description
ValueError

If loading fails

Source code in src/llmling_agent/models/manifest.py
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
@classmethod
def from_file(cls, path: StrPath) -> Self:
    """Load agent configuration from YAML file.

    Args:
        path: Path to the configuration file

    Returns:
        Loaded agent definition

    Raises:
        ValueError: If loading fails
    """
    import yamling

    try:
        data = yamling.load_yaml_file(path, resolve_inherit=True)
        agent_def = cls.model_validate(data)
        # Update all agents with the config file path and ensure names
        agents = {
            name: config.model_copy(update={"config_file_path": str(path)})
            for name, config in agent_def.agents.items()
        }
        return agent_def.model_copy(update={"agents": agents})
    except Exception as exc:
        msg = f"Failed to load agent config from {path}"
        raise ValueError(msg) from exc

get_mcp_servers

get_mcp_servers() -> list[MCPServerConfig]

Get processed MCP server configurations.

Converts string entries to StdioMCPServerConfig configs by splitting into command and arguments.

Returns:

Type Description
list[MCPServerConfig]

List of MCPServerConfig instances

Raises:

Type Description
ValueError

If string entry is empty

Source code in src/llmling_agent/models/manifest.py
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
def get_mcp_servers(self) -> list[MCPServerConfig]:
    """Get processed MCP server configurations.

    Converts string entries to StdioMCPServerConfig configs by splitting
    into command and arguments.

    Returns:
        List of MCPServerConfig instances

    Raises:
        ValueError: If string entry is empty
    """
    configs: list[MCPServerConfig] = []

    for server in self.mcp_servers:
        match server:
            case str():
                parts = server.split()
                if not parts:
                    msg = "Empty MCP server command"
                    raise ValueError(msg)

                configs.append(StdioMCPServerConfig(command=parts[0], args=parts[1:]))
            case BaseMCPServerConfig():
                configs.append(server)

    return configs

get_result_type

get_result_type(agent_name: str) -> type[Any] | None

Get the resolved result type for an agent.

Returns None if no result type is configured.

Source code in src/llmling_agent/models/manifest.py
473
474
475
476
477
478
479
480
481
482
483
484
485
def get_result_type(self, agent_name: str) -> type[Any] | None:
    """Get the resolved result type for an agent.

    Returns None if no result type is configured.
    """
    agent_config = self.agents[agent_name]
    if not agent_config.result_type:
        return None
    logger.debug("Building response model for %r", agent_config.result_type)
    if isinstance(agent_config.result_type, str):
        response_def = self.responses[agent_config.result_type]
        return response_def.create_model()  # type: ignore
    return agent_config.result_type.create_model()  # type: ignore

get_used_providers

get_used_providers() -> set[str]

Get all providers configured in this manifest.

Source code in src/llmling_agent/models/manifest.py
420
421
422
423
424
425
426
427
428
429
430
431
432
def get_used_providers(self) -> set[str]:
    """Get all providers configured in this manifest."""
    providers = set[str]()

    for agent_config in self.agents.values():
        match agent_config.provider:
            case "pydantic_ai":
                providers.add("pydantic_ai")
            case "litellm":
                providers.add("litellm")
            case BaseProviderConfig():
                providers.add(agent_config.provider.type)
    return providers

normalize_workers classmethod

normalize_workers(data: dict[str, Any]) -> dict[str, Any]

Convert string workers to appropriate WorkerConfig for all agents.

Source code in src/llmling_agent/models/manifest.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
@model_validator(mode="before")
@classmethod
def normalize_workers(cls, data: dict[str, Any]) -> dict[str, Any]:
    """Convert string workers to appropriate WorkerConfig for all agents."""
    teams = data.get("teams", {})
    agents = data.get("agents", {})

    # Process workers for all agents that have them
    for agent_name, agent_config in agents.items():
        if isinstance(agent_config, dict):
            workers = agent_config.get("workers", [])
        else:
            workers = agent_config.workers

        if workers:
            normalized: list[BaseWorkerConfig] = []

            for worker in workers:
                match worker:
                    case str() as name:
                        # Determine type based on presence in teams/agents
                        if name in teams:
                            normalized.append(TeamWorkerConfig(name=name))
                        elif name in agents:
                            normalized.append(AgentWorkerConfig(name=name))
                        else:
                            # Default to agent if type can't be determined
                            normalized.append(AgentWorkerConfig(name=name))

                    case dict() as config:
                        # If type is explicitly specified, use it
                        if worker_type := config.get("type"):
                            match worker_type:
                                case "team":
                                    normalized.append(TeamWorkerConfig(**config))
                                case "agent":
                                    normalized.append(AgentWorkerConfig(**config))
                                case _:
                                    msg = f"Invalid worker type: {worker_type}"
                                    raise ValueError(msg)
                        else:
                            # Determine type based on worker name
                            worker_name = config.get("name")
                            if not worker_name:
                                msg = "Worker config missing name"
                                raise ValueError(msg)

                            if worker_name in teams:
                                normalized.append(TeamWorkerConfig(**config))
                            else:
                                normalized.append(AgentWorkerConfig(**config))

                    case BaseWorkerConfig():  # Already normalized
                        normalized.append(worker)

                    case _:
                        msg = f"Invalid worker configuration: {worker}"
                        raise ValueError(msg)

            if isinstance(agent_config, dict):
                agent_config["workers"] = normalized
            else:
                # Need to create a new dict with updated workers
                agent_dict = agent_config.model_dump()
                agent_dict["workers"] = normalized
                agents[agent_name] = agent_dict

    return data

resolve_inheritance classmethod

resolve_inheritance(data: dict) -> dict

Resolve agent inheritance chains.

Source code in src/llmling_agent/models/manifest.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
@model_validator(mode="before")
@classmethod
def resolve_inheritance(cls, data: dict) -> dict:
    """Resolve agent inheritance chains."""
    nodes = data.get("agents", {})
    resolved: dict[str, dict] = {}
    seen: set[str] = set()

    def resolve_node(name: str) -> dict:
        if name in resolved:
            return resolved[name]

        if name in seen:
            msg = f"Circular inheritance detected: {name}"
            raise ValueError(msg)

        seen.add(name)
        config = (
            nodes[name].model_copy()
            if hasattr(nodes[name], "model_copy")
            else nodes[name].copy()
        )
        inherit = (
            config.get("inherits") if isinstance(config, dict) else config.inherits
        )
        if inherit:
            if inherit not in nodes:
                msg = f"Parent agent {inherit} not found"
                raise ValueError(msg)

            # Get resolved parent config
            parent = resolve_node(inherit)
            # Merge parent with child (child overrides parent)
            merged = parent.copy()
            merged.update(config)
            config = merged

        seen.remove(name)
        resolved[name] = config
        return config

    # Resolve all nodes
    for name in nodes:
        resolved[name] = resolve_node(name)

    # Update nodes with resolved configs
    data["agents"] = resolved
    return data

set_instrument_libraries

set_instrument_libraries() -> Self

Auto-set libraries to instrument based on used providers.

Source code in src/llmling_agent/models/manifest.py
297
298
299
300
301
302
303
304
305
306
@model_validator(mode="after")
def set_instrument_libraries(self) -> Self:
    """Auto-set libraries to instrument based on used providers."""
    if (
        not self.observability.enabled
        or self.observability.instrument_libraries is not None
    ):
        return self
    self.observability.instrument_libraries = list(self.get_used_providers())
    return self

Show source on GitHub