Skip to content

Types API Reference

Type definitions for pydantic-ai-skills.

Overview

The package uses dataclasses for type-safe skill representation:

  • Skill - Complete skill with metadata, resources, and scripts
  • SkillResource - Resource file (content) or callable resource within a skill
  • SkillScript - Executable script (file or callable function) within a skill
  • SkillWrapper - Generic decorator return type for @toolset.skill() supporting attachment of resources and scripts

File-Based vs Programmatic

These types support both file-based skills (loaded from directories) and programmatic skills (created in Python code). For file-based skills, see Creating Skills. For programmatic skills, see Programmatic Skills. For advanced patterns, see Advanced Features.

Skill Class

A skill instance with metadata, content, resources, and scripts.

Can be created programmatically or loaded from filesystem directories.

Example - Programmatic skill with decorators
from pydantic_ai import RunContext
from pydantic_ai.toolsets.skills import Skill, SkillResource

# Create a skill (uri is optional and only for file-based skills)
my_skill = Skill(
    name='hr-analytics-skill',
    description='Skill for HR analytics',
    content='Use this skill for HR data analysis...',
    resources=[
        SkillResource(name='table-schemas', content='Schema definitions...')
    ]
)

# Add callable resources
@my_skill.resource
def get_db_context() -> str:
    return "Dynamic database context."

@my_skill.resource
async def get_samples(ctx: RunContext[MyDeps]) -> str:
    return await ctx.deps.get_samples()

# Add callable scripts
@my_skill.script
async def load_dataset(ctx: RunContext[MyDeps]) -> str:
    await ctx.deps.load_data()
    return 'Dataset loaded.'

@my_skill.script
async def run_query(ctx: RunContext[MyDeps], query: str) -> str:
    result = await ctx.deps.db.execute(query)
    return str(result)

Attributes:

Name Type Description
name str

Skill name.

description str

Brief description of what the skill does.

content str

Main instructional content.

license str | None

Optional license information.

compatibility str | None

Optional environment requirements (max 500 chars).

resources list[SkillResource]

List of resources (files or callables).

scripts list[SkillScript]

List of scripts (functions or file-based).

uri str | None

URI for the skill's base location. When not provided, a skill://{name} (scheme-based URI) is automatically assigned for internal reference. For filesystem-based skills, this is explicitly set by the filesystem discovery/loading utilities to the resolved directory path; it can also be overridden explicitly when constructing a Skill.

metadata dict[str, Any] | None

Additional metadata fields.

Source code in pydantic_ai_skills/types.py
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
@dataclass
class Skill:
    """A skill instance with metadata, content, resources, and scripts.

    Can be created programmatically or loaded from filesystem directories.

    Example - Programmatic skill with decorators:
        ```python
        from pydantic_ai import RunContext
        from pydantic_ai.toolsets.skills import Skill, SkillResource

        # Create a skill (uri is optional and only for file-based skills)
        my_skill = Skill(
            name='hr-analytics-skill',
            description='Skill for HR analytics',
            content='Use this skill for HR data analysis...',
            resources=[
                SkillResource(name='table-schemas', content='Schema definitions...')
            ]
        )

        # Add callable resources
        @my_skill.resource
        def get_db_context() -> str:
            return "Dynamic database context."

        @my_skill.resource
        async def get_samples(ctx: RunContext[MyDeps]) -> str:
            return await ctx.deps.get_samples()

        # Add callable scripts
        @my_skill.script
        async def load_dataset(ctx: RunContext[MyDeps]) -> str:
            await ctx.deps.load_data()
            return 'Dataset loaded.'

        @my_skill.script
        async def run_query(ctx: RunContext[MyDeps], query: str) -> str:
            result = await ctx.deps.db.execute(query)
            return str(result)
        ```

    Attributes:
        name: Skill name.
        description: Brief description of what the skill does.
        content: Main instructional content.
        license: Optional license information.
        compatibility: Optional environment requirements (max 500 chars).
        resources: List of resources (files or callables).
        scripts: List of scripts (functions or file-based).
        uri: URI for the skill's base location. When not provided, a ``skill://{name}``
            (scheme-based URI) is automatically assigned for internal reference. For filesystem-based skills,
            this is explicitly set by the filesystem discovery/loading utilities to the resolved directory path;
            it can also be overridden explicitly when constructing a ``Skill``.
        metadata: Additional metadata fields.
    """

    name: str
    description: str
    content: str
    license: str | None = None
    compatibility: str | None = None
    resources: list[SkillResource] = field(default_factory=list)
    scripts: list[SkillScript] = field(default_factory=list)
    uri: str | None = None
    metadata: dict[str, Any] | None = None

    def __post_init__(self) -> None:
        """Auto-assign a skill:// URI for any Skill instantiated with no URI.

        This fires for any ``Skill`` where ``uri=None`` at construction time, including
        programmatic skills. Filesystem-based skills have their ``uri`` set explicitly
        by the filesystem discovery/loading utilities (overwriting this default), so the
        auto-assigned value is effectively a transient default for those cases.
        The resulting URI follows the convention: ``skill://{name}``.
        """
        if self.uri is None:
            self.uri = f'skill://{self.name}'

    def resource(
        self,
        func: Callable[..., Any] | None = None,
        *,
        name: str | None = None,
        description: str | None = None,
        takes_ctx: bool | None = None,
        docstring_format: _function_schema.DocstringFormat = 'auto',
        schema_generator: type[GenerateJsonSchema] | None = None,
    ) -> Callable[..., Any] | Callable[[Callable[..., Any]], Callable[..., Any]]:
        """Decorator to register a callable as a skill resource.

        The decorated function can optionally take RunContext as its first argument
        for accessing dependencies. This is auto-detected if not specified.

        Example:
            ```python
            @my_skill.resource
            def get_context() -> str:
                return "Static context"

            @my_skill.resource
            async def get_data(ctx: RunContext[MyDeps]) -> str:
                return await ctx.deps.fetch_data()
            ```

        Args:
            func: The function to register as a resource.
            name: Resource name (defaults to function name).
            description: Resource description (inferred from docstring if not provided).
            takes_ctx: Whether function takes RunContext (auto-detected if None).
            docstring_format: Format of the docstring ('auto', 'google', 'numpy', 'sphinx').
            schema_generator: Custom JSON schema generator class.

        Returns:
            The original function (allows use as decorator).
        """

        def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
            resource_name = name or f.__name__
            gen = schema_generator or GenerateToolJsonSchema
            func_schema = _function_schema.function_schema(
                f,
                schema_generator=gen,
                takes_ctx=takes_ctx,
                docstring_format=docstring_format,
                require_parameter_descriptions=False,
            )
            resource = SkillResource(
                name=resource_name,
                description=description or func_schema.description,
                function=f,
                takes_ctx=func_schema.takes_ctx,
                function_schema=func_schema,
            )
            self.resources.append(resource)
            return f

        if func is None:
            # Called with arguments: @my_skill.resource(name="custom")
            return decorator
        else:
            # Called without arguments: @my_skill.resource
            return decorator(func)

    def script(
        self,
        func: Callable[..., Any] | None = None,
        *,
        name: str | None = None,
        description: str | None = None,
        takes_ctx: bool | None = None,
        docstring_format: _function_schema.DocstringFormat = 'auto',
        schema_generator: type[GenerateJsonSchema] | None = None,
    ) -> Callable[..., Any]:
        """Decorator to register a callable as a skill script.

        The decorated function can optionally take RunContext as its first argument
        for accessing dependencies. This is auto-detected if not specified.

        Scripts accept named arguments (kwargs) matching their function signature.

        Example:
            ```python
            @my_skill.script
            async def load_data(ctx: RunContext[MyDeps]) -> str:
                await ctx.deps.load()
                return 'Loaded'

            @my_skill.script
            async def run_query(ctx: RunContext[MyDeps], query: str, limit: int = 10) -> str:
                result = await ctx.deps.db.execute(query, limit)
                return str(result)
            ```

        Args:
            func: The function to register as a script.
            name: Script name (defaults to function name).
            description: Script description (inferred from docstring if not provided).
            takes_ctx: Whether function takes RunContext (auto-detected if None).
            docstring_format: Format of the docstring ('auto', 'google', 'numpy', 'sphinx').
            schema_generator: Custom JSON schema generator class.

        Returns:
            The original function (allows use as decorator).
        """

        def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
            script_name = name or f.__name__
            gen = schema_generator or GenerateToolJsonSchema
            func_schema = _function_schema.function_schema(
                f,
                schema_generator=gen,
                takes_ctx=takes_ctx,
                docstring_format=docstring_format,
                require_parameter_descriptions=False,
            )
            script = SkillScript(
                name=script_name,
                description=description or func_schema.description,
                function=f,
                takes_ctx=func_schema.takes_ctx,
                function_schema=func_schema,
                skill_name=self.name,
            )
            self.scripts.append(script)
            return f

        if func is None:
            # Called with arguments: @my_skill.script(name="custom")
            return decorator
        else:
            # Called without arguments: @my_skill.script
            return decorator(func)

__post_init__

__post_init__() -> None

Auto-assign a skill:// URI for any Skill instantiated with no URI.

This fires for any Skill where uri=None at construction time, including programmatic skills. Filesystem-based skills have their uri set explicitly by the filesystem discovery/loading utilities (overwriting this default), so the auto-assigned value is effectively a transient default for those cases. The resulting URI follows the convention: skill://{name}.

Source code in pydantic_ai_skills/types.py
251
252
253
254
255
256
257
258
259
260
261
def __post_init__(self) -> None:
    """Auto-assign a skill:// URI for any Skill instantiated with no URI.

    This fires for any ``Skill`` where ``uri=None`` at construction time, including
    programmatic skills. Filesystem-based skills have their ``uri`` set explicitly
    by the filesystem discovery/loading utilities (overwriting this default), so the
    auto-assigned value is effectively a transient default for those cases.
    The resulting URI follows the convention: ``skill://{name}``.
    """
    if self.uri is None:
        self.uri = f'skill://{self.name}'

resource

resource(func: Callable[..., Any] | None = None, *, name: str | None = None, description: str | None = None, takes_ctx: bool | None = None, docstring_format: DocstringFormat = 'auto', schema_generator: type[GenerateJsonSchema] | None = None) -> Callable[..., Any] | Callable[[Callable[..., Any]], Callable[..., Any]]

Decorator to register a callable as a skill resource.

The decorated function can optionally take RunContext as its first argument for accessing dependencies. This is auto-detected if not specified.

Example
@my_skill.resource
def get_context() -> str:
    return "Static context"

@my_skill.resource
async def get_data(ctx: RunContext[MyDeps]) -> str:
    return await ctx.deps.fetch_data()

Parameters:

Name Type Description Default
func Callable[..., Any] | None

The function to register as a resource.

None
name str | None

Resource name (defaults to function name).

None
description str | None

Resource description (inferred from docstring if not provided).

None
takes_ctx bool | None

Whether function takes RunContext (auto-detected if None).

None
docstring_format DocstringFormat

Format of the docstring ('auto', 'google', 'numpy', 'sphinx').

'auto'
schema_generator type[GenerateJsonSchema] | None

Custom JSON schema generator class.

None

Returns:

Type Description
Callable[..., Any] | Callable[[Callable[..., Any]], Callable[..., Any]]

The original function (allows use as decorator).

Source code in pydantic_ai_skills/types.py
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
def resource(
    self,
    func: Callable[..., Any] | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    takes_ctx: bool | None = None,
    docstring_format: _function_schema.DocstringFormat = 'auto',
    schema_generator: type[GenerateJsonSchema] | None = None,
) -> Callable[..., Any] | Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator to register a callable as a skill resource.

    The decorated function can optionally take RunContext as its first argument
    for accessing dependencies. This is auto-detected if not specified.

    Example:
        ```python
        @my_skill.resource
        def get_context() -> str:
            return "Static context"

        @my_skill.resource
        async def get_data(ctx: RunContext[MyDeps]) -> str:
            return await ctx.deps.fetch_data()
        ```

    Args:
        func: The function to register as a resource.
        name: Resource name (defaults to function name).
        description: Resource description (inferred from docstring if not provided).
        takes_ctx: Whether function takes RunContext (auto-detected if None).
        docstring_format: Format of the docstring ('auto', 'google', 'numpy', 'sphinx').
        schema_generator: Custom JSON schema generator class.

    Returns:
        The original function (allows use as decorator).
    """

    def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
        resource_name = name or f.__name__
        gen = schema_generator or GenerateToolJsonSchema
        func_schema = _function_schema.function_schema(
            f,
            schema_generator=gen,
            takes_ctx=takes_ctx,
            docstring_format=docstring_format,
            require_parameter_descriptions=False,
        )
        resource = SkillResource(
            name=resource_name,
            description=description or func_schema.description,
            function=f,
            takes_ctx=func_schema.takes_ctx,
            function_schema=func_schema,
        )
        self.resources.append(resource)
        return f

    if func is None:
        # Called with arguments: @my_skill.resource(name="custom")
        return decorator
    else:
        # Called without arguments: @my_skill.resource
        return decorator(func)

script

script(func: Callable[..., Any] | None = None, *, name: str | None = None, description: str | None = None, takes_ctx: bool | None = None, docstring_format: DocstringFormat = 'auto', schema_generator: type[GenerateJsonSchema] | None = None) -> Callable[..., Any]

Decorator to register a callable as a skill script.

The decorated function can optionally take RunContext as its first argument for accessing dependencies. This is auto-detected if not specified.

Scripts accept named arguments (kwargs) matching their function signature.

Example
@my_skill.script
async def load_data(ctx: RunContext[MyDeps]) -> str:
    await ctx.deps.load()
    return 'Loaded'

@my_skill.script
async def run_query(ctx: RunContext[MyDeps], query: str, limit: int = 10) -> str:
    result = await ctx.deps.db.execute(query, limit)
    return str(result)

Parameters:

Name Type Description Default
func Callable[..., Any] | None

The function to register as a script.

None
name str | None

Script name (defaults to function name).

None
description str | None

Script description (inferred from docstring if not provided).

None
takes_ctx bool | None

Whether function takes RunContext (auto-detected if None).

None
docstring_format DocstringFormat

Format of the docstring ('auto', 'google', 'numpy', 'sphinx').

'auto'
schema_generator type[GenerateJsonSchema] | None

Custom JSON schema generator class.

None

Returns:

Type Description
Callable[..., Any]

The original function (allows use as decorator).

Source code in pydantic_ai_skills/types.py
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
def script(
    self,
    func: Callable[..., Any] | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    takes_ctx: bool | None = None,
    docstring_format: _function_schema.DocstringFormat = 'auto',
    schema_generator: type[GenerateJsonSchema] | None = None,
) -> Callable[..., Any]:
    """Decorator to register a callable as a skill script.

    The decorated function can optionally take RunContext as its first argument
    for accessing dependencies. This is auto-detected if not specified.

    Scripts accept named arguments (kwargs) matching their function signature.

    Example:
        ```python
        @my_skill.script
        async def load_data(ctx: RunContext[MyDeps]) -> str:
            await ctx.deps.load()
            return 'Loaded'

        @my_skill.script
        async def run_query(ctx: RunContext[MyDeps], query: str, limit: int = 10) -> str:
            result = await ctx.deps.db.execute(query, limit)
            return str(result)
        ```

    Args:
        func: The function to register as a script.
        name: Script name (defaults to function name).
        description: Script description (inferred from docstring if not provided).
        takes_ctx: Whether function takes RunContext (auto-detected if None).
        docstring_format: Format of the docstring ('auto', 'google', 'numpy', 'sphinx').
        schema_generator: Custom JSON schema generator class.

    Returns:
        The original function (allows use as decorator).
    """

    def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
        script_name = name or f.__name__
        gen = schema_generator or GenerateToolJsonSchema
        func_schema = _function_schema.function_schema(
            f,
            schema_generator=gen,
            takes_ctx=takes_ctx,
            docstring_format=docstring_format,
            require_parameter_descriptions=False,
        )
        script = SkillScript(
            name=script_name,
            description=description or func_schema.description,
            function=f,
            takes_ctx=func_schema.takes_ctx,
            function_schema=func_schema,
            skill_name=self.name,
        )
        self.scripts.append(script)
        return f

    if func is None:
        # Called with arguments: @my_skill.script(name="custom")
        return decorator
    else:
        # Called without arguments: @my_skill.script
        return decorator(func)

Key Attributes

Attribute Type Description
name str Unique skill identifier. Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$, max 64 chars.
description str Brief description of the skill. Max 1024 characters.
content str Main skill instructions in markdown format.
resources list[SkillResource] \| None Additional resources (documentation, schemas, data).
scripts list[SkillScript] \| None Executable scripts (file-based or callable functions).
uri str \| None Base URI/path. Set for file-based skills, None for programmatic.
metadata dict[str, Any] \| None Custom metadata (version, author, license, compatibility, etc.).

Methods

Method Description
@resource Decorator to attach a callable resource to the skill.
@script Decorator to attach a callable script to the skill.

SkillResource Class

A skill resource: static content or callable that generates content.

Attributes:

Name Type Description
name str

Resource name (e.g., "FORMS.md" or "get_samples").

description str | None

Description of what the resource provides.

content str | None

Static content string.

function Callable[..., Any | Awaitable[Any]] | None

Callable that generates content dynamically.

takes_ctx bool

Whether the function takes RunContext as first argument.

function_schema FunctionSchema | None

Function schema for callable resources (auto-generated).

uri str | None

Optional URI string for file-based resources (internal use).

Source code in pydantic_ai_skills/types.py
 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
@dataclass
class SkillResource:
    """A skill resource: static content or callable that generates content.

    Attributes:
        name: Resource name (e.g., "FORMS.md" or "get_samples").
        description: Description of what the resource provides.
        content: Static content string.
        function: Callable that generates content dynamically.
        takes_ctx: Whether the function takes RunContext as first argument.
        function_schema: Function schema for callable resources (auto-generated).
        uri: Optional URI string for file-based resources (internal use).
    """

    name: str
    description: str | None = None
    content: str | None = None
    function: Callable[..., Any | Awaitable[Any]] | None = None
    takes_ctx: bool = False
    function_schema: _function_schema.FunctionSchema | None = None
    uri: str | None = None

    def __post_init__(self) -> None:
        """Validate that resource has either content, function, or uri.

        For programmatic resources, content or function is required.
        For file-based resources (subclasses), uri is sufficient.
        """
        if self.content is None and self.function is None and self.uri is None:
            raise ValueError(f"Resource '{self.name}' must have either content, function, or uri")
        if self.function is not None and self.function_schema is None:
            raise ValueError(f"Resource '{self.name}' with function must have function_schema")

    async def load(self, ctx: Any, args: dict[str, Any] | None = None) -> Any:
        """Load resource content.

        File-based subclasses override to load from disk.

        Args:
            ctx: RunContext for accessing dependencies.
            args: Named arguments for callable resources.

        Returns:
            Resource content (any type).

        Raises:
            ValueError: If resource has no content or function.
        """
        if self.function and self.function_schema:
            return await self.function_schema.call(args or {}, ctx)
        elif self.content:
            return self.content
        else:
            raise ValueError(f"Resource '{self.name}' has no content or function")

__post_init__

__post_init__() -> None

Validate that resource has either content, function, or uri.

For programmatic resources, content or function is required. For file-based resources (subclasses), uri is sufficient.

Source code in pydantic_ai_skills/types.py
 94
 95
 96
 97
 98
 99
100
101
102
103
def __post_init__(self) -> None:
    """Validate that resource has either content, function, or uri.

    For programmatic resources, content or function is required.
    For file-based resources (subclasses), uri is sufficient.
    """
    if self.content is None and self.function is None and self.uri is None:
        raise ValueError(f"Resource '{self.name}' must have either content, function, or uri")
    if self.function is not None and self.function_schema is None:
        raise ValueError(f"Resource '{self.name}' with function must have function_schema")

load async

load(ctx: Any, args: dict[str, Any] | None = None) -> Any

Load resource content.

File-based subclasses override to load from disk.

Parameters:

Name Type Description Default
ctx Any

RunContext for accessing dependencies.

required
args dict[str, Any] | None

Named arguments for callable resources.

None

Returns:

Type Description
Any

Resource content (any type).

Raises:

Type Description
ValueError

If resource has no content or function.

Source code in pydantic_ai_skills/types.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
async def load(self, ctx: Any, args: dict[str, Any] | None = None) -> Any:
    """Load resource content.

    File-based subclasses override to load from disk.

    Args:
        ctx: RunContext for accessing dependencies.
        args: Named arguments for callable resources.

    Returns:
        Resource content (any type).

    Raises:
        ValueError: If resource has no content or function.
    """
    if self.function and self.function_schema:
        return await self.function_schema.call(args or {}, ctx)
    elif self.content:
        return self.content
    else:
        raise ValueError(f"Resource '{self.name}' has no content or function")

Key Attributes

Attribute Type Description
name str Resource identifier within the skill.
description str \| None Optional description of the resource.
content str \| None Static content (for static resources or file-based).
function Callable \| None Callable function (for programmatic dynamic resources).
takes_ctx bool Whether function accepts RunContext as first parameter. Auto-detected.
function_schema FunctionSchema \| None JSON schema for function parameters (Pydantic AI generated).
uri str \| None File URI for file-based resources.

File-Based Variants

FileSkillResource - Loads resource content from file: - Auto-detects file type by extension - Supports: .md, .json, .yaml, .yml, .csv, .xml, .txt - JSON/YAML files are parsed; others returned as text - Automatically validates path traversal safety

SkillScript Class

An executable script within a skill.

Can be programmatic (function) or file-based (executed via subprocess).

Attributes:

Name Type Description
name str

Script name (includes .py extension for file-based).

description str | None

Description of what the script does.

function Callable[..., Any] | None

Callable that implements the script (programmatic).

takes_ctx bool

Whether the function takes RunContext as first argument.

function_schema FunctionSchema | None

Function schema for callable scripts (auto-generated).

uri str | None

Optional URI for file-based scripts (internal use).

skill_name str | None

Optional parent skill name (internal use).

Source code in pydantic_ai_skills/types.py
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
@dataclass
class SkillScript:
    """An executable script within a skill.

    Can be programmatic (function) or file-based (executed via subprocess).

    Attributes:
        name: Script name (includes .py extension for file-based).
        description: Description of what the script does.
        function: Callable that implements the script (programmatic).
        takes_ctx: Whether the function takes RunContext as first argument.
        function_schema: Function schema for callable scripts (auto-generated).
        uri: Optional URI for file-based scripts (internal use).
        skill_name: Optional parent skill name (internal use).
    """

    name: str
    description: str | None = None
    function: Callable[..., Any] | None = None
    takes_ctx: bool = False
    function_schema: _function_schema.FunctionSchema | None = None
    uri: str | None = None
    skill_name: str | None = None

    def __post_init__(self) -> None:
        """Validate that script has either function or uri.

        For programmatic scripts, function is required.
        For file-based scripts (subclasses), uri is sufficient.
        """
        if self.function is None and self.uri is None:
            raise ValueError(f"Script '{self.name}' must have either function or uri")
        if self.function is not None and self.function_schema is None:
            raise ValueError(f"Script '{self.name}' with function must have function_schema")

    async def run(self, ctx: Any, args: dict[str, Any] | None = None) -> Any:
        """Execute the script.

        File-based subclasses override to execute via subprocess.

        Args:
            ctx: RunContext for accessing dependencies.
            args: Named arguments for the script.

        Returns:
            Script output (any type).

        Raises:
            ValueError: If script has no function.
        """
        if self.function and self.function_schema:
            return await self.function_schema.call(args or {}, ctx)
        else:
            raise ValueError(f"Script '{self.name}' has no function")

__post_init__

__post_init__() -> None

Validate that script has either function or uri.

For programmatic scripts, function is required. For file-based scripts (subclasses), uri is sufficient.

Source code in pydantic_ai_skills/types.py
152
153
154
155
156
157
158
159
160
161
def __post_init__(self) -> None:
    """Validate that script has either function or uri.

    For programmatic scripts, function is required.
    For file-based scripts (subclasses), uri is sufficient.
    """
    if self.function is None and self.uri is None:
        raise ValueError(f"Script '{self.name}' must have either function or uri")
    if self.function is not None and self.function_schema is None:
        raise ValueError(f"Script '{self.name}' with function must have function_schema")

run async

run(ctx: Any, args: dict[str, Any] | None = None) -> Any

Execute the script.

File-based subclasses override to execute via subprocess.

Parameters:

Name Type Description Default
ctx Any

RunContext for accessing dependencies.

required
args dict[str, Any] | None

Named arguments for the script.

None

Returns:

Type Description
Any

Script output (any type).

Raises:

Type Description
ValueError

If script has no function.

Source code in pydantic_ai_skills/types.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
async def run(self, ctx: Any, args: dict[str, Any] | None = None) -> Any:
    """Execute the script.

    File-based subclasses override to execute via subprocess.

    Args:
        ctx: RunContext for accessing dependencies.
        args: Named arguments for the script.

    Returns:
        Script output (any type).

    Raises:
        ValueError: If script has no function.
    """
    if self.function and self.function_schema:
        return await self.function_schema.call(args or {}, ctx)
    else:
        raise ValueError(f"Script '{self.name}' has no function")

Key Attributes

Attribute Type Description
name str Script identifier within the skill.
description str \| None Optional description of what the script does.
function Callable \| None Callable Python function (for programmatic scripts).
takes_ctx bool Whether function accepts RunContext as first parameter. Auto-detected.
function_schema FunctionSchema \| None JSON schema for function parameters.
uri str \| None File URI for file-based scripts.
skill_name str \| None Parent skill name (auto-set for file-based scripts).

File-Based Variants

LocalSkillScriptExecutor - Executes file-based scripts: - Runs Python scripts via subprocess - Converts dict arguments to CLI flags: {"query": "test"}--query test - Combines stdout/stderr - Default timeout: 30 seconds - Execution directory: script's parent folder

CallableSkillScriptExecutor - Executes callable functions: - Runs Python functions directly - Passes dict arguments as function kwargs - Supports async functions - Receives RunContext for dependency access

SkillWrapper[T] Class

Generic type returned by @toolset.skill() decorator that enables type-safe dependency injection and attachment of resources/scripts.

Generic Parameter: T - The dependency type available in RunContext[T]

Decorators

@skill.resource
def my_resource() -> str:
    """Attach a callable resource."""
    return "..."

@skill.resource
async def context_resource(ctx: RunContext[MyDeps]) -> str:
    """Resource with access to dependencies."""
    return str(ctx.deps)

@skill.script
def my_script() -> str:
    """Attach a callable script."""
    return "..."

@skill.script
async def context_script(ctx: RunContext[MyDeps], param: str) -> str:
    """Script with dependencies and parameters."""
    return f"Executed with {param}"

Utility Functions

normalize_skill_name()

def normalize_skill_name(name: str) -> str:
    """Convert a name to valid skill name format.

    Rules:
    - Convert underscores to hyphens
    - Convert to lowercase
    - Ensure matches ^[a-z0-9]+(-[a-z0-9]+)*$

    Args:
        name: Input string (e.g., "MySkillName", "my_skill_name")

    Returns:
        Normalized skill name (e.g., "my-skill-name")
    """

Type Structures

Skill Structure

Skill
├── name: str                              # Unique identifier (lowercase, hyphens only)
├── description: str                       # Brief description (max 1024 chars)
├── content: str                           # Main instructions (markdown)
├── resources: list[SkillResource] | None  # Additional resources
├── scripts: list[SkillScript] | None      # Executable scripts
├── uri: str | None                        # Base path (file-based: set, programmatic: None)
└── metadata: dict[str, Any] | None        # Custom fields (version, author, license, etc.)

SkillResource Structure

SkillResource
├── name: str                      # Resource identifier
├── description: str | None        # Optional description
├── content: str | None            # Static content
├── function: Callable | None      # Callable (for dynamic resources)
├── takes_ctx: bool                # Requires RunContext
├── function_schema: FunctionSchema| None
└── uri: str | None                # File path (file-based only)

SkillScript Structure

SkillScript
├── name: str                      # Script identifier
├── description: str | None        # Optional description
├── function: Callable | None      # Callable (for programmatic)
├── takes_ctx: bool                # Requires RunContext
├── function_schema: FunctionSchema| None
├── uri: str | None                # File path (file-based only)
└── skill_name: str | None         # Parent skill (file-based only)

Common Metadata Fields

When creating skills, these metadata fields are commonly used:

Field Type Purpose
version str Semantic version (e.g., "1.0.0")
author str Creator/maintainer
license str License identifier (e.g., "Apache-2.0")
compatibility str Environment requirements
tags list[str] Categorization tags
requires dict[str, str] External dependencies
deprecated bool Whether skill is deprecated
deprecation_message str Explanation if deprecated

Usage Examples

Creating Programmatic Skills

from pydantic_ai import RunContext
from pydantic_ai.toolsets.skills import Skill, SkillResource

# Create a skill with static resources
my_skill = Skill(
    name='my-skill',
    description='A programmatic skill',
    content='Instructions for using this skill...',
    resources=[
        SkillResource(
            name='reference',
            content='## Reference\n\nStatic documentation here...'
        )
    ]
)

# Add dynamic resources
@my_skill.resource
def get_info() -> str:
    """Get dynamic information."""
    return "Dynamic content generated at runtime"

@my_skill.resource
async def get_data(ctx: RunContext[MyDeps]) -> str:
    """Get data from dependencies."""
    return await ctx.deps.fetch_data()

# Add scripts
@my_skill.script
async def process(ctx: RunContext[MyDeps], query: str) -> str:
    """Process a query.

    Args:
        query: The query to process
    """
    result = await ctx.deps.process(query)
    return f"Processed: {result}"

Working with File-Based Skills

from pydantic_ai_skills import SkillsToolset

toolset = SkillsToolset(directories=["./skills"])

# Access skills
for name, skill in toolset.skills.items():
    print(f"\nSkill: {name}")
    print(f"  Description: {skill.description}")

    if skill.uri:  # File-based skill
        print(f"  URI: {skill.uri}")

    # Access metadata
    if skill.metadata and "version" in skill.metadata:
        print(f"  Version: {skill.metadata['version']}")

    # List resources
    if skill.resources:
        print(f"  Resources:")
        for resource in skill.resources:
            print(f"    - {resource.name}")

    # List scripts
    if skill.scripts:
        print(f"  Scripts:")
        for script in skill.scripts:
            print(f"    - {script.name}")

Mixing Programmatic and File-Based Skills

from pydantic_ai import RunContext
from pydantic_ai.toolsets.skills import Skill, SkillsToolset

# Create programmatic skill
custom_skill = Skill(
    name='custom-skill',
    description='Programmatic skill',
    content='Custom instructions...'
)

@custom_skill.script
async def custom_action(ctx: RunContext[MyDeps]) -> str:
    """Perform custom action."""
    return 'Action completed'

# Combine with file-based skills
toolset = SkillsToolset(
    directories=["./skills"],  # File-based skills
    skills=[custom_skill]      # Programmatic skills
)

print(f"Total skills: {len(toolset.skills)}")

Type Structures

Skill Structure

Skill
├── name: str                    # Unique skill identifier
├── description: str             # Brief description
├── content: str                 # Main instructions (markdown)
├── resources: list[SkillResource]  # Additional resources
├── scripts: list[SkillScript]   # Executable scripts
├── uri: str | None             # Base URI (file-based only)
└── metadata: dict[str, Any] | None  # Additional metadata

SkillResource Structure

SkillResource
├── name: str                    # Resource identifier
├── description: str | None     # Optional description
├── content: str | None         # Static content (for file-based or inline)
├── function: Callable | None   # Callable function (programmatic)
├── takes_ctx: bool             # Whether function takes RunContext
├── function_schema: FunctionSchema | None  # Schema for callable
└── uri: str | None             # File URI (file-based only)

SkillScript Structure

SkillScript
├── name: str                    # Script identifier
├── description: str | None     # Optional description
├── function: Callable | None   # Callable function (programmatic)
├── takes_ctx: bool             # Whether function takes RunContext
├── function_schema: FunctionSchema | None  # Schema for callable
├── uri: str | None             # File URI (file-based only)
└── skill_name: str | None      # Parent skill name

Programmatic vs File-Based

File-Based Skills

Loaded from filesystem directories:

  • uri points to file/directory location
  • content is loaded from SKILL.md
  • Resources loaded from additional .md files
  • Scripts loaded from scripts/ directory and executed via subprocess

Programmatic Skills

Created in Python code:

  • No uri (no filesystem location)
  • content provided directly as string
  • Resources can be static content or callable functions
  • Scripts are Python functions with RunContext support
  • Supports dependency injection via RunContext

See Also