lionagi 0.3.7__py3-none-any.whl → 0.4.0__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 (36) hide show
  1. lionagi/core/director/operations/select.py +2 -92
  2. lionagi/core/rule/choice.py +2 -2
  3. lionagi/operations/__init__.py +6 -0
  4. lionagi/operations/brainstorm.py +87 -0
  5. lionagi/operations/config.py +6 -0
  6. lionagi/operations/rank.py +102 -0
  7. lionagi/operations/score.py +144 -0
  8. lionagi/operations/select.py +141 -0
  9. lionagi/version.py +1 -1
  10. lionagi-0.4.0.dist-info/METADATA +241 -0
  11. {lionagi-0.3.7.dist-info → lionagi-0.4.0.dist-info}/RECORD +13 -30
  12. lionagi/core/director/models/__init__.py +0 -13
  13. lionagi/core/director/models/action_model.py +0 -61
  14. lionagi/core/director/models/brainstorm_model.py +0 -42
  15. lionagi/core/director/models/plan_model.py +0 -51
  16. lionagi/core/director/models/reason_model.py +0 -63
  17. lionagi/core/director/models/step_model.py +0 -65
  18. lionagi/core/operations/__init__.py +0 -0
  19. lionagi/core/operations/chat/__init__.py +0 -0
  20. lionagi/core/operations/direct/__init__.py +0 -0
  21. lionagi/core/operative/__init__.py +0 -0
  22. lionagi/operations/brainstorm/__init__.py +0 -0
  23. lionagi/operations/chat/__init__.py +0 -0
  24. lionagi/operations/models/__init__.py +0 -0
  25. lionagi/operations/plan/__init__.py +0 -0
  26. lionagi/operations/plan/base.py +0 -0
  27. lionagi/operations/query/__init__.py +0 -0
  28. lionagi/operations/rank/__init__.py +0 -0
  29. lionagi/operations/react/__init__.py +0 -0
  30. lionagi/operations/route/__init__.py +0 -0
  31. lionagi/operations/score/__init__.py +0 -0
  32. lionagi/operations/select/__init__.py +0 -0
  33. lionagi/operations/strategize/__init__.py +0 -0
  34. lionagi-0.3.7.dist-info/METADATA +0 -70
  35. {lionagi-0.3.7.dist-info → lionagi-0.4.0.dist-info}/LICENSE +0 -0
  36. {lionagi-0.3.7.dist-info → lionagi-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,93 +1,3 @@
1
- from __future__ import annotations
1
+ from lionagi.operations.select import select
2
2
 
3
- from collections.abc import Callable
4
- from enum import Enum
5
-
6
- from lionfuncs import choose_most_similar
7
- from pydantic import BaseModel
8
-
9
- from lionagi.core.director.models import ReasonModel
10
- from lionagi.core.session.branch import Branch
11
-
12
- from .utils import is_enum
13
-
14
- PROMPT = "Please select up to {max_num_selections} items from the following list {choices}. Provide the selection(s), and no comments from you"
15
-
16
-
17
- class SelectionModel(BaseModel):
18
- selected: list[str | Enum]
19
-
20
-
21
- class ReasonSelectionModel(BaseModel):
22
- selected: list[str | Enum]
23
- reason: ReasonModel
24
-
25
-
26
- async def select(
27
- choices: list[str] | type[Enum],
28
- max_num_selections: int = 1,
29
- instruction=None,
30
- context=None,
31
- system=None,
32
- sender=None,
33
- recipient=None,
34
- reason: bool = False,
35
- return_enum: bool = False,
36
- enum_parser: Callable = None, # parse the model string response to appropriate type
37
- branch: Branch = None,
38
- return_pydantic_model=False,
39
- **kwargs, # additional chat arguments
40
- ):
41
- selections = []
42
- if return_enum and not is_enum(choices):
43
- raise ValueError("return_enum can only be True if choices is an Enum")
44
-
45
- if is_enum(choices):
46
- selections = [selection.value for selection in choices]
47
- else:
48
- selections = choices
49
-
50
- prompt = PROMPT.format(
51
- max_num_selections=max_num_selections, choices=selections
52
- )
53
-
54
- if instruction:
55
- prompt = f"{instruction}\n\n{prompt} \n\n "
56
-
57
- branch = branch or Branch()
58
- response: SelectionModel | ReasonSelectionModel | str = await branch.chat(
59
- instruction=prompt,
60
- context=context,
61
- system=system,
62
- sender=sender,
63
- recipient=recipient,
64
- pydantic_model=SelectionModel if not reason else ReasonSelectionModel,
65
- return_pydantic_model=True,
66
- **kwargs,
67
- )
68
-
69
- selected = response
70
- if isinstance(response, SelectionModel | ReasonSelectionModel):
71
- selected = response.selected
72
- selected = [selected] if not isinstance(selected, list) else selected
73
- corrected_selections = [
74
- choose_most_similar(selection, selections) for selection in selected
75
- ]
76
-
77
- if return_enum:
78
- out = []
79
- if not enum_parser:
80
- enum_parser = lambda x: x
81
- for selection in corrected_selections:
82
- selection = enum_parser(selection)
83
- for member in choices.__members__.values():
84
- if member.value == selection:
85
- out.append(member)
86
- corrected_selections = out
87
-
88
- if return_pydantic_model:
89
- if not isinstance(response, SelectionModel | ReasonSelectionModel):
90
- return SelectionModel(selected=corrected_selections)
91
- response.selected = corrected_selections
92
- return response
93
- return corrected_selections
3
+ __all__ = ["select"]
@@ -1,4 +1,4 @@
1
- from lionfuncs import choose_most_similar
1
+ from lionfuncs import string_similarity
2
2
 
3
3
  from lionagi.core.rule.base import Rule
4
4
 
@@ -45,4 +45,4 @@ class ChoiceRule(Rule):
45
45
  Returns:
46
46
  str: The most similar value from the set of predefined choices.
47
47
  """
48
- return choose_most_similar(value, self.keys)
48
+ return string_similarity(value, self.keys, choose_most_similar=True)
@@ -0,0 +1,6 @@
1
+ from .brainstorm import brainstorm
2
+ from .rank import rank
3
+ from .score import score
4
+ from .select import select
5
+
6
+ __all__ = ["brainstorm", "rank", "score", "select"]
@@ -0,0 +1,87 @@
1
+ from typing import Any
2
+
3
+ from lion_core.operative.step_model import StepModel
4
+ from lion_core.session.branch import Branch
5
+ from lion_service import iModel
6
+ from pydantic import BaseModel, Field
7
+
8
+ from .config import DEFAULT_CHAT_CONFIG
9
+
10
+
11
+ class BrainstormModel(BaseModel):
12
+
13
+ topic: str = Field(
14
+ default_factory=str,
15
+ description="**Specify the topic or theme for the brainstorming session.**",
16
+ )
17
+ ideas: list[StepModel] = Field(
18
+ default_factory=list,
19
+ description="**Provide a list of ideas needed to accomplish the objective. Each step should be as described in a `PlanStepModel`.**",
20
+ )
21
+
22
+
23
+ PROMPT = "Please follow prompt and provide {num_steps} different ideas for the next step"
24
+
25
+
26
+ async def brainstorm(
27
+ num_steps: int = 3,
28
+ instruction=None,
29
+ guidance=None,
30
+ context=None,
31
+ system=None,
32
+ reason: bool = False,
33
+ actions: bool = False,
34
+ tools: Any = None,
35
+ imodel: iModel = None,
36
+ branch: Branch = None,
37
+ sender=None,
38
+ recipient=None,
39
+ clear_messages: bool = False,
40
+ system_sender=None,
41
+ system_datetime=None,
42
+ return_branch=False,
43
+ num_parse_retries: int = 3,
44
+ retry_imodel: iModel = None,
45
+ branch_user=None,
46
+ **kwargs, # additional operate arguments
47
+ ):
48
+ if branch and branch.imodel:
49
+ imodel = imodel or branch.imodel
50
+ else:
51
+ imodel = imodel or iModel(**DEFAULT_CHAT_CONFIG)
52
+
53
+ prompt = PROMPT.format(num_steps=num_steps)
54
+
55
+ branch = branch or Branch(imodel=imodel)
56
+ if branch_user:
57
+ branch.user = branch_user
58
+
59
+ if system:
60
+ branch.add_message(
61
+ system=system,
62
+ system_datetime=system_datetime,
63
+ sender=system_sender,
64
+ )
65
+ _context = [{"operation": prompt}]
66
+ if context:
67
+ _context.append(context)
68
+
69
+ response = await branch.operate(
70
+ instruction=instruction,
71
+ guidance=guidance,
72
+ context=_context,
73
+ sender=sender,
74
+ recipient=recipient,
75
+ reason=reason,
76
+ actions=actions,
77
+ tools=tools,
78
+ clear_messages=clear_messages,
79
+ operative_model=BrainstormModel,
80
+ retry_imodel=retry_imodel,
81
+ num_parse_retries=num_parse_retries,
82
+ imodel=imodel,
83
+ **kwargs,
84
+ )
85
+ if return_branch:
86
+ return response, branch
87
+ return response
@@ -0,0 +1,6 @@
1
+ DEFAULT_CHAT_CONFIG = {
2
+ "provider": "openai",
3
+ "task": "chat",
4
+ "model": "gpt-4o-mini",
5
+ "api_key": "OPENAI_API_KEY",
6
+ }
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+ from lion_core.session.branch import Branch
6
+ from lion_core.session.session import Session
7
+ from lion_service import iModel
8
+ from lionfuncs import alcall, to_list
9
+
10
+ from .config import DEFAULT_CHAT_CONFIG
11
+ from .score import score
12
+
13
+ PROMPT = (
14
+ "Given all items: \n {choices} \n\n Please follow prompt and give score "
15
+ "to the item of interest: \n {item} \n\n"
16
+ )
17
+
18
+
19
+ async def rank(
20
+ choices: list[Any],
21
+ num_scorers: int = 5,
22
+ instruction=None,
23
+ guidance=None,
24
+ context=None,
25
+ system=None,
26
+ reason: bool = False,
27
+ actions: bool = False,
28
+ tools: Any = None,
29
+ imodel: iModel = None,
30
+ branch: Branch = None, # branch won't be used for the vote, it is for configuration
31
+ clear_messages: bool = False,
32
+ system_sender=None,
33
+ system_datetime=None,
34
+ num_parse_retries: int = 0,
35
+ retry_imodel: iModel = None,
36
+ return_session: bool = False,
37
+ **kwargs, # additional kwargs for score function
38
+ ) -> dict:
39
+
40
+ if branch and branch.imodel:
41
+ imodel = imodel or branch.imodel
42
+ else:
43
+ imodel = imodel or iModel(**DEFAULT_CHAT_CONFIG)
44
+
45
+ branch = branch or Branch(imodel=imodel)
46
+ session = Session(default_branch=branch)
47
+
48
+ async def _score(item):
49
+ async with session.branches.async_lock:
50
+ b_ = session.new_branch(messages=session.default_branch.messages)
51
+
52
+ prompt = PROMPT.format(choices=choices, item=item)
53
+ if instruction:
54
+ prompt = f"{instruction}\n\n{prompt} \n\n "
55
+
56
+ kwargs["branch"] = b_
57
+ kwargs["score_range"] = kwargs.get("score_range", (1, 10))
58
+ kwargs["num_scores"] = kwargs.get("num_scores", 1)
59
+ kwargs["precision"] = kwargs.get("precision", 1)
60
+
61
+ response = await score(
62
+ instruction=prompt,
63
+ guidance=guidance,
64
+ context=context,
65
+ system=system,
66
+ system_datetime=system_datetime,
67
+ system_sender=system_sender,
68
+ sender=session.ln_id,
69
+ recipient=b_.ln_id,
70
+ default_score=-1,
71
+ reason=reason,
72
+ actions=actions,
73
+ tools=tools,
74
+ clear_messages=clear_messages,
75
+ num_parse_retries=num_parse_retries,
76
+ retry_imodel=retry_imodel,
77
+ **kwargs,
78
+ )
79
+
80
+ if response.score == -1:
81
+ return None
82
+
83
+ return response
84
+
85
+ async def _group_score(item):
86
+ tasks = [asyncio.create_task(_score(item)) for _ in range(num_scorers)]
87
+ responses = await asyncio.gather(*tasks)
88
+ responses = [i for i in responses if i is not None]
89
+ scores = to_list(
90
+ [i.score for i in responses], dropna=True, flatten=True
91
+ )
92
+ return {
93
+ "item": item,
94
+ "scores": scores,
95
+ "average": np.mean(scores) if scores else -1,
96
+ }
97
+
98
+ results = await alcall(choices, _group_score)
99
+ results = sorted(results, key=lambda x: x["average"], reverse=True)
100
+ if return_session:
101
+ return results, session
102
+ return results
@@ -0,0 +1,144 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+ from lion_core.session.branch import Branch
6
+ from lion_service import iModel
7
+ from lionfuncs import to_num
8
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
9
+
10
+ from .config import DEFAULT_CHAT_CONFIG
11
+
12
+ PROMPT = "Please follow prompt and provide {num_scores} numeric score(s) in {score_range} for the given context. Return as {return_precision} format"
13
+
14
+
15
+ class ScoreModel(BaseModel):
16
+
17
+ score: list | float = Field(
18
+ default_factory=list,
19
+ description="** A numeric score or a list of numeric scores.**",
20
+ )
21
+
22
+ model_config = ConfigDict(
23
+ population_by_field_name=True,
24
+ arbitrary_types_allowed=True,
25
+ )
26
+
27
+ @field_validator("score", mode="before")
28
+ def validate_score(cls, value) -> list:
29
+ return [value] if not isinstance(value, list) else value
30
+
31
+
32
+ async def score(
33
+ score_range=(1, 10),
34
+ instruction=None,
35
+ guidance=None,
36
+ context=None,
37
+ system=None,
38
+ reason: bool = False,
39
+ actions: bool = False,
40
+ tools: Any = None,
41
+ imodel: iModel = None,
42
+ branch: Branch = None,
43
+ sender=None,
44
+ recipient=None,
45
+ clear_messages: bool = False,
46
+ system_sender=None,
47
+ system_datetime=None,
48
+ return_branch=False,
49
+ num_parse_retries: int = 0,
50
+ retry_imodel: iModel = None,
51
+ num_scores: int = 1,
52
+ use_average: bool = False,
53
+ precision: int = 0,
54
+ default_score=np.nan,
55
+ **kwargs,
56
+ ) -> ScoreModel:
57
+
58
+ if branch and branch.imodel:
59
+ imodel = imodel or branch.imodel
60
+ else:
61
+ imodel = imodel or iModel(**DEFAULT_CHAT_CONFIG)
62
+
63
+ branch = branch or Branch(imodel=imodel)
64
+
65
+ return_precision = "integer" if precision == 0 else f"num:{precision}f"
66
+ prompt = PROMPT.format(
67
+ num_scores=num_scores,
68
+ score_range=score_range,
69
+ return_precision=return_precision,
70
+ )
71
+ if instruction:
72
+ prompt = f"{instruction}\n\n{prompt} \n\n "
73
+
74
+ if system:
75
+ branch.add_message(
76
+ system=system,
77
+ system_datetime=system_datetime,
78
+ sender=system_sender,
79
+ )
80
+
81
+ _context = [{"operation": prompt}]
82
+ if context:
83
+ _context.append(context)
84
+
85
+ kwargs["frozen"] = False
86
+ response = await branch.operate(
87
+ instruction=instruction,
88
+ guidance=guidance,
89
+ context=_context,
90
+ sender=sender,
91
+ recipient=recipient,
92
+ reason=reason,
93
+ actions=actions,
94
+ tools=tools,
95
+ clear_messages=clear_messages,
96
+ operative_model=ScoreModel,
97
+ imodel=imodel,
98
+ retry_imodel=retry_imodel,
99
+ num_parse_retries=num_parse_retries,
100
+ **kwargs,
101
+ )
102
+
103
+ return_kind = int if precision == 0 else float
104
+ err = None
105
+ try:
106
+ if isinstance(response, dict):
107
+ response = ScoreModel(**response)
108
+
109
+ response.score = [
110
+ to_num(
111
+ i,
112
+ upper_bound=score_range[1],
113
+ lower_bound=score_range[0],
114
+ num_type=return_kind,
115
+ precision=precision,
116
+ num_count=num_scores,
117
+ )
118
+ for i in response.score
119
+ ]
120
+ if use_average:
121
+ scores = response.score
122
+ scores = [scores] if not isinstance(scores, list) else scores
123
+ response.score = np.mean(scores)
124
+
125
+ if response.score and num_scores == 1:
126
+ if isinstance(response.score, list):
127
+ response.score = response.score[0]
128
+
129
+ if return_branch:
130
+ return response, branch
131
+ return response
132
+
133
+ except Exception as e:
134
+ err = e
135
+ pass
136
+
137
+ logging.error(
138
+ f"Error converting score to {return_kind}: {err}, "
139
+ f"value is set to default: {default_score}"
140
+ )
141
+ response.score = default_score
142
+ if return_branch:
143
+ return response, branch
144
+ return response
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from collections.abc import Callable
5
+ from enum import Enum
6
+ from typing import Any
7
+
8
+ from lion_core.session.branch import Branch
9
+ from lion_service import iModel
10
+ from lionfuncs import string_similarity
11
+ from pydantic import BaseModel, Field
12
+
13
+ from lionagi.libs.sys_util import SysUtil
14
+
15
+ from .config import DEFAULT_CHAT_CONFIG
16
+
17
+ PROMPT = "Please select up to {max_num_selections} items from the following list {choices}. Provide the selection(s) into appropriate field in format required, and no comments from you"
18
+
19
+
20
+ class SelectionModel(BaseModel):
21
+ selected: list[str | Enum] = Field(default_factory=list)
22
+
23
+
24
+ async def select(
25
+ choices: list[str] | type[Enum],
26
+ max_num_selections: int = 1,
27
+ instruction=None,
28
+ guidance=None,
29
+ context=None,
30
+ system=None,
31
+ reason: bool = False,
32
+ actions: bool = False,
33
+ tools: Any = None,
34
+ imodel: iModel = None,
35
+ branch: Branch = None,
36
+ sender=None,
37
+ recipient=None,
38
+ return_enum: bool = False,
39
+ enum_parser: Callable = None, # parse the model string response to appropriate type
40
+ clear_messages: bool = False,
41
+ system_sender=None,
42
+ system_datetime=None,
43
+ return_branch=False,
44
+ num_parse_retries: int = 3,
45
+ retry_imodel: iModel = None,
46
+ branch_user=None,
47
+ **kwargs,
48
+ ) -> SelectionModel | tuple[SelectionModel, Branch]:
49
+
50
+ if branch and branch.imodel:
51
+ imodel = imodel or branch.imodel
52
+ else:
53
+ imodel = imodel or iModel(**DEFAULT_CHAT_CONFIG)
54
+
55
+ selections = []
56
+ if return_enum and not _is_enum(choices):
57
+ raise ValueError("return_enum can only be True if choices is an Enum")
58
+
59
+ if _is_enum(choices):
60
+ selections = [selection.value for selection in choices]
61
+ else:
62
+ selections = choices
63
+
64
+ prompt = PROMPT.format(
65
+ max_num_selections=max_num_selections, choices=selections
66
+ )
67
+
68
+ if instruction:
69
+ prompt = f"{instruction}\n\n{prompt} \n\n "
70
+
71
+ branch = branch or Branch(imodel=imodel)
72
+ if branch_user:
73
+ try:
74
+ a = SysUtil.get_id(branch_user)
75
+ branch.user = a
76
+ except:
77
+ branch.user = branch_user
78
+ if system:
79
+ branch.add_message(
80
+ system=system,
81
+ system_datetime=system_datetime,
82
+ system_sender=system_sender,
83
+ )
84
+
85
+ kwargs["frozen"] = False
86
+ response_model: SelectionModel = await branch.operate(
87
+ instruction=prompt,
88
+ guidance=guidance,
89
+ context=context,
90
+ sender=sender,
91
+ recipient=recipient,
92
+ reason=reason,
93
+ actions=actions,
94
+ operative_model=SelectionModel,
95
+ clear_messages=clear_messages,
96
+ imodel=imodel,
97
+ num_parse_retries=num_parse_retries,
98
+ retry_imodel=retry_imodel,
99
+ tools=tools,
100
+ **kwargs,
101
+ )
102
+
103
+ selected = response_model
104
+ if isinstance(response_model, BaseModel) and hasattr(
105
+ response_model, "selected"
106
+ ):
107
+ selected = response_model.selected
108
+ selected = [selected] if not isinstance(selected, list) else selected
109
+ corrected_selections = [
110
+ string_similarity(
111
+ word=selection,
112
+ correct_words=selections,
113
+ return_most_similar=True,
114
+ )
115
+ for selection in selected
116
+ ]
117
+
118
+ if return_enum:
119
+ out = []
120
+ if not enum_parser:
121
+ enum_parser = lambda x: x
122
+ for selection in corrected_selections:
123
+ selection = enum_parser(selection)
124
+ for member in choices.__members__.values():
125
+ if member.value == selection:
126
+ out.append(member)
127
+ corrected_selections = out
128
+
129
+ if isinstance(response_model, BaseModel):
130
+ response_model.selected = corrected_selections
131
+
132
+ elif isinstance(response_model, dict):
133
+ response_model["selected"] = corrected_selections
134
+
135
+ if return_branch:
136
+ return response_model, branch
137
+ return response_model
138
+
139
+
140
+ def _is_enum(choices):
141
+ return inspect.isclass(choices) and issubclass(choices, Enum)
lionagi/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.3.7"
1
+ __version__ = "0.4.0"