Skip to content

Commit bbaeae5

Browse files
committed
feat: Added sidebar nav to docs. Moved json validation to base class (though code is not there yet). Updated (a little) the usage docs. Working on LangGraph mapper example.
1 parent ffec2ee commit bbaeae5

12 files changed

+278
-113
lines changed

Diff for: agntcy_iomapper/__init__.py

+1-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
)
1010
from .base import (
1111
BaseIOMapper,
12+
BaseIOMapperConfig,
1213
BaseIOMapperInput,
1314
BaseIOMapperOutput,
1415
)
@@ -17,18 +18,3 @@
1718
ImperativeIOMapperInput,
1819
ImperativeIOMapperOutput,
1920
)
20-
from .pydantic_ai import (
21-
PydanticAIAgentIOMapperConfig,
22-
PydanticAIAgentIOMapperInput,
23-
PydanticAIAgentIOMapperOutput,
24-
PydanticAIIOAgentIOMapper,
25-
)
26-
27-
"""
28-
from .langgraph import (
29-
LangGraphAgentIOMapper,
30-
LangGraphIOMapperConfig,
31-
LangGraphIOMapperInput,
32-
LangGraphIOMapperOutput,
33-
)
34-
"""

Diff for: agntcy_iomapper/agent_iomapper.py

+23-18
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
import re
66
from abc import abstractmethod
77
from typing import ClassVar
8+
89
import jsonschema
910
from jinja2 import Environment
1011
from jinja2.sandbox import SandboxedEnvironment
11-
from pydantic import BaseModel, Field
12+
from pydantic import Field
1213

13-
from .base import BaseIOMapper, BaseIOMapperInput, BaseIOMapperOutput
14+
from .base import (
15+
BaseIOMapper,
16+
BaseIOMapperConfig,
17+
BaseIOMapperInput,
18+
BaseIOMapperOutput,
19+
)
1420

1521
logger = logging.getLogger(__name__)
1622

@@ -26,13 +32,7 @@ class AgentIOMapperInput(BaseIOMapperInput):
2632
AgentIOMapperOutput = BaseIOMapperOutput
2733

2834

29-
class AgentIOMapperConfig(BaseModel):
30-
validate_json_input: bool = Field(
31-
default=False, description="Validate input against JSON schema."
32-
)
33-
validate_json_output: bool = Field(
34-
default=False, description="Validate output against JSON schema."
35-
)
35+
class AgentIOMapperConfig(BaseIOMapperConfig):
3636
system_prompt_template: str = Field(
3737
max_length=4096,
3838
default="You are a translation machine. You translate both natural language and object formats for computers.",
@@ -52,14 +52,13 @@ class AgentIOMapper(BaseIOMapper):
5252

5353
def __init__(
5454
self,
55-
config: AgentIOMapperConfig,
56-
*,
55+
config: AgentIOMapperConfig | None = None,
5756
jinja_env: Environment | None = None,
5857
jinja_env_async: Environment | None = None,
5958
):
60-
super().__init__()
61-
62-
self.config = config
59+
if config is None:
60+
config = AgentIOMapperConfig()
61+
super().__init__(config)
6362

6463
if jinja_env is not None and jinja_env.is_async:
6564
raise ValueError("Async Jinja env passed to jinja_env argument")
@@ -167,7 +166,8 @@ def invoke(self, input: AgentIOMapperInput, **kwargs) -> AgentIOMapperOutput:
167166
user_template = self.user_template
168167
user_prompt = user_template.render(render_env)
169168

170-
outputs = self._invoke(input,
169+
outputs = self._invoke(
170+
input,
171171
messages=[
172172
{"role": "system", "content": system_prompt},
173173
{"role": "user", "content": user_prompt},
@@ -180,7 +180,9 @@ def invoke(self, input: AgentIOMapperInput, **kwargs) -> AgentIOMapperOutput:
180180
return output
181181

182182
@abstractmethod
183-
def _invoke(self, input: AgentIOMapperInput, messages: list[dict[str, str]], **kwargs) -> str:
183+
def _invoke(
184+
self, input: AgentIOMapperInput, messages: list[dict[str, str]], **kwargs
185+
) -> str:
184186
"""Invoke internal model to process messages.
185187
Args:
186188
messages: the messages to send to the LLM
@@ -201,7 +203,8 @@ async def ainvoke(self, input: AgentIOMapperInput, **kwargs) -> AgentIOMapperOut
201203
user_template_async = self.user_template_async
202204
user_prompt = await user_template_async.render_async(render_env)
203205

204-
outputs = await self._ainvoke(input,
206+
outputs = await self._ainvoke(
207+
input,
205208
messages=[
206209
{"role": "system", "content": system_prompt},
207210
{"role": "user", "content": user_prompt},
@@ -214,7 +217,9 @@ async def ainvoke(self, input: AgentIOMapperInput, **kwargs) -> AgentIOMapperOut
214217
return output
215218

216219
@abstractmethod
217-
async def _ainvoke(self, input: AgentIOMapperInput, messages: list[dict[str, str]], **kwargs) -> str:
220+
async def _ainvoke(
221+
self, input: AgentIOMapperInput, messages: list[dict[str, str]], **kwargs
222+
) -> str:
218223
"""Async invoke internal model to process messages.
219224
Args:
220225
messages: the messages to send to the LLM

Diff for: agntcy_iomapper/base.py

+15
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,26 @@ class BaseIOMapperOutput(BaseModel):
3838
)
3939

4040

41+
class BaseIOMapperConfig(BaseModel):
42+
validate_json_input: bool = Field(
43+
default=False, description="Validate input against JSON schema."
44+
)
45+
validate_json_output: bool = Field(
46+
default=False, description="Validate output against JSON schema."
47+
)
48+
49+
4150
class BaseIOMapper(ABC):
4251
"""Abstract base class for interfacing with io mapper.
4352
All io mappers wrappers inherited from BaseIOMapper.
4453
"""
4554

55+
def __init__(
56+
self,
57+
config: BaseIOMapperConfig | None = None,
58+
):
59+
self.config = config if config is not None else BaseIOMapperConfig()
60+
4661
@abstractmethod
4762
def invoke(self, input: BaseIOMapperInput) -> BaseIOMapperOutput:
4863
"""Pass input data

Diff for: agntcy_iomapper/imperative.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222
import jsonschema
2323
from jsonpath_ng.ext import parse
2424

25-
from .base import BaseIOMapper, BaseIOMapperInput, BaseIOMapperOutput
25+
from .base import (
26+
BaseIOMapper,
27+
BaseIOMapperConfig,
28+
BaseIOMapperInput,
29+
BaseIOMapperOutput,
30+
)
2631

2732
logger = logging.getLogger(__name__)
2833

@@ -37,8 +42,12 @@ class ImperativeIOMapper(BaseIOMapper):
3742
and values are JSONPath (strings) representing how the mapping
3843
"""
3944

40-
def __init__(self, field_mapping: dict[str, Union[str, Callable]] | None) -> None:
41-
super().__init__()
45+
def __init__(
46+
self,
47+
field_mapping: dict[str, Union[str, Callable]] | None,
48+
config: BaseIOMapperConfig | None = None,
49+
) -> None:
50+
super().__init__(config)
4251
self.field_mapping = field_mapping
4352

4453
def invoke(self, input: ImperativeIOMapperInput) -> ImperativeIOMapperOutput | None:

Diff for: agntcy_iomapper/langgraph.py

+40-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025 Cisco and/or its affiliates.
22
# SPDX-License-Identifier: Apache-2.0
33
import logging
4-
54
from typing import Any
5+
6+
from langchain.chat_models import init_chat_model
67
from langchain_core.language_models import BaseChatModel
78
from langchain_core.runnables import Runnable, RunnableConfig
89
from langgraph.utils.runnable import RunnableCallable
10+
from pydantic import Field
911

1012
from .agent_iomapper import (
13+
AgentIOMapper,
1114
AgentIOMapperConfig,
1215
AgentIOMapperInput,
1316
AgentIOMapperOutput,
14-
AgentIOMapper,
1517
)
1618

1719
logger = logging.getLogger(__name__)
@@ -21,47 +23,72 @@
2123

2224

2325
class LangGraphIOMapperConfig(AgentIOMapperConfig):
24-
llm: BaseChatModel | str
26+
llm: BaseChatModel | str = Field(
27+
default="anthropic:claude-3-5-sonnet-latest",
28+
description="Model to use for translation as LangChain description or model class.",
29+
)
2530

2631

2732
class _LangGraphAgentIOMapper(AgentIOMapper):
2833
def __init__(
2934
self,
30-
config: LangGraphIOMapperConfig,
35+
config: LangGraphIOMapperConfig | None = None,
3136
**kwargs,
3237
):
38+
if config is None:
39+
config = LangGraphIOMapperConfig()
3340
super().__init__(config, **kwargs)
41+
if isinstance(config.llm, str):
42+
self.llm = init_chat_model(config.llm)
43+
else:
44+
self.llm = config.llm
3445

35-
def _invoke(self, input: LangGraphIOMapperInput, messages: list[dict[str, str]], *, config: RunnableConfig | None = None, **kwargs) -> str:
36-
response = self.config.llm.invoke(messages, config, **kwargs)
46+
def _invoke(
47+
self,
48+
input: LangGraphIOMapperInput,
49+
messages: list[dict[str, str]],
50+
*,
51+
config: RunnableConfig | None = None,
52+
**kwargs,
53+
) -> str:
54+
response = self.llm.invoke(messages, config, **kwargs)
3755
return response.content
3856

39-
async def _ainvoke(self, input: LangGraphIOMapperOutput, messages: list[dict[str, str]], *, config: RunnableConfig | None = None, **kwargs) -> str:
40-
response = await self.config.llm.ainvoke(messages, config, **kwargs)
57+
async def _ainvoke(
58+
self,
59+
input: LangGraphIOMapperOutput,
60+
messages: list[dict[str, str]],
61+
*,
62+
config: RunnableConfig | None = None,
63+
**kwargs,
64+
) -> str:
65+
response = await self.llm.ainvoke(messages, config, **kwargs)
4166
return response.content
4267

4368

4469
class LangGraphIOMapper:
4570
def __init__(self, config: LangGraphIOMapperConfig):
4671
self._iomapper = _LangGraphAgentIOMapper(config)
4772

48-
async def ainvoke(self, state: dict[str,Any], config: RunnableConfig) -> dict:
73+
async def ainvoke(self, state: dict[str, Any], config: RunnableConfig) -> dict:
4974
response = await self._iomapper.ainvoke(input=state["input"], config=config)
5075
if response is not None:
51-
return { "output": response }
76+
return {"output": response}
5277
else:
5378
return {}
5479

55-
def invoke(self, state: dict[str,Any], config: RunnableConfig) -> dict:
80+
def invoke(self, state: dict[str, Any], config: RunnableConfig) -> dict:
5681
response = self._iomapper.invoke(input=state["input"], config=config)
5782
if response is not None:
58-
return { "output": response }
83+
return {"output": response}
5984
else:
6085
return {}
6186

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

6590

66-
def create_iomapper(config: LangGraphIOMapperConfig) -> Runnable[LangGraphIOMapperInput, LangGraphIOMapperOutput]:
91+
def create_iomapper(
92+
config: LangGraphIOMapperConfig,
93+
) -> Runnable[LangGraphIOMapperInput, LangGraphIOMapperOutput]:
6794
return LangGraphIOMapper(config).as_runnable()

Diff for: agntcy_iomapper/pydantic_ai.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def _get_model_settings(self, input: PydanticAIAgentIOMapperInput):
144144
model_name = input.model
145145
else:
146146
model_name = self.config.default_model
147-
147+
148148
if model_name not in self.config.models:
149149
raise ValueError(f"requested model {model_name} not found")
150150
elif hasattr(input, "model_settings") and input.model_settings is not None:
@@ -197,7 +197,12 @@ def _get_prompts(
197197

198198
return (system_prompt, user_prompt, message_history)
199199

200-
def _invoke(self, input: PydanticAIAgentIOMapperInput, messages: list[dict[str, str]], **kwargs) -> str:
200+
def _invoke(
201+
self,
202+
input: PydanticAIAgentIOMapperInput,
203+
messages: list[dict[str, str]],
204+
**kwargs,
205+
) -> str:
201206
system_prompt, user_prompt, message_history = self._get_prompts(messages)
202207

203208
agent = self._get_agent(input, system_prompt)
@@ -208,7 +213,12 @@ def _invoke(self, input: PydanticAIAgentIOMapperInput, messages: list[dict[str,
208213
)
209214
return response.data
210215

211-
async def _ainvoke(self, input: PydanticAIAgentIOMapperInput, messages: list[dict[str, str]], **kwargs) -> str:
216+
async def _ainvoke(
217+
self,
218+
input: PydanticAIAgentIOMapperInput,
219+
messages: list[dict[str, str]],
220+
**kwargs,
221+
) -> str:
212222
system_prompt, user_prompt, message_history = self._get_prompts(messages)
213223

214224
agent = self._get_agent(input, system_prompt)

Diff for: docs/_navbar.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
* [Getting Started](#getting-started)
2+
* [Usage](usage#usage)
3+
* [Pydantic-AI Agentic IOMapper](usage#pydantic-ai)
4+
* [LangGraph Agentic IOMapper](usage#langgraph)
5+
* [JSON Deterministic IOMapper](usage#use-imperative--deterministic-mapper)
6+
* [Contributing](CONTRIBUTING#how-to-contribute)

Diff for: docs/index.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
name: 'iomapper-agnt',
1616
repo: 'agntcy/iomapper-agnt',
1717
homepage : 'README.md',
18-
coverpage: true
18+
coverpage: true,
19+
loadNavbar: true
1920
}
2021
</script>
2122
<!-- Docsify v4 -->

0 commit comments

Comments
 (0)