lionagi 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. lionagi/__init__.py +2 -0
  2. lionagi/libs/token_transform/__init__.py +0 -0
  3. lionagi/libs/token_transform/llmlingua.py +1 -0
  4. lionagi/libs/token_transform/perplexity.py +439 -0
  5. lionagi/libs/token_transform/synthlang.py +409 -0
  6. lionagi/operations/ReAct/ReAct.py +126 -0
  7. lionagi/operations/ReAct/utils.py +28 -0
  8. lionagi/operations/__init__.py +1 -9
  9. lionagi/operations/_act/act.py +73 -0
  10. lionagi/operations/chat/__init__.py +3 -0
  11. lionagi/operations/chat/chat.py +173 -0
  12. lionagi/operations/communicate/__init__.py +0 -0
  13. lionagi/operations/communicate/communicate.py +167 -0
  14. lionagi/operations/instruct/__init__.py +3 -0
  15. lionagi/operations/instruct/instruct.py +29 -0
  16. lionagi/operations/interpret/__init__.py +3 -0
  17. lionagi/operations/interpret/interpret.py +40 -0
  18. lionagi/operations/operate/__init__.py +3 -0
  19. lionagi/operations/operate/operate.py +189 -0
  20. lionagi/operations/parse/__init__.py +3 -0
  21. lionagi/operations/parse/parse.py +125 -0
  22. lionagi/operations/plan/plan.py +3 -3
  23. lionagi/operations/select/__init__.py +0 -4
  24. lionagi/operations/select/select.py +11 -30
  25. lionagi/operations/select/utils.py +13 -2
  26. lionagi/operations/translate/__init__.py +0 -0
  27. lionagi/operations/translate/translate.py +47 -0
  28. lionagi/operations/types.py +25 -3
  29. lionagi/operatives/action/function_calling.py +1 -1
  30. lionagi/operatives/action/manager.py +22 -26
  31. lionagi/operatives/action/tool.py +1 -1
  32. lionagi/operatives/strategies/__init__.py +3 -0
  33. lionagi/{operations → operatives}/strategies/params.py +18 -2
  34. lionagi/protocols/adapters/__init__.py +0 -0
  35. lionagi/protocols/adapters/adapter.py +95 -0
  36. lionagi/protocols/adapters/json_adapter.py +101 -0
  37. lionagi/protocols/adapters/pandas_/__init__.py +0 -0
  38. lionagi/protocols/adapters/pandas_/csv_adapter.py +50 -0
  39. lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -0
  40. lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +31 -0
  41. lionagi/protocols/adapters/pandas_/pd_series_adapter.py +17 -0
  42. lionagi/protocols/adapters/types.py +18 -0
  43. lionagi/protocols/generic/pile.py +22 -1
  44. lionagi/protocols/graph/node.py +17 -1
  45. lionagi/protocols/types.py +3 -3
  46. lionagi/service/__init__.py +1 -14
  47. lionagi/service/endpoints/base.py +1 -1
  48. lionagi/service/endpoints/rate_limited_processor.py +2 -1
  49. lionagi/service/manager.py +1 -1
  50. lionagi/service/types.py +18 -0
  51. lionagi/session/branch.py +1098 -929
  52. lionagi/version.py +1 -1
  53. {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/METADATA +4 -4
  54. {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/RECORD +66 -38
  55. lionagi/libs/compress/models.py +0 -66
  56. lionagi/libs/compress/utils.py +0 -69
  57. lionagi/operations/select/prompt.py +0 -5
  58. lionagi/protocols/_adapter.py +0 -224
  59. /lionagi/{libs/compress → operations/ReAct}/__init__.py +0 -0
  60. /lionagi/operations/{strategies → _act}/__init__.py +0 -0
  61. /lionagi/{operations → operatives}/strategies/base.py +0 -0
  62. /lionagi/{operations → operatives}/strategies/concurrent.py +0 -0
  63. /lionagi/{operations → operatives}/strategies/concurrent_chunk.py +0 -0
  64. /lionagi/{operations → operatives}/strategies/concurrent_sequential_chunk.py +0 -0
  65. /lionagi/{operations → operatives}/strategies/sequential.py +0 -0
  66. /lionagi/{operations → operatives}/strategies/sequential_chunk.py +0 -0
  67. /lionagi/{operations → operatives}/strategies/sequential_concurrent_chunk.py +0 -0
  68. /lionagi/{operations → operatives}/strategies/utils.py +0 -0
  69. {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/WHEEL +0 -0
  70. {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from typing import TYPE_CHECKING, Any, Literal
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from lionagi.libs.validate.fuzzy_validate_mapping import fuzzy_validate_mapping
10
+ from lionagi.operatives.types import Operative
11
+ from lionagi.utils import breakdown_pydantic_annotation
12
+
13
+ if TYPE_CHECKING:
14
+ from lionagi.session.branch import Branch
15
+
16
+
17
+ async def parse(
18
+ branch: "Branch",
19
+ text: str,
20
+ handle_validation: Literal[
21
+ "raise", "return_value", "return_none"
22
+ ] = "return_value",
23
+ max_retries: int = 3,
24
+ request_type: type[BaseModel] = None,
25
+ operative: Operative = None,
26
+ similarity_algo="jaro_winkler",
27
+ similarity_threshold: float = 0.85,
28
+ fuzzy_match: bool = True,
29
+ handle_unmatched: Literal[
30
+ "ignore", "raise", "remove", "fill", "force"
31
+ ] = "force",
32
+ fill_value: Any = None,
33
+ fill_mapping: dict[str, Any] | None = None,
34
+ strict: bool = False,
35
+ suppress_conversion_errors: bool = False,
36
+ response_format=None,
37
+ ):
38
+ """Attempts to parse text into a structured Pydantic model.
39
+
40
+ Uses optional fuzzy matching to handle partial or unclear fields.
41
+
42
+ Args:
43
+ text (str): The raw text to parse.
44
+ handle_validation (Literal["raise","return_value","return_none"]):
45
+ What to do if parsing fails. Defaults to "return_value".
46
+ max_retries (int):
47
+ How many times to retry parsing if it fails.
48
+ request_type (type[BaseModel], optional):
49
+ The Pydantic model to parse into.
50
+ operative (Operative, optional):
51
+ If provided, uses its model and max_retries setting.
52
+ similarity_algo (str):
53
+ The similarity algorithm for fuzzy field matching.
54
+ similarity_threshold (float):
55
+ A threshold for fuzzy matching (0.0 - 1.0).
56
+ fuzzy_match (bool):
57
+ If True, tries to match unrecognized keys to known ones.
58
+ handle_unmatched (Literal["ignore","raise","remove","fill","force"]):
59
+ How to handle unmatched fields.
60
+ fill_value (Any):
61
+ A default value used when fill is needed.
62
+ fill_mapping (dict[str, Any] | None):
63
+ A mapping from field -> fill value override.
64
+ strict (bool):
65
+ If True, raises errors on ambiguous fields or data types.
66
+ suppress_conversion_errors (bool):
67
+ If True, logs or ignores errors during data conversion.
68
+
69
+ Returns:
70
+ BaseModel | Any | None:
71
+ The parsed model instance, or a dict/string/None depending
72
+ on the handling mode.
73
+ """
74
+ _should_try = True
75
+ num_try = 0
76
+ response_model = text
77
+ if operative is not None:
78
+ max_retries = operative.max_retries
79
+ response_format = operative.request_type
80
+
81
+ while (
82
+ _should_try
83
+ and num_try < max_retries
84
+ and not isinstance(response_model, BaseModel)
85
+ ):
86
+ num_try += 1
87
+ _, res = await branch.chat(
88
+ instruction="reformat text into specified model",
89
+ guidane="follow the required response format, using the model schema as a guide",
90
+ context=[{"text_to_format": text}],
91
+ response_format=response_format or request_type,
92
+ sender=branch.user,
93
+ recipient=branch.id,
94
+ imodel=branch.parse_model,
95
+ return_ins_res_message=True,
96
+ )
97
+ if operative is not None:
98
+ response_model = operative.update_response_model(res.response)
99
+ else:
100
+ response_model = fuzzy_validate_mapping(
101
+ res.response,
102
+ breakdown_pydantic_annotation(request_type),
103
+ similarity_algo=similarity_algo,
104
+ similarity_threshold=similarity_threshold,
105
+ fuzzy_match=fuzzy_match,
106
+ handle_unmatched=handle_unmatched,
107
+ fill_value=fill_value,
108
+ fill_mapping=fill_mapping,
109
+ strict=strict,
110
+ suppress_conversion_errors=suppress_conversion_errors,
111
+ )
112
+ response_model = request_type.model_validate(response_model)
113
+
114
+ if not isinstance(response_model, BaseModel):
115
+ match handle_validation:
116
+ case "return_value":
117
+ return response_model
118
+ case "return_none":
119
+ return None
120
+ case "raise":
121
+ raise ValueError(
122
+ "Failed to parse response into request format"
123
+ )
124
+
125
+ return response_model
@@ -27,9 +27,9 @@ from .prompt import EXPANSION_PROMPT, PLAN_PROMPT
27
27
  class PlanOperation(BaseModel):
28
28
  """
29
29
  Stores all relevant outcomes for a multi-step Plan:
30
- * initial: The result of the initial plan prompt
31
- * plan: A list of plan steps (Instruct objects) generated from the initial planning
32
- * execute: Any responses from executing those plan steps
30
+ * initial: The result of the initial plan prompt
31
+ * plan: A list of plan steps (Instruct objects) generated from the initial planning
32
+ * execute: Any responses from executing those plan steps
33
33
  """
34
34
 
35
35
  initial: Any
@@ -1,7 +1,3 @@
1
1
  # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
-
5
- from .select import select
6
-
7
- __all__ = ["select"]
@@ -2,56 +2,37 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
-
6
5
  from enum import Enum
7
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
8
7
 
9
- from pydantic import BaseModel, Field
8
+ from pydantic import BaseModel
10
9
 
11
- from lionagi import Branch
12
10
  from lionagi.operatives.types import Instruct
13
11
 
14
- from .prompt import PROMPT
15
- from .utils import parse_selection, parse_to_representation
16
-
12
+ from .utils import SelectionModel
17
13
 
18
- class SelectionModel(BaseModel):
19
- """Model representing the selection output."""
20
-
21
- selected: list[Any] = Field(default_factory=list)
14
+ if TYPE_CHECKING:
15
+ from lionagi.session.branch import Branch
22
16
 
23
17
 
24
18
  async def select(
19
+ branch: "Branch",
25
20
  instruct: Instruct | dict[str, Any],
26
21
  choices: list[str] | type[Enum] | dict[str, Any],
27
22
  max_num_selections: int = 1,
28
- branch: Branch | None = None,
29
23
  branch_kwargs: dict[str, Any] | None = None,
30
24
  return_branch: bool = False,
31
25
  verbose: bool = False,
32
26
  **kwargs: Any,
33
- ) -> SelectionModel | tuple[SelectionModel, Branch]:
34
- """Perform a selection operation from given choices.
35
-
36
- Args:
37
- instruct: Instruction model or dictionary.
38
- choices: Options to select from.
39
- max_num_selections: Maximum selections allowed.
40
- branch: Existing branch or None to create a new one.
41
- branch_kwargs: Additional arguments for branch creation.
42
- return_branch: If True, return the branch with the selection.
43
- verbose: Whether to enable verbose output.
44
- **kwargs: Additional keyword arguments.
45
-
46
- Returns:
47
- A SelectionModel instance, optionally with the branch.
48
- """
27
+ ) -> SelectionModel | tuple[SelectionModel, "Branch"]:
49
28
  if verbose:
50
29
  print(f"Starting selection with up to {max_num_selections} choices.")
51
30
 
31
+ from .utils import SelectionModel, parse_selection, parse_to_representation
32
+
52
33
  branch = branch or Branch(**(branch_kwargs or {}))
53
34
  selections, contents = parse_to_representation(choices)
54
- prompt = PROMPT.format(
35
+ prompt = SelectionModel.PROMPT.format(
55
36
  max_num_selections=max_num_selections, choices=selections
56
37
  )
57
38
 
@@ -73,7 +54,7 @@ async def select(
73
54
  instruct["context"] = context
74
55
 
75
56
  response_model: SelectionModel = await branch.operate(
76
- operative_model=SelectionModel,
57
+ response_format=SelectionModel,
77
58
  **kwargs,
78
59
  **instruct,
79
60
  )
@@ -4,14 +4,25 @@
4
4
 
5
5
  import inspect
6
6
  from enum import Enum
7
- from typing import Any
7
+ from typing import Any, ClassVar
8
8
 
9
- from pydantic import BaseModel, JsonValue
9
+ from pydantic import BaseModel, Field, JsonValue
10
10
 
11
11
  from lionagi.libs.validate.string_similarity import string_similarity
12
12
  from lionagi.utils import is_same_dtype
13
13
 
14
14
 
15
+ # TODO: Make select a field to be added into a model, much like reason and action
16
+ class SelectionModel(BaseModel):
17
+ """Model representing the selection output."""
18
+
19
+ PROMPT: ClassVar[str] = (
20
+ "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"
21
+ )
22
+
23
+ selected: list[Any] = Field(default_factory=list)
24
+
25
+
15
26
  def parse_to_representation(
16
27
  choices: Enum | dict | list | tuple | set,
17
28
  ) -> tuple[list[str], JsonValue]:
File without changes
@@ -0,0 +1,47 @@
1
+ from typing import TYPE_CHECKING, Literal
2
+
3
+ from lionagi.service.imodel import iModel
4
+
5
+ if TYPE_CHECKING:
6
+ from lionagi.session.branch import Branch
7
+
8
+
9
+ async def translate(
10
+ branch: "Branch",
11
+ text: str,
12
+ technique: Literal["SynthLang"] = "SynthLang",
13
+ technique_kwargs: dict = None,
14
+ compress: bool = False,
15
+ chat_model: iModel = None,
16
+ compress_model: iModel = None,
17
+ compression_ratio: float = 0.2,
18
+ compress_kwargs=None,
19
+ verbose: bool = True,
20
+ new_branch: bool = True,
21
+ **kwargs,
22
+ ):
23
+ if technique == "SynthLang":
24
+ from lionagi.libs.token_transform.synthlang import (
25
+ translate_to_synthlang,
26
+ )
27
+
28
+ if not technique_kwargs:
29
+ technique_kwargs = {}
30
+ if not technique_kwargs.get("template_name"):
31
+ technique_kwargs["template_name"] = "symbolic_systems"
32
+
33
+ technique_kwargs = {**technique_kwargs, **kwargs}
34
+
35
+ return await translate_to_synthlang(
36
+ text=text,
37
+ compress=compress,
38
+ chat_model=chat_model or branch.chat_model,
39
+ compress_model=compress_model,
40
+ compression_ratio=compression_ratio,
41
+ compress_kwargs=compress_kwargs,
42
+ verbose=verbose,
43
+ branch=branch if not new_branch else None,
44
+ **technique_kwargs,
45
+ )
46
+
47
+ raise ValueError(f"Technique {technique} is not supported.")
@@ -2,6 +2,28 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from .brainstorm import *
6
- from .plan import *
7
- from .select import *
5
+ from .brainstorm.brainstorm import brainstorm
6
+ from .chat.chat import chat
7
+ from .communicate.communicate import communicate
8
+ from .instruct.instruct import instruct
9
+ from .interpret.interpret import interpret
10
+ from .operate.operate import operate
11
+ from .parse.parse import parse
12
+ from .plan.plan import plan
13
+ from .ReAct.ReAct import ReAct
14
+ from .select.select import select
15
+ from .translate.translate import translate
16
+
17
+ __all__ = (
18
+ "brainstorm",
19
+ "plan",
20
+ "select",
21
+ "chat",
22
+ "communicate",
23
+ "instruct",
24
+ "interpret",
25
+ "operate",
26
+ "parse",
27
+ "ReAct",
28
+ "translate",
29
+ )
@@ -7,7 +7,7 @@ from typing import Any, Self
7
7
 
8
8
  from pydantic import Field, model_validator
9
9
 
10
- from lionagi.protocols.types import Event, EventStatus
10
+ from lionagi.protocols.generic.event import Event, EventStatus
11
11
  from lionagi.utils import is_coro_func
12
12
 
13
13
  from .tool import Tool
@@ -4,13 +4,9 @@
4
4
 
5
5
  from typing import Any
6
6
 
7
- from lionagi.protocols.types import (
8
- ActionRequest,
9
- EventStatus,
10
- Execution,
11
- Log,
12
- Manager,
13
- )
7
+ from lionagi.protocols._concepts import Manager
8
+ from lionagi.protocols.generic.event import Execution
9
+ from lionagi.protocols.messages.action_request import ActionRequest
14
10
  from lionagi.utils import to_list
15
11
 
16
12
  from .function_calling import FunctionCalling
@@ -116,19 +112,28 @@ class ActionManager(Manager):
116
112
  ]
117
113
 
118
114
  def match_tool(
119
- self, action_request: ActionRequest | ActionRequestModel
115
+ self, action_request: ActionRequest | ActionRequestModel | dict
120
116
  ) -> FunctionCalling:
121
- if not isinstance(action_request, ActionRequest | ActionRequestModel):
117
+ if not isinstance(
118
+ action_request, ActionRequest | ActionRequestModel | dict
119
+ ):
122
120
  raise TypeError(f"Unsupported type {type(action_request)}")
123
121
 
124
- tool = self.registry.get(action_request.function, None)
125
- if not isinstance(tool, Tool):
126
- raise ValueError(
127
- f"Function {action_request.function} is not registered."
128
- )
129
- return FunctionCalling(
130
- func_tool=tool, arguments=action_request.arguments
122
+ func = (
123
+ action_request["function"]
124
+ if isinstance(action_request, dict)
125
+ else action_request.function
126
+ )
127
+ args = (
128
+ action_request["arguments"]
129
+ if isinstance(action_request, dict)
130
+ else action_request.arguments
131
131
  )
132
+ tool = self.registry.get(func, None)
133
+
134
+ if not isinstance(tool, Tool):
135
+ raise ValueError(f"Function {func} is not registered.")
136
+ return FunctionCalling(func_tool=tool, arguments=args)
132
137
 
133
138
  async def invoke(
134
139
  self, func_call: ActionRequestModel | ActionRequest
@@ -151,16 +156,7 @@ class ActionManager(Manager):
151
156
  Raises:
152
157
  ValueError: If function not registered or call format invalid.
153
158
  """
154
- try:
155
- function_calling = self.match_tool(func_call)
156
- except ValueError as e:
157
- return Log(
158
- content={
159
- "event_type": "function_call",
160
- "status": EventStatus.FAILED,
161
- "error": str(e),
162
- }
163
- )
159
+ function_calling = self.match_tool(func_call)
164
160
  await function_calling.invoke()
165
161
  return function_calling
166
162
 
@@ -10,7 +10,7 @@ from pydantic import Field, field_validator, model_validator
10
10
 
11
11
  from lionagi.libs.schema.function_to_schema import function_to_schema
12
12
  from lionagi.libs.validate.common_field_validators import validate_callable
13
- from lionagi.protocols.types import Element
13
+ from lionagi.protocols.generic.element import Element
14
14
 
15
15
  __all__ = (
16
16
  "Tool",
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
@@ -10,9 +10,25 @@ from lionagi.operatives.instruct.instruct import (
10
10
  LIST_INSTRUCT_FIELD_MODEL,
11
11
  Instruct,
12
12
  )
13
- from lionagi.protocols.types import FieldModel, SchemaModel
13
+ from lionagi.operatives.types import FieldModel, SchemaModel
14
14
  from lionagi.session.session import Branch, Session
15
- from lionagi.utils import RCallParams
15
+
16
+
17
+ class RCallParams(SchemaModel):
18
+ """Parameters for remote function calls."""
19
+
20
+ timeout: float = Field(
21
+ default=60, description="Timeout for remote function call"
22
+ )
23
+ max_retries: int = Field(
24
+ default=3, description="Maximum number of retries"
25
+ )
26
+ retry_delay: float = Field(
27
+ default=0.5, description="Delay between retries"
28
+ )
29
+ retry_backoff: float = Field(
30
+ default=2, description="Backoff factor for retry delay"
31
+ )
16
32
 
17
33
 
18
34
  class StrategyParams(SchemaModel):
File without changes
@@ -0,0 +1,95 @@
1
+ # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import logging
6
+ from typing import Any, Protocol, TypeVar, runtime_checkable
7
+
8
+ from typing_extensions import get_protocol_members
9
+
10
+ T = TypeVar("T")
11
+
12
+ __all__ = (
13
+ "Adapter",
14
+ "ADAPTER_MEMBERS",
15
+ "AdapterRegistry",
16
+ )
17
+
18
+
19
+ @runtime_checkable
20
+ class Adapter(Protocol):
21
+
22
+ obj_key: str
23
+
24
+ @classmethod
25
+ def from_obj(
26
+ cls,
27
+ subj_cls: type[T],
28
+ obj: Any,
29
+ /,
30
+ *,
31
+ many: bool,
32
+ **kwargs,
33
+ ) -> dict | list[dict]: ...
34
+
35
+ @classmethod
36
+ def to_obj(
37
+ cls,
38
+ subj: T,
39
+ /,
40
+ *,
41
+ many: bool,
42
+ **kwargs,
43
+ ) -> Any: ...
44
+
45
+
46
+ ADAPTER_MEMBERS = get_protocol_members(Adapter) # duck typing
47
+
48
+
49
+ class AdapterRegistry:
50
+
51
+ _adapters: dict[str, Adapter] = {}
52
+
53
+ @classmethod
54
+ def list_adapters(cls) -> list[tuple[str | type, ...]]:
55
+ return list(cls._adapters.keys())
56
+
57
+ @classmethod
58
+ def register(cls, adapter: type[Adapter]) -> None:
59
+ for member in ADAPTER_MEMBERS:
60
+ if not hasattr(adapter, member):
61
+ _str = getattr(adapter, "obj_key", None) or repr(adapter)
62
+ _str = _str[:50] if len(_str) > 50 else _str
63
+ raise AttributeError(
64
+ f"Adapter {_str} missing required methods."
65
+ )
66
+
67
+ if isinstance(adapter, type):
68
+ cls._adapters[adapter.obj_key] = adapter()
69
+ else:
70
+ cls._adapters[adapter.obj_key] = adapter
71
+
72
+ @classmethod
73
+ def get(cls, obj_key: type | str) -> Adapter:
74
+ try:
75
+ return cls._adapters[obj_key]
76
+ except Exception as e:
77
+ logging.error(f"Error getting adapter for {obj_key}. Error: {e}")
78
+
79
+ @classmethod
80
+ def adapt_from(
81
+ cls, subj_cls: type[T], obj: Any, obj_key: type | str, **kwargs
82
+ ) -> dict | list[dict]:
83
+ try:
84
+ return cls.get(obj_key).from_obj(subj_cls, obj, **kwargs)
85
+ except Exception as e:
86
+ logging.error(f"Error adapting data from {obj_key}. Error: {e}")
87
+ raise e
88
+
89
+ @classmethod
90
+ def adapt_to(cls, subj: T, obj_key: type | str, **kwargs) -> Any:
91
+ try:
92
+ return cls.get(obj_key).to_obj(subj, **kwargs)
93
+ except Exception as e:
94
+ logging.error(f"Error adapting data to {obj_key}. Error: {e}")
95
+ raise e
@@ -0,0 +1,101 @@
1
+ import json
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ from lionagi.protocols._concepts import Collective
6
+
7
+ from .adapter import Adapter, T
8
+
9
+
10
+ class JsonAdapter(Adapter):
11
+
12
+ obj_key = "json"
13
+
14
+ @classmethod
15
+ def from_obj(
16
+ cls,
17
+ subj_cls: type[T],
18
+ obj: str,
19
+ /,
20
+ *,
21
+ many: bool = False,
22
+ **kwargs,
23
+ ) -> dict | list[dict]:
24
+ """
25
+ kwargs for json.loads(s, **kwargs)
26
+ """
27
+ result = json.loads(obj, **kwargs)
28
+ if many:
29
+ return result if isinstance(result, list) else [result]
30
+ return (
31
+ result[0]
32
+ if isinstance(result, list) and len(result) > 0
33
+ else result
34
+ )
35
+
36
+ @classmethod
37
+ def to_obj(
38
+ cls,
39
+ subj: T,
40
+ *,
41
+ many: bool = False,
42
+ **kwargs,
43
+ ):
44
+ """
45
+ kwargs for json.dumps(obj, **kwargs)
46
+ """
47
+ if many:
48
+ if isinstance(subj, Collective):
49
+ return json.dumps([i.to_dict() for i in subj], **kwargs)
50
+ return json.dumps([subj.to_dict()], **kwargs)
51
+ return json.dumps(subj.to_dict(), **kwargs)
52
+
53
+
54
+ class JsonFileAdapter(Adapter):
55
+
56
+ obj_key = ".json"
57
+
58
+ @classmethod
59
+ def from_obj(
60
+ cls,
61
+ subj_cls: type[T],
62
+ obj: str | Path,
63
+ /,
64
+ *,
65
+ many: bool = False,
66
+ **kwargs,
67
+ ) -> dict | list[dict]:
68
+ """
69
+ kwargs for json.load(fp, **kwargs)
70
+ """
71
+ with open(obj) as f:
72
+ result = json.load(f, **kwargs)
73
+ if many:
74
+ return result if isinstance(result, list) else [result]
75
+ return (
76
+ result[0]
77
+ if isinstance(result, list) and len(result) > 0
78
+ else result
79
+ )
80
+
81
+ @classmethod
82
+ def to_obj(
83
+ cls,
84
+ subj: T,
85
+ /,
86
+ *,
87
+ fp: str | Path,
88
+ many: bool = False,
89
+ **kwargs,
90
+ ):
91
+ """
92
+ kwargs for json.dump(obj, fp, **kwargs)
93
+ """
94
+ if many:
95
+ if isinstance(subj, Collective):
96
+ json.dump([i.to_dict() for i in subj], fp=fp, **kwargs)
97
+ return
98
+ json.dump([subj.to_dict()], fp=fp, **kwargs)
99
+ return
100
+ json.dump(subj.to_dict(), fp=fp, **kwargs)
101
+ logging.info(f"Successfully saved data to {fp}")
File without changes