Skip to content

feat(io-mapper): io mapping from manifests #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions agntcy_iomapper/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Cisco and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
# ruff: noqa: F401
from .agent_iomapper import (
from .base import (
AgentIOMapper,
AgentIOMapperConfig,
AgentIOMapperInput,
AgentIOMapperOutput,
)
from .base import (
BaseIOMapper,
BaseIOMapperConfig,
BaseIOMapperInput,
Expand Down
4 changes: 4 additions & 0 deletions agntcy_iomapper/base/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Cisco and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
from .agent_iomapper import *
from .base import *
43 changes: 39 additions & 4 deletions agntcy_iomapper/base.py → agntcy_iomapper/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,62 @@


class ArgumentsDescription(BaseModel):
"""
ArgumentsDescription a pydantic model that defines
the details necessary to perfom io mapping between two agents
"""

json_schema: Schema | None = Field(
default=None, description="Data format JSON schema"
)
description: str | None = Field(
default=None, description="Data (semantic) natural language description"
)
agent_manifest: dict[str, Any] | None = Field(
default=None,
description="Agent Manifest definition as per https://agntcy.github.io/acp-spec/openapi.html#model/agentmanifest",
)

@model_validator(mode="after")
def _validate_obj(self) -> Self:
if self.json_schema is None and self.description is None:
if (
self.json_schema is None
and self.description is None
and self.agent_manifest
):
raise ValueError(
'Either the "schema" field and/or the "description" field must be specified.'
'Either the "schema" field and/or the "description" or agent_manifest field must be specified.'
)
return self


class BaseIOMapperInput(BaseModel):
input: ArgumentsDescription = Field(description="Input data descriptions")
output: ArgumentsDescription = Field(description="Output data descriptions")
input: ArgumentsDescription = Field(
description="Input data descriptions",
)
output: ArgumentsDescription = Field(
description="Output data descriptions",
)
data: Any = Field(description="Data to translate")

@model_validator(mode="after")
def _validate_obj(self) -> Self:
if self.input.agent_manifest is not None:
# given an input agents manifest map its ouput definition
# because the data to be mapped is the result of calling the input agent
self.input.json_schema = Schema.model_validate(
self.input.agent_manifest["specs"]["output"]
)

if self.output.agent_manifest:
# given an output agents manifest map its input definition
# because the data to be mapped would be mapped to it's input
self.output.json_schema = Schema.model_validate(
self.output.agent_manifest["specs"]["input"]
)

return self


class BaseIOMapperOutput(BaseModel):
data: Any = Field(default=None, description="Data after translation")
Expand Down
8 changes: 8 additions & 0 deletions agntcy_iomapper/imperative/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Cisco and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0"

from .imperative import (
ImperativeIOMapper,
ImperativeIOMapperInput,
ImperativeIOMapperOutput,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import jsonschema
from jsonpath_ng.ext import parse

from .base import (
from agntcy_iomapper.base import (
BaseIOMapper,
BaseIOMapperConfig,
BaseIOMapperInput,
Expand Down
11 changes: 11 additions & 0 deletions agntcy_iomapper/langgraph/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Cisco and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0"

from .langgraph import (
LangGraphIOMapper,
LangGraphIOMapperConfig,
LangGraphIOMapperInput,
LangGraphIOMapperOutput,
)

from .create_langraph_iomapper import create_langraph_iomapper
22 changes: 22 additions & 0 deletions agntcy_iomapper/langgraph/create_langraph_iomapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Cisco and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0"
from langchain_core.runnables import Runnable

from .langgraph import (
LangGraphIOMapper,
LangGraphIOMapperConfig,
LangGraphIOMapperInput,
LangGraphIOMapperOutput,
)


def create_langraph_iomapper(
config: LangGraphIOMapperConfig,
) -> Runnable[LangGraphIOMapperInput, LangGraphIOMapperOutput]:
"""Creates a langgraph agent
Args:
config: The configuration of the llm that would be used during the mapping
Returns:
A runnable representing an agent. It returns as output the mapping result
"""
return LangGraphIOMapper(config).as_runnable()
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

from langchain.chat_models import init_chat_model
from langchain_core.language_models import BaseChatModel
from langchain_core.runnables import Runnable, RunnableConfig
from langchain_core.runnables import RunnableConfig
from langgraph.utils.runnable import RunnableCallable
from pydantic import Field

from .agent_iomapper import (
from agntcy_iomapper.base import (
AgentIOMapper,
AgentIOMapperConfig,
AgentIOMapperInput,
Expand Down Expand Up @@ -86,9 +86,3 @@ def invoke(self, state: dict[str, Any], config: RunnableConfig) -> dict:

def as_runnable(self):
return RunnableCallable(self.invoke, self.ainvoke, name="extract", trace=False)


def create_iomapper(
config: LangGraphIOMapperConfig,
) -> Runnable[LangGraphIOMapperInput, LangGraphIOMapperOutput]:
return LangGraphIOMapper(config).as_runnable()
2 changes: 1 addition & 1 deletion agntcy_iomapper/pydantic_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from pydantic_ai.models.openai import OpenAIModel
from typing_extensions import Self

from .agent_iomapper import (
from agntcy_iomapper.base import (
AgentIOMapper,
AgentIOMapperConfig,
AgentIOMapperInput,
Expand Down
30 changes: 15 additions & 15 deletions docs/usage.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Usage

The Agntcy IO Mapper functions provided an easy to use package for mapping output from
one agent to another. The data can be described in JSON or with natural language. The
The Agntcy IO Mapper functions provided an easy to use package for mapping output from
one agent to another. The data can be described in JSON or with natural language. The
(incomplete) [pydantic](https://docs.pydantic.dev/latest/) models follow:

```python
Expand All @@ -23,16 +23,16 @@ class BaseIOMapperConfig(BaseModel):
validate_json_output: bool = Field(description="Validate output against JSON schema.")
```

There are several different ways to leverage the IO Mapper functions in Python. There
is an [agentic interface](#use-agent-io-mapper) using models that can be invoked on
different AI platforms and a [imperative interface](#use-imperative--deterministic-io-mapper)
There are several different ways to leverage the IO Mapper functions in Python. There
is an [agentic interface](#use-agent-io-mapper) using models that can be invoked on
different AI platforms and a [imperative interface](#use-imperative--deterministic-io-mapper)
that does deterministic JSON remapping without using any AI models.

## Use Agent IO Mapper

The Agent IO Mapper uses an LLM/model to transform the inputs (typically output of the
The Agent IO Mapper uses an LLM/model to transform the inputs (typically output of the
first agent) to match the desired output (typically the input of a second agent). As such,
it additionally supports specifying the model prompts for the translation. The configuration
it additionally supports specifying the model prompts for the translation. The configuration
object provides a specification for the system and default user prompts:

```python
Expand All @@ -54,11 +54,11 @@ class AgentIOMapperInput(BaseIOMapperInput):
)
```

Further specification of models and their arguments is left to the underlying supported
Further specification of models and their arguments is left to the underlying supported
packages:
* [Pydantic-AI](#pydantic-ai)
* [LangGraph](#langgraph)

- [Pydantic-AI](#pydantic-ai)
- [LangGraph](#langgraph)

### Pydantic-AI

Expand All @@ -70,8 +70,8 @@ This project supports specifying model interations using [LangGraph](https://lan

## Use Imperative / Deterministic IO Mapper

The code snippet below illustrates a fully functional deterministic mapping that
transforms the output of one agent into input for a second agent. The code for the
The code snippet below illustrates a fully functional deterministic mapping that
transforms the output of one agent into input for a second agent. The code for the
agents is omitted.

```python
Expand All @@ -89,7 +89,7 @@ agents is omitted.
}
}

# the mapping object using jsonpath, note: the value of the mapping
# the mapping object using jsonpath, note: the value of the mapping
# can be either a jsonpath or a function
mapping_object = {
"prof_question": "$.question",
Expand All @@ -115,8 +115,8 @@ agents is omitted.

### Use Examples

1. To run the examples we strongly recommend that a
[virtual environment is created](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
1. To run the examples we strongly recommend that a
[virtual environment is created](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
2. Install the requirements file
3. From within examples folder run:

Expand Down
Loading