Skip to content

Commit

Permalink
generator will always use generatoroutput in parameter, setup compone…
Browse files Browse the repository at this point in the history
…nt clearly and make many previous component only a class
  • Loading branch information
liyin2015 committed Dec 29, 2024
1 parent 4af0b8d commit daa8178
Show file tree
Hide file tree
Showing 21 changed files with 386 additions and 166 deletions.
152 changes: 129 additions & 23 deletions adalflow/adalflow/components/agent/react.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from adalflow.optim.parameter import Parameter, ParameterType
from adalflow.core.func_tool import FunctionTool, AsyncCallable
from adalflow.core.tool_manager import ToolManager
from adalflow.core.component import Component
from adalflow.components.output_parsers import JsonOutputParser
from adalflow.core.types import (
StepOutput,
Expand Down Expand Up @@ -176,7 +177,7 @@ class ReActOutput(DataClass):
answer: Any = field(metadata={"desc": "The final answer."}, default=None)


class ReActAgent(GradComponent):
class ReActAgent(Component):
__doc__ = r"""ReActAgent uses generator as a planner that runs multiple and sequential functional call steps to generate the final response.
Users need to set up:
Expand Down Expand Up @@ -241,7 +242,7 @@ def __init__(
# template for the planner
template: Optional[str] = None, # allow users to customize the template
context_variables: Optional[Dict] = None, # context variables
debug: bool = False,
debug: bool = True,
):
super().__init__()
template = template or DEFAULT_REACT_AGENT_SYSTEM_PROMPT
Expand All @@ -267,7 +268,9 @@ def __init__(
self._examples = examples + [example]

output_parser = JsonOutputParser(
data_class=ouput_data_class, examples=self._examples, return_data_class=True
data_class=ouput_data_class,
examples=self._examples,
return_data_class=True,
)
prompt_kwargs = {
"tools": self.tool_manager.yaml_definitions,
Expand Down Expand Up @@ -350,35 +353,110 @@ def _execute_action(

if isinstance(response, Parameter):

try:
def handle_error(response: Parameter, e: str):
from adalflow.optim.grad_component import fun_to_grad_component

function_output_to_step_output = FunctionOutputToStepOutput()
print(f"action_step: {action_step}")

@fun_to_grad_component
def set_step_output_with_error(
step_output: StepOutput, error: str, response: Any
):
"""Set the step_output with error."""
step_output.observation = f"erro: {error} at {response.data}"
return step_output

# printc(f"response: {response}", color="yellow")
# TO FunctionExpression
response.add_successor_map_fn(
successor=self.tool_manager, map_fn=lambda x: x.full_response
successor=set_step_output_with_error, map_fn=lambda x: x.data
)
return set_step_output_with_error.forward(action_step, e, response)

try:
function_output_to_step_output = FunctionOutputToStepOutput()
# TO FunctionExpression

func: Union[Function, Parameter] = self.tool_manager(
expr_or_fun=response, step="parse"
expr_or_fun=response, step="parse", map_fn=lambda x: x.data.data
)
# add action to the step_output
action_step.action = response.data.data
# parse failed
if not isinstance(func, Parameter):
raise ValueError(
f"Expected Parameter, but got {type(func)}: {func}"
)
if isinstance(func, str):
# create dummy step output
from adalflow.optim.grad_component import fun_to_grad_component

@fun_to_grad_component
def set_step_output_with_error(
step_output: StepOutput, data: FunctionExpression, error: str
):
"""Set the step_output with error."""
step_output.observation = f"Error in parsing the FunctionExperession to Function: {error}"
return step_output

response.add_successor_map_fn(
successor=set_step_output_with_error,
map_fn=lambda x: x.data.data,
)
action_step = set_step_output_with_error.forward(
action_step, response, error=func
)
return action_step

except Exception as e:
e = f"{e} at parsing error at functionexpression: {response.data}"
return handle_error(response, e)

try:

# printc(f"func: {func}", color="yellow")
# replace the id
if isinstance(func, Parameter):
func.data.kwargs["id"] = id

result: Parameter = self.tool_manager(expr_or_fun=func, step="execute")
if self.debug:
printc(f"func: {func.data}", color="yellow")

result: Parameter = self.tool_manager(
expr_or_fun=func, step="execute", map_fn=lambda x: x.data
)

if isinstance(result, str):
# create dummy step output
from adalflow.optim.grad_component import fun_to_grad_component

@fun_to_grad_component
def set_step_output_with_error(step_output: StepOutput, data: str):
"""Set the step_output with error."""
step_output.observation = f"Error {data} in executing action."

return step_output

response.add_successor_map_fn(
successor=set_step_output_with_error,
map_fn=lambda x: x.data.data,
)
action_step = set_step_output_with_error.forward(
action_step, response
)

return action_step

except Exception as e:
e = f"{e} Error executing function: {func}"
return handle_error(response, e)

try:
# printc(f"result: {result}", color="red")
result.add_successor_map_fn(
successor=function_output_to_step_output, map_fn=lambda x: x.data
)
response.add_successor_map_fn(
successor=function_output_to_step_output, map_fn=lambda x: x.data
successor=function_output_to_step_output,
map_fn=lambda x: x.data.data,
)
func.add_successor_map_fn(
successor=function_output_to_step_output, map_fn=lambda x: x.data
Expand All @@ -391,12 +469,11 @@ def _execute_action(
)

return action_step

except Exception as e:
log.error(f"Error executing {response}: {e}")
# pass the error as observation so that the agent can continue and correct the error in the next step
action_step.observation = f"Error executing {response}: {e}"
return action_step
e = f"{e} Error converting function output to step output: {result.data}"

return handle_error(response, e)

else:

return self._execute_action_eval_mode(
Expand Down Expand Up @@ -433,19 +510,15 @@ def _execute_action_eval_mode(
)

step_output.function = fun

result: FunctionOutput = self.tool_manager(
expr_or_fun=fun, step="execute"
)
step_output.observation = result.output

# step_output = execute_action(step_output, id)
if self.debug:
printc(f"Step {step}: \n{step_output}\n_______\n", color="blue")
return step_output
else:
if self.debug:

printc(f"Failed to parse response for step {step}", color="red")
log.error(f"Failed to parse response for step {step}")
return step_output
Expand Down Expand Up @@ -513,9 +586,36 @@ def _run_one_step(
try:

if self.training and isinstance(response, Parameter):
# printc(f"response: {response}", color="yellow")

step_output: Parameter = self._execute_action(step_output, response, id)
if not isinstance(response.data, GeneratorOutput):
raise ValueError(
f"Expected GeneratorOutput, but got {type(response.data)}, value: {response.data}"
)

if not isinstance(response.data.data, FunctionExpression):
from adalflow.optim.grad_component import fun_to_grad_component

@fun_to_grad_component
def set_step_output_with_error(
step_output: StepOutput, data: GeneratorOutput
):
"""Set the step_output with error."""
step_output.observation = f"Error {data.error} in parsing response: {data.raw_response}, data type: {type(data.data)}"
return step_output

response.add_successor_map_fn(
successor=set_step_output_with_error,
map_fn=lambda x: x.data,
)
step_output = set_step_output_with_error.forward(
step_output, response
)

else:

step_output: Parameter = self._execute_action(
step_output, response, id
)

# printc(f"step_output: {step_output}", color="red")
if not isinstance(step_output, Parameter):
Expand All @@ -537,6 +637,10 @@ def _run_one_step(
step_history.add_successor_map_fn(
successor=self.planner, map_fn=lambda x: x.data
)
if self.debug:
printc(
f"step_history: {step_history.get_prompt_data()}", color="red"
)
return step_history

else:
Expand Down Expand Up @@ -689,7 +793,7 @@ def _extra_repr(self) -> str:

setup_env()

class App(GradComponent):
class App(Component):
def __init__(self):
super().__init__()
self.llm_tool = Generator(
Expand Down Expand Up @@ -719,6 +823,8 @@ def forward(
) -> Union[str, "Parameter"]:
return self.react_agent(input, id=id)

# print(OutputParameter.__mro__)

app = App()
app.train()
output = app("I want to multiply 3 and 4.", id="123")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from typing import Any, Literal, List, Optional
import logging

from adalflow.core.component import Component
from adalflow.core.prompt_builder import Prompt
from adalflow.core.string_parser import YamlParser, JsonParser
from adalflow.core.base_data_class import DataClass, DataClassFormatType
from adalflow.core.base_data_class import ExcludeType, IncludeType


__all__ = ["DataClassParser"]

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -42,7 +42,7 @@
"""


class DataClassParser(Component):
class DataClassParser:
__doc__ = r"""Made the structured output even simpler compared with JsonOutputParser and YamlOutputParser.
1. Understands __input_fields__ and __output_fields__ from the DataClass (no need to use include/exclude to decide fields).
Expand Down Expand Up @@ -166,6 +166,9 @@ def get_examples_str(
examples_str = Prompt(template=EXAMPLES_FORMAT)(examples=str_examples)
return examples_str

def __call__(self, *args, **kwargs):
return self.call(*args, **kwargs)

def call(self, input: str) -> Any:
r"""Parse the output string to the desired format and return the parsed output."""
try:
Expand Down
6 changes: 4 additions & 2 deletions adalflow/adalflow/components/output_parsers/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from typing import Dict, Any, Optional, List
import logging

from adalflow.core.component import Component
from adalflow.core.prompt_builder import Prompt
from adalflow.core.string_parser import YamlParser, ListParser, JsonParser
from adalflow.core.base_data_class import DataClass, DataClassFormatType
Expand Down Expand Up @@ -69,7 +68,7 @@
YAML_OUTPUT_PARSER_OUTPUT_TYPE = Dict[str, Any]


class OutputParser(Component):
class OutputParser:
__doc__ = r"""The abstract class for all output parsers.
This interface helps users customize output parsers with consistent interfaces for the Generator.
Expand All @@ -88,6 +87,9 @@ def format_instructions(self) -> str:
r"""Return the formatted instructions to use in prompt for the output format."""
raise NotImplementedError("This is an abstract method.")

def __call__(self, *args: Any, **kwds: Any) -> Any:
return self.call(*args, **kwds)

def call(self, input: str) -> Any:
r"""Parse the output string to the desired format and return the parsed output."""
raise NotImplementedError("This is an abstract method.")
Expand Down
28 changes: 24 additions & 4 deletions adalflow/adalflow/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,11 +519,31 @@ def named_parameters(
# )
# plt.show()

# TODO: do we need to disable this format of calling instead use call and acall extensively?
def __call__(self, *args, **kwargs):
r"""In default, we use sync call."""
output = self.call(*args, **kwargs)
return output
from adalflow.optim.parameter import Parameter

if self.training:
output = self.forward(*args, **kwargs)
print(f"{isinstance(output, Parameter)}")

if not isinstance(output, Parameter):
raise ValueError(
f"Output should be of type Parameter, but got {type(output)}"
)
return output
else:
output = self.call(*args, **kwargs)
if isinstance(output, Parameter):
raise ValueError(
f"Output should not be of type OutputParameter, but got {type(output)}"
)
return output

def forward(self, *args, **kwargs):
r"""Forward pass for training mode."""
raise NotImplementedError(
f"Component {type(self).__name__} is missing the 'forward' method for training mode."
)

def call(self, *args, **kwargs):
raise NotImplementedError(
Expand Down
2 changes: 0 additions & 2 deletions adalflow/adalflow/core/func_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,6 @@ def sync_function_1():
# raise ValueError(f"Error: {e}")
error = str(e)

print(f"typeof output: {type(output)}")

if isinstance(output, Parameter):
if not self.training:
raise ValueError(
Expand Down
Loading

0 comments on commit daa8178

Please sign in to comment.