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.
- lionagi/core/execute/structure_executor.py +21 -1
- lionagi/core/flow/monoflow/ReAct.py +3 -1
- lionagi/core/flow/monoflow/followup.py +3 -1
- lionagi/core/generic/component.py +197 -120
- lionagi/core/generic/condition.py +2 -0
- lionagi/core/generic/edge.py +33 -33
- lionagi/core/graph/graph.py +1 -1
- lionagi/core/tool/tool_manager.py +10 -9
- lionagi/experimental/report/form.py +64 -0
- lionagi/experimental/report/report.py +138 -0
- lionagi/experimental/report/util.py +47 -0
- lionagi/experimental/tool/schema.py +3 -3
- lionagi/experimental/tool/tool_manager.py +1 -1
- lionagi/experimental/validator/rule.py +139 -0
- lionagi/experimental/validator/validator.py +56 -0
- lionagi/experimental/work/__init__.py +10 -0
- lionagi/experimental/work/async_queue.py +54 -0
- lionagi/experimental/work/schema.py +60 -17
- lionagi/experimental/work/work_function.py +55 -77
- lionagi/experimental/work/worker.py +56 -12
- lionagi/experimental/work2/__init__.py +0 -0
- lionagi/experimental/work2/form.py +371 -0
- lionagi/experimental/work2/report.py +289 -0
- lionagi/experimental/work2/schema.py +30 -0
- lionagi/experimental/{work → work2}/tests.py +1 -1
- lionagi/experimental/work2/util.py +0 -0
- lionagi/experimental/work2/work.py +0 -0
- lionagi/experimental/work2/work_function.py +89 -0
- lionagi/experimental/work2/worker.py +12 -0
- lionagi/integrations/storage/storage_util.py +4 -4
- lionagi/integrations/storage/structure_excel.py +268 -0
- lionagi/integrations/storage/to_excel.py +18 -9
- lionagi/libs/__init__.py +4 -0
- lionagi/tests/test_core/generic/__init__.py +0 -0
- lionagi/tests/test_core/generic/test_component.py +89 -0
- lionagi/version.py +1 -1
- {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
- {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/RECORD +43 -27
- lionagi/experimental/work/_logger.py +0 -25
- /lionagi/experimental/{work/exchange.py → report/__init__.py} +0 -0
- /lionagi/experimental/{work/util.py → validator/__init__.py} +0 -0
- {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
- {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
- {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
|
4
|
+
class WorkQueue:
|
5
|
+
|
6
|
+
def __init__(self, capacity=5):
|
7
|
+
|
8
|
+
self.queue = asyncio.Queue()
|
9
|
+
self._stop_event = asyncio.Event()
|
10
|
+
self.capacity = capacity
|
11
|
+
self.semaphore = asyncio.Semaphore(capacity)
|
12
|
+
|
13
|
+
async def enqueue(self, work) -> None:
|
14
|
+
await self.queue.put(work)
|
15
|
+
|
16
|
+
async def dequeue(self):
|
17
|
+
return await self.queue.get()
|
18
|
+
|
19
|
+
async def join(self) -> None:
|
20
|
+
await self.queue.join()
|
21
|
+
|
22
|
+
async def stop(self) -> None:
|
23
|
+
self._stop_event.set()
|
24
|
+
|
25
|
+
@property
|
26
|
+
def available_capacity(self):
|
27
|
+
if (a:= self.capacity - self.queue.qsize()) > 0:
|
28
|
+
return a
|
29
|
+
return None
|
30
|
+
|
31
|
+
@property
|
32
|
+
def stopped(self) -> bool:
|
33
|
+
return self._stop_event.is_set()
|
34
|
+
|
35
|
+
|
36
|
+
async def process(self, refresh_time=1) -> None:
|
37
|
+
tasks = set()
|
38
|
+
while self.queue.qsize() > 0 and not self.stopped:
|
39
|
+
if not self.available_capacity and tasks:
|
40
|
+
_, done = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
41
|
+
tasks.difference_update(done)
|
42
|
+
|
43
|
+
async with self.semaphore:
|
44
|
+
next = await self.dequeue()
|
45
|
+
if next is None:
|
46
|
+
break
|
47
|
+
task = asyncio.create_task(next.perform())
|
48
|
+
tasks.add(task)
|
49
|
+
|
50
|
+
if tasks:
|
51
|
+
await asyncio.wait(tasks)
|
52
|
+
await asyncio.sleep(refresh_time)
|
53
|
+
|
54
|
+
|
@@ -1,8 +1,12 @@
|
|
1
|
+
from collections import deque
|
1
2
|
from enum import Enum
|
2
|
-
|
3
|
-
from
|
3
|
+
import asyncio
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from lionagi.libs import SysUtil
|
4
7
|
from lionagi.core.generic import BaseComponent
|
5
8
|
|
9
|
+
from .async_queue import WorkQueue
|
6
10
|
|
7
11
|
class WorkStatus(str, Enum):
|
8
12
|
"""Enum to represent different statuses of work."""
|
@@ -11,20 +15,59 @@ class WorkStatus(str, Enum):
|
|
11
15
|
IN_PROGRESS = "IN_PROGRESS"
|
12
16
|
COMPLETED = "COMPLETED"
|
13
17
|
FAILED = "FAILED"
|
14
|
-
|
18
|
+
|
19
|
+
|
20
|
+
class Work(BaseComponent):
|
21
|
+
status: WorkStatus = WorkStatus.PENDING
|
22
|
+
result: Any = None
|
23
|
+
error: Any = None
|
24
|
+
async_task: asyncio.Task | None = None
|
25
|
+
completion_timestamp: str | None = None
|
26
|
+
|
27
|
+
async def perform(self):
|
28
|
+
try:
|
29
|
+
result = await self.async_task
|
30
|
+
self.result = result
|
31
|
+
self.status = WorkStatus.COMPLETED
|
32
|
+
self.async_task = None
|
33
|
+
except Exception as e:
|
34
|
+
self.error = e
|
35
|
+
self.status = WorkStatus.FAILED
|
36
|
+
finally:
|
37
|
+
self.completion_timestamp = SysUtil.get_timestamp()
|
15
38
|
|
16
39
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
)
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
def __str__(self):
|
41
|
+
return f"Work(id={self.id_}, status={self.status}, created_at={self.timestamp}, completed_at={self.completion_timestamp})"
|
42
|
+
|
43
|
+
class WorkLog:
|
44
|
+
|
45
|
+
def __init__(self, capacity=5, pile=None):
|
46
|
+
self.pile = pile or {}
|
47
|
+
self.pending_sequence = deque()
|
48
|
+
self.queue = WorkQueue(capacity=capacity)
|
49
|
+
|
50
|
+
async def append(self, work: Work):
|
51
|
+
self.pile[work.id_] = work
|
52
|
+
self.pending_sequence.append(work.id_)
|
53
|
+
|
54
|
+
async def forward(self):
|
55
|
+
if not self.queue.available_capacity:
|
56
|
+
return
|
57
|
+
else:
|
58
|
+
while self.pending_sequence and self.queue.available_capacity:
|
59
|
+
work = self.pile[self.pending_sequence.popleft()]
|
60
|
+
work.status = WorkStatus.IN_PROGRESS
|
61
|
+
await self.queue.enqueue(work)
|
62
|
+
|
63
|
+
|
64
|
+
async def stop(self):
|
65
|
+
await self.queue.stop()
|
66
|
+
|
67
|
+
@property
|
68
|
+
def stopped(self):
|
69
|
+
return self.queue.stopped
|
70
|
+
|
71
|
+
@property
|
72
|
+
def completed_work(self):
|
73
|
+
return {k: v for k, v in self.pile.items() if v.status == WorkStatus.COMPLETED}
|
@@ -1,89 +1,67 @@
|
|
1
1
|
import asyncio
|
2
|
-
from typing import
|
3
|
-
from pydantic import Field
|
4
|
-
from functools import wraps
|
5
|
-
from lionagi import logging as _logging
|
2
|
+
from typing import Callable, Any
|
6
3
|
from lionagi.libs import func_call
|
7
|
-
from
|
4
|
+
from functools import wraps
|
5
|
+
from pydantic import Field
|
8
6
|
|
9
|
-
from .
|
10
|
-
from ._logger import WorkLog
|
11
|
-
from .worker import Worker
|
7
|
+
from lionagi.core.generic import BaseComponent
|
12
8
|
|
9
|
+
from .schema import Work, WorkLog
|
13
10
|
|
14
|
-
class WorkFunction(BaseComponent):
|
15
|
-
"""Work function management and execution."""
|
16
11
|
|
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
12
|
|
13
|
+
class WorkFunction:
|
14
|
+
|
15
|
+
def __init__(
|
16
|
+
self, assignment, function, retry_kwargs=None,
|
17
|
+
instruction = None, capacity=5
|
18
|
+
):
|
19
|
+
|
20
|
+
self.assignment = assignment
|
21
|
+
self.function = function
|
22
|
+
self.retry_kwargs = retry_kwargs or {}
|
23
|
+
self.instruction = instruction or function.__doc__
|
24
|
+
self.worklog = WorkLog(capacity=capacity)
|
25
|
+
|
26
|
+
|
29
27
|
@property
|
30
28
|
def name(self):
|
31
|
-
"""Get the name of the work function."""
|
32
29
|
return self.function.__name__
|
33
30
|
|
34
|
-
async def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
31
|
+
async def perform(self, *args, **kwargs):
|
32
|
+
kwargs = {**self.retry_kwargs, **kwargs}
|
33
|
+
return await func_call.rcall(self.function, *args, **kwargs)
|
34
|
+
|
35
|
+
async def process(self, refresh_time=1):
|
36
|
+
await self.worklog.process(refresh_time=refresh_time)
|
37
|
+
|
38
|
+
async def stop(self):
|
39
|
+
await self.worklog.queue.stop()
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
def work(assignment, capacity=5):
|
44
|
+
def decorator(func):
|
45
|
+
@wraps(func)
|
46
|
+
async def wrapper(self, *args, retry_kwargs=None, instruction=None, **kwargs):
|
47
|
+
if getattr(self, "work_functions", None) is None:
|
48
|
+
self.work_functions = {}
|
49
|
+
|
50
|
+
if func.__name__ not in self.work_functions:
|
51
|
+
self.work_functions[func.__name__] = WorkFunction(
|
52
|
+
assignment=assignment,
|
53
|
+
function=func,
|
54
|
+
retry_kwargs=retry_kwargs or {},
|
55
|
+
instruction=instruction or func.__doc__,
|
56
|
+
capacity=capacity
|
50
57
|
)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
return
|
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
|
58
|
+
|
59
|
+
work_func: WorkFunction = self.work_functions[func.__name__]
|
60
|
+
task = asyncio.create_task(work_func.perform(*args, **kwargs))
|
61
|
+
work = Work(async_task=task)
|
62
|
+
work_func: WorkFunction = self.work_functions[func.__name__]
|
63
|
+
await work_func.worklog.append(work)
|
64
|
+
return True
|
65
|
+
|
66
|
+
return wrapper
|
67
|
+
return decorator
|
@@ -1,12 +1,56 @@
|
|
1
|
-
from abc import ABC
|
2
|
-
from
|
3
|
-
from
|
4
|
-
|
5
|
-
|
6
|
-
class Worker(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from lionagi import logging as _logging
|
3
|
+
from .work_function import WorkFunction
|
4
|
+
import asyncio
|
5
|
+
|
6
|
+
class Worker(ABC):
|
7
|
+
# This is a class that will be used to create a worker object
|
8
|
+
# work_functions are keyed by assignment {assignment: WorkFunction}
|
9
|
+
|
10
|
+
name: str = "Worker"
|
11
|
+
work_functions: dict[str, WorkFunction] = {}
|
12
|
+
|
13
|
+
def __init__(self) -> None:
|
14
|
+
self.stopped = False
|
15
|
+
|
16
|
+
async def stop(self):
|
17
|
+
self.stopped = True
|
18
|
+
_logging.info(f"Stopping worker {self.name}")
|
19
|
+
non_stopped_ = []
|
20
|
+
|
21
|
+
for func in self.work_functions.values():
|
22
|
+
worklog = func.worklog
|
23
|
+
await worklog.stop()
|
24
|
+
if not worklog.stopped:
|
25
|
+
non_stopped_.append(func.name)
|
26
|
+
|
27
|
+
if len(non_stopped_) > 0:
|
28
|
+
_logging.error(f"Could not stop worklogs: {non_stopped_}")
|
29
|
+
|
30
|
+
_logging.info(f"Stopped worker {self.name}")
|
31
|
+
|
32
|
+
|
33
|
+
async def process(self, refresh_time=1):
|
34
|
+
while not self.stopped:
|
35
|
+
tasks = [
|
36
|
+
asyncio.create_task(func.process(refresh_time=refresh_time))
|
37
|
+
for func in self.work_functions.values()
|
38
|
+
]
|
39
|
+
await asyncio.wait(tasks)
|
40
|
+
await asyncio.sleep(refresh_time)
|
41
|
+
|
42
|
+
|
43
|
+
# # Example
|
44
|
+
# from lionagi import Session
|
45
|
+
# from lionagi.experimental.work.work_function import work
|
46
|
+
|
47
|
+
|
48
|
+
# class MyWorker(Worker):
|
49
|
+
|
50
|
+
# @work(assignment="instruction, context -> response")
|
51
|
+
# async def chat(instruction=None, context=None):
|
52
|
+
# session = Session()
|
53
|
+
# return await session.chat(instruction=instruction, context=context)
|
54
|
+
|
55
|
+
|
56
|
+
# await a.chat(instruction="Hello", context={})
|
File without changes
|