lionagi 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. lionagi/core/execute/structure_executor.py +21 -1
  2. lionagi/core/flow/monoflow/ReAct.py +3 -1
  3. lionagi/core/flow/monoflow/followup.py +3 -1
  4. lionagi/core/generic/component.py +197 -120
  5. lionagi/core/generic/condition.py +2 -0
  6. lionagi/core/generic/edge.py +33 -33
  7. lionagi/core/graph/graph.py +1 -1
  8. lionagi/core/tool/tool_manager.py +10 -9
  9. lionagi/experimental/report/form.py +64 -0
  10. lionagi/experimental/report/report.py +138 -0
  11. lionagi/experimental/report/util.py +47 -0
  12. lionagi/experimental/tool/schema.py +3 -3
  13. lionagi/experimental/tool/tool_manager.py +1 -1
  14. lionagi/experimental/validator/rule.py +139 -0
  15. lionagi/experimental/validator/validator.py +56 -0
  16. lionagi/experimental/work/__init__.py +10 -0
  17. lionagi/experimental/work/async_queue.py +54 -0
  18. lionagi/experimental/work/schema.py +60 -17
  19. lionagi/experimental/work/work_function.py +55 -77
  20. lionagi/experimental/work/worker.py +56 -12
  21. lionagi/experimental/work2/__init__.py +0 -0
  22. lionagi/experimental/work2/form.py +371 -0
  23. lionagi/experimental/work2/report.py +289 -0
  24. lionagi/experimental/work2/schema.py +30 -0
  25. lionagi/experimental/{work → work2}/tests.py +1 -1
  26. lionagi/experimental/work2/util.py +0 -0
  27. lionagi/experimental/work2/work.py +0 -0
  28. lionagi/experimental/work2/work_function.py +89 -0
  29. lionagi/experimental/work2/worker.py +12 -0
  30. lionagi/integrations/storage/storage_util.py +4 -4
  31. lionagi/integrations/storage/structure_excel.py +268 -0
  32. lionagi/integrations/storage/to_excel.py +18 -9
  33. lionagi/libs/__init__.py +4 -0
  34. lionagi/tests/test_core/generic/__init__.py +0 -0
  35. lionagi/tests/test_core/generic/test_component.py +89 -0
  36. lionagi/version.py +1 -1
  37. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
  38. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/RECORD +43 -27
  39. lionagi/experimental/work/_logger.py +0 -25
  40. /lionagi/experimental/{work/exchange.py → report/__init__.py} +0 -0
  41. /lionagi/experimental/{work/util.py → validator/__init__.py} +0 -0
  42. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
  43. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
  44. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,89 @@
1
+ import asyncio
2
+ from typing import Any, Callable, Dict, List
3
+ from pydantic import Field
4
+ from functools import wraps
5
+ from lionagi import logging as _logging
6
+ from lionagi.libs import func_call
7
+ from lionagi.core.generic import BaseComponent
8
+
9
+ from .schema import Work, WorkStatus
10
+ from ..work.worklog import WorkLog
11
+ from .worker import Worker
12
+
13
+
14
+ class WorkFunction(BaseComponent):
15
+ """Work function management and execution."""
16
+
17
+ function: Callable
18
+ args: List[Any] = Field(default_factory=list)
19
+ kwargs: Dict[str, Any] = Field(default_factory=dict)
20
+ retry_kwargs: Dict[str, Any] = Field(default_factory=dict)
21
+ worklog: WorkLog = Field(default_factory=WorkLog)
22
+ instruction: str = Field(
23
+ default="", description="Instruction for the work function"
24
+ )
25
+ refresh_time: float = Field(
26
+ default=0.5, description="Time to wait before checking for pending work"
27
+ )
28
+
29
+ @property
30
+ def name(self):
31
+ """Get the name of the work function."""
32
+ return self.function.__name__
33
+
34
+ async def execute(self):
35
+ """Execute pending work items."""
36
+ while self.worklog.pending:
37
+ work_id = self.worklog.pending.popleft()
38
+ work = self.worklog.logs[work_id]
39
+ if work.status == WorkStatus.PENDING:
40
+ try:
41
+ await func_call.rcall(self._execute, work, **work.retry_kwargs)
42
+ except Exception as e:
43
+ work.status = WorkStatus.FAILED
44
+ _logging.error(f"Work {work.id_} failed with error: {e}")
45
+ self.worklog.errored.append(work.id_)
46
+ else:
47
+ _logging.warning(
48
+ f"Work {work.id_} is in {work.status} state "
49
+ "and cannot be executed."
50
+ )
51
+ await asyncio.sleep(self.refresh_time)
52
+
53
+ async def _execute(self, work: Work):
54
+ """Execute a single work item."""
55
+ work.status = WorkStatus.IN_PROGRESS
56
+ result = await self.function(*self.args, **self.kwargs)
57
+ work.deliverables = result
58
+ work.status = WorkStatus.COMPLETED
59
+ return result
60
+
61
+
62
+ def workfunc(func):
63
+
64
+ @wraps(func)
65
+ async def wrapper(self: Worker, *args, **kwargs):
66
+ # Retrieve the worker instance ('self')
67
+ if not hasattr(self, "work_functions"):
68
+ self.work_functions = {}
69
+
70
+ if func.__name__ not in self.work_functions:
71
+ # Create WorkFunction with the function and its docstring as instruction
72
+ self.work_functions[func.__name__] = WorkFunction(
73
+ function=func,
74
+ instruction=func.__doc__,
75
+ args=args,
76
+ kwargs=kwargs,
77
+ retry_kwargs=kwargs.pop("retry_kwargs", {}),
78
+ )
79
+
80
+ # Retrieve the existing WorkFunction
81
+ work_function: WorkFunction = self.work_functions[func.__name__]
82
+ # Update args and kwargs for this call
83
+ work_function.args = args
84
+ work_function.kwargs = kwargs
85
+
86
+ # Execute the function using WorkFunction's managed execution process
87
+ return await work_function.execute()
88
+
89
+ return wrapper
@@ -0,0 +1,12 @@
1
+ from abc import ABC
2
+ from pydantic import Field
3
+ from lionagi.core.generic import BaseComponent
4
+
5
+
6
+ class Worker(BaseComponent, ABC):
7
+ form_templates: dict = Field(
8
+ default={}, description="The form templates of the worker"
9
+ )
10
+ work_functions: dict = Field(
11
+ default={}, description="The work functions of the worker"
12
+ )
@@ -29,10 +29,10 @@ def output_node_list(structure):
29
29
  structure_output = {
30
30
  "id": structure.id_,
31
31
  "timestamp": structure.timestamp,
32
- "type": structure.class_name(),
32
+ "type": structure.class_name,
33
33
  }
34
34
  summary_list.append(structure_output.copy())
35
- structure_output["head_nodes"] = [i.id_ for i in structure.get_heads()]
35
+ structure_output["head_nodes"] = json.dumps([i.id_ for i in structure.get_heads()])
36
36
  # structure_output['nodes'] = json.dumps([i for i in structure.internal_nodes.keys()])
37
37
  # structure_output['edges'] = json.dumps([i for i in structure.internal_edges.keys()])
38
38
  output[structure_output["type"]] = [structure_output]
@@ -40,7 +40,7 @@ def output_node_list(structure):
40
40
  node_output = {
41
41
  "id": node.id_,
42
42
  "timestamp": node.timestamp,
43
- "type": node.class_name(),
43
+ "type": node.class_name,
44
44
  }
45
45
  summary_list.append(node_output.copy())
46
46
  if isinstance(node, System) or isinstance(node, Instruction):
@@ -286,4 +286,4 @@ class ParseNode:
286
286
  if key == "self":
287
287
  continue
288
288
  init_params[key] = args[key]
289
- return cls(**init_params)
289
+ return cls(**init_params)
@@ -0,0 +1,268 @@
1
+ import pandas as pd
2
+ import json
3
+ from pathlib import Path
4
+
5
+ from lionagi.integrations.storage.storage_util import ParseNode
6
+ from lionagi.core.execute.structure_executor import StructureExecutor
7
+ from lionagi.core.agent.base_agent import BaseAgent
8
+ from lionagi.core.execute.base_executor import BaseExecutor
9
+ from lionagi.core.execute.instruction_map_executor import InstructionMapExecutor
10
+
11
+
12
+ def excel_reload(structure_name=None, structure_id=None, dir="structure_storage"):
13
+ """
14
+ Loads a structure from an Excel file into a StructureExecutor instance.
15
+
16
+ This function uses the StructureExcel class to handle the reloading process. It identifies the
17
+ Excel file based on the provided structure name or ID and reloads the structure from it.
18
+
19
+ Args:
20
+ structure_name (str, optional): The name of the structure to reload.
21
+ structure_id (str, optional): The unique identifier of the structure to reload.
22
+ dir (str): The directory path where the Excel files are stored.
23
+
24
+ Returns:
25
+ StructureExecutor: An instance of StructureExecutor containing the reloaded structure.
26
+
27
+ Raises:
28
+ ValueError: If neither structure_name nor structure_id is provided, or if multiple or no files
29
+ are found matching the criteria.
30
+ """
31
+ excel_structure = StructureExcel(structure_name, structure_id, dir)
32
+ excel_structure.reload()
33
+ return excel_structure.structure
34
+
35
+
36
+ class StructureExcel:
37
+ """
38
+ Manages the reloading of structures from Excel files.
39
+
40
+ This class handles the identification and parsing of structure data from an Excel workbook. It supports
41
+ loading from specifically named Excel files based on the structure name or ID.
42
+
43
+ Attributes:
44
+ structure (StructureExecutor): The loaded structure, ready for execution.
45
+ default_agent_executable (BaseExecutor): The default executor for agents within the structure.
46
+
47
+ Methods:
48
+ get_heads(): Retrieves the head nodes of the loaded structure.
49
+ parse_node(info_dict): Parses a node from the structure based on the provided dictionary.
50
+ get_next(node_id): Gets the next nodes connected by outgoing edges from a given node.
51
+ relate(parent_node, node): Relates two nodes within the structure based on the edge definitions.
52
+ parse(node_list, parent_node=None): Recursively parses nodes and their relationships from the structure.
53
+ reload(): Reloads the structure from the Excel file based on the initially provided parameters.
54
+ """
55
+ structure: StructureExecutor = StructureExecutor()
56
+ default_agent_executable: BaseExecutor = InstructionMapExecutor()
57
+
58
+ def __init__(self, structure_name=None, structure_id=None, file_path="structure_storage"):
59
+ """
60
+ Initializes the StructureExcel class with specified parameters.
61
+
62
+ This method sets up the paths and reads the Excel file, preparing the internal dataframes used for
63
+ structure parsing.
64
+
65
+ Args:
66
+ structure_name (str, optional): The name of the structure to reload.
67
+ structure_id (str, optional): The unique identifier of the structure to reload.
68
+ file_path (str): The base path where the Excel files are stored.
69
+
70
+ Raises:
71
+ ValueError: If both structure_name and structure_id are provided but do not correspond to a valid file,
72
+ or if multiple or no files are found when one of the identifiers is provided.
73
+ """
74
+ self.file_path = file_path
75
+ if not structure_name and not structure_id:
76
+ raise ValueError("Please provide the structure name or id")
77
+ if structure_name and structure_id:
78
+ self.filename = f"{file_path}/{structure_name}_{structure_id}.xlsx"
79
+ self.file = pd.read_excel(self.filename, sheet_name=None)
80
+ elif structure_name and not structure_id:
81
+ dir_path = Path(file_path)
82
+ files = list(dir_path.glob(f"{structure_name}*.xlsx"))
83
+ filename = []
84
+ for file in files:
85
+ try:
86
+ name = file.name
87
+ name = name.rsplit("_", 1)[0]
88
+ if name == structure_name:
89
+ filename.append(file.name)
90
+ except:
91
+ continue
92
+ if len(filename) > 1:
93
+ raise ValueError(
94
+ f"Multiple files starting with the same structure name {structure_name} has found, please specify the structure id")
95
+ self.filename = f"{file_path}/{filename[0]}"
96
+ self.file = pd.read_excel(self.filename, sheet_name=None)
97
+ elif structure_id and not structure_name:
98
+ dir_path = Path(file_path)
99
+ files = list(dir_path.glob(f"*{structure_id}.xlsx"))
100
+ filename = [file.name for file in files]
101
+ if len(filename) > 1:
102
+ raise ValueError(
103
+ f"Multiple files with the same structure id {structure_id} has found, please double check the stored structure")
104
+ self.filename = f"{file_path}/{filename[0]}"
105
+ self.file = pd.read_excel(self.filename, sheet_name=None)
106
+ self.nodes = self.file["Nodes"]
107
+ self.edges = self.file["Edges"]
108
+
109
+ def get_heads(self):
110
+ """
111
+ Retrieves the list of head node identifiers from the loaded structure data.
112
+
113
+ This method parses the 'StructureExecutor' sheet in the loaded Excel file to extract the list of head nodes.
114
+
115
+ Returns:
116
+ list: A list of identifiers for the head nodes in the structure.
117
+ """
118
+ structure_df = self.file["StructureExecutor"]
119
+ head_list = json.loads(structure_df["head_nodes"].iloc[0])
120
+ return head_list
121
+
122
+ def _reload_info_dict(self, node_id):
123
+ """
124
+ Retrieves detailed information about a specific node from the Excel file based on its identifier.
125
+
126
+ This method looks up a node's information within the loaded Excel sheets and returns a dictionary
127
+ containing all the relevant details.
128
+
129
+ Args:
130
+ node_id (str): The identifier of the node to look up.
131
+
132
+ Returns:
133
+ dict: A dictionary containing the properties and values for the specified node.
134
+ """
135
+ node_type = self.nodes[self.nodes["id"] == node_id]["type"].iloc[0]
136
+ node_file = self.file[node_type]
137
+ row = node_file[node_file["id"] == node_id].iloc[0]
138
+ info_dict = row.to_dict()
139
+ return info_dict
140
+
141
+ def parse_agent(self, info_dict):
142
+ """
143
+ Parses an agent node from the structure using the agent's specific details provided in a dictionary.
144
+
145
+ This method creates an agent instance based on the information from the dictionary, which includes
146
+ dynamically loading the output parser code.
147
+
148
+ Args:
149
+ info_dict (dict): A dictionary containing details about the agent node, including its class, structure ID,
150
+ and output parser code.
151
+
152
+ Returns:
153
+ BaseAgent: An initialized agent object.
154
+ """
155
+ output_parser = ParseNode.convert_to_def(info_dict["output_parser"])
156
+
157
+ structure_excel = StructureExcel(structure_id=info_dict["structure_id"], file_path=self.file_path)
158
+ structure_excel.reload()
159
+ structure = structure_excel.structure
160
+ agent = BaseAgent(structure=structure, executable=self.default_agent_executable, output_parser=output_parser)
161
+ agent.id_ = info_dict["id"]
162
+ agent.timestamp = info_dict["timestamp"]
163
+ return agent
164
+
165
+ def parse_node(self, info_dict):
166
+ """
167
+ Parses a node from its dictionary representation into a specific node type like System, Instruction, etc.
168
+
169
+ This method determines the type of node from the info dictionary and uses the appropriate parsing method
170
+ to create an instance of that node type.
171
+
172
+ Args:
173
+ info_dict (dict): A dictionary containing node data, including the node type and associated properties.
174
+
175
+ Returns:
176
+ Node: An instance of the node corresponding to the type specified in the info_dict.
177
+ """
178
+ if info_dict["type"] == "System":
179
+ return ParseNode.parse_system(info_dict)
180
+ elif info_dict["type"] == "Instruction":
181
+ return ParseNode.parse_instruction(info_dict)
182
+ elif info_dict["type"] == "Tool":
183
+ return ParseNode.parse_tool(info_dict)
184
+ elif info_dict["type"] == "ActionSelection":
185
+ return ParseNode.parse_actionSelection(info_dict)
186
+ elif info_dict["type"] == "BaseAgent":
187
+ return self.parse_agent(info_dict)
188
+
189
+ def get_next(self, node_id):
190
+ """
191
+ Retrieves the list of identifiers for nodes that are directly connected via outgoing edges from the specified node.
192
+
193
+ This method searches the 'Edges' DataFrame for all entries where the specified node is a head and returns
194
+ a list of the tail node identifiers.
195
+
196
+ Args:
197
+ node_id (str): The identifier of the node whose successors are to be found.
198
+
199
+ Returns:
200
+ list[str]: A list of identifiers for the successor nodes.
201
+ """
202
+ return self.edges[self.edges["head"] == node_id]["tail"].to_list()
203
+
204
+ def relate(self, parent_node, node):
205
+ """
206
+ Establishes a relationship between two nodes in the structure based on the Excel data for edges.
207
+
208
+ This method looks up the edge details connecting the two nodes and applies any conditions associated
209
+ with the edge to the structure being rebuilt.
210
+
211
+ Args:
212
+ parent_node (Node): The parent node in the relationship.
213
+ node (Node): The child node in the relationship.
214
+
215
+ Raises:
216
+ ValueError: If there are issues with the edge data such as multiple undefined edges.
217
+ """
218
+ if not parent_node:
219
+ return
220
+ row = self.edges[(self.edges["head"] == parent_node.id_) & (self.edges["tail"] == node.id_)]
221
+ if len(row) > 1:
222
+ raise ValueError(
223
+ f"currently does not support handle multiple edges between two nodes, Error node: from {parent_node.id_} to {node.id_}")
224
+ if row['condition'].isna().any():
225
+ self.structure.relate_nodes(parent_node, node)
226
+ else:
227
+ cond = json.loads(row["condition"].iloc[0])
228
+ cond_cls = cond["class"]
229
+ cond_row = self.file["EdgesCondClass"][self.file["EdgesCondClass"]["class_name"] == cond_cls]
230
+ cond_code = cond_row["class"].iloc[0]
231
+ condition = ParseNode.parse_condition(cond, cond_code)
232
+ self.structure.relate_nodes(parent_node, node, condition=condition)
233
+
234
+ def parse(self, node_list, parent_node=None):
235
+ """
236
+ Recursively parses a list of nodes and establishes their interconnections based on the Excel data.
237
+
238
+ This method processes each node ID in the list, parsing individual nodes and relating them according
239
+ to their connections defined in the Excel file.
240
+
241
+ Args:
242
+ node_list (list[str]): A list of node identifiers to be parsed.
243
+ parent_node (Node, optional): The parent node to which the nodes in the list are connected.
244
+
245
+ Raises:
246
+ ValueError: If an error occurs during parsing or relating nodes.
247
+ """
248
+ for node_id in node_list:
249
+ info_dict = self._reload_info_dict(node_id)
250
+ node = self.parse_node(info_dict)
251
+
252
+ if node.id_ not in self.structure.internal_nodes:
253
+ self.structure.add_node(node)
254
+ self.relate(parent_node, node)
255
+
256
+ next_node_list = self.get_next(node_id)
257
+ self.parse(next_node_list, node)
258
+
259
+ def reload(self):
260
+ """
261
+ Reloads the entire structure from the Excel file.
262
+
263
+ This method initializes a new StructureExecutor and uses the Excel data to rebuild the entire structure,
264
+ starting from the head nodes and recursively parsing and connecting all nodes defined within.
265
+ """
266
+ self.structure = StructureExecutor()
267
+ heads = self.get_heads()
268
+ self.parse(heads)
@@ -4,9 +4,7 @@ from lionagi.libs import SysUtil
4
4
  from lionagi.integrations.storage.storage_util import output_node_list, output_edge_list
5
5
 
6
6
 
7
- def _output_excel(
8
- node_list, node_dict, edge_list, edge_cls_list, filename="structure_storage"
9
- ):
7
+ def _output_excel(node_list, node_dict, edge_list, edge_cls_list, structure_name, dir="structure_storage"):
10
8
  """
11
9
  Writes provided node and edge data into multiple sheets of a single Excel workbook.
12
10
 
@@ -20,7 +18,7 @@ def _output_excel(
20
18
  node attributes for nodes of that type.
21
19
  edge_list (list): A list of dictionaries where each dictionary contains attributes of a single edge.
22
20
  edge_cls_list (list): A list of dictionaries where each dictionary contains attributes of edge conditions.
23
- filename (str): The base name for the output Excel file. The '.xlsx' extension will be added
21
+ structure_name (str): The base name for the output Excel file. The '.xlsx' extension will be added
24
22
  automatically if not included.
25
23
 
26
24
  Returns:
@@ -31,20 +29,30 @@ def _output_excel(
31
29
  """
32
30
  SysUtil.check_import("openpyxl")
33
31
 
32
+ structure_id = ""
33
+
34
34
  tables = {"Nodes": pd.DataFrame(node_list), "Edges": pd.DataFrame(edge_list)}
35
35
  if edge_cls_list:
36
36
  tables["EdgesCondClass"] = pd.DataFrame(edge_cls_list)
37
37
  for i in node_dict:
38
+ if i == "StructureExecutor":
39
+ structure_node = node_dict[i][0]
40
+ structure_node["name"] = structure_name
41
+ structure_id = structure_node["id"]
38
42
  tables[i] = pd.DataFrame(node_dict[i])
39
43
 
40
- filename = filename + ".xlsx"
44
+ import os
45
+ filepath = os.path.join(dir, f"{structure_name}_{structure_id}.xlsx")
46
+
47
+ if not os.path.exists(dir):
48
+ os.makedirs(dir)
41
49
 
42
- with pd.ExcelWriter(filename) as writer:
50
+ with pd.ExcelWriter(filepath) as writer:
43
51
  for i in tables:
44
52
  tables[i].to_excel(writer, sheet_name=i, index=False)
45
53
 
46
54
 
47
- def to_excel(structure, filename="structure_storage"):
55
+ def to_excel(structure, structure_name, dir="structure_storage"):
48
56
  """
49
57
  Converts a structure into a series of Excel sheets within a single workbook.
50
58
 
@@ -55,7 +63,7 @@ def to_excel(structure, filename="structure_storage"):
55
63
  Args:
56
64
  structure: An object representing the structure to be serialized. This should have methods
57
65
  to return lists of nodes and edges suitable for output.
58
- filename (str): The base name of the output Excel file. The '.xlsx' extension will be added
66
+ structure_name (str): The base name of the output Excel file. The '.xlsx' extension will be added
59
67
  automatically if not included.
60
68
 
61
69
  Returns:
@@ -64,4 +72,5 @@ def to_excel(structure, filename="structure_storage"):
64
72
  node_list, node_dict = output_node_list(structure)
65
73
  edge_list, edge_cls_list = output_edge_list(structure)
66
74
 
67
- _output_excel(node_list, node_dict, edge_list, edge_cls_list, filename)
75
+ _output_excel(node_list, node_dict, edge_list, edge_cls_list, structure_name, dir)
76
+
lionagi/libs/__init__.py CHANGED
@@ -16,6 +16,9 @@ from lionagi.libs.ln_api import (
16
16
  PayloadPackage,
17
17
  )
18
18
 
19
+ from lionagi.libs.ln_validate import validation_funcs
20
+
21
+
19
22
  __all__ = [
20
23
  "SysUtil",
21
24
  "convert",
@@ -31,4 +34,5 @@ __all__ = [
31
34
  "StatusTracker",
32
35
  "SimpleRateLimiter",
33
36
  "CallDecorator",
37
+ "validation_funcs",
34
38
  ]
File without changes
@@ -0,0 +1,89 @@
1
+ import unittest
2
+ from pydantic import BaseModel
3
+ from pandas import Series
4
+ import pandas as pd
5
+ from lionagi.core.generic.component import BaseNode
6
+
7
+ class TestBaseNode(unittest.TestCase):
8
+
9
+ def test_basic_properties(self):
10
+ node = BaseNode(content="Hello World")
11
+ self.assertIsInstance(node.id_, str)
12
+ self.assertEqual(len(node.id_), 32)
13
+ self.assertEqual(node.content, "Hello World")
14
+ self.assertEqual(node.metadata, {})
15
+
16
+ def test_serialization_json(self):
17
+ node = BaseNode(content="Hello World")
18
+ self.assertIn('id_', node.to_json_str())
19
+
20
+ def test_serialization_dict(self):
21
+ node = BaseNode(content="Hello World")
22
+ result = node.to_dict()
23
+ self.assertIn('id_', result)
24
+ self.assertEqual(result['content'], "Hello World")
25
+
26
+ def test_serialization_xml(self):
27
+ node = BaseNode(content="Hello World")
28
+ xml_str = node.to_xml()
29
+ self.assertIn('<BaseNode>', xml_str)
30
+ self.assertIn('</BaseNode>', xml_str)
31
+
32
+ def test_conversion_to_pandas_series(self):
33
+ node = BaseNode(content="Hello World")
34
+ series = node.to_pd_series()
35
+ self.assertIsInstance(series, Series)
36
+ self.assertEqual(series['content'], "Hello World")
37
+
38
+ def test_field_management(self):
39
+ node = BaseNode(content="Hello World")
40
+ node._add_field("new_field", annotation=str, default="default value")
41
+ self.assertIn('new_field', node._field_annotations)
42
+ self.assertEqual(getattr(node, 'new_field', None), "default value")
43
+
44
+ def test_creation_from_dict(self):
45
+ data = {"a": 1, "b": 2}
46
+ node = BaseNode.from_obj(data)
47
+ self.assertIn('a', node.to_dict())
48
+
49
+ def test_creation_from_json_str(self):
50
+ json_str = '{"a": 1, "b": 2}'
51
+ node = BaseNode.from_obj(json_str)
52
+ self.assertIn('a', node.to_dict())
53
+
54
+ def test_creation_from_fuzzy_json_str(self):
55
+ json_str = '{"name": "John", "age": 30, "city": ["New York", "DC", "LA"]'
56
+ node = BaseNode.from_obj(json_str, fuzzy_parse=True)
57
+ self.assertIn('name', node.to_dict())
58
+
59
+ def test_creation_from_pandas_series(self):
60
+ series = pd.Series({"a": 1, "b": 2})
61
+ node = BaseNode.from_obj(series)
62
+ self.assertIn('a', node.to_dict())
63
+
64
+ def test_creation_from_pandas_dataframe(self):
65
+ df = pd.DataFrame({"a": [1, 2], "b": [3, 4]}, index=["row1", "row2"])
66
+ nodes = BaseNode.from_obj(df)
67
+ self.assertIsInstance(nodes, list)
68
+ self.assertEqual(len(nodes), 2)
69
+
70
+ def test_creation_from_pydantic_model(self):
71
+ class CustomModel(BaseModel):
72
+ a: int
73
+ b: str
74
+
75
+ custom_model = CustomModel(a=1, b="hello")
76
+ node = BaseNode.from_obj(custom_model)
77
+ self.assertIn('a', node.to_dict())
78
+
79
+ def test_metadata_manipulation(self):
80
+ node = BaseNode()
81
+ node.meta_insert("key", "value")
82
+ self.assertEqual(node.metadata['key'], "value")
83
+ node.meta_change_key("key", "new_key")
84
+ self.assertIn("new_key", node.metadata)
85
+ node.meta_merge({"additional_key": "additional_value"})
86
+ self.assertIn("additional_key", node.metadata)
87
+
88
+ if __name__ == '__main__':
89
+ unittest.main()
lionagi/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.1"
1
+ __version__ = "0.1.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lionagi
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Towards automated general intelligence.
5
5
  Author: HaiyangLi
6
6
  Author-email: Haiyang Li <ocean@lionagi.ai>