|
2 | 2 | # SPDX-License-Identifier: Apache-2.0
|
3 | 3 |
|
4 | 4 | import logging
|
5 |
| -from typing import Any, Optional, Tuple, Union |
| 5 | +from typing import Any, Callable, List, Optional, Union |
6 | 6 |
|
7 | 7 | from langchain_core.language_models import BaseChatModel
|
8 | 8 | from langchain_core.runnables import Runnable
|
9 |
| -from openapi_pydantic import Schema |
| 9 | +from llama_index.core.base.llms.base import BaseLLM |
| 10 | +from llama_index.core.tools import BaseTool |
| 11 | +from llama_index.core.workflow import ( |
| 12 | + Workflow, |
| 13 | +) |
10 | 14 | from pydantic import BaseModel, Field, model_validator
|
11 | 15 | from typing_extensions import Self
|
12 | 16 |
|
13 |
| -from agntcy_iomapper.agent.models import ( |
| 17 | +from agntcy_iomapper.base.models import ( |
14 | 18 | AgentIOMapperInput,
|
| 19 | + ArgumentsDescription, |
15 | 20 | FieldMetadata,
|
16 | 21 | IOMappingAgentMetadata,
|
17 | 22 | )
|
18 |
| -from agntcy_iomapper.base import ArgumentsDescription |
19 |
| -from agntcy_iomapper.base.utils import create_type_from_schema, extract_nested_fields |
| 23 | +from agntcy_iomapper.base.utils import extract_nested_fields, get_io_types |
| 24 | +from agntcy_iomapper.imperative import ( |
| 25 | + ImperativeIOMapper, |
| 26 | + ImperativeIOMapperInput, |
| 27 | +) |
20 | 28 | from agntcy_iomapper.langgraph import LangGraphIOMapper, LangGraphIOMapperConfig
|
| 29 | +from agntcy_iomapper.llamaindex.llamaindex import ( |
| 30 | + LLamaIndexIOMapper, |
| 31 | +) |
21 | 32 |
|
22 | 33 | logger = logging.getLogger(__name__)
|
23 | 34 |
|
24 | 35 |
|
25 | 36 | class IOMappingAgent(BaseModel):
|
26 |
| - metadata: IOMappingAgentMetadata = Field( |
| 37 | + """This class exposes all |
| 38 | + The IOMappingAgent class is designed for developers building sophisticated multi-agent software that require seamless integration and interaction between |
| 39 | + the different agents and workflow steps. |
| 40 | + By utilizing the methods provided, developers can construct complex workflows and softwares. |
| 41 | + The IOMappingAgent class is intended to serve as a foundational component in applications requiring advanced IO mapping agents in multi-agent systems. |
| 42 | + """ |
| 43 | + |
| 44 | + metadata: Optional[IOMappingAgentMetadata] = Field( |
27 | 45 | ...,
|
28 | 46 | description="Details about the fields to be used in the translation and about the output",
|
29 | 47 | )
|
30 |
| - llm: Optional[Union[BaseChatModel, str]] = ( |
31 |
| - Field( |
32 |
| - None, |
33 |
| - description="Model to use for translation as LangChain description or model class.", |
34 |
| - ), |
| 48 | + llm: Optional[Union[BaseChatModel, str]] = Field( |
| 49 | + None, |
| 50 | + description="Model to use for translation as LangChain description or model class.", |
35 | 51 | )
|
36 | 52 |
|
37 | 53 | @model_validator(mode="after")
|
38 | 54 | def _validate_obj(self) -> Self:
|
| 55 | + if not self.metadata: |
| 56 | + return self |
39 | 57 |
|
40 | 58 | if not self.metadata.input_fields or len(self.metadata.input_fields) == 0:
|
41 | 59 | raise ValueError("input_fields not found in the metadata")
|
@@ -70,38 +88,12 @@ def _validate_obj(self) -> Self:
|
70 | 88 |
|
71 | 89 | return self
|
72 | 90 |
|
73 |
| - def _get_io_types(self, data: Any) -> Tuple[Schema, Schema]: |
74 |
| - data_schema = None |
75 |
| - if isinstance(data, BaseModel): |
76 |
| - data_schema = data.model_json_schema() |
77 |
| - # If input schema is provided it overwrites the data schema |
78 |
| - input_schema = ( |
79 |
| - self.metadata.input_schema if self.metadata.input_schema else data_schema |
80 |
| - ) |
81 |
| - # If output schema is provided it overwrites the data schema |
82 |
| - output_schema = ( |
83 |
| - self.metadata.output_schema if self.metadata.output_schema else data_schema |
84 |
| - ) |
85 |
| - |
86 |
| - if not input_schema or not output_schema: |
87 |
| - raise ValueError( |
88 |
| - "input_schema, and or output_schema are missing from the metadata, for a better accuracy you are required to provide them in this scenario, or we could not infer the type from the state" |
89 |
| - ) |
90 |
| - |
91 |
| - input_type = Schema.model_validate( |
92 |
| - create_type_from_schema(input_schema, self.metadata.input_fields) |
93 |
| - ) |
94 |
| - |
95 |
| - output_type = Schema.model_validate( |
96 |
| - create_type_from_schema(output_schema, self.metadata.output_fields) |
97 |
| - ) |
98 |
| - |
99 |
| - return (input_type, output_type) |
100 |
| - |
101 | 91 | def langgraph_node(self, data: Any, config: Optional[dict] = None) -> Runnable:
|
| 92 | + """This method is used to add a language graph node to a langgraph multi-agent software. |
| 93 | + It leverages language models for IO mapping, ensuring efficient communication between agents. |
| 94 | + """ |
102 | 95 |
|
103 |
| - # If there is a template for the output the output_schema is going to be ignored in the translation |
104 |
| - input_type, output_type = self._get_io_types(data) |
| 96 | + input_type, output_type = get_io_types(data, self.metadata) |
105 | 97 |
|
106 | 98 | data_to_be_mapped = extract_nested_fields(
|
107 | 99 | data, fields=self.metadata.input_fields
|
@@ -132,3 +124,64 @@ def langgraph_node(self, data: Any, config: Optional[dict] = None) -> Runnable:
|
132 | 124 |
|
133 | 125 | iomapper_config = LangGraphIOMapperConfig(llm=self.llm)
|
134 | 126 | return LangGraphIOMapper(iomapper_config, input).as_runnable()
|
| 127 | + |
| 128 | + def langgraph_imperative( |
| 129 | + self, data: Any, config: Optional[dict] = None |
| 130 | + ) -> Runnable: |
| 131 | + """ |
| 132 | + Description: Similar to langgraph_node, this method adds a language graph node to a multi-agent software. |
| 133 | + However, it does not utilize a language model for IO mapping, offering an imperative approach to agent integration. |
| 134 | + """ |
| 135 | + |
| 136 | + input_type, output_type = self._get_io_types(data, self.metadata) |
| 137 | + |
| 138 | + data_to_be_mapped = extract_nested_fields( |
| 139 | + data, fields=self.metadata.input_fields |
| 140 | + ) |
| 141 | + |
| 142 | + input = ImperativeIOMapperInput( |
| 143 | + input=ArgumentsDescription( |
| 144 | + json_schema=input_type, |
| 145 | + ), |
| 146 | + output=ArgumentsDescription(json_schema=output_type), |
| 147 | + data=data_to_be_mapped, |
| 148 | + ) |
| 149 | + |
| 150 | + if not self.metadata.field_mapping: |
| 151 | + raise ValueError( |
| 152 | + "In order to use imperative mapping field_mapping must be provided in the metadata" |
| 153 | + ) |
| 154 | + |
| 155 | + imperative_io_mapper = ImperativeIOMapper( |
| 156 | + input=input, field_mapping=self.metadata.field_mapping |
| 157 | + ) |
| 158 | + return imperative_io_mapper.as_runnable() |
| 159 | + |
| 160 | + @staticmethod |
| 161 | + def as_worfklow_step(workflow: Workflow) -> Callable: |
| 162 | + """This static method allows for the addition of a step to a LlamaIndex workflow. |
| 163 | + It integrates seamlessly into workflows, enabling structured progression and task execution. |
| 164 | + """ |
| 165 | + io_mapper_step = LLamaIndexIOMapper.llamaindex_mapper(workflow) |
| 166 | + return io_mapper_step |
| 167 | + |
| 168 | + @staticmethod |
| 169 | + def as_workflow_agent( |
| 170 | + mapping_metadata: IOMappingAgentMetadata, |
| 171 | + llm: BaseLLM, |
| 172 | + name: str, |
| 173 | + description: str, |
| 174 | + can_handoff_to: Optional[List[str]] = None, |
| 175 | + tools: Optional[List[Union[BaseTool, Callable]]] = [], |
| 176 | + ): |
| 177 | + """This static method returns an instance of an agent that can be integrated into a Multi AgentWorkflow. |
| 178 | + It provides robust IO mapping capabilities essential for complex multi agent workflow interactions. |
| 179 | + """ |
| 180 | + return LLamaIndexIOMapper( |
| 181 | + mapping_metadata=mapping_metadata, |
| 182 | + llm=llm, |
| 183 | + tools=tools, |
| 184 | + name=name, |
| 185 | + description=description, |
| 186 | + can_handoff_to=can_handoff_to, |
| 187 | + ) |
0 commit comments