lionagi 0.12.2__py3-none-any.whl → 0.12.4__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 (86) hide show
  1. lionagi/config.py +123 -0
  2. lionagi/fields/file.py +1 -1
  3. lionagi/fields/reason.py +1 -1
  4. lionagi/libs/file/concat.py +1 -6
  5. lionagi/libs/file/concat_files.py +1 -5
  6. lionagi/libs/file/save.py +1 -1
  7. lionagi/libs/package/imports.py +8 -177
  8. lionagi/libs/parse.py +30 -0
  9. lionagi/libs/schema/load_pydantic_model_from_schema.py +259 -0
  10. lionagi/libs/token_transform/perplexity.py +2 -4
  11. lionagi/libs/token_transform/synthlang_/resources/frameworks/framework_options.json +46 -46
  12. lionagi/libs/token_transform/synthlang_/translate_to_synthlang.py +1 -1
  13. lionagi/operations/chat/chat.py +2 -2
  14. lionagi/operations/communicate/communicate.py +20 -5
  15. lionagi/operations/parse/parse.py +131 -43
  16. lionagi/protocols/generic/log.py +1 -2
  17. lionagi/protocols/generic/pile.py +18 -4
  18. lionagi/protocols/messages/assistant_response.py +20 -1
  19. lionagi/protocols/messages/templates/README.md +6 -10
  20. lionagi/service/connections/__init__.py +15 -0
  21. lionagi/service/connections/api_calling.py +230 -0
  22. lionagi/service/connections/endpoint.py +410 -0
  23. lionagi/service/connections/endpoint_config.py +137 -0
  24. lionagi/service/connections/header_factory.py +56 -0
  25. lionagi/service/connections/match_endpoint.py +49 -0
  26. lionagi/service/connections/providers/__init__.py +3 -0
  27. lionagi/service/connections/providers/anthropic_.py +87 -0
  28. lionagi/service/connections/providers/exa_.py +33 -0
  29. lionagi/service/connections/providers/oai_.py +166 -0
  30. lionagi/service/connections/providers/ollama_.py +122 -0
  31. lionagi/service/connections/providers/perplexity_.py +29 -0
  32. lionagi/service/imodel.py +36 -144
  33. lionagi/service/manager.py +1 -7
  34. lionagi/service/{endpoints/rate_limited_processor.py → rate_limited_processor.py} +4 -2
  35. lionagi/service/resilience.py +545 -0
  36. lionagi/service/third_party/README.md +71 -0
  37. lionagi/service/third_party/__init__.py +0 -0
  38. lionagi/service/third_party/anthropic_models.py +159 -0
  39. lionagi/service/third_party/exa_models.py +165 -0
  40. lionagi/service/third_party/openai_models.py +18241 -0
  41. lionagi/service/third_party/pplx_models.py +156 -0
  42. lionagi/service/types.py +5 -4
  43. lionagi/session/branch.py +12 -7
  44. lionagi/tools/file/reader.py +1 -1
  45. lionagi/tools/memory/tools.py +497 -0
  46. lionagi/utils.py +921 -123
  47. lionagi/version.py +1 -1
  48. {lionagi-0.12.2.dist-info → lionagi-0.12.4.dist-info}/METADATA +33 -16
  49. {lionagi-0.12.2.dist-info → lionagi-0.12.4.dist-info}/RECORD +53 -63
  50. lionagi/libs/file/create_path.py +0 -80
  51. lionagi/libs/file/file_util.py +0 -358
  52. lionagi/libs/parse/__init__.py +0 -3
  53. lionagi/libs/parse/fuzzy_parse_json.py +0 -117
  54. lionagi/libs/parse/to_dict.py +0 -336
  55. lionagi/libs/parse/to_json.py +0 -61
  56. lionagi/libs/parse/to_num.py +0 -378
  57. lionagi/libs/parse/to_xml.py +0 -57
  58. lionagi/libs/parse/xml_parser.py +0 -148
  59. lionagi/libs/schema/breakdown_pydantic_annotation.py +0 -48
  60. lionagi/service/endpoints/__init__.py +0 -3
  61. lionagi/service/endpoints/base.py +0 -706
  62. lionagi/service/endpoints/chat_completion.py +0 -116
  63. lionagi/service/endpoints/match_endpoint.py +0 -72
  64. lionagi/service/providers/__init__.py +0 -3
  65. lionagi/service/providers/anthropic_/__init__.py +0 -3
  66. lionagi/service/providers/anthropic_/messages.py +0 -99
  67. lionagi/service/providers/exa_/models.py +0 -3
  68. lionagi/service/providers/exa_/search.py +0 -80
  69. lionagi/service/providers/exa_/types.py +0 -7
  70. lionagi/service/providers/groq_/__init__.py +0 -3
  71. lionagi/service/providers/groq_/chat_completions.py +0 -56
  72. lionagi/service/providers/ollama_/__init__.py +0 -3
  73. lionagi/service/providers/ollama_/chat_completions.py +0 -134
  74. lionagi/service/providers/openai_/__init__.py +0 -3
  75. lionagi/service/providers/openai_/chat_completions.py +0 -101
  76. lionagi/service/providers/openai_/spec.py +0 -14
  77. lionagi/service/providers/openrouter_/__init__.py +0 -3
  78. lionagi/service/providers/openrouter_/chat_completions.py +0 -62
  79. lionagi/service/providers/perplexity_/__init__.py +0 -3
  80. lionagi/service/providers/perplexity_/chat_completions.py +0 -44
  81. lionagi/service/providers/perplexity_/models.py +0 -5
  82. lionagi/service/providers/types.py +0 -17
  83. /lionagi/{service/providers/exa_/__init__.py → py.typed} +0 -0
  84. /lionagi/service/{endpoints/token_calculator.py → token_calculator.py} +0 -0
  85. {lionagi-0.12.2.dist-info → lionagi-0.12.4.dist-info}/WHEEL +0 -0
  86. {lionagi-0.12.2.dist-info → lionagi-0.12.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,52 +1,52 @@
1
1
  {
2
- "options": {
3
- "math": {
4
- "name": "Mathematical Framework",
5
- "description": "Offers a suite of math glyphs and notation rules.",
6
- "glyphs": [
7
- {
8
- "symbol": "\u21b9",
9
- "name": "Focus/Filter",
10
- "description": "Used for focusing instructions"
2
+ "options": {
3
+ "math": {
4
+ "name": "Mathematical Framework",
5
+ "description": "Offers a suite of math glyphs and notation rules.",
6
+ "glyphs": [
7
+ {
8
+ "symbol": "\u21b9",
9
+ "name": "Focus/Filter",
10
+ "description": "Used for focusing instructions"
11
+ },
12
+ {
13
+ "symbol": "\u03a3",
14
+ "name": "Summarize",
15
+ "description": "Condense large sets of data"
16
+ },
17
+ {
18
+ "symbol": "\u2295",
19
+ "name": "Combine/Merge",
20
+ "description": "Merge multiple data sources"
21
+ },
22
+ {
23
+ "symbol": "\u2022",
24
+ "name": "Group Operation",
25
+ "description": "Binary operation in group theory"
26
+ }
27
+ ]
11
28
  },
12
- {
13
- "symbol": "\u03a3",
14
- "name": "Summarize",
15
- "description": "Condense large sets of data"
29
+ "optim": {
30
+ "name": "Optimization Framework",
31
+ "description": "Compression and optimization for code/math expressions.",
32
+ "glyphs": [
33
+ {
34
+ "symbol": "IF",
35
+ "name": "Conditional Operator",
36
+ "description": "Represents branching logic"
37
+ }
38
+ ]
16
39
  },
17
- {
18
- "symbol": "\u2295",
19
- "name": "Combine/Merge",
20
- "description": "Merge multiple data sources"
21
- },
22
- {
23
- "symbol": "\u2022",
24
- "name": "Group Operation",
25
- "description": "Binary operation in group theory"
26
- }
27
- ]
28
- },
29
- "optim": {
30
- "name": "Optimization Framework",
31
- "description": "Compression and optimization for code/math expressions.",
32
- "glyphs": [
33
- {
34
- "symbol": "IF",
35
- "name": "Conditional Operator",
36
- "description": "Represents branching logic"
37
- }
38
- ]
39
- },
40
- "custom_algebra": {
41
- "name": "Custom Algebraic Framework",
42
- "description": "Extra rules for ring, group, field expansions.",
43
- "glyphs": [
44
- {
45
- "symbol": "\u221e",
46
- "name": "Infinite Operator",
47
- "description": "Represents unbounded algebraic ops"
40
+ "custom_algebra": {
41
+ "name": "Custom Algebraic Framework",
42
+ "description": "Extra rules for ring, group, field expansions.",
43
+ "glyphs": [
44
+ {
45
+ "symbol": "\u221e",
46
+ "name": "Infinite Operator",
47
+ "description": "Represents unbounded algebraic ops"
48
+ }
49
+ ]
48
50
  }
49
- ]
50
51
  }
51
- }
52
52
  }
@@ -93,7 +93,7 @@ async def translate_to_synthlang(
93
93
  else:
94
94
  branch = Branch(system=final_prompt, chat_model=chat_model)
95
95
 
96
- from lionagi.service.endpoints.token_calculator import TokenCalculator
96
+ from lionagi.service.token_calculator import TokenCalculator
97
97
 
98
98
  calculator = TokenCalculator()
99
99
 
@@ -122,8 +122,8 @@ async def chat(
122
122
  _msgs.append(i)
123
123
  messages = _msgs
124
124
 
125
- imodel = imodel or branch.chat_model
126
- if branch.msgs.system and imodel.sequential_exchange:
125
+ # All endpoints now assume sequential exchange (system message embedded in first user message)
126
+ if branch.msgs.system:
127
127
  messages = [msg for msg in messages if msg.role != "system"]
128
128
  first_instruction = None
129
129
 
@@ -91,12 +91,27 @@ async def communicate(
91
91
  return res.response
92
92
 
93
93
  if response_format is not None:
94
- return await branch.parse(
95
- text=res.response,
96
- request_type=response_format,
97
- max_retries=num_parse_retries,
94
+ # Default to raising errors unless explicitly set in fuzzy_match_kwargs
95
+ parse_kwargs = {
96
+ "handle_validation": "raise", # Default to raising errors
98
97
  **(fuzzy_match_kwargs or {}),
99
- )
98
+ }
99
+
100
+ try:
101
+ return await branch.parse(
102
+ text=res.response,
103
+ request_type=response_format,
104
+ max_retries=num_parse_retries,
105
+ **parse_kwargs,
106
+ )
107
+ except ValueError as e:
108
+ # Re-raise with more context
109
+ logging.error(
110
+ f"Failed to parse response '{res.response}' into {response_format}: {e}"
111
+ )
112
+ raise ValueError(
113
+ f"Failed to parse model response into {response_format.__name__}: {e}"
114
+ ) from e
100
115
 
101
116
  if request_fields is not None:
102
117
  _d = fuzzy_validate_mapping(
@@ -35,35 +35,117 @@ async def parse(
35
35
  suppress_conversion_errors: bool = False,
36
36
  response_format=None,
37
37
  ):
38
- _should_try = True
39
- num_try = 0
40
- response_model = text
41
38
  if operative is not None:
42
39
  max_retries = operative.max_retries
43
- response_format = operative.request_type
40
+ response_format = operative.request_type or response_format
41
+ request_type = request_type or operative.request_type
44
42
 
45
- while (
46
- _should_try
47
- and num_try < max_retries
48
- and not isinstance(response_model, BaseModel)
49
- ):
50
- num_try += 1
51
- if num_try == max_retries:
52
- _should_try = False
53
- _, res = await branch.chat(
54
- instruction="reformat text into specified model",
55
- guidane="follow the required response format, using the model schema as a guide",
56
- context=[{"text_to_format": text}],
57
- response_format=response_format or request_type,
58
- sender=branch.user,
59
- recipient=branch.id,
60
- imodel=branch.parse_model,
61
- return_ins_res_message=True,
43
+ if not request_type and not response_format:
44
+ raise ValueError(
45
+ "Either request_type or response_format must be provided"
46
+ )
47
+
48
+ request_type = request_type or response_format
49
+
50
+ # First attempt: try to parse the text directly
51
+ import logging
52
+
53
+ initial_error = None
54
+ parsed_data = None # Initialize to avoid scoping issues
55
+
56
+ try:
57
+ # Try fuzzy validation first
58
+ parsed_data = fuzzy_validate_mapping(
59
+ text,
60
+ breakdown_pydantic_annotation(request_type),
61
+ similarity_algo=similarity_algo,
62
+ similarity_threshold=similarity_threshold,
63
+ fuzzy_match=fuzzy_match,
64
+ handle_unmatched=handle_unmatched,
65
+ fill_value=fill_value,
66
+ fill_mapping=fill_mapping,
67
+ strict=strict,
68
+ suppress_conversion_errors=False, # Don't suppress on first attempt
62
69
  )
70
+
71
+ logging.debug(f"Parsed data from fuzzy validation: {parsed_data}")
72
+
73
+ # Validate with pydantic
63
74
  if operative is not None:
64
- response_model = operative.update_response_model(res.response)
75
+ response_model = operative.update_response_model(parsed_data)
65
76
  else:
66
- response_model = fuzzy_validate_mapping(
77
+ response_model = request_type.model_validate(parsed_data)
78
+
79
+ # If successful, return immediately
80
+ if isinstance(response_model, BaseModel):
81
+ return response_model
82
+
83
+ except Exception as e:
84
+ initial_error = e
85
+ # Log the initial parsing error for debugging
86
+ logging.debug(
87
+ f"Initial parsing failed for text '{text[:100]}...': {e}"
88
+ )
89
+ logging.debug(
90
+ f"Parsed data was: {locals().get('parsed_data', 'not set')}"
91
+ )
92
+
93
+ # Only continue if we have retries left
94
+ if max_retries <= 0:
95
+ if handle_validation == "raise":
96
+ raise ValueError(f"Failed to parse response: {e}") from e
97
+ elif handle_validation == "return_none":
98
+ return None
99
+ else: # return_value
100
+ return text
101
+
102
+ # If direct parsing failed, try using the parse model
103
+ num_try = 0
104
+ last_error = initial_error
105
+
106
+ # Check if the parsed_data exists but just failed validation
107
+ # This might mean we have the right structure but wrong values
108
+ if parsed_data is not None and isinstance(parsed_data, dict):
109
+ logging.debug(
110
+ f"Have parsed_data dict, checking if it's close to valid..."
111
+ )
112
+ # If we got a dict with the right keys, maybe we just need to clean it up
113
+ expected_fields = set(request_type.model_fields.keys())
114
+ parsed_fields = set(parsed_data.keys())
115
+ if expected_fields == parsed_fields and all(
116
+ parsed_data.get(k) is not None for k in expected_fields
117
+ ):
118
+ # We have the right structure with non-None values, don't retry with parse model
119
+ logging.debug(
120
+ "Structure matches with valid values, returning original error"
121
+ )
122
+ if handle_validation == "raise":
123
+ raise ValueError(
124
+ f"Failed to parse response: {initial_error}"
125
+ ) from initial_error
126
+ elif handle_validation == "return_none":
127
+ return None
128
+ else:
129
+ return text
130
+
131
+ while num_try < max_retries:
132
+ num_try += 1
133
+
134
+ try:
135
+ logging.debug(f"Retry {num_try}: Using parse model to reformat")
136
+ _, res = await branch.chat(
137
+ instruction="reformat text into specified model",
138
+ guidance="follow the required response format, using the model schema as a guide",
139
+ context=[{"text_to_format": text}],
140
+ response_format=request_type,
141
+ sender=branch.user,
142
+ recipient=branch.id,
143
+ imodel=branch.parse_model,
144
+ return_ins_res_message=True,
145
+ )
146
+
147
+ # Try to parse the reformatted response
148
+ parsed_data = fuzzy_validate_mapping(
67
149
  res.response,
68
150
  breakdown_pydantic_annotation(request_type),
69
151
  similarity_algo=similarity_algo,
@@ -75,25 +157,31 @@ async def parse(
75
157
  strict=strict,
76
158
  suppress_conversion_errors=suppress_conversion_errors,
77
159
  )
78
- try:
79
- response_model = request_type.model_validate(response_model)
80
- except InterruptedError as e:
81
- raise e
82
- except Exception:
83
- if _should_try:
84
- continue
85
- else:
86
- break
87
-
88
- if not isinstance(response_model, BaseModel):
89
- match handle_validation:
90
- case "return_value":
160
+
161
+ if operative is not None:
162
+ response_model = operative.update_response_model(parsed_data)
163
+ else:
164
+ response_model = request_type.model_validate(parsed_data)
165
+
166
+ # If successful, return
167
+ if isinstance(response_model, BaseModel):
91
168
  return response_model
92
- case "return_none":
93
- return None
94
- case "raise":
95
- raise ValueError(
96
- "Failed to parse response into request format"
97
- )
98
169
 
99
- return response_model
170
+ except InterruptedError as e:
171
+ raise e
172
+ except Exception as e:
173
+ last_error = e
174
+ # Continue to next retry
175
+ continue
176
+
177
+ # All retries exhausted
178
+ match handle_validation:
179
+ case "return_value":
180
+ return text
181
+ case "return_none":
182
+ return None
183
+ case "raise":
184
+ error_msg = "Failed to parse response into request format"
185
+ if last_error:
186
+ error_msg += f": {last_error}"
187
+ raise ValueError(error_msg) from last_error
@@ -11,8 +11,7 @@ from typing import Any
11
11
 
12
12
  from pydantic import BaseModel, Field, PrivateAttr, field_validator
13
13
 
14
- from lionagi.libs.file.create_path import create_path
15
- from lionagi.utils import to_dict
14
+ from lionagi.utils import create_path, to_dict
16
15
 
17
16
  from .._concepts import Manager
18
17
  from .element import Element
@@ -19,7 +19,7 @@ from pathlib import Path
19
19
  from typing import Any, ClassVar, Generic, TypeVar
20
20
 
21
21
  import pandas as pd
22
- from pydantic import Field, field_serializer
22
+ from pydantic import Field
23
23
  from pydantic.fields import FieldInfo
24
24
  from typing_extensions import Self, override
25
25
 
@@ -910,9 +910,23 @@ class Pile(Element, Collective[E], Generic[E]):
910
910
  self.progression.insert(index, item_order)
911
911
  self.collections.update(item_dict)
912
912
 
913
- @field_serializer("collections")
914
- def _(self, value: dict[str, T]):
915
- return [i.to_dict() for i in value.values()]
913
+ def to_dict(self) -> dict[str, Any]:
914
+ """Convert pile to dictionary, properly handling collections."""
915
+ # Get base dict from parent class
916
+ dict_ = super().to_dict()
917
+
918
+ # Manually serialize collections
919
+ collections_list = []
920
+ for item in self.collections.values():
921
+ if hasattr(item, "to_dict"):
922
+ collections_list.append(item.to_dict())
923
+ elif hasattr(item, "model_dump"):
924
+ collections_list.append(item.model_dump())
925
+ else:
926
+ collections_list.append(str(item))
927
+
928
+ dict_["collections"] = collections_list
929
+ return dict_
916
930
 
917
931
  class AsyncPileIterator:
918
932
  def __init__(self, pile: Pile):
@@ -46,7 +46,7 @@ def prepare_assistant_response(
46
46
  elif isinstance(j, str):
47
47
  text_contents.append(j)
48
48
 
49
- # openai standard
49
+ # openai chat completions standard
50
50
  elif "choices" in i:
51
51
  choices = i["choices"]
52
52
  choices = (
@@ -58,6 +58,25 @@ def prepare_assistant_response(
58
58
  elif "delta" in j:
59
59
  text_contents.append(j["delta"]["content"] or "")
60
60
 
61
+ # openai responses API standard
62
+ elif "output" in i:
63
+ output = i["output"]
64
+ output = [output] if not isinstance(output, list) else output
65
+ for item in output:
66
+ if isinstance(item, dict):
67
+ if item.get("type") == "message":
68
+ # Extract content from message
69
+ content = item.get("content", [])
70
+ if isinstance(content, list):
71
+ for c in content:
72
+ if (
73
+ isinstance(c, dict)
74
+ and c.get("type") == "output_text"
75
+ ):
76
+ text_contents.append(c.get("text", ""))
77
+ elif isinstance(c, str):
78
+ text_contents.append(c)
79
+
61
80
  elif isinstance(i, str):
62
81
  text_contents.append(i)
63
82
 
@@ -19,14 +19,10 @@ message_text = template.render(**args)
19
19
  print(message_text)
20
20
  ```
21
21
 
22
- Benefits and Customization • You can easily rearrange sections within these
23
- templates without changing your code logic. • Each message type is clearly
24
- separated, making it simpler to maintain or adjust one message format without
25
- affecting the others. • If you find that you use some snippet (like rendering a
26
- schema) in multiple templates, you can factor it out into its own partial
27
- template (like tool_schemas.jinja2) and include it where needed. • Over time,
28
- you can add more templates or split existing ones if certain messages become too
29
- complex.
22
+ Benefits and Customization
23
+ • You can easily rearrange sections within these templates without changing your code logic.
24
+ • Each message type is clearly separated, making it simpler to maintain or adjust one message format without affecting the others.
25
+ • If you find that you use some snippet (like rendering a schema) in multiple templates, you can factor it out into its own partial template (like tool_schemas.jinja2) and include it where needed.
26
+ • Over time, you can add more templates or split existing ones if certain messages become too complex.
30
27
 
31
- By establishing this set of base templates and arguments, you have a starting
32
- point. You can expand or refine as your requirements evolve.
28
+ By establishing this set of base templates and arguments, you have a starting point. You can expand or refine as your requirements evolve.
@@ -0,0 +1,15 @@
1
+ # Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from .endpoint import Endpoint
6
+ from .endpoint_config import EndpointConfig
7
+ from .header_factory import HeaderFactory
8
+ from .match_endpoint import match_endpoint
9
+
10
+ __all__ = (
11
+ "Endpoint",
12
+ "EndpointConfig",
13
+ "HeaderFactory",
14
+ "match_endpoint",
15
+ )