lionagi 0.6.0__py3-none-any.whl → 0.7.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 (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