lionagi 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|