lionagi 0.1.0__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.
Files changed (103) hide show
  1. lionagi/core/agent/base_agent.py +2 -3
  2. lionagi/core/branch/base.py +1 -1
  3. lionagi/core/branch/branch.py +2 -1
  4. lionagi/core/branch/flow_mixin.py +1 -1
  5. lionagi/core/branch/util.py +1 -1
  6. lionagi/core/execute/base_executor.py +1 -4
  7. lionagi/core/execute/branch_executor.py +66 -3
  8. lionagi/core/execute/instruction_map_executor.py +48 -0
  9. lionagi/core/execute/neo4j_executor.py +381 -0
  10. lionagi/core/execute/structure_executor.py +120 -4
  11. lionagi/core/flow/monoflow/ReAct.py +21 -19
  12. lionagi/core/flow/monoflow/chat_mixin.py +1 -1
  13. lionagi/core/flow/monoflow/followup.py +14 -13
  14. lionagi/core/flow/polyflow/__init__.py +1 -1
  15. lionagi/core/generic/component.py +197 -122
  16. lionagi/core/generic/condition.py +3 -1
  17. lionagi/core/generic/edge.py +77 -25
  18. lionagi/core/graph/graph.py +1 -1
  19. lionagi/core/mail/mail_manager.py +3 -2
  20. lionagi/core/session/session.py +1 -1
  21. lionagi/core/tool/tool_manager.py +10 -9
  22. lionagi/experimental/__init__.py +0 -0
  23. lionagi/experimental/directive/__init__.py +0 -0
  24. lionagi/experimental/directive/evaluator/__init__.py +0 -0
  25. lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
  26. lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
  27. lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
  28. lionagi/experimental/directive/evaluator/script_engine.py +83 -0
  29. lionagi/experimental/directive/parser/__init__.py +0 -0
  30. lionagi/experimental/directive/parser/base_parser.py +215 -0
  31. lionagi/experimental/directive/schema.py +36 -0
  32. lionagi/experimental/directive/template_/__init__.py +0 -0
  33. lionagi/experimental/directive/template_/base_template.py +63 -0
  34. lionagi/experimental/report/__init__.py +0 -0
  35. lionagi/experimental/report/form.py +64 -0
  36. lionagi/experimental/report/report.py +138 -0
  37. lionagi/experimental/report/util.py +47 -0
  38. lionagi/experimental/tool/__init__.py +0 -0
  39. lionagi/experimental/tool/function_calling.py +43 -0
  40. lionagi/experimental/tool/manual.py +66 -0
  41. lionagi/experimental/tool/schema.py +59 -0
  42. lionagi/experimental/tool/tool_manager.py +138 -0
  43. lionagi/experimental/tool/util.py +16 -0
  44. lionagi/experimental/validator/__init__.py +0 -0
  45. lionagi/experimental/validator/rule.py +139 -0
  46. lionagi/experimental/validator/validator.py +56 -0
  47. lionagi/experimental/work/__init__.py +10 -0
  48. lionagi/experimental/work/async_queue.py +54 -0
  49. lionagi/experimental/work/schema.py +73 -0
  50. lionagi/experimental/work/work_function.py +67 -0
  51. lionagi/experimental/work/worker.py +56 -0
  52. lionagi/experimental/work2/__init__.py +0 -0
  53. lionagi/experimental/work2/form.py +371 -0
  54. lionagi/experimental/work2/report.py +289 -0
  55. lionagi/experimental/work2/schema.py +30 -0
  56. lionagi/experimental/work2/tests.py +72 -0
  57. lionagi/experimental/work2/util.py +0 -0
  58. lionagi/experimental/work2/work.py +0 -0
  59. lionagi/experimental/work2/work_function.py +89 -0
  60. lionagi/experimental/work2/worker.py +12 -0
  61. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  62. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  63. lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
  64. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  65. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  66. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  67. lionagi/integrations/config/oai_configs.py +1 -1
  68. lionagi/integrations/config/ollama_configs.py +1 -1
  69. lionagi/integrations/config/openrouter_configs.py +1 -1
  70. lionagi/integrations/storage/__init__.py +3 -0
  71. lionagi/integrations/storage/neo4j.py +673 -0
  72. lionagi/integrations/storage/storage_util.py +289 -0
  73. lionagi/integrations/storage/structure_excel.py +268 -0
  74. lionagi/integrations/storage/to_csv.py +63 -0
  75. lionagi/integrations/storage/to_excel.py +76 -0
  76. lionagi/libs/__init__.py +4 -0
  77. lionagi/libs/ln_knowledge_graph.py +405 -0
  78. lionagi/libs/ln_queue.py +101 -0
  79. lionagi/libs/ln_tokenizer.py +57 -0
  80. lionagi/libs/sys_util.py +1 -1
  81. lionagi/lions/__init__.py +0 -0
  82. lionagi/lions/coder/__init__.py +0 -0
  83. lionagi/lions/coder/add_feature.py +20 -0
  84. lionagi/lions/coder/base_prompts.py +22 -0
  85. lionagi/lions/coder/coder.py +121 -0
  86. lionagi/lions/coder/util.py +91 -0
  87. lionagi/lions/researcher/__init__.py +0 -0
  88. lionagi/lions/researcher/data_source/__init__.py +0 -0
  89. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  90. lionagi/lions/researcher/data_source/google_.py +199 -0
  91. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  92. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  93. lionagi/tests/libs/test_queue.py +67 -0
  94. lionagi/tests/test_core/generic/__init__.py +0 -0
  95. lionagi/tests/test_core/generic/test_component.py +89 -0
  96. lionagi/tests/test_core/test_branch.py +0 -1
  97. lionagi/version.py +1 -1
  98. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
  99. lionagi-0.1.2.dist-info/RECORD +206 -0
  100. lionagi-0.1.0.dist-info/RECORD +0 -136
  101. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
  102. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
  103. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,59 @@
1
+ from typing import Any
2
+ from pydantic import field_serializer
3
+ from functools import singledispatchmethod
4
+ from lionagi import logging as _logging
5
+ from lionagi.libs import func_call, AsyncUtil
6
+ from lionagi.core.generic.node import Node
7
+ from .function_calling import FunctionCalling
8
+
9
+
10
+ class Tool(Node):
11
+
12
+ func: Any
13
+ schema_: dict | None = None
14
+ manual: Any | None = None
15
+ parser: Any | None = None
16
+ pre_processor: Any | None = None
17
+ post_processor: Any | None = None
18
+
19
+ @property
20
+ def name(self):
21
+ return self.schema_["function"]["name"]
22
+
23
+ @field_serializer("func")
24
+ def serialize_func(self, func):
25
+ return func.__name__
26
+
27
+ @singledispatchmethod
28
+ async def invoke(self, values: Any) -> Any:
29
+ raise TypeError(f"Unsupported type {type(values)}")
30
+
31
+ @invoke.register
32
+ async def _(self, kwargs: dict):
33
+
34
+ out = None
35
+
36
+ if self.pre_processor:
37
+ kwargs = await func_call.call_handler(self.pre_processor, kwargs)
38
+ try:
39
+ out = await func_call.call_handler(self.func, **kwargs)
40
+
41
+ except Exception as e:
42
+ _logging.error(f"Error invoking function {self.func_name}: {e}")
43
+ return None
44
+
45
+ if self.post_processor:
46
+ return await func_call.call_handler(self.post_processor, out)
47
+
48
+ return out
49
+
50
+ @invoke.register
51
+ async def _(self, function_calls: FunctionCalling):
52
+ return await self.invoke(function_calls.kwargs)
53
+
54
+ @invoke.register
55
+ async def _(self, values: list):
56
+ return await func_call.alcall(self.invoke, values)
57
+
58
+
59
+ TOOL_TYPE = bool | Tool | str | list[Tool | str | dict] | dict
@@ -0,0 +1,138 @@
1
+ import asyncio
2
+ from functools import singledispatchmethod
3
+ from collections import deque
4
+ from typing import Tuple, Any, TypeVar, Callable
5
+ from lionagi.libs import func_call, convert, ParseUtil
6
+ from lionagi import logging as _logging
7
+ from .schema import Tool, TOOL_TYPE
8
+ from .util import func_to_tool, parse_tool_response
9
+ from .function_calling import FunctionCalling
10
+
11
+ T = TypeVar("T", bound=Tool)
12
+
13
+
14
+ class ToolManager:
15
+
16
+ def __init__(
17
+ self,
18
+ tool_registry: dict = {},
19
+ function_calling_tasks: dict[str : deque[FunctionCalling]] = {},
20
+ ):
21
+ self.registry = tool_registry
22
+ self.function_calling_tasks = function_calling_tasks
23
+
24
+ @singledispatchmethod
25
+ def register_tools(self, tools: Any):
26
+ raise TypeError(f"Unsupported type {type(tools)}")
27
+
28
+ @register_tools.register(Tool)
29
+ def _(self, tools):
30
+ name = tools.schema_["function"]["name"]
31
+ if self._has_name(name):
32
+ err_msg = f"Function {name} is already registered."
33
+ _logging.error(err_msg)
34
+ raise ValueError(err_msg)
35
+ else:
36
+ self.registry[name] = tools
37
+ self.function_calling_tasks[name] = deque()
38
+ return True
39
+
40
+ @register_tools.register(Callable)
41
+ def _(self, tools):
42
+ tool = func_to_tool(tools)[0]
43
+ return self.register_tools(tool)
44
+
45
+ @register_tools.register(list)
46
+ def _(self, tools):
47
+ return func_call.lcall(tools, self.register_tools)
48
+
49
+ @singledispatchmethod
50
+ def register_function_calling(self, func_params: Any):
51
+ raise TypeError(f"Unsupported type {type(func_params)}")
52
+
53
+ @register_function_calling.register(tuple)
54
+ def _(self, func_params):
55
+ func = self.registry[func_params[0]].func
56
+ kwargs = func_params[1]
57
+ _function_calling = FunctionCalling(func=func, kwargs=kwargs)
58
+ self.function_calling_tasks[func.__name__].append(_function_calling)
59
+ return True
60
+
61
+ @register_function_calling.register(dict)
62
+ def _(self, response):
63
+ tuple_ = parse_tool_response(response)
64
+ return self.register_function_calling(tuple_)
65
+
66
+ @register_function_calling.register(list)
67
+ def _(self, func_params):
68
+ return func_call.lcall(func_params, self.register_function_calling)
69
+
70
+ async def invoke(self, func_params: Tuple[str, dict[str, Any]]) -> Any:
71
+ name, kwargs = func_params
72
+ if not self._has_name(name):
73
+ raise ValueError(f"Function {name} is not registered.")
74
+ tool = self.registry[name]
75
+ func = tool.func
76
+ parser = tool.parser
77
+ try:
78
+ out = await func_call.call_handler(func, **kwargs)
79
+ return parser(out) if parser else out
80
+
81
+ except Exception as e:
82
+ raise ValueError(
83
+ f"Error when invoking function {name} with arguments {kwargs} with error message {e}"
84
+ ) from e
85
+
86
+ @property
87
+ def _schema_list(self) -> list[dict[str, Any]]:
88
+ return [tool.schema_ for tool in self.registry.values()]
89
+
90
+ def get_tool_schema(self, tools: TOOL_TYPE, **kwargs):
91
+ if isinstance(tools, bool):
92
+ tool_kwarg = {"tools": self._schema_list}
93
+ return tool_kwarg | kwargs
94
+
95
+ else:
96
+ if not isinstance(tools, list):
97
+ tools = [tools]
98
+ tool_kwarg = {"tools": self._get_tool_schema(tools)}
99
+ return tool_kwarg | kwargs
100
+
101
+ def _has_name(self, name: str) -> bool:
102
+ return name in self.registry
103
+
104
+ @singledispatchmethod
105
+ def _get_tool_schema(self, tool: Any) -> dict:
106
+ raise TypeError(f"Unsupported type {type(tool)}")
107
+
108
+ @_get_tool_schema.register(dict)
109
+ def _(self, tool):
110
+ """
111
+ assuming that the tool is a schema
112
+ """
113
+ return tool
114
+
115
+ @_get_tool_schema.register(Tool)
116
+ def _(self, tool):
117
+ if self._has_name(tool.name):
118
+ return self.registry[tool.name].schema_
119
+ else:
120
+ err_msg = f"Function {tool.name} is not registered."
121
+ _logging.error(err_msg)
122
+ raise ValueError(err_msg)
123
+
124
+ @_get_tool_schema.register(str)
125
+ def _(self, tool):
126
+ """
127
+ assuming that the tool is a name
128
+ """
129
+ if self._has_name(tool):
130
+ return self.registry[tool].schema_
131
+ else:
132
+ err_msg = f"Function {tool} is not registered."
133
+ _logging.error(err_msg)
134
+ raise ValueError(err_msg)
135
+
136
+ @_get_tool_schema.register(list)
137
+ def _(self, tools):
138
+ return func_call.lcall(tools, self._get_tool_schema)
@@ -0,0 +1,16 @@
1
+ from typing import Tuple
2
+ from lionagi.libs import convert
3
+
4
+
5
+ def parse_tool_response(response: dict) -> Tuple[str, dict]:
6
+ try:
7
+ func = response["action"][7:]
8
+ args = convert.to_dict(response["arguments"])
9
+ return func, args
10
+ except Exception:
11
+ try:
12
+ func = response["recipient_name"].split(".")[-1]
13
+ args = response["parameters"]
14
+ return func, args
15
+ except:
16
+ raise ValueError("response is not a valid function call")
File without changes
@@ -0,0 +1,139 @@
1
+ from lionagi.libs import validation_funcs
2
+ from abc import abstractmethod
3
+
4
+
5
+ class Rule:
6
+
7
+ def __init__(self, **kwargs):
8
+ self.validation_kwargs = kwargs
9
+ self.fix = kwargs.get("fix", False)
10
+
11
+ @abstractmethod
12
+ def condition(self, **kwargs):
13
+ pass
14
+
15
+ @abstractmethod
16
+ async def validate(self, value, **kwargs):
17
+ pass
18
+
19
+
20
+ class ChoiceRule(Rule):
21
+
22
+ def condition(self, choices=None):
23
+ return choices is not None
24
+
25
+ def check(self, choices=None):
26
+ if choices and not isinstance(choices, list):
27
+ try:
28
+ choices = [i.value for i in choices]
29
+ except Exception as e:
30
+ raise ValueError(f"failed to get choices") from e
31
+ return choices
32
+
33
+ def fix(self, value, choices=None, **kwargs):
34
+ v_ = validation_funcs["enum"](value, choices=choices, fix_=True, **kwargs)
35
+ return v_
36
+
37
+ async def validate(self, value, choices=None, **kwargs):
38
+ if self.condition(choices):
39
+ if value in self.check(choices):
40
+ return value
41
+ if self.fix:
42
+ kwargs = {**self.validation_kwargs, **kwargs}
43
+ return self.fix(value, choices, **kwargs)
44
+ raise ValueError(f"{value} is not in chocies {choices}")
45
+
46
+
47
+ class ActionRequestRule(Rule):
48
+
49
+ def condition(self, annotation=None):
50
+ return any("actionrequest" in i for i in annotation)
51
+
52
+ async def validate(self, value, annotation=None):
53
+ if self.condition(annotation):
54
+ try:
55
+ return validation_funcs["action"](value)
56
+ except Exception as e:
57
+ raise ValueError(f"failed to validate field") from e
58
+
59
+
60
+ class BooleanRule(Rule):
61
+
62
+ def condition(self, annotation=None):
63
+ return "bool" in annotation and "str" not in annotation
64
+
65
+ async def validate(self, value, annotation=None):
66
+ if self.condition(annotation):
67
+ try:
68
+ return validation_funcs["bool"](
69
+ value, fix_=self.fix, **self.validation_kwargs
70
+ )
71
+ except Exception as e:
72
+ raise ValueError(f"failed to validate field") from e
73
+
74
+
75
+ class NumberRule(Rule):
76
+
77
+ def condition(self, annotation=None):
78
+ return (
79
+ any([i in annotation for i in ["int", "float", "number"]])
80
+ and "str" not in annotation
81
+ )
82
+
83
+ async def validate(self, value, annotation=None):
84
+ if self.condition(annotation):
85
+ if "float" in annotation:
86
+ self.validation_kwargs["num_type"] = float
87
+ if "precision" not in self.validation_kwargs:
88
+ self.validation_kwargs["precision"] = 32
89
+
90
+ try:
91
+ return validation_funcs["number"](
92
+ value, fix_=self.fix, **self.validation_kwargs
93
+ )
94
+ except Exception as e:
95
+ raise ValueError(f"failed to validate field") from e
96
+
97
+
98
+ class DictRule(Rule):
99
+
100
+ def condition(self, annotation=None):
101
+ return "dict" in annotation
102
+
103
+ async def validate(self, value, annotation=None, keys=None):
104
+ if self.condition(annotation):
105
+ if "str" not in annotation or keys:
106
+ try:
107
+ return validation_funcs["dict"](
108
+ value, keys=keys, fix_=self.fix, **self.validation_kwargs
109
+ )
110
+ except Exception as e:
111
+ raise ValueError(f"failed to validate field") from e
112
+ raise ValueError(f"failed to validate field")
113
+
114
+
115
+ class StringRule(Rule):
116
+
117
+ def condition(self, annotation=None):
118
+ return "str" in annotation
119
+
120
+ async def validate(self, value, annotation=None):
121
+ if self.condition(annotation):
122
+ try:
123
+ return validation_funcs["str"](
124
+ value, fix_=self.fix, **self.validation_kwargs
125
+ )
126
+ except Exception as e:
127
+ raise ValueError(f"failed to validate field") from e
128
+
129
+
130
+ from enum import Enum
131
+
132
+
133
+ class DEFAULT_RULES(Enum):
134
+ CHOICE = ChoiceRule
135
+ ACTION_REQUEST = ActionRequestRule
136
+ BOOL = BooleanRule
137
+ NUMBER = NumberRule
138
+ DICT = DictRule
139
+ STR = StringRule
@@ -0,0 +1,56 @@
1
+ from pydantic import BaseModel, Field
2
+ from .rule import DEFAULT_RULES, Rule
3
+
4
+
5
+ rules_ = {
6
+ "choice": DEFAULT_RULES.CHOICE.value,
7
+ "actionrequest": DEFAULT_RULES.ACTION_REQUEST.value,
8
+ "bool": DEFAULT_RULES.BOOL.value,
9
+ "number": DEFAULT_RULES.NUMBER.value,
10
+ "dict": DEFAULT_RULES.DICT.value,
11
+ "str": DEFAULT_RULES.STR.value,
12
+ }
13
+
14
+ order_ = [
15
+ "choice",
16
+ "actionrequest",
17
+ "bool",
18
+ "number",
19
+ "dict",
20
+ "str",
21
+ ]
22
+
23
+
24
+ class Validator(BaseModel):
25
+ """
26
+ rules contain all rules that this validator can apply to data
27
+ the order determines which rule gets applied in what sequence.
28
+ notice, if a rule is not present in the orders, it will not be applied.
29
+ """
30
+
31
+ rules: dict[str, Rule] = Field(
32
+ default=rules_,
33
+ description="The rules to be used for validation.",
34
+ )
35
+
36
+ order: list[str] = Field(
37
+ default=order_,
38
+ description="The order in which the rules should be applied.",
39
+ )
40
+
41
+ async def validate(self, value, *args, strict=False, **kwargs):
42
+
43
+ for i in self.order:
44
+ if i in self.rules:
45
+ try:
46
+ if (
47
+ a := await self.rules[i].validate(value, *args, **kwargs)
48
+ is not None
49
+ ):
50
+ return a
51
+ except Exception as e:
52
+ raise ValueError(f"failed to validate field") from e
53
+ if strict:
54
+ raise ValueError(f"failed to validate field")
55
+
56
+ return value
@@ -0,0 +1,10 @@
1
+ from .schema import WorkLog
2
+ from .work_function import WorkFunction, work
3
+ from .worker import Worker
4
+
5
+ __all__ = [
6
+ "WorkLog",
7
+ "WorkFunction",
8
+ "work",
9
+ "Worker",
10
+ ]
@@ -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
+
@@ -0,0 +1,73 @@
1
+ from collections import deque
2
+ from enum import Enum
3
+ import asyncio
4
+ from typing import Any
5
+
6
+ from lionagi.libs import SysUtil
7
+ from lionagi.core.generic import BaseComponent
8
+
9
+ from .async_queue import WorkQueue
10
+
11
+ class WorkStatus(str, Enum):
12
+ """Enum to represent different statuses of work."""
13
+
14
+ PENDING = "PENDING"
15
+ IN_PROGRESS = "IN_PROGRESS"
16
+ COMPLETED = "COMPLETED"
17
+ FAILED = "FAILED"
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()
38
+
39
+
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}
@@ -0,0 +1,67 @@
1
+ import asyncio
2
+ from typing import Callable, Any
3
+ from lionagi.libs import func_call
4
+ from functools import wraps
5
+ from pydantic import Field
6
+
7
+ from lionagi.core.generic import BaseComponent
8
+
9
+ from .schema import Work, WorkLog
10
+
11
+
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
+
27
+ @property
28
+ def name(self):
29
+ return self.function.__name__
30
+
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
57
+ )
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
@@ -0,0 +1,56 @@
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