From daa81786bc5c9b120f50b658633f6b1970392983 Mon Sep 17 00:00:00 2001
From: Li Yin
Date: Sat, 28 Dec 2024 23:03:26 -0800
Subject: [PATCH] generator will always use generatoroutput in parameter, setup
component clearly and make many previous component only a class
---
adalflow/adalflow/components/agent/react.py | 152 +++++++++++++++---
.../output_parsers/dataclass_parser.py | 7 +-
.../components/output_parsers/outputs.py | 6 +-
adalflow/adalflow/core/component.py | 28 +++-
adalflow/adalflow/core/func_tool.py | 2 -
adalflow/adalflow/core/generator.py | 47 +++---
adalflow/adalflow/core/prompt_builder.py | 9 +-
adalflow/adalflow/core/string_parser.py | 6 +-
adalflow/adalflow/core/tool_manager.py | 88 +++++++---
adalflow/adalflow/core/types.py | 10 +-
adalflow/adalflow/optim/grad_component.py | 11 --
adalflow/adalflow/optim/parameter.py | 39 ++---
.../optim/text_grad/backend_engine_prompt.py | 79 ++++++---
.../optim/text_grad/text_loss_with_eval_fn.py | 14 +-
.../adalflow/optim/text_grad/tgd_optimizer.py | 3 +-
adalflow/adalflow/optim/trainer/trainer.py | 7 +-
adalflow/tests/test_parameter.py | 13 ++
.../hotpot_qa/adal_exp/train_agent_rag.py | 5 +-
use_cases/classification/train.py | 10 +-
.../bbh/object_count/task.py | 7 +-
.../bbh/object_count/train_new.py | 9 +-
21 files changed, 386 insertions(+), 166 deletions(-)
diff --git a/adalflow/adalflow/components/agent/react.py b/adalflow/adalflow/components/agent/react.py
index 60824847..f6336334 100644
--- a/adalflow/adalflow/components/agent/react.py
+++ b/adalflow/adalflow/components/agent/react.py
@@ -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,
@@ -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:
@@ -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
@@ -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,
@@ -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
@@ -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(
@@ -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
@@ -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):
@@ -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:
@@ -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(
@@ -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")
diff --git a/adalflow/adalflow/components/output_parsers/dataclass_parser.py b/adalflow/adalflow/components/output_parsers/dataclass_parser.py
index 6d2e56dd..39fda03c 100644
--- a/adalflow/adalflow/components/output_parsers/dataclass_parser.py
+++ b/adalflow/adalflow/components/output_parsers/dataclass_parser.py
@@ -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__)
@@ -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).
@@ -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:
diff --git a/adalflow/adalflow/components/output_parsers/outputs.py b/adalflow/adalflow/components/output_parsers/outputs.py
index 288cba67..807fb996 100644
--- a/adalflow/adalflow/components/output_parsers/outputs.py
+++ b/adalflow/adalflow/components/output_parsers/outputs.py
@@ -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
@@ -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.
@@ -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.")
diff --git a/adalflow/adalflow/core/component.py b/adalflow/adalflow/core/component.py
index b239f521..e72b3a9c 100644
--- a/adalflow/adalflow/core/component.py
+++ b/adalflow/adalflow/core/component.py
@@ -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(
diff --git a/adalflow/adalflow/core/func_tool.py b/adalflow/adalflow/core/func_tool.py
index ad135b74..7c5e0f1d 100644
--- a/adalflow/adalflow/core/func_tool.py
+++ b/adalflow/adalflow/core/func_tool.py
@@ -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(
diff --git a/adalflow/adalflow/core/generator.py b/adalflow/adalflow/core/generator.py
index 950682a5..a0fa2a87 100644
--- a/adalflow/adalflow/core/generator.py
+++ b/adalflow/adalflow/core/generator.py
@@ -518,6 +518,10 @@ def forward(
}
output = self.call(**input_args, id=id)
+ if not isinstance(output, GeneratorOutput):
+ raise ValueError(
+ f"Output should be of type GeneratorOutput, got {type(output)}"
+ )
# 2. Generate a Parameter object from the output
combined_prompt_kwargs = compose_model_kwargs(self.prompt_kwargs, prompt_kwargs)
# if self.data_map_func is None:
@@ -528,19 +532,26 @@ def forward(
]
log.debug(f"Predecessors: {predecessors} for generator {self.name}")
- param_data = (
- # output.raw_response
- output.data
- if output and not output.error
- else f"Error: {output.error}, raw_response: {output.raw_response}"
- )
+
+ def data_to_prompt_map_fn(data: Parameter) -> str:
+ data: GeneratorOutput = data.data
+ if data.data is not None:
+ return data.data
+ if data.error is not None:
+ return f"Response: {data.raw_response} parsed with error: {data.error}"
+ return f"Response: {data.raw_response}"
+
+ # TODO: all parameter should just wrap the whole output.
+ # this is for training.
+ param_data = output
response: Parameter = OutputParameter(
data=param_data,
name=self.name + "_output",
role_desc=f"Output from (llm) {self.name}",
param_type=ParameterType.GENERATOR_OUTPUT,
data_id=id,
- full_response=output.data, # the data structure
+ full_response=output, # the data structure
+ data_in_prompt=data_to_prompt_map_fn,
)
response.set_predecessors(predecessors)
response.trace_forward_pass(
@@ -582,10 +593,7 @@ def forward(
backward_fn=self.backward,
backward_engine=self.backward_engine,
response=response,
- prompt_kwargs={
- k: v.data if isinstance(v, Parameter) else v
- for k, v in prompt_kwargs.items()
- },
+ prompt_kwargs=prompt_kwargs,
template=self.template,
prompt_str=self.get_prompt(**combined_prompt_kwargs),
id=id,
@@ -594,7 +602,6 @@ def forward(
)
return response
- # == pytorch custom autograd function ==
def backward(
self,
response: Parameter, # the output of the forward pass
@@ -687,15 +694,15 @@ def _backward_through_all_predecessors(
# instruction and objective is the same for all the children
instruction_str, objective_str = None, None
- # 1. Generate the conversation string
+ # 1. Generate the conversation input and output
input_prompt_kwargs = {
- k: v.data if isinstance(v, Parameter) else v
+ k: v.get_prompt_data() if isinstance(v, Parameter) else v
for k, v in prompt_kwargs.items()
}
conversation_prompt_kwargs = {
"input_value": input_prompt_kwargs,
- "llm_output": response.data,
+ "llm_output": response.get_prompt_data(),
}
conversation_str = Prompt(
@@ -704,7 +711,7 @@ def _backward_through_all_predecessors(
)()
all_pred_info = Prompt(
- prompt_kwargs={"variables": children_params},
+ prompt_kwargs={"variables": [p.get_param_info() for p in children_params]},
template=ALL_PRED_INFO,
)()
@@ -849,10 +856,8 @@ def _backward_through_one_predecessor(
}
conversation_prompt_kwargs = {
- # "variable_name": pred.name,
- # "variable_desc": pred.role_desc,
"input_value": input_prompt_kwargs,
- "llm_output": response.data,
+ "llm_output": response.get_prompt_data(),
}
conversation_str = Prompt(
@@ -912,10 +917,6 @@ def _backward_through_one_predecessor(
data=manual_response, raw_response=manual_response
)
else:
- # manual_response = f"You get score: {response._score}."
- # gradient_output = GeneratorOutput(
- # data=manual_response, raw_response=manual_response
- # )
gradient_output: GeneratorOutput = backward_engine(
prompt_kwargs=backward_engine_prompt_kwargs
diff --git a/adalflow/adalflow/core/prompt_builder.py b/adalflow/adalflow/core/prompt_builder.py
index 0d998b63..d5f3e9ae 100644
--- a/adalflow/adalflow/core/prompt_builder.py
+++ b/adalflow/adalflow/core/prompt_builder.py
@@ -7,9 +7,10 @@
from jinja2 import Template, Environment, StrictUndefined, meta
-from adalflow.core.component import Component
from adalflow.core.default_prompt_template import DEFAULT_ADALFLOW_SYSTEM_PROMPT
from adalflow.optim.parameter import Parameter
+from dataclasses import dataclass
+from adalflow.core.base_data_class import DataClass
logger = logging.getLogger(__name__)
@@ -17,7 +18,8 @@
T = TypeVar("T")
-class Prompt(Component):
+@dataclass
+class Prompt(DataClass):
__doc__ = r"""Renders a text string(prompt) from a Jinja2 template string.
In default, we use the :ref:`DEFAULT_ADALFLOW_SYSTEM_PROMPT` as the template.
@@ -125,6 +127,9 @@ def print_prompt(self, **kwargs) -> str:
except Exception as e:
raise ValueError(f"Error rendering Jinja2 template: {e}")
+ def __call__(self, *args: Any, **kwds: Any) -> Any:
+ return self.call(*args, **kwds)
+
def call(self, **kwargs) -> str:
"""
Renders the prompt template with keyword arguments. Allow None values.
diff --git a/adalflow/adalflow/core/string_parser.py b/adalflow/adalflow/core/string_parser.py
index 3001b512..c7526c79 100644
--- a/adalflow/adalflow/core/string_parser.py
+++ b/adalflow/adalflow/core/string_parser.py
@@ -5,7 +5,6 @@
from typing import Dict, List, Union
import logging
-from adalflow.core.component import Component
import adalflow.core.functional as F
log = logging.getLogger(__name__)
@@ -13,12 +12,15 @@
BOOLEAN_PARSER_OUTPUT_TYPE = bool
-class Parser(Component):
+class Parser:
__doc__ = r"""Base class for all string parsers."""
def __init__(self):
super().__init__()
+ def __call__(self, input: str) -> object:
+ return self.call(input)
+
def call(self, input: str) -> object:
raise NotImplementedError(
"Parser subclasses must implement the __call__ method"
diff --git a/adalflow/adalflow/core/tool_manager.py b/adalflow/adalflow/core/tool_manager.py
index 1db303e4..4ac17607 100644
--- a/adalflow/adalflow/core/tool_manager.py
+++ b/adalflow/adalflow/core/tool_manager.py
@@ -22,6 +22,7 @@
from adalflow.core.container import ComponentList
from adalflow.optim.grad_component import GradComponent
+from adalflow.core.component import Component
from adalflow.core.func_tool import FunctionTool
from adalflow.core.types import (
FunctionDefinition,
@@ -57,7 +58,7 @@ def run_async_in_new_loop(coro):
asyncio.set_event_loop(None)
-class CallFunctionTool(GradComponent):
+class CallFunctionTool(Component):
__doc__ = """Contains other unit gradcomponent such as calling
a FunctionTool"""
@@ -76,11 +77,13 @@ def bicall(
context: Dict[str, object] = {},
):
if isinstance(func, Parameter):
- # data = func.successor_map_fn(func)
printc(f"context: {context}", color="yellow")
- tool: FunctionTool = context[func.data.name]
+ func_data: Function = func.map_to_successor(self)
+ if not isinstance(func_data, Function):
+ raise ValueError(f"Error parsing function expression: {func}")
+ tool: FunctionTool = context[func_data.name]
print(f"tool training: {tool.training}")
- output = tool.forward(*func.data.args, **func.data.kwargs)
+ output = tool.forward(*func_data.args, **func_data.kwargs)
from adalflow.optim.grad_component import fun_to_grad_component
@@ -108,25 +111,35 @@ class FunctionExperssionToFunction(GradComponent):
def __init__(self):
super().__init__()
- def call(self, expr: FunctionExpression, context: Dict[str, object]):
- print("DummpyGradComponent call")
- print(expr)
+ def call(self, expr: FunctionExpression, context: Dict[str, object]) -> Function:
+
+ assert isinstance(
+ expr, FunctionExpression
+ ), f"Expected FunctionExpression, got {type(expr)}"
expr_str = expr.action
func_name, args, kwargs = parse_function_call_expr(expr_str, context)
- return Function(
+ printc(
+ f"func_name: {func_name}, args: {args}, kwargs: {kwargs}", color="yellow"
+ )
+ output = Function(
name=func_name,
args=args,
kwargs=kwargs,
thought=expr.thought,
)
+ printc(f"output: {output}", color="yellow")
+ return output
# TODO: good to track all the failed function calls
# Tool manager is a task component
-class ToolManager(GradComponent):
+class ToolManager(Component):
__doc__ = r""""Manage a list of tools, context, and all ways to execute functions.
+
+ ToolManager is a task component that does not need its own backward function.
+
yaml and json definitions are for quick access to the definitions of the tools.
If you need more specification, such as using exclude field, you can use the function_definitions.
"""
@@ -207,20 +220,27 @@ def function_definitions(self) -> List[FunctionDefinition]:
return [tool.definition for tool in self.tools]
def parse_func_expr(
- self, expr: Union[FunctionExpression, Parameter]
+ self,
+ expr: Union[FunctionExpression, Parameter],
+ map_fn: Callable = lambda x: x.data,
) -> Union[Function, Parameter]:
r"""Parse the function call expression."""
if isinstance(expr, Parameter):
try:
- dummy = FunctionExperssionToFunction()
+ func = FunctionExperssionToFunction()
+ expr.add_successor_map_fn(func, map_fn=map_fn)
print("FunctionExperssionToFunction")
- return dummy.forward(expr, context=self.context)
+ output = func.forward(expr, context=self.context)
+ print(f"output data: {output.data}")
+ return output
except Exception as e:
- log.error(f"Error {e} parsing function call expression: {expr}")
- raise ValueError(f"Error {e} parsing function call expression: {expr}")
+ error_msg = (
+ f"Error {e} parsing function call expression: {map_fn(expr)}"
+ )
+ return error_msg
else:
try:
expr_str = expr.action
@@ -276,35 +296,50 @@ def forward(
*,
expr_or_fun: Union[FunctionExpression, Function, Parameter],
step: Literal["parse", "execute"] = "execute",
+ map_fn: Callable = lambda x: x.data, # how to map the parameter to the needed data
) -> Union[FunctionOutput, Function, Parameter]:
+ "Run a forward pass on the tool manager such as parsing function expression or executing function."
if isinstance(expr_or_fun, Parameter):
+ expr_or_fun_data = map_fn(expr_or_fun)
+ print(f"expr_or_fun_data: {expr_or_fun_data}")
if step == "execute":
- if isinstance(expr_or_fun.data, Function):
- return self.execute_func(expr_or_fun)
+ if isinstance(expr_or_fun_data, Function):
+ return self.execute_func(expr_or_fun, map_fn=map_fn)
else:
raise NotImplementedError(
- "Only function call expressions are supported for now."
+ "Only Function expressions are supported for now."
)
else:
- if isinstance(expr_or_fun.data, FunctionExpression):
- return self.parse_func_expr(expr_or_fun)
+ if isinstance(expr_or_fun_data, FunctionExpression):
+ print(f"start parsing: {expr_or_fun_data}")
+ output = self.parse_func_expr(expr_or_fun, map_fn=map_fn)
+ print(f"output 3: {output.data}")
+ return output
else:
raise NotImplementedError(
- f"Only function call expressions are supported for now. Got {expr_or_fun.data}"
+ f"Only function call expressions are supported for now. Got {expr_or_fun_data}"
)
else:
raise ValueError(f"expr_or_fun should be a Parameter. Got {expr_or_fun}")
# return self.call(expr_or_fun=expr_or_fun, step=step)
def execute_func(
- self, func: Union[Function, Parameter]
+ self, func: Union[Function, Parameter], map_fn: Callable = lambda x: x.data
) -> Union[FunctionOutput, Parameter]:
r"""Execute the function. If the function is async, use asyncio.run to execute it."""
if isinstance(func, Parameter):
+ try:
+
+ call_func_tool = CallFunctionTool()
+ func.add_successor_map_fn(call_func_tool, map_fn=map_fn)
+ return call_func_tool.forward(func, context=self.context)
+
+ except Exception as e:
+ log.error(f"Error {e} executing function: {func.data}")
+ error_msg = f"Error {e} executing function: {func.data}"
+ return error_msg
- call_func_tool = CallFunctionTool()
- return call_func_tool.forward(func, context=self.context)
else:
try:
tool: FunctionTool = self.context[func.name]
@@ -342,12 +377,15 @@ async def execute_func_async(self, func: Function) -> FunctionOutput:
raise ValueError(f"Error {e} executing function: {func}")
def execute_func_expr(
- self, expr: Union[FunctionExpression, Parameter]
+ self,
+ expr: Union[FunctionExpression, Parameter],
+ map_fn: Callable = lambda x: x.data,
) -> Union[FunctionOutput, Parameter]:
r"""Execute the function expression. Support both sync and async functions."""
if isinstance(expr, Parameter):
- func: Parameter = self.parse_func_expr(expr.data)
+
+ func: Parameter = self.parse_func_expr(expr, map_fn=map_fn)
if not isinstance(func, Parameter):
raise ValueError(f"Error parsing function expression: {expr}")
diff --git a/adalflow/adalflow/core/types.py b/adalflow/adalflow/core/types.py
index db3f27d0..eceac117 100644
--- a/adalflow/adalflow/core/types.py
+++ b/adalflow/adalflow/core/types.py
@@ -411,12 +411,10 @@ def add(a, b):
The benefits are less failed function calls.
"""
- question: Optional[str] = field(
- default=None, metadata={"desc": "The question to ask the LLM"}
- )
- thought: Optional[str] = field(
- default=None, metadata={"desc": "Why the function is called"}
- )
+ # question: str = field(
+ # default=None, metadata={"desc": "The question to ask the LLM"}
+ # )
+ thought: str = field(default=None, metadata={"desc": "Why the function is called"})
action: str = field(
default_factory=required_field,
metadata={"desc": _action_desc},
diff --git a/adalflow/adalflow/optim/grad_component.py b/adalflow/adalflow/optim/grad_component.py
index 0602f411..d306a508 100644
--- a/adalflow/adalflow/optim/grad_component.py
+++ b/adalflow/adalflow/optim/grad_component.py
@@ -45,12 +45,6 @@ def __init__(self, *args, **kwargs):
super().__setattr__("backward_engine", None)
super().__setattr__("id", str(uuid.uuid4()))
- def __call__(self, *args, **kwargs):
- if self.training:
- return self.forward(*args, **kwargs)
- else:
- return self.call(*args, **kwargs)
-
def set_backward_engine(self, backward_engine: "BackwardEngine", *args, **kwargs):
raise NotImplementedError("set_backward_engine method is not implemented")
@@ -74,11 +68,6 @@ def forward(self, *args, **kwargs) -> "Parameter":
f"Forwarding through {self.name} with args: {args} and kwargs: {kwargs}"
)
- # if "id" not in kwargs:
- # raise ValueError(
- # "id must be provided in the kwargs of a GradComponent for tracing."
- # )
-
# 1. get all predecessors from all args and kwargs
input_args = OrderedDict()
diff --git a/adalflow/adalflow/optim/parameter.py b/adalflow/adalflow/optim/parameter.py
index 83f2ea1e..6984128e 100644
--- a/adalflow/adalflow/optim/parameter.py
+++ b/adalflow/adalflow/optim/parameter.py
@@ -265,7 +265,11 @@ def __init__(
self.eval_input = eval_input
self.successor_map_fn = successor_map_fn or {}
- self.data_in_prompt = lambda x: x.data if not data_in_prompt else data_in_prompt
+
+ def default_prompt_map_fn(param: Parameter):
+ return param.data
+
+ self.data_in_prompt = data_in_prompt or default_prompt_map_fn
def map_to_successor(self, successor: object) -> T:
"""Apply the map function to the successor based on the successor's id."""
@@ -309,6 +313,9 @@ def get_gradients_names(self) -> str:
names = ", ".join(names)
return names
+ def get_prompt_data(self) -> str:
+ return self.data_in_prompt(self)
+
def get_gradients_str(self) -> str:
if not self.gradients:
return ""
@@ -551,8 +558,10 @@ def get_param_info(self):
return {
"name": self.name,
"role_desc": self.role_desc,
- "data": self.data_in_prompt(self),
+ "prompt_data": self.data_in_prompt(self), # default to use all data
"param_type": self.param_type,
+ "requires_opt": self.requires_opt,
+ "eval_input": self.eval_input, # for output passing to the eval_fn
}
def set_peers(self, peers: List["Parameter"] = None):
@@ -688,7 +697,8 @@ def get_short_value(self, n_words_offset: int = 10) -> str:
:type n_words_offset: int
"""
# 1. ensure the data is a string
- data = self.data
+ # data = self.data
+ data = self.get_prompt_data()
if not isinstance(self.data, str):
data = str(self.data)
words = data.split(" ")
@@ -775,19 +785,14 @@ def generate_node_html(node: "Parameter", output_dir="node_pages"):
filename = f"{output_dir}/{node.name}.html"
- # inspect everything about the gradients
- # gradients = ""
- # for i, g in enumerate(node.gradients):
- # # Format each gradient as YAML with proper indentation and replace \n\n\n with actual line breaks
- # gradient_yaml = g.to_yaml().replace("\\n", "\n").replace("\n\n\n", "\n")
- # gradients += f"{i}:\n{gradient_yaml}\n"
-
# Gather gradients as JSON objects
gradients = []
for i, g in enumerate(node.gradients):
- gradients.append(
- g.to_json_obj()
- ) # Use to_json_obj for proper JSON object structure
+ gradient = g.to_json_obj()
+ for k, v in gradient.items():
+ if isinstance(v, str):
+ gradient[k] = v.replace("<", "<").replace(">", ">")
+ gradients.append(gradient)
data_json = None
node_data_type = str(type(node.data)).replace("<", "<").replace(">", ">")
@@ -863,14 +868,11 @@ def draw_interactive_html_graph(
"""
from jinja2 import Template
- # Define the output file path
output_file = "interactive_graph.html"
final_file = filepath + "_" + output_file if filepath else output_file
- # Create a pyvis Network instance
net = Network(height="750px", width="100%", directed=True)
- # different color per node type
node_colors = {
ParameterType.PROMPT: "lightblue",
ParameterType.DEMOS: "orange",
@@ -919,7 +921,6 @@ def draw_interactive_html_graph(
f"Skipping edge from {source.name} to {target.name} as one of the nodes does not exist."
)
- # Enable physics for better layout
net.toggle_physics(True)
net.template = Template(
"""
@@ -977,8 +978,6 @@ def draw_interactive_html_graph(
"""
)
- # Save the graph as an HTML file
-
net.show(final_file)
print(f"Interactive graph saved to {final_file}")
@@ -1587,6 +1586,7 @@ def __init__(
score: Optional[float] = None,
eval_input: object = None,
successor_map_fn: Optional[Dict[str, Callable]] = None,
+ data_in_prompt: Optional[Callable] = None,
full_response: Optional[Any] = None,
):
super().__init__(
@@ -1602,6 +1602,7 @@ def __init__(
score=score,
eval_input=eval_input,
successor_map_fn=successor_map_fn,
+ data_in_prompt=data_in_prompt,
)
self.component_trace = ComponentTrace()
diff --git a/adalflow/adalflow/optim/text_grad/backend_engine_prompt.py b/adalflow/adalflow/optim/text_grad/backend_engine_prompt.py
index bc9582ae..f13f79c2 100644
--- a/adalflow/adalflow/optim/text_grad/backend_engine_prompt.py
+++ b/adalflow/adalflow/optim/text_grad/backend_engine_prompt.py
@@ -24,14 +24,19 @@
If the same DataID has multiple gradients, it means this component/variable is called multiple times in the compound system(with a cycle) in the same order as it appears in the gradient list.
+{% if output_format_str %}
+{{output_format_str}}
+{% endif %}
+
+
{{conversation_sec}}
+
{{objective_instruction_sec}}
-{% if output_format_str %}
-{{output_format_str}}
-{% endif %}
+
+
"""
##############################################
# Loss Component
@@ -48,6 +53,7 @@
# """
# Your only goal is to clearly states how it obtained the "".
+
OBJECTIVE_INSTRUCTION_BASE = r"""
Your task is to provide the response with specific feedback based on the ground truth and the score in the "".
Especially when the score is low.
@@ -57,13 +63,46 @@
"""
+### NOTE: Last node's feedback
+OBJECTIVE_INSTRUCTION_CHAIN = r"""This conversation is part of a larger system. The was later used as "{{response_name}}: {{response_desc}}".
+
+Your only goal is to clearly states how it obtained the "Eval output/score": {{response_gradient}}.
+Especially when the score is low.
+Be CONCISE.
+If you have enough context, add a more specific feedback on how it failed.
+"""
+
+### Loss/Score Information ###
+# INPUTS: parameter.get_param_info():
+# the input_output of a GradientContext
+
+# response_value -> response.get_prompt_data()
+LOSS_CONVERSATION_TEMPLATE_STRING = r"""
+The variable is passed to the eval function and compared with a target/ground truth value.
+
+EVAL_FUNC: {{eval_fn_desc}}
+
+INPUTS:
+{% for key, (value, eval_type) in inputs.items() %}
+({{ key }}) (role: {{ value.role_desc }}),
+data: {{ value.prompt_data }},
+input_to_eval_fn: {{ value.eval_input }},
+data_type: {{ eval_type }}
+{% endfor %}
+
+OUTPUTS/SCORE: {{response_value}}
+{% if metadata %}
+Note: {{metadata}}
+{% endif %}"""
+
+
### Variable to get feedback on, often it is pred in the loss component
# pass parameter.get_param_info() to get the variable info
LOSS_CONVERSATION_START_INSTRUCTION_STRING_FN = r"""
TARGET VARIABLE:
{{variable.name}}
{{variable.role_desc}}
- {{variable.data}}
+ {{variable.prompt_data}}
{{conversation_str}}
"""
@@ -76,7 +115,7 @@
INPUTS:
{% for key, (value, eval_type) in inputs.items() %}
({{ key }}) (role: {{ value.role_desc }}),
-full response: {{ value.data }},
+data: {{ value.prompt_data }},
input_to_eval_fn: {{ value.eval_input }},
data_type: {{ eval_type }}
{% endfor %}
@@ -108,23 +147,13 @@
{% endif %}
"""
-### Backward engine: user prompt
-# First part to provide context of LLM as gradComponent
-# The target variable is used as either input or a task instruction to a language model (LM):
-# replace the "The target variable is used as either input or a task instruction to a language model (LM):" with the {{variable_desc}}
-# NAME: {{variable_name}}
-# Description: {{variable_desc}}
-LLM_CONVERSATION_TEMPLATE = r"""
-LM_INPUT: {{input_value}}
-LM_OUTPUT: {{llm_output}}"""
-
VARIABLE_AND_PEERS_INFO = r"""
{{variable.name}}
{{variable.param_type}}
{{variable.role_desc}}
-{{ variable.data}}
+{{ variable.prompt_data}}
{% if peers %}
@@ -135,8 +164,8 @@
PEER_TYPE: {{peer.param_type}},
PEER_ROLE: {{peer.role_desc}}
WILL_BE_OPTIMIZED: {{peer.requires_opt}}
-{% if peer.data %}
-PEER_VARIABLE: {{peer.data}}
+{% if peer.prompt_data %}
+PEER_VARIABLE: {{peer.prompt_data}}
{% else %}
PEER_VARIABLE: EMPTY
{% endif %}
@@ -145,6 +174,7 @@
{% endif %}
"""
+
# a list of variables
ALL_PRED_INFO = r"""
@@ -156,12 +186,23 @@
TYPE: {{variable.param_type}},
ROLE: {{variable.role_desc}}
WILL_BE_OPTIMIZED: {{variable.requires_opt}}
-VARIABLE: {{ variable.data}}
+VARIABLE: {{ variable.prompt_data}}
{% endfor %}
{% endif %}
"""
+
+### Backward engine: user prompt
+# First part to provide context of LLM as gradComponent
+# The target variable is used as either input or a task instruction to a language model (LM):
+# replace the "The target variable is used as either input or a task instruction to a language model (LM):" with the {{variable_desc}}
+# NAME: {{variable_name}}
+# Description: {{variable_desc}}
+LLM_CONVERSATION_TEMPLATE = r"""
+LM_INPUT: {{input_value}}
+LM_OUTPUT: {{llm_output}}"""
+
OUTPUT_INSTRUCTION = r"""
You will create a feedback for each of the variable in the list above.
If a variable will not be optimied, you just output empty string.
diff --git a/adalflow/adalflow/optim/text_grad/text_loss_with_eval_fn.py b/adalflow/adalflow/optim/text_grad/text_loss_with_eval_fn.py
index 9d7ac36c..6cefdfb6 100644
--- a/adalflow/adalflow/optim/text_grad/text_loss_with_eval_fn.py
+++ b/adalflow/adalflow/optim/text_grad/text_loss_with_eval_fn.py
@@ -25,21 +25,13 @@
LOSS_CONVERSATION_TEMPLATE_STRING,
LOSS_CONVERSATION_START_INSTRUCTION_STRING_FN,
OBJECTIVE_INSTRUCTION_BASE,
+ OBJECTIVE_INSTRUCTION_CHAIN,
)
log = logging.getLogger(__name__)
-OBJECTIVE_INSTRUCTION_CHAIN = r"""This conversation is part of a larger system. The was later used as "{{response_name}}: {{response_desc}}".
-
-Your only goal is to clearly states how it obtained the "Eval output/score": {{response_gradient}}.
-Especially when the score is low.
-Be CONCISE.
-If you have enough context, add a more specific feedback on how it failed.
-"""
-
-
class EvalFnToTextLoss(LossComponent):
__doc__ = """Convert an evaluation function to a text loss.
@@ -219,7 +211,7 @@ def _backward_through_one_predecessor(
inputs = {}
for k, v in kwargs.items():
- inputs[k] = (v, str(type(v.eval_input)))
+ inputs[k] = (v.get_param_info(), str(type(v.eval_input)))
# response information
conversation_str = Prompt(
@@ -227,7 +219,7 @@ def _backward_through_one_predecessor(
prompt_kwargs={
"inputs": inputs,
"eval_fn_desc": eval_fn_desc,
- "response_value": response.data,
+ "response_value": response.get_prompt_data(),
"metadata": json.dumps(metadata) if metadata else None,
},
)()
diff --git a/adalflow/adalflow/optim/text_grad/tgd_optimizer.py b/adalflow/adalflow/optim/text_grad/tgd_optimizer.py
index 4247c5cf..26bbf38e 100644
--- a/adalflow/adalflow/optim/text_grad/tgd_optimizer.py
+++ b/adalflow/adalflow/optim/text_grad/tgd_optimizer.py
@@ -369,8 +369,9 @@ def get_gradient_memory_text(self, param: Parameter) -> str:
def _get_user_prompt_kwargs(self, param: Parameter) -> Dict[str, str]:
+ peers_params = [p.get_param_info() for p in self.params if p.id != param.id]
variable_and_peer_info = self.variable_and_peers_info.call(
- variable=param.get_param_info(), peers=param.peers # param.peers
+ variable=param.get_param_info(), peers=peers_params
)
variable_grad = param.get_gradients_component_schema(skip_correct_sample=True)
diff --git a/adalflow/adalflow/optim/trainer/trainer.py b/adalflow/adalflow/optim/trainer/trainer.py
index bf35a282..5262d5ad 100644
--- a/adalflow/adalflow/optim/trainer/trainer.py
+++ b/adalflow/adalflow/optim/trainer/trainer.py
@@ -763,7 +763,7 @@ def _fit_demos_one_step_for_debug(
# print(f"Teacher y_preds: {y_preds[0].to_dict()}")
- y_preds_outputs = [p.full_response for p in y_preds]
+ y_preds_outputs = [p.data for p in y_preds]
batch_eval: EvaluationResult = self.adaltask.evaluate_samples(
batch, y_preds_outputs
@@ -824,7 +824,7 @@ def _fit_demos_one_step_for_debug(
# for loss in losses_student:
# loss.backward()
# Check the eval result
- y_preds_outputs = [p.full_response for p in y_preds_student]
+ y_preds_outputs = [p.data for p in y_preds_student]
eval_result = self.adaltask.evaluate_samples(batch, y_preds_outputs)
print(f"Eval result: {eval_result.avg_score}")
# eval_score_per_item = eval_result.per_item_scores
@@ -1116,7 +1116,7 @@ def _fit_text_grad_demo_mix_constrained(
all_losses.extend(losses) # student losses
# extract the non-parameter y_preds
all_y_preds.extend(
- [y.full_response for y in y_preds if isinstance(y, Parameter)]
+ [y.data for y in y_preds if isinstance(y, Parameter)]
)
# for loss in losses:
@@ -1901,6 +1901,7 @@ def _text_grad_constraint_propose_step(
raise ValueError("Loss should be a Parameter object")
self.adaltask.eval()
move_batch_eval = self.adaltask.evaluate_samples(all_samples, all_y_preds)
+ print(f"Moving batch eval: {move_batch_eval}")
move_batch_score = move_batch_eval.avg_score
move_batch_acc_score_list = move_batch_eval.per_item_scores
diff --git a/adalflow/tests/test_parameter.py b/adalflow/tests/test_parameter.py
index 3da290da..a8f64f0e 100644
--- a/adalflow/tests/test_parameter.py
+++ b/adalflow/tests/test_parameter.py
@@ -46,6 +46,19 @@ def test_update_value(self, data, new_data):
param.update_value(new_data)
assert param.data == new_data, "Parameter data should be updated correctly"
+ def test_data_in_prompt_callable(self):
+ param = Parameter(
+ data=10, requires_opt=False, data_in_prompt=lambda x: f"Data: {x.data}"
+ )
+
+ assert (
+ param.data_in_prompt(param) == "Data: 10"
+ ), "Data should be correctly formatted in the prompt"
+
+ assert (
+ param.get_prompt_data() == "Data: 10"
+ ), "Data should be correctly formatted in the prompt"
+
# def test_update_value_incorrect_type(self):
# """Test updating the parameter with an incorrect type."""
# param = Parameter[int](data=10)
diff --git a/benchmarks/hotpot_qa/adal_exp/train_agent_rag.py b/benchmarks/hotpot_qa/adal_exp/train_agent_rag.py
index 1ea541f6..93bd1fc5 100644
--- a/benchmarks/hotpot_qa/adal_exp/train_agent_rag.py
+++ b/benchmarks/hotpot_qa/adal_exp/train_agent_rag.py
@@ -161,7 +161,7 @@ def train(
# )
train(
- debug=False,
+ debug=True,
max_steps=12,
# resume_from_ckpt="/Users/liyin/.adalflow/ckpt/AgenticRAGAdal/constrained_max_steps_4_dca7e_run_1.json",
)
@@ -170,3 +170,6 @@ def train(
# 0.7, 0.72 /Users/liyin/.adalflow/ckpt/AgenticRAGAdal/constrained_max_steps_2_b7523_run_1.json
# 208.085706949234s, 2 steps, maximum 4 steps allow for an agent.
# 0.72->0.74, 4 steps, 366s, /Users/liyin/.adalflow/ckpt/AgenticRAGAdal/constrained_max_steps_4_dca7e_run_1.json [Already faster, still lots to optimize]
+
+ # 1246s, 12 steps, 0.8 val, /Users/liyin/.adalflow/ckpt/AgenticRAGAdal/constrained_max_steps_12_defe7_run_1.json
+ # v2149s, both gradients, 0.68 -> 0.78 /Users/liyin/.adalflow/ckpt/AgenticRAGAdal/constrained_max_steps_12_8a24a_run_1.json
diff --git a/use_cases/classification/train.py b/use_cases/classification/train.py
index ec110506..1f419140 100644
--- a/use_cases/classification/train.py
+++ b/use_cases/classification/train.py
@@ -10,7 +10,6 @@
from use_cases.config import (
gpt_3_model,
gpt_4o_model,
- gpt_4o_mini_model,
)
@@ -52,7 +51,7 @@ def prepare_eval(
def prepare_loss(
self, sample: TRECExtendedData, y_pred: adal.Parameter, *args, **kwargs
) -> Tuple[Callable[..., Any], Dict]:
- full_response = y_pred.full_response
+ full_response = y_pred.data
y_label = -1 # default value for failed prediction
if (
full_response
@@ -87,8 +86,8 @@ def train(
adal_component = TrecClassifierAdal(
model_client=model_client,
model_kwargs=model_kwargs,
- text_optimizer_model_config=gpt_4o_mini_model,
- backward_engine_model_config=gpt_4o_mini_model,
+ text_optimizer_model_config=gpt_4o_model,
+ backward_engine_model_config=gpt_4o_model,
teacher_model_config=gpt_4o_model,
)
print(adal_component)
@@ -157,7 +156,8 @@ def train(
# no past history, 83% only. 84 /Users/liyin/.adalflow/ckpt/TrecClassifierAdal/constrained_max_steps_12_ca5ac_run_1.json
# past history, both gradients, 88.89% in 12 steps /Users/liyin/.adalflow/ckpt/TrecClassifierAdal/constrained_max_steps_12_b4612_run_1.json 1477s
# /Users/liyin/.adalflow/ckpt/TrecClassifierAdal/constrained_max_steps_12_f1e5a_run_1.json 811s 89.58% both positive and negative gradients
- # /Users/liyin/.adalflow/ckpt/TrecClassifierAdal/constrained_max_steps_12_05a8e_run_1.json 1518 85.41% only negative gradients
+ # /Users/liyin/.adalflow/ckpt/TrecClassifierAdal/constrained_max_steps_12_05a8e_run_1.json 1518s 85.41% only negative gradients
+ # /Users/liyin/.adalflow/ckpt/TrecClassifierAdal/constrained_max_steps_12_e0f86_run_1.json 1247s, 88.88 both gradients
# theory: all few-shots demo or instruction, all so that the llm can reason better. Once it reches to its limits, no more shots can help or further instruction can.
diff --git a/use_cases/question_answering/bbh/object_count/task.py b/use_cases/question_answering/bbh/object_count/task.py
index d3b1e8ed..53fb5057 100644
--- a/use_cases/question_answering/bbh/object_count/task.py
+++ b/use_cases/question_answering/bbh/object_count/task.py
@@ -40,7 +40,7 @@ def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict):
few_shot_demos = adal.Parameter(
data=None,
role_desc="To provide few shot demos to the language model",
- requires_opt=True,
+ requires_opt=False,
param_type=ParameterType.DEMOS,
)
@@ -62,7 +62,7 @@ def call(
output = self.llm_counter(prompt_kwargs={"input_str": question}, id=id)
# print(f"output: {output}, training: {self.training}")
if self.training:
- if output.full_response.error and "429" in output.full_response.error:
+ if output.data.error and "429" in output.data.error:
raise ValueError("Rate limit exceeded")
else:
if output.error and "429" in output.error:
@@ -85,8 +85,9 @@ def test_object_count_task():
task_pipeline.train()
answer: adal.Parameter = task_pipeline(question, id="1")
print(answer)
- print(f"full_response: {answer.full_response}")
+ print(f"data: {answer.data}")
answer.draw_graph()
+ print(f"prompt_data: {answer.get_prompt_data()}")
if __name__ == "__main__":
diff --git a/use_cases/question_answering/bbh/object_count/train_new.py b/use_cases/question_answering/bbh/object_count/train_new.py
index b0cb024a..c0400a9c 100644
--- a/use_cases/question_answering/bbh/object_count/train_new.py
+++ b/use_cases/question_answering/bbh/object_count/train_new.py
@@ -43,6 +43,7 @@ def prepare_eval(
self, sample: Example, y_pred: adal.GeneratorOutput
) -> Tuple[float, Dict[str, Any]]:
y_label = -1
+ print(f"y_pred: {y_pred}")
if (
y_pred is not None and y_pred.data is not None
): # if y_pred and y_pred.data: might introduce bug when the data is 0
@@ -58,7 +59,7 @@ def prepare_loss(
eval_input=sample.answer,
requires_opt=False,
)
- pred.eval_input = pred.full_response.data
+ pred.eval_input = pred.data.data
return self.loss_fn, {"kwargs": {"y": pred, "y_gt": y_gt}, "id": sample.id}
@@ -159,7 +160,7 @@ def train(
ckpt = train(
debug=False,
- max_steps=1,
+ max_steps=12,
strategy=set_strategy,
exclude_input_fields_from_bootstrap_demos=True,
)
@@ -180,3 +181,7 @@ def train(
# without gradients -> 0.9 on tests
# without positive gradients -> /Users/liyin/.adalflow/ckpt/ObjectCountAdalComponent/constrained_max_steps_12_8ac70_run_1.json 0.84->0.94 val, 0.82 -> 0.88 test
+
+ # /Users/liyin/.adalflow/ckpt/ObjectCountAdalComponent/constrained_max_steps_12_1f358_run_1.json 1 val 0.96 val 955s
+ # 0.94 val, 0.89 test, /Users/liyin/.adalflow/ckpt/ObjectCountAdalComponent/constrained_max_steps_12_e1bb5_run_1.json 907s, with both positive and negatives
+ # 92, 91 test /Users/liyin/.adalflow/ckpt/ObjectCountAdalComponent/constrained_max_steps_12_18e8d_run_1.json 747s