From f5988c0bb457ae36f055aa3c86f8f6b5a66c3da1 Mon Sep 17 00:00:00 2001
From: Li Yin
Date: Wed, 10 Jul 2024 16:20:23 -0700
Subject: [PATCH 01/11] improve stepout to directly use output data type from
function call as action and add ways for DatClass to handle generic data
types, adapted react agent
---
README.md | 4 +-
docs/source/apis/index.rst | 3 +-
.../lightrag_design_philosophy.rst | 2 +-
lightrag/.gitignore | 2 +-
lightrag/README.md | 2 +-
lightrag/lightrag/components/agent/react.py | 74 ++++++------
.../components/output_parsers/outputs.py | 1 -
lightrag/lightrag/core/base_data_class.py | 7 +-
lightrag/lightrag/core/functional.py | 51 ++++++--
lightrag/lightrag/core/types.py | 80 ++++++++-----
lightrag/test.py | 19 +++
lightrag/tests/test_lazy_import.py | 32 +++++
lightrag/tests/test_model_client.py | 40 +++++++
use_cases/yaml_output.py | 109 +++++++++++++++++-
14 files changed, 340 insertions(+), 86 deletions(-)
create mode 100644 lightrag/test.py
create mode 100644 lightrag/tests/test_lazy_import.py
create mode 100644 lightrag/tests/test_model_client.py
diff --git a/README.md b/README.md
index 9bf7f67f..640d2cc2 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
[![](https://dcbadge.vercel.app/api/server/zt2mTPcu?compact=true&style=flat)](https://discord.gg/zt2mTPcu)
-### ⚡ The PyTorch Library for Large Language Model Applications ⚡
+### ⚡ The PyTorch-like Library for Large Language Model Applications ⚡
*LightRAG* helps developers with both building and optimizing *Retriever-Agent-Generator* pipelines.
It is *light*, *modular*, and *robust*, with a 100% readable codebase.
@@ -20,7 +20,7 @@ It is *light*, *modular*, and *robust*, with a 100% readable codebase.
# Why LightRAG?
-LLMs are like water; they can almost do anything, from GenAI applications such as chatbots, translation, summarization, code generation, and autonomous agents to classical NLP tasks like text classification and named entity recognition. They interact with the world beyond the model’s internal knowledge via retrievers, memory, and tools (function calls). Each use case is unique in its data, business logic, and user experience.
+LLMs are like water; they can be shaped into anything, from GenAI applications such as chatbots, translation, summarization, code generation, and autonomous agents to classical NLP tasks like text classification and named entity recognition. They interact with the world beyond the model’s internal knowledge via retrievers, memory, and tools (function calls). Each use case is unique in its data, business logic, and user experience.
Because of this, no library can provide out-of-the-box solutions. Users must build toward their own use case. This requires the library to be modular, robust, and have a clean, readable codebase. The only code you should put into production is code you either 100% trust or are 100% clear about how to customize and iterate.
diff --git a/docs/source/apis/index.rst b/docs/source/apis/index.rst
index 362cfcc2..d3adfe41 100644
--- a/docs/source/apis/index.rst
+++ b/docs/source/apis/index.rst
@@ -104,10 +104,11 @@ Utils
.. autosummary::
utils.logger
+ utils.setup_env
+ utils.lazy_import
utils.serialization
utils.config
utils.registry
- utils.setup_env
.. toctree::
diff --git a/docs/source/developer_notes/lightrag_design_philosophy.rst b/docs/source/developer_notes/lightrag_design_philosophy.rst
index e7242a0d..faee40f1 100644
--- a/docs/source/developer_notes/lightrag_design_philosophy.rst
+++ b/docs/source/developer_notes/lightrag_design_philosophy.rst
@@ -50,7 +50,7 @@ The above principles are distilled from our experiences and continuous learning
**Developers are the ultimate heroes**
-LLMs are like `water`, they can almost do anything, from GenAI applications such as `chatbot`, `translation`, `summarization`, `code generation`, `autonomous agent` to classical NLP tasks like `text classification`, and `named entity recognition`.
+LLMs are like `water`, they can be shaped into anything, from GenAI applications such as `chatbot`, `translation`, `summarization`, `code generation`, `autonomous agent` to classical NLP tasks like `text classification`, and `named entity recognition`.
They interact with the world beyond the model's internal knowledge via `retriever`, `memory`, and `tools` (`function calls`).
Each use case is unique in its data, its business logic, and its unique user experience.
diff --git a/lightrag/.gitignore b/lightrag/.gitignore
index 799780fd..53607e8b 100644
--- a/lightrag/.gitignore
+++ b/lightrag/.gitignore
@@ -1 +1 @@
-test*.py
+tests/log
diff --git a/lightrag/README.md b/lightrag/README.md
index 0207856a..e4ce3222 100644
--- a/lightrag/README.md
+++ b/lightrag/README.md
@@ -1,7 +1,7 @@
![LightRAG Logo](https://raw.githubusercontent.com/SylphAI-Inc/LightRAG/main/docs/source/_static/images/LightRAG-logo-doc.jpeg)
-### ⚡ The PyTorch Library for Large Language Model Applications ⚡
+### ⚡ The PyTorch-like Library for Large Language Model Applications ⚡
*LightRAG* helps developers with both building and optimizing *Retriever-Agent-Generator (RAG)* pipelines.
It is *light*, *modular*, and *robust*.
diff --git a/lightrag/lightrag/components/agent/react.py b/lightrag/lightrag/components/agent/react.py
index 6b37b6bd..74185fad 100644
--- a/lightrag/lightrag/components/agent/react.py
+++ b/lightrag/lightrag/components/agent/react.py
@@ -23,6 +23,7 @@
log = logging.getLogger(__name__)
+
DEFAULT_REACT_AGENT_SYSTEM_PROMPT = r"""<>
{# role/task description #}
You are a helpful assistant.
@@ -78,7 +79,7 @@
{% for history in step_history %}
Step {{ loop.index }}.
{
- "thought": "{{history.thought}}",
+ "thought": "{{history.action.thought}}",
"action": "{{history.action.action}}",
}
"Observation": "{{history.observation}}"
@@ -164,7 +165,9 @@ def __init__(
func=self._finish,
answer="final answer: 'answer'",
)
- output_parser = JsonOutputParser(data_class=ouput_data_class, example=example)
+ output_parser = JsonOutputParser(
+ data_class=ouput_data_class, examples=[example], return_data_class=True
+ )
prompt_kwargs = {
"tools": self.tool_manager.yaml_definitions,
"output_format_str": output_parser.format_instructions(),
@@ -217,7 +220,7 @@ def finish(answer: str) -> str:
if self.add_llm_as_fallback:
tools.append(llm_tool)
tools.append(finish)
- self.tool_manager = ToolManager(tools=tools)
+ self.tool_manager: ToolManager = ToolManager(tools=tools)
def reset(self):
r"""Reset the agent to start a new query."""
@@ -230,12 +233,10 @@ def _execute_action(self, action_step: StepOutput) -> Optional[StepOutput]:
action = action_step.action
try:
- fun: Function = self.tool_manager.parse_function_call_expr(action)
- result: FunctionOutput = self.tool_manager.execute_function(fun)
+ fun: Function = self.tool_manager.parse_func_expr(action)
+ result: FunctionOutput = self.tool_manager.execute_func(fun)
# TODO: optimize the action_step
- action_step.fun_name = fun.name
- action_step.fun_args = fun.args
- action_step.fun_kwargs = fun.kwargs
+ action_step.function = fun
action_step.observation = result.output
return action_step
except Exception as e:
@@ -246,10 +247,9 @@ def _execute_action(self, action_step: StepOutput) -> Optional[StepOutput]:
def _run_one_step(self, step: int, prompt_kwargs: Dict, model_kwargs: Dict) -> str:
"""
- Run one step of the agent.
+ Run one step of the agent. Plan and execute the action for the step.
"""
- # step_history is the only per-query variable, and should not be controlled by the user
- # add the step_history to the prompt_kwargs
+ step_output: StepOutput = StepOutput(step=step)
prompt_kwargs["step_history"] = self.step_history
log.debug(
@@ -260,27 +260,28 @@ def _run_one_step(self, step: int, prompt_kwargs: Dict, model_kwargs: Dict) -> s
response: GeneratorOutput = self.planner(
prompt_kwargs=prompt_kwargs, model_kwargs=model_kwargs
)
- step_output: StepOutput = None
- try:
- fun_expr: FunctionExpression = FunctionExpression.from_dict(response.data)
- step_output = StepOutput(
- step=step, thought=fun_expr.thought, action=fun_expr
- )
- # print the func expr
- log.debug(f"Step {step}: {fun_expr}")
-
- # execute the action
- if step_output and step_output.action:
- step_output = self._execute_action(step_output)
- printc(f"Step {step}: \n{step_output}\n_______\n", color="blue")
- else:
- log.error(f"Failed to parse response for step {step}")
- except Exception as e:
- log.error(f"Error running step {step}: {e}")
- if step_output is None:
- step_output = StepOutput(step=step, thought="", action="")
- else:
- step_output.observation = f"Error running step {step}: {e}"
+ if response.error:
+ error_msg = f"Error planning step {step}: {response.error}"
+ step_output.observation = error_msg
+ log.error(error_msg)
+ else:
+ try:
+ fun_expr: FunctionExpression = response.data
+ step_output.action = fun_expr
+ # print the func expr
+ log.debug(f"Step {step}: {fun_expr}")
+
+ # execute the action
+ if step_output and step_output.action:
+ step_output = self._execute_action(step_output)
+ printc(f"Step {step}: \n{step_output}\n_______\n", color="blue")
+ else:
+ log.error(f"Failed to parse response for step {step}")
+ except Exception as e:
+ error_msg = f"Error parsing response for step {step}: {e}"
+ step_output.observation = error_msg
+ log.error(error_msg)
+
self.step_history.append(step_output)
return response
@@ -300,8 +301,8 @@ def call(
try:
self._run_one_step(step, prompt_kwargs, model_kwargs)
if (
- self.step_history[-1].fun_name
- and self.step_history[-1].fun_name == "finish"
+ self.step_history[-1].function
+ and self.step_history[-1].function.name == "finish"
):
break
except Exception as e:
@@ -315,15 +316,16 @@ def call(
def _extra_repr(self) -> str:
s = f"max_steps={self.max_steps}, add_llm_as_fallback={self.add_llm_as_fallback}"
- s += super()._extra_repr()
return s
if __name__ == "__main__":
- from components.model_client import GroqAPIClient
+ from lightrag.components.model_client import GroqAPIClient
from lightrag.core.types import ModelClientType
from lightrag.utils import setup_env
+ # get_logger()
+
setup_env()
def multiply(a: int, b: int) -> int:
diff --git a/lightrag/lightrag/components/output_parsers/outputs.py b/lightrag/lightrag/components/output_parsers/outputs.py
index 92ac9975..04cc3bb4 100644
--- a/lightrag/lightrag/components/output_parsers/outputs.py
+++ b/lightrag/lightrag/components/output_parsers/outputs.py
@@ -239,7 +239,6 @@ def __init__(
self.output_processors = JsonParser()
self.examples = examples
- # TODO: make exclude works with both
def format_instructions(
self,
format_type: Optional[DataClassFormatType] = None,
diff --git a/lightrag/lightrag/core/base_data_class.py b/lightrag/lightrag/core/base_data_class.py
index 6cbd33b2..c5d576ae 100644
--- a/lightrag/lightrag/core/base_data_class.py
+++ b/lightrag/lightrag/core/base_data_class.py
@@ -363,7 +363,9 @@ def to_schema(cls, exclude: ExcludeType = None) -> Dict[str, Dict[str, Any]]:
excluded = deepcopy(exclude)
else:
excluded = None
- return get_dataclass_schema(cls, excluded)
+ return get_dataclass_schema(
+ cls, excluded, getattr(cls, "__type_var_map__", None)
+ )
@classmethod
def to_schema_str(cls, exclude: ExcludeType = None) -> str:
@@ -476,6 +478,9 @@ def format_example_str(
else:
raise ValueError(f"Unsupported format type: {format_type}")
+ # TODO:support Generic[Type[T]] for the type of fields
+ # it will automatically use __type_var_map__ attribute
+
"""Reserved for Agent to automatically create a dataclass and to manipulate the code"""
diff --git a/lightrag/lightrag/core/functional.py b/lightrag/lightrag/core/functional.py
index 83417816..7916ea3b 100644
--- a/lightrag/lightrag/core/functional.py
+++ b/lightrag/lightrag/core/functional.py
@@ -318,20 +318,36 @@ def is_dataclass_instance(obj):
return hasattr(obj, "__dataclass_fields__")
-def get_type_schema(type_obj, exclude: ExcludeType = None) -> str:
+def get_type_schema(
+ type_obj,
+ exclude: ExcludeType = None,
+ type_var_map: Optional[Dict] = None,
+) -> str:
"""Retrieve the type name, handling complex and nested types."""
origin = get_origin(type_obj)
+ type_var_map = type_var_map or {}
+
+ # Replace type variables with their actual types to support Generic[T/To]
+ if hasattr(type_obj, "__origin__") and type_obj.__origin__ is not None:
+ type_obj = type_var_map.get(type_obj.__origin__, type_obj)
+ else:
+ type_obj = type_var_map.get(type_obj, type_obj)
+
if origin is Union:
# Handle Optional[Type] and other unions
args = get_args(type_obj)
- types = [get_type_schema(arg, exclude) for arg in args if arg is not type(None)]
+ types = [
+ get_type_schema(arg, exclude, type_var_map)
+ for arg in args
+ if arg is not type(None)
+ ]
return (
f"Optional[{types[0]}]" if len(types) == 1 else f"Union[{', '.join(types)}]"
)
elif origin in {List, list}:
args = get_args(type_obj)
if args:
- inner_type = get_type_schema(args[0], exclude)
+ inner_type = get_type_schema(args[0], exclude, type_var_map)
return f"List[{inner_type}]"
else:
return "List"
@@ -339,34 +355,42 @@ def get_type_schema(type_obj, exclude: ExcludeType = None) -> str:
elif origin in {Dict, dict}:
args = get_args(type_obj)
if args and len(args) >= 2:
- key_type = get_type_schema(args[0], exclude)
- value_type = get_type_schema(args[1], exclude)
+ key_type = get_type_schema(args[0], exclude, type_var_map)
+ value_type = get_type_schema(args[1], exclude, type_var_map)
return f"Dict[{key_type}, {value_type}]"
else:
return "Dict"
elif origin in {Set, set}:
args = get_args(type_obj)
- return f"Set[{get_type_schema(args[0], exclude)}]" if args else "Set"
+ return (
+ f"Set[{get_type_schema(args[0],exclude, type_var_map)}]" if args else "Set"
+ )
elif origin is Sequence:
args = get_args(type_obj)
- return f"Sequence[{get_type_schema(args[0], exclude)}]" if args else "Sequence"
+ return (
+ f"Sequence[{get_type_schema(args[0], exclude,type_var_map)}]"
+ if args
+ else "Sequence"
+ )
elif origin in {Tuple, tuple}:
args = get_args(type_obj)
if args:
- return f"Tuple[{', '.join(get_type_schema(arg, exclude) for arg in args)}]"
+ return f"Tuple[{', '.join(get_type_schema(arg,exclude,type_var_map) for arg in args)}]"
return "Tuple"
elif is_dataclass(type_obj):
# Recursively handle nested dataclasses
- output = str(get_dataclass_schema(type_obj, exclude))
+ output = str(get_dataclass_schema(type_obj, exclude, type_var_map))
return output
return type_obj.__name__ if hasattr(type_obj, "__name__") else str(type_obj)
def get_dataclass_schema(
- cls, exclude: ExcludeType = None
+ cls,
+ exclude: ExcludeType = None,
+ type_var_map: Optional[Dict] = None,
) -> Dict[str, Dict[str, object]]:
"""Generate a schema dictionary for a dataclass including nested structures.
@@ -374,11 +398,14 @@ def get_dataclass_schema(
2. Support nested dataclasses, even with generics like List, Dict, etc.
3. Support metadata in the dataclass fields.
"""
+
if not is_dataclass(cls):
raise ValueError(
"Provided class is not a dataclass, please decorate your class with @dataclass"
)
+ type_var_map = type_var_map or {}
+
schema = {"type": cls.__name__, "properties": {}, "required": []}
# get the exclude list for the current class
current_exclude = exclude.get(cls.__name__, []) if exclude else []
@@ -386,7 +413,9 @@ def get_dataclass_schema(
if f.name in current_exclude:
continue
# prepare field schema, it weill be done recursively for nested dataclasses
- field_schema = {"type": get_type_schema(f.type, exclude)}
+
+ field_type = type_var_map.get(f.type, f.type)
+ field_schema = {"type": get_type_schema(field_type, exclude, type_var_map)}
# check required field
is_required = _is_required_field(f)
diff --git a/lightrag/lightrag/core/types.py b/lightrag/lightrag/core/types.py
index efcea9a7..deac261b 100644
--- a/lightrag/lightrag/core/types.py
+++ b/lightrag/lightrag/core/types.py
@@ -13,6 +13,7 @@
Literal,
Callable,
Awaitable,
+ Type,
)
from collections import OrderedDict
from dataclasses import (
@@ -43,6 +44,7 @@
logger = logging.getLogger(__name__)
T_co = TypeVar("T_co", covariant=True)
+T = TypeVar("T") # invariant type
#######################################################################################
@@ -318,6 +320,13 @@ def add(a, b):
)
+_action_desc = """FuncName() \
+Valid function call expression. \
+Example: "FuncName(a=1, b=2)" \
+Follow the data type specified in the function parameters.\
+e.g. for Type object with x,y properties, use "ObjectType(x=1, y=2)"""
+
+
@dataclass
class FunctionExpression(DataClass):
__doc__ = r"""The data modeling of a function expression for a call, including the name and arguments.
@@ -357,13 +366,7 @@ def add(a, b):
action: str = field(
default_factory=required_field,
# metadata={"desc": "FuncName(, )"},
- metadata={
- "desc": """FuncName() \
- Valid function call expression. \
- Example: "FuncName(a=1, b=2)" \
- Follow the data type specified in the function parameters.\
- e.g. for Type object with x,y properties, use "ObjectType(x=1, y=2)"""
- },
+ metadata={"desc": _action_desc},
)
@classmethod
@@ -429,34 +432,57 @@ class FunctionOutput(DataClass):
# Data modeling for agent component
######################################################################################
@dataclass
-class StepOutput(DataClass):
- __doc__ = r"""The output of a single step in the agent."""
+class StepOutput(DataClass, Generic[T]):
+ __doc__ = r"""The output of a single step in the agent. Suits for serial planning agent such as React"""
step: int = field(
default=0, metadata={"desc": "The order of the step in the agent"}
)
- thought: Optional[str] = field(
- default="", metadata={"desc": "The thought of the agent in the step"}
- )
- action: str = field(
- default="", metadata={"desc": "The action of the agent in the step"}
- )
- fun_name: Optional[str] = field(
- default=None, metadata={"desc": "The function named parsed from action"}
- )
- fun_args: Optional[List[Any]] = field(
- default=None,
- metadata={"desc": "The function positional arguments parsed from action"},
+
+ # This action can be in Function, or Function Exptression, or just str
+ # it includes the thought and action already
+ # directly the output from planner LLMs
+ action: T = field(
+ default=None, metadata={"desc": "The action the agent takes at this step"}
)
- fun_kwargs: Optional[Dict[str, Any]] = field(
- default=None,
- metadata={"desc": "The function keyword arguments parsed from action"},
+
+ function: Optional[Function] = field(
+ default=None, metadata={"desc": "The parsed function from the action"}
)
+
observation: Optional[str] = field(
- default=None, metadata={"desc": "The result of the action"}
+ default=None, metadata={"desc": "The execution result shown for this action"}
)
- def __str__(self):
- return f"Thought {self.step}: {self.thought}\nAction {self.step}: {self.action}\nObservation {self.step}: {self.observation}"
+ @classmethod
+ def with_action_type(cls, action_type: Type[T]) -> Type["StepOutput[T]"]:
+ """
+ Create a new StepOutput class with the specified action type.
+
+ Use this if you want to create schema for StepOutput with a specific action type.
+
+ Args:
+ action_type (Type[T]): The type to set for the action attribute.
+
+ Returns:
+ Type[StepOutput[T]]: A new subclass of StepOutput with the specified action type.
+
+ Example:
+
+ .. code-block:: python
+
+ from lightrag.core.types import StepOutput, FunctionExpression
+
+ StepOutputWithFunctionExpression = StepOutput.with_action_type(FunctionExpression)
+ """
+ # Create a new type variable map
+ type_var_map = {T: action_type}
+
+ # Create a new subclass with the updated type
+ new_cls = type(cls.__name__, (cls,), {"__type_var_map__": type_var_map})
+
+ # Update the __annotations__ to reflect the new type of action
+ new_cls.__annotations__["action"] = action_type
+ return new_cls
#######################################################################################
diff --git a/lightrag/test.py b/lightrag/test.py
new file mode 100644
index 00000000..0a1ef43a
--- /dev/null
+++ b/lightrag/test.py
@@ -0,0 +1,19 @@
+from lightrag.core.types import StepOutput, FunctionExpression
+from dataclasses import dataclass
+
+
+@dataclass
+class MyStepOutput(StepOutput[FunctionExpression]):
+ pass
+
+
+StepOutputClass = StepOutput.with_action_type(FunctionExpression)
+schema = StepOutputClass.to_schema()
+
+print(schema)
+
+instance = StepOutput(
+ step=0, action=FunctionExpression(thought="hello", action="print")
+)
+
+print(instance)
diff --git a/lightrag/tests/test_lazy_import.py b/lightrag/tests/test_lazy_import.py
new file mode 100644
index 00000000..90dc4d14
--- /dev/null
+++ b/lightrag/tests/test_lazy_import.py
@@ -0,0 +1,32 @@
+from types import ModuleType
+import pytest
+import unittest
+from lightrag.utils.lazy_import import safe_import
+
+
+class TestSafeImport(unittest.TestCase):
+ def test_import_installed_package(self):
+ module = safe_import("numpy", "Please install math with: pip install numpy")
+ self.assertIsInstance(
+ module, ModuleType, f"Expected module type, got {type(module)}"
+ )
+ self.assertTrue(
+ hasattr(module, "__version__"),
+ "Module 'numpy' should have attribute '__version__'",
+ )
+
+ def test_import_nonexistent_package(self):
+ with self.assertRaises(ImportError) as cm:
+ safe_import(
+ "nonexistent_package",
+ "Please install nonexistent_package with: pip install nonexistent_package",
+ )
+ self.assertIn(
+ "Please install nonexistent_package with: pip install nonexistent_package",
+ str(cm.exception),
+ (f"Expected error message not found in {str(cm.exception)}"),
+ )
+
+
+if __name__ == "__main__":
+ pytest.main()
diff --git a/lightrag/tests/test_model_client.py b/lightrag/tests/test_model_client.py
new file mode 100644
index 00000000..5b21760b
--- /dev/null
+++ b/lightrag/tests/test_model_client.py
@@ -0,0 +1,40 @@
+from typing import Any
+import unittest
+from unittest.mock import patch
+
+from lightrag.components.model_client import OpenAIClient as OpenAIClientLazyImport
+
+
+class TestLazyImportSubclassing(unittest.TestCase):
+ def test_subclassing_raises_error(self):
+ with self.assertRaises(TypeError):
+
+ class InvalidCustomizeOpenAIClient(OpenAIClientLazyImport):
+ def __init__(self):
+ super().__init__()
+
+ def parse_chat_completion(self, completion: Any) -> Any:
+ """Parse the completion to a str."""
+ print(f"completion: {completion}")
+ return self.chat_completion_parser(completion)
+
+ InvalidCustomizeOpenAIClient()
+
+ @patch.dict("os.environ", {"OPENAI_API_KEY": "test_api_key"})
+ def test_correct_subclassing(self):
+ from lightrag.components.model_client.openai_client import OpenAIClient
+
+ class CorrectCustomizeOpenAIClient(OpenAIClient):
+ def __init__(self):
+ super().__init__()
+
+ def parse_chat_completion(self, completion: Any) -> Any:
+ """Parse the completion to a str."""
+ print(f"completion: {completion}")
+ return self.chat_completion_parser(completion)
+
+ CorrectCustomizeOpenAIClient()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/use_cases/yaml_output.py b/use_cases/yaml_output.py
index 0a26367c..7463028d 100644
--- a/use_cases/yaml_output.py
+++ b/use_cases/yaml_output.py
@@ -3,6 +3,7 @@
from lightrag.components.model_client import GroqAPIClient
from lightrag.components.output_parsers import YamlOutputParser
from dataclasses import dataclass
+from lightrag.utils import setup_env
from lightrag.core.base_data_class import DataClass, field
@@ -20,17 +21,18 @@ class JokeOutput(DataClass):
punchline="Because he was outstanding in his field.",
)
+setup_env()
+
+# Our parser not only helps format the output format, but also examples
class JokeGenerator(Component):
def __init__(self):
super().__init__()
- yaml_parser = YamlOutputParser(data_class=JokeOutput, example=joke_example)
+ yaml_parser = YamlOutputParser(data_class=JokeOutput, examples=[joke_example])
self.generator = Generator(
model_client=GroqAPIClient(),
model_kwargs={"model": "llama3-8b-8192", "temperature": 1.0},
- preset_prompt_kwargs={
- "output_format_str": yaml_parser.format_instructions()
- },
+ prompt_kwargs={"output_format_str": yaml_parser.format_instructions()},
output_processors=yaml_parser,
)
@@ -54,3 +56,102 @@ def call(self, query: str, model_kwargs: dict = {}) -> JokeOutput:
answer = joke_generator.call("Tell me two jokes.", model_kwargs={"temperature": 1})
print(answer)
print(f"typeof answer: {type(answer)}")
+
+ from langchain_core.output_parsers import (
+ JsonOutputParser as LangchainJsonOutputParser,
+ )
+ from langchain_core.pydantic_v1 import BaseModel, Field
+ from typing import TypeVar, Generic
+
+ T = TypeVar("T")
+ # 1. we dont include default value in the schema as it is how the program wants to handle it and not the job of llm
+ # 2. nested class is defined in definitions.
+ # we directly include in that type, and we should remove the additional "type" and make that part a dict instead of a string.
+ ours = {
+ "type": "MyStepOutput",
+ "properties": {
+ "step": {"type": "int", "desc": "The order of the step in the agent"},
+ "action": {
+ "type": "{'type': 'FunctionExpression', 'properties': {'thought': {'type': 'Optional[str]', 'desc': 'Why the function is called'}, 'action': {'type': 'str', 'desc': 'FuncName() Valid function call expression. Example: \"FuncName(a=1, b=2)\" Follow the data type specified in the function parameters.e.g. for Type object with x,y properties, use \"ObjectType(x=1, y=2)'}}, 'required': ['action']}",
+ "desc": "The action the agent takes at this step",
+ },
+ "function": {
+ "type": "Optional[{'type': 'Function', 'properties': {'thought': {'type': 'Optional[str]', 'desc': 'Why the function is called'}, 'name': {'type': 'str', 'desc': 'The name of the function'}, 'args': {'type': 'Optional[List[object]]', 'desc': 'The positional arguments of the function'}, 'kwargs': {'type': 'Optional[Dict[str, object]]', 'desc': 'The keyword arguments of the function'}}, 'required': []}]",
+ "desc": "The parsed function from the action",
+ },
+ "observation": {
+ "type": "Optional[str]",
+ "desc": "The execution result shown for this action",
+ },
+ },
+ "required": [],
+ }
+ langchains = {
+ "properties": {
+ "setup": {
+ "title": "Setup",
+ "description": "question to set up a joke",
+ "default": "",
+ "allOf": [{"$ref": "#/definitions/JokeOutput"}],
+ },
+ "punchline": {
+ "title": "Punchline",
+ "description": "answer to resolve the joke",
+ "default": "",
+ "type": "string",
+ },
+ },
+ "definitions": {
+ "JokeOutput": {
+ "title": "JokeOutput",
+ "type": "object",
+ "properties": {
+ "setup": {
+ "title": "Setup",
+ "default": "",
+ "desc": "question to set up a joke",
+ "type": "string",
+ },
+ "punchline": {
+ "title": "Punchline",
+ "default": "",
+ "desc": "answer to resolve the joke",
+ "type": "string",
+ },
+ },
+ }
+ },
+ }
+
+ # subtype: allOf": [{"$ref": "#/definitions/JokeOutput"}]},
+ definitions = {
+ "definitions": {
+ "JokeOutput": {
+ "title": "JokeOutput",
+ "type": "object",
+ "properties": {
+ "setup": {
+ "title": "Setup",
+ "default": "",
+ "desc": "question to set up a joke",
+ "type": "string",
+ },
+ "punchline": {
+ "title": "Punchline",
+ "default": "",
+ "desc": "answer to resolve the joke",
+ "type": "string",
+ },
+ },
+ }
+ }
+ }
+
+ class JokeOutputV1(BaseModel, Generic[T]):
+ setup: JokeOutput = Field(description="question to set up a joke", default="")
+ punchline: str = Field(description="answer to resolve the joke", default="")
+
+ json_parser = LangchainJsonOutputParser(pydantic_object=JokeOutputV1)
+ print("langchain instruction")
+ # failed to detect the generic type of the pydantic object
+ print(json_parser.get_format_instructions())
From 4c5053de2652208067943585550fd33fcaa50dad Mon Sep 17 00:00:00 2001
From: Li Yin
Date: Wed, 10 Jul 2024 18:59:10 -0700
Subject: [PATCH 02/11] cleaned up all react agent code and finished first
draft of react agent
---
developer_notes/react_note.py | 74 ++++
docs/source/_static/images/query_1.png | Bin 0 -> 145288 bytes
docs/source/_static/images/query_2.png | Bin 0 -> 140890 bytes
docs/source/developer_notes/agent.rst | 374 +++++++++++++++++++-
lightrag/lightrag/components/agent/react.py | 42 +--
5 files changed, 462 insertions(+), 28 deletions(-)
create mode 100644 developer_notes/react_note.py
create mode 100644 docs/source/_static/images/query_1.png
create mode 100644 docs/source/_static/images/query_2.png
diff --git a/developer_notes/react_note.py b/developer_notes/react_note.py
new file mode 100644
index 00000000..f5d68979
--- /dev/null
+++ b/developer_notes/react_note.py
@@ -0,0 +1,74 @@
+from lightrag.components.agent import ReActAgent
+from lightrag.core import Generator, ModelClientType, ModelClient
+from lightrag.utils import setup_env
+
+setup_env()
+
+
+# Define tools
+def multiply(a: int, b: int) -> int:
+ """
+ Multiply two numbers.
+ """
+ return a * b
+
+
+def add(a: int, b: int) -> int:
+ """
+ Add two numbers.
+ """
+ return a + b
+
+
+def divide(a: float, b: float) -> float:
+ """
+ Divide two numbers.
+ """
+ return float(a) / b
+
+
+llama3_model_kwargs = {
+ "model": "llama3-70b-8192", # llama3 70b works better than 8b here.
+ "temperature": 0.0,
+}
+gpt_model_kwargs = {
+ "model": "gpt-3.5-turbo",
+ "temperature": 0.0,
+}
+
+
+def test_react_agent(model_client: ModelClient, model_kwargs: dict):
+ tools = [multiply, add, divide]
+ queries = [
+ "What is the capital of France? and what is 465 times 321 then add 95297 and then divide by 13.2?",
+ "Give me 5 words rhyming with cool, and make a 4-sentence poem using them",
+ ]
+ # define a generator without tools for comparison
+
+ generator = Generator(
+ model_client=model_client,
+ model_kwargs=model_kwargs,
+ )
+
+ react = ReActAgent(
+ max_steps=6,
+ add_llm_as_fallback=True,
+ tools=tools,
+ model_client=model_client,
+ model_kwargs=model_kwargs,
+ )
+ # print(react)
+
+ for query in queries:
+ print(f"Query: {query}")
+ agent_response = react.call(query)
+ llm_response = generator.call(prompt_kwargs={"input_str": query})
+ print(f"Agent response: {agent_response}")
+ print(f"LLM response: {llm_response}")
+ print("")
+
+
+if __name__ == "__main__":
+ test_react_agent(ModelClientType.GROQ(), llama3_model_kwargs)
+ # test_react_agent(ModelClientType.OPENAI(), gpt_model_kwargs)
+ print("Done")
diff --git a/docs/source/_static/images/query_1.png b/docs/source/_static/images/query_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..53e4d6d2b6ac04cf3dd4805d8764b7aafd3146f0
GIT binary patch
literal 145288
zcmb5V1z42b+BQrHNF&n1h)M|3or5A>A~8q}h)8$GFi5JDf`oJ<-5o=Npmf*JT|+Z4
zGao+tefQqqe%|*#zJHG6KJGQE>ssqPuj{%$z0y!5C1N1L!ong|R(kmw3yUBd3k!RM
z5dSuYxBmeI3yY}KT2AhjvYgz*S1yk4t?ewZu#`TH}?`%j1nx8|QM=)5uN+>X(;u(AaGSKEvs6)GrU{#{{tK
z9RM+yA?RXC16=ZO-gU6W_3)QB)+?(AUpc7baUZHDvXY%STfXe=5^(CoBIXDns{Vi-
zmi+W>NH8AO%Z(n`Ko~QYdbH>7Jag3b1%~rj%AbuEivw@j%Y;pobXogj>&EAT5h-Tb`>S{OU4kZ)Q;LDE`X;DKF7}b<6Sa(E1&zjS{T&bpb`gUI;0r`TI
zW{j&Ox-KVIWy6K;p^C=6>(ED!nP#fOo^GRG)ASJB8L9{nT?KY3--CY?-+1rSHOL>m
zy3L!K%y6Ht_b!fOt&j$dq+it4cM~($XBy;j8!8mOD
zAwEd+692dJdzC~?b9C4^PkgY-?j|v0e)Ggfbt*0XD&dT1dbHt<@^b5FjqAp3(wh^0
z!R*``#ATYvi45gys8)QFPy&bW%bg+3v#+y)Rh>DmRbOz$fub)P!+t)1eG$h`Ya_;c
zNRIn5fWnmYSpaJ}!Nc%$Nr=J+#YPUzXG&`QZ_B?J_E?sO7@gc9;UeZ@#L7F|+xzNu
z;k3c?!tJKHq@Nn=@C6>{hP?+5mQWVft5@lD+w!=wFUjl-$v}?=(`wYg>;c#>9}-*1
z-m$_lHpTlDKwXZj(MDP@O^z4jM1dQR%cF#2Elc7ei>*)aS@!NUzQ+fma=-&T;UzjT
z?4MBDA108-cxQ;KFB^CjHG;D>|+S`RsMaIBvrzdU_+ho6PEkC-UnMJ62w
znM!c*7d*}Y5n0w(Pj#@F0&FtVGX;m`wZ4-94tU6
zJ=WI8UzH@D5RAT!38;?O(U8z+RM=sMuyK7+?_*xeeRtQJ_=>P0^5)}B)a@j{n&+ta
z_N<>qg?s(pI{kVqiEfWkD0gS~YU%i3g^9
zNC|tO=ul&nJ5S$0;IewGYCVkFi3p#_gh(pnMMw})!hQT79ECg+
zLH)T78O&KGnPj8oLUIhr@oHR|Oxf-uJ@={a+uwgo?Mlr>J@oW^M9U3VA~{V3t^$01
zn5oHOecQoHmE>;>-wrq+I(ph3+8bQ7T%w$CP}(cAA6^AHKkPu(dftVf^@O#Ii<7b&
zHP1fp{9sW3tsGgTSM2d(ze1weA&a@N^))KPCm&fKnim=!s$?GB!y2ij-k6vCzH&q3
z_es)Y108N!?hlh1CBz22^q)y+9>-}7*8id#)0;YpKRsh;Tyqp&0?)*Zv<*M(H_d)t)n1-i$uh?WFJCO5
z`uKLu=9`(0z1t9-?Jpq>{zY5AIXsaHVP$~=p#}@08K$}&Ifi}v_t!3E_O_Z%+Y#0yLoSB`_;y|Z{<^NU~c-l&w$=QogSN>
zQISfK@8o@3$6et`?ny^GHTz4EYdhxI*Y>#fqO-Mg{xwx~RCB`9k<+^~y)&R0py_BL
z%dg+RY{}>dJi~;^ESY2^XT{%(r#an>{rvT`yI|1T>+A9(be3E=eu{gF_#i>bwGkbU
zGY_!ww85sy#i+yz$FEq;1mFb*5&IALt9y^-n&^k;ZW7ejeG@OE$IOLts2IM^VRCfFlbt5Yv{
zA_PL{Ne*W~Kk49jac_kADPx0BrO2`QH%lFB9q?(#mhOnCxsEv=KX*m_bl-Gm#j0v^
z{GNe8{kVtq$Y`2H=zvRtF}So*%KfT=x{^zbOQXkZC2M78rPV1=d?E$m<>9A$LpK%F
zlzjbpvuExnXqEx!MnM4J3kG6lDwxFV-(vTqi}~gkf=TR9efS-6QCWa9hIY&
ztB@;_w>-8oI!H(z{Ip|WJ88RNd*P9`*4H1ro&4sYp2TUepBPoYQT$Adyj)VY#;31q
zlFHCpRS%2v{FJ_w*oNbVj`=~U*k*NaxvRx1V9Cp}wNg9YVIF9PMeLD`_0a|
zaHB2J6iMmX8^fxodLD~~vR&@UgiOu|SLE7;_iEkjYvvjq89kDn42L|X_Jwng
zbFXr7C2>53_TTM2TfFk0`fNNkVAp?s$aWCFcfdu?;W(3BXE~R~ORY?O!L`7}STtTF
zq6?WHnDezvpKA?8FF9
zwa@;Yol#>uy--43+ISvMcwIPBm|ggyrmNa59U(ckxHIm9q&)#grA){edTSu&Cr`Rg
zLN@bx*1&X*R5g(%YRzK9ty!p_xm&6iJiM**?%kOFnxaJzuY0)!kCAJu$9d!GnV@K*
zXp-aXg56H_z7(oxzbwCLuQADC25!@^@6=S|t95ZDS?bBNc-HKD#(cf_`}+AslkX!R
zW*L2C#v#k$@=^Acf(^kF*WQ=oUBmANY3CD;%X+|uuq)_p
zs!DRwJ?E~J?|N97pl|=M}z9I
zDI$IdXQ|s`Z9P_bArtcOaM=N?wl;pp2qQUGq!~W1g*DHnPm_r)-5uQyw5W~7s4ie0
zd=T1!_du~thVfXWP!X%Q09MaJU-`YdIu_?`m=Fs)%o+>tHiUiqFx);^Shz8vSOmB4
zd$-Ta44nT?CCJXe{qHdL$e)bQwdItRZ{OPHE*2IJu2zn2BHW~Iw^dD9>*%}btE)+v
zJKFP@y>onP!Q*A`^rr}xl$XS9(B8t$?4g&vor9}{m-M5*a!A~U|3vdXdiYlsH(TjP
z`s%M9$~n4NJQU&Ki3m&mmH%%k|7!8yO6vcsB)_=u|5fzglK!)(uB(NMoTL41n{G1yTAKfA_umu$
ztDqF`pP~QTRQzL}|BAhxXc;0Y-hVEd43VQ19R>?a7EAf%a~&`2J!67;WBD4B?Vsw0
zEf*?EN{3G}X|?cP=6rl4^2%HDKJlf_VrSs!eTr{+j{;8_B<=>w`W!Yq6>YgdPXaFn
z8?N`7^7pES8aCOAuUcKt7qur1PKJ_ZEPfXkjAzM*uddM#N}Z@7`$4TQV3*yzR@3VOJ{xs(2>VI#J?(E>Uu7l2GdNby{CNVpDq4Z!~cAVQBwRE?%ATk
zmi?;rk3Rh~>8~FCfBnmNh7&iSpd9hP_56Pv&R>yVNIn$){H$vIJY(hYE0}_Nqy1&Z
zw+uMcQf0?TL&E{jV3J2k*9o-+FhvWa0*7+v4rtL~^@g>MgZ;&b5tna+yGa8%V76%MvFO|9gf0Ierd>Hk%0M>E@@A
z_H&s^h&l#OX*_Q!F?WmbIgZl(+By*#)7;kg2C?rfC-e!DDkBb^=eRd&%d9fNu|3s37YD`uIfDvJ#zc5&iSeamcU4rPS%2Y+|=#WEx#TBEh+pIK(?caN)pzZfpZ$E
zLX+$I-ne0Hrni|}i7l<3q+1S=D}wrVi6uNU#teikvhCfI8k~uNu##2Z{j}zvKD^PZ
z(Z9iJj>QJC`k{7bWol=l`q+jT!i|B``BVRZPoW8Jhc6ZwCbK!?iiTbM2^t*r5YopH
zWcCHnu^#JZb%>V{VK|2z)}KO&0e&vV&>*WxtErQQp%W&$=|#M5PQ
zO@H@#zA3``#yQfF2VVx2pCt{f-+8kAAA;ZZDKKC{{el!1`8pr}%$gp$)K1V>Vdr-P
z(~{G6-_?Qfpi+Qhb!DK0_`_-W?78ZkC6yPTtNSc64PU*N&8>MwZZ7d@F$}sz9FOTq
zEG~shb+r-0fO;Mk-
zQcKo5g_(y|m^C`wZxC?)Yqxy`o==z>ZB~9o_f@RAQ8SwPi+XseMCBak6IyJ)V;;Cd
zH=e!8ikB7uK5*eAeQR7~)t(CkA5Mk04wnd0GFlUR9%PObmtAsX$&I2jx8L~$kF0-A
zfad^*K8vK(kG6XX><;Xy8wyaf*sk+^c+VPLaYab23q1J{vN4ko`5@7Xikk1BhJ(ZS
zDA^*UQYL%N4ZvsFe)M)#G3Q?O92|cU>d2&2hQ8nIwXkniUX#J
zD%LtYxAiz&|5-V={q22j&n9yu!z;%$iX3;E-a;vH4u_+5_NlXIF2Uobv?1dm&(aGj
z@OQtXlU#R!bysO%)9KWNKMztFj^R`>Od&zWx!oukv}RRXyds;rTxEa3Ot{!lkfX0FCeuI5RkJmZl+B*$cNXy`A3b@*WVdfV2De)xr#C|4&YvF#S7mHsaX0O6
zaiy1_GY;ok+1|uNkY{Y~0N8H)b{uWNM;a1nQ+JMe$K(UeM4^hqBQtkRd|3Umrqp+K
zoA4kG2`$u+E_B#=JFEnvC8GkRah%hDLw1A4r6~b>Fn<-^j?X`~8AY=bK}wuZE`J%Z
zzKe_!akprP&LK8fG#VN3ZU@k4m$ypXpS(!Uv$lCQY=(ItwMAYSACu{#_fM>u-sZo?F$
zP8*Ihr4b`FMbxyry+xf%dW@O$(0A?q)_BE_Lizi3nY|H|mnjm{A7X)#c!TY
z2y&_pH3Pc4A_sE-jnB=sElZiQMrboO9;EN(dZ|k;L%~}1!o=qSD6fe|0wRZy>6Bpb
zFl9C&wAQ9H+uCNN=bPE4)a0Bg{W~A$85Jb$*k~qgSsrH58ucT?0D(662>oW9GYGGY
z^rzC7Cwna8Pk4bKs^z$Vaq=Hs?0yX@Fo|Taac|7SP⪼Cad^0->mnsoQirV)2H}3
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
z
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