Metadata-Version: 2.4
Name: agentskills-langchain
Version: 0.5.0
Summary: LangChain integration for the Agent Skills format (https://agentskills.io)
License: MIT
Author: Pratik Panda
Requires-Python: >=3.12,<4.0
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Requires-Dist: agentskills-core (>=0.5.0,<1.0)
Requires-Dist: langchain-core (>=1.0,<2.0)
Project-URL: Homepage, https://agentskills.io
Project-URL: Repository, https://github.com/pratikxpanda/agentskills-sdk
Description-Content-Type: text/markdown

# agentskills-langchain

[![PyPI](https://img.shields.io/pypi/v/agentskills-langchain)](https://pypi.org/project/agentskills-langchain/)
[![Python 3.12 | 3.13](https://img.shields.io/pypi/pyversions/agentskills-langchain)](https://pypi.org/project/agentskills-langchain/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/pratikxpanda/agentskills-sdk/blob/main/LICENSE)

> LangChain integration for the [Agent Skills SDK](https://github.com/pratikxpanda/agentskills-sdk) - turn a skill registry into LangChain tools.

Generates a set of [LangChain](https://python.langchain.com/) `StructuredTool` instances from a `SkillRegistry`, ready to be passed to any LangChain agent.

## Installation

```bash
pip install agentskills-langchain
```

Requires Python 3.12 or newer. Installs `agentskills-core` and `langchain-core` as dependencies.

## Usage

```python
from pathlib import Path

from agentskills_core import SkillRegistry
from agentskills_fs import LocalFileSystemSkillProvider
from agentskills_langchain import get_tools, get_tools_usage_instructions

# Set up registry
provider = LocalFileSystemSkillProvider(Path("./skills"))
registry = SkillRegistry()
await registry.register("incident-response", provider)

# Build tools + system prompt
tools = get_tools(registry)
catalog = await registry.get_skills_catalog(format="xml")
instructions = get_tools_usage_instructions()
system_prompt = f"{catalog}\n\n{instructions}"
```

Pass `tools` to your LangChain agent and inject `system_prompt` into the system message. The catalog tells the agent *what* skills exist; the usage instructions tell it *how* to use the tools.

## Generated Tools

| Tool | Parameters | Description |
| --- | --- | --- |
| `get_skill_metadata` | `skill_id` | Get structured metadata (name, description, etc.) |
| `get_skill_body` | `skill_id` | Load the full markdown instructions |
| `get_skill_outline` | `skill_id` | List the body's sections, keys and token costs |
| `get_skill_section` | `skill_id`, `key` | Load one section of the body |
| `list_skill_resources` | `skill_id` | List bundled references, scripts and assets |
| `get_skill_reference` | `skill_id`, `name` | Read a reference document |
| `get_skill_script` | `skill_id`, `name` | Read a script |
| `get_skill_asset` | `skill_id`, `name` | Read an asset |

All tools are async-compatible (`StructuredTool` with `coroutine`).

`get_skill_outline` exists so a large skill is not all-or-nothing. Its rendered text carries the whole-body cost alongside the per-section costs and says outright when `get_skill_body` is the cheaper call — a section fetch is not free, it costs a tool call and a model turn on top of the outline. Section keys are flat slugs and sections do not nest, so fetching a parent does not include what is indented under it in the outline.

`list_skill_resources` returns a JSON object keyed by resource kind. Not every backend can enumerate resources — a plain static HTTP host cannot. Rather than surfacing an exception, the tool returns `{"supported": false, "note": "..."}` in that case: "this cannot be listed" is something the model can act on by falling back to the names in the skill body, not an error worth retrying.

## Single-Skill Fast Path

An agent with one skill pays the whole discovery apparatus — a catalog listing one entry, eight tool definitions, usage instructions describing a selection workflow, and a model round trip while it calls `get_skill_body` — to reach content there was never a choice about.

```python
from agentskills_core import resolve_fast_path

fast_path = await resolve_fast_path(registry)

if fast_path is not None:
    system_prompt = fast_path.prompt          # the body, inlined
    tools = get_tools(registry, fast_path=fast_path)   # resource tools only
else:
    catalog = await registry.get_skills_catalog()
    system_prompt = f"{catalog}\n\n{get_tools_usage_instructions()}"
    tools = get_tools(registry)
```

`resolve_fast_path` returns `None` unless the effective skill set is exactly one and its body fits under a token ceiling, and `get_tools(registry, fast_path=None)` is the normal eight-tool list — so nothing changes unless it fires. Pass `include=selection.skill_ids` to resolve against a set narrowed by [agentskills-retrieval](https://github.com/pratikxpanda/agentskills-sdk/tree/main/packages/retrieval/agentskills-retrieval) rather than the whole registry.

The ceiling, the arithmetic behind its default, and why the resource tools stay are documented in the [core README](https://github.com/pratikxpanda/agentskills-sdk/tree/main/packages/core/agentskills-core#single-skill-fast-path).

## API

### `get_tools(registry: SkillRegistry, *, max_inline_binary_bytes: int = 65536, fast_path: FastPath | None = None) -> list[StructuredTool]`

Returns a list of LangChain structured tools bound to the given registry. With a `fast_path`, the four body-access tools are omitted because the body is already in the prompt.

### `get_tools_usage_instructions() -> str`

Returns a markdown string explaining the progressive-disclosure workflow - read metadata, then body, then fetch resources on demand. Designed for system-prompt injection alongside the skill catalog.

## Example

See [examples/langchain/](https://github.com/pratikxpanda/agentskills-sdk/tree/main/examples/langchain) for a full working demo.

## Error Handling

| Scenario | Exception |
| --- | --- |
| Skill not found in registry | `SkillNotFoundError` |
| Resource not found in skill | `ResourceNotFoundError` |
| Provider errors (HTTP, filesystem) | `AgentSkillsError` |

All exceptions inherit from `AgentSkillsError` (from `agentskills-core`).

## Binary Resources

Skill resources may be arbitrary files. Valid UTF-8 is returned as-is; anything else is returned as a JSON envelope, so a binary payload is never silently mangled into replacement characters:

```json
{
  "name": "architecture.png",
  "media_type": "image/png",
  "size_bytes": 20481,
  "encoding": "base64",
  "content": "iVBORw0KGgo..."
}
```

Base64 costs roughly 1.37 characters per byte, so binaries above 64 KiB are described rather than inlined - `"encoding": "none"` plus a `note` explaining the omission. Adjust the ceiling with:

```python
tools = get_tools(registry, max_inline_binary_bytes=256 * 1024)
```

## Images

A base64 envelope is the right answer for an opaque binary and the wrong one for
a diagram: the model gets a wall of characters where a picture was. Pass
`vision=True` and bundled images come back as native LangChain image content
blocks instead:

```python
tools = get_tools(registry, vision=True)
```

```python
[{"type": "image", "source_type": "base64", "mime_type": "image/png", "data": "iVBORw0..."}]
```

It is off by default because handing an image block to a text-only model is an
API error from the provider, not a degraded answer, and there is no reliable way
to ask a model whether it can see. You know which model your tools are bound to;
the library does not.

PNG, JPEG, GIF and WebP qualify, and only when the leading bytes say so - a name
is a claim, bytes are evidence. PDF is excluded because support varies by model,
and SVG because it is already text the model can read. Everything else keeps the
JSON envelope exactly as above, including images past `max_inline_image_bytes`
(5 MiB by default, against 64 KiB for opaque binaries - base64 in a text field is
billed per byte, while a native image is billed by tile count).

See [ADR 0009](https://github.com/pratikxpanda/agentskills-sdk/blob/main/docs/adr/0009-native-image-content.md).

## License

MIT

