lionagi 0.17.10__py3-none-any.whl → 0.18.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 (164) hide show
  1. lionagi/__init__.py +1 -2
  2. lionagi/_class_registry.py +1 -2
  3. lionagi/_errors.py +1 -2
  4. lionagi/adapters/async_postgres_adapter.py +2 -10
  5. lionagi/config.py +1 -2
  6. lionagi/fields/action.py +1 -2
  7. lionagi/fields/base.py +3 -0
  8. lionagi/fields/code.py +3 -0
  9. lionagi/fields/file.py +3 -0
  10. lionagi/fields/instruct.py +1 -2
  11. lionagi/fields/reason.py +1 -2
  12. lionagi/fields/research.py +3 -0
  13. lionagi/libs/__init__.py +1 -2
  14. lionagi/libs/file/__init__.py +1 -2
  15. lionagi/libs/file/chunk.py +1 -2
  16. lionagi/libs/file/process.py +1 -2
  17. lionagi/libs/schema/__init__.py +1 -2
  18. lionagi/libs/schema/as_readable.py +1 -2
  19. lionagi/libs/schema/extract_code_block.py +1 -2
  20. lionagi/libs/schema/extract_docstring.py +1 -2
  21. lionagi/libs/schema/function_to_schema.py +1 -2
  22. lionagi/libs/schema/load_pydantic_model_from_schema.py +1 -2
  23. lionagi/libs/schema/minimal_yaml.py +98 -0
  24. lionagi/libs/validate/__init__.py +1 -2
  25. lionagi/libs/validate/common_field_validators.py +1 -2
  26. lionagi/libs/validate/validate_boolean.py +1 -2
  27. lionagi/ln/fuzzy/_string_similarity.py +1 -2
  28. lionagi/ln/types.py +32 -5
  29. lionagi/models/__init__.py +1 -2
  30. lionagi/models/field_model.py +9 -1
  31. lionagi/models/hashable_model.py +4 -2
  32. lionagi/models/model_params.py +1 -2
  33. lionagi/models/operable_model.py +1 -2
  34. lionagi/models/schema_model.py +1 -2
  35. lionagi/operations/ReAct/ReAct.py +475 -239
  36. lionagi/operations/ReAct/__init__.py +1 -2
  37. lionagi/operations/ReAct/utils.py +4 -2
  38. lionagi/operations/__init__.py +1 -2
  39. lionagi/operations/act/__init__.py +2 -0
  40. lionagi/operations/act/act.py +206 -0
  41. lionagi/operations/brainstorm/__init__.py +1 -2
  42. lionagi/operations/brainstorm/brainstorm.py +1 -2
  43. lionagi/operations/brainstorm/prompt.py +1 -2
  44. lionagi/operations/builder.py +1 -2
  45. lionagi/operations/chat/__init__.py +1 -2
  46. lionagi/operations/chat/chat.py +131 -116
  47. lionagi/operations/communicate/communicate.py +102 -44
  48. lionagi/operations/flow.py +5 -6
  49. lionagi/operations/instruct/__init__.py +1 -2
  50. lionagi/operations/instruct/instruct.py +1 -2
  51. lionagi/operations/interpret/__init__.py +1 -2
  52. lionagi/operations/interpret/interpret.py +66 -22
  53. lionagi/operations/operate/__init__.py +1 -2
  54. lionagi/operations/operate/operate.py +213 -108
  55. lionagi/operations/parse/__init__.py +1 -2
  56. lionagi/operations/parse/parse.py +171 -144
  57. lionagi/operations/plan/__init__.py +1 -2
  58. lionagi/operations/plan/plan.py +1 -2
  59. lionagi/operations/plan/prompt.py +1 -2
  60. lionagi/operations/select/__init__.py +1 -2
  61. lionagi/operations/select/select.py +79 -19
  62. lionagi/operations/select/utils.py +2 -3
  63. lionagi/operations/types.py +120 -25
  64. lionagi/operations/utils.py +1 -2
  65. lionagi/protocols/__init__.py +1 -2
  66. lionagi/protocols/_concepts.py +1 -2
  67. lionagi/protocols/action/__init__.py +1 -2
  68. lionagi/protocols/action/function_calling.py +3 -20
  69. lionagi/protocols/action/manager.py +34 -4
  70. lionagi/protocols/action/tool.py +1 -2
  71. lionagi/protocols/contracts.py +1 -2
  72. lionagi/protocols/forms/__init__.py +1 -2
  73. lionagi/protocols/forms/base.py +1 -2
  74. lionagi/protocols/forms/flow.py +1 -2
  75. lionagi/protocols/forms/form.py +1 -2
  76. lionagi/protocols/forms/report.py +1 -2
  77. lionagi/protocols/generic/__init__.py +1 -2
  78. lionagi/protocols/generic/element.py +17 -65
  79. lionagi/protocols/generic/event.py +1 -2
  80. lionagi/protocols/generic/log.py +17 -14
  81. lionagi/protocols/generic/pile.py +3 -4
  82. lionagi/protocols/generic/processor.py +1 -2
  83. lionagi/protocols/generic/progression.py +1 -2
  84. lionagi/protocols/graph/__init__.py +1 -2
  85. lionagi/protocols/graph/edge.py +1 -2
  86. lionagi/protocols/graph/graph.py +1 -2
  87. lionagi/protocols/graph/node.py +1 -2
  88. lionagi/protocols/ids.py +1 -2
  89. lionagi/protocols/mail/__init__.py +1 -2
  90. lionagi/protocols/mail/exchange.py +1 -2
  91. lionagi/protocols/mail/mail.py +1 -2
  92. lionagi/protocols/mail/mailbox.py +1 -2
  93. lionagi/protocols/mail/manager.py +1 -2
  94. lionagi/protocols/mail/package.py +1 -2
  95. lionagi/protocols/messages/__init__.py +28 -2
  96. lionagi/protocols/messages/action_request.py +87 -186
  97. lionagi/protocols/messages/action_response.py +74 -133
  98. lionagi/protocols/messages/assistant_response.py +131 -161
  99. lionagi/protocols/messages/base.py +27 -20
  100. lionagi/protocols/messages/instruction.py +281 -626
  101. lionagi/protocols/messages/manager.py +113 -64
  102. lionagi/protocols/messages/message.py +88 -199
  103. lionagi/protocols/messages/system.py +53 -125
  104. lionagi/protocols/operatives/__init__.py +1 -2
  105. lionagi/protocols/operatives/operative.py +1 -2
  106. lionagi/protocols/operatives/step.py +1 -2
  107. lionagi/protocols/types.py +1 -4
  108. lionagi/service/connections/__init__.py +1 -2
  109. lionagi/service/connections/api_calling.py +1 -2
  110. lionagi/service/connections/endpoint.py +1 -10
  111. lionagi/service/connections/endpoint_config.py +1 -2
  112. lionagi/service/connections/header_factory.py +1 -2
  113. lionagi/service/connections/match_endpoint.py +1 -2
  114. lionagi/service/connections/mcp/__init__.py +1 -2
  115. lionagi/service/connections/mcp/wrapper.py +1 -2
  116. lionagi/service/connections/providers/__init__.py +1 -2
  117. lionagi/service/connections/providers/anthropic_.py +1 -2
  118. lionagi/service/connections/providers/claude_code_cli.py +1 -2
  119. lionagi/service/connections/providers/exa_.py +1 -2
  120. lionagi/service/connections/providers/nvidia_nim_.py +2 -27
  121. lionagi/service/connections/providers/oai_.py +30 -96
  122. lionagi/service/connections/providers/ollama_.py +4 -4
  123. lionagi/service/connections/providers/perplexity_.py +1 -2
  124. lionagi/service/hooks/__init__.py +1 -1
  125. lionagi/service/hooks/_types.py +1 -1
  126. lionagi/service/hooks/_utils.py +1 -1
  127. lionagi/service/hooks/hook_event.py +1 -1
  128. lionagi/service/hooks/hook_registry.py +1 -1
  129. lionagi/service/hooks/hooked_event.py +3 -4
  130. lionagi/service/imodel.py +1 -2
  131. lionagi/service/manager.py +1 -2
  132. lionagi/service/rate_limited_processor.py +1 -2
  133. lionagi/service/resilience.py +1 -2
  134. lionagi/service/third_party/anthropic_models.py +1 -2
  135. lionagi/service/third_party/claude_code.py +4 -4
  136. lionagi/service/third_party/openai_models.py +433 -0
  137. lionagi/service/token_calculator.py +1 -2
  138. lionagi/session/__init__.py +1 -2
  139. lionagi/session/branch.py +171 -180
  140. lionagi/session/session.py +4 -11
  141. lionagi/tools/__init__.py +1 -2
  142. lionagi/tools/base.py +1 -2
  143. lionagi/tools/file/__init__.py +1 -2
  144. lionagi/tools/file/reader.py +3 -4
  145. lionagi/tools/types.py +1 -2
  146. lionagi/utils.py +1 -2
  147. lionagi/version.py +1 -1
  148. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
  149. lionagi-0.18.0.dist-info/RECORD +191 -0
  150. lionagi/operations/_act/__init__.py +0 -3
  151. lionagi/operations/_act/act.py +0 -87
  152. lionagi/protocols/messages/templates/README.md +0 -28
  153. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  154. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  155. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  156. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  157. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  158. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  159. lionagi/service/connections/providers/types.py +0 -28
  160. lionagi/service/third_party/openai_model_names.py +0 -198
  161. lionagi/service/types.py +0 -59
  162. lionagi-0.17.10.dist-info/RECORD +0 -199
  163. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
  164. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,23 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
- import logging
6
- from typing import TYPE_CHECKING
4
+ import warnings
5
+ from typing import TYPE_CHECKING, Any, Literal
7
6
 
7
+ from pydantic import JsonValue
8
+
9
+ from lionagi.ln.fuzzy import FuzzyMatchKeysParams
8
10
  from lionagi.ln.fuzzy._fuzzy_validate import fuzzy_validate_mapping
9
- from lionagi.utils import UNDEFINED
11
+ from lionagi.ln.types import Undefined
12
+
13
+ from ..types import ChatParam, ParseParam
10
14
 
11
15
  if TYPE_CHECKING:
16
+ from lionagi.protocols.messages.instruction import Instruction
12
17
  from lionagi.session.branch import Branch
13
18
 
14
19
 
15
- async def communicate(
20
+ def prepare_communicate_kw(
16
21
  branch: "Branch",
17
22
  instruction=None,
18
23
  *,
@@ -30,7 +35,7 @@ async def communicate(
30
35
  parse_model=None,
31
36
  skip_validation=False,
32
37
  images=None,
33
- image_detail="auto",
38
+ image_detail: Literal["low", "high", "auto"] = "auto",
34
39
  num_parse_retries=3,
35
40
  fuzzy_match_kwargs=None,
36
41
  clear_messages=False,
@@ -38,88 +43,141 @@ async def communicate(
38
43
  include_token_usage_to_model: bool = False,
39
44
  **kwargs,
40
45
  ):
46
+ # Handle deprecated parameters
41
47
  if operative_model:
42
- logging.warning(
43
- "operative_model is deprecated. Use response_format instead."
48
+ warnings.warn(
49
+ "Parameter 'operative_model' is deprecated. Use 'response_format' instead.",
50
+ DeprecationWarning,
51
+ stacklevel=2,
44
52
  )
53
+
45
54
  if (
46
55
  (operative_model and response_format)
47
56
  or (operative_model and request_model)
48
57
  or (response_format and request_model)
49
58
  ):
50
59
  raise ValueError(
51
- "Cannot specify both operative_model and response_format"
52
- "or operative_model and request_model as they are aliases"
60
+ "Cannot specify both operative_model and response_format "
61
+ "or operative_model and request_model as they are aliases "
53
62
  "for the same parameter."
54
63
  )
55
64
 
56
65
  response_format = response_format or operative_model or request_model
57
-
58
66
  imodel = imodel or chat_model or branch.chat_model
59
67
  parse_model = parse_model or branch.parse_model
60
68
 
61
- if clear_messages:
62
- branch.msgs.clear_messages()
63
-
64
69
  if num_parse_retries > 5:
65
- logging.warning(
66
- f"Are you sure you want to retry {num_parse_retries} "
67
- "times? lowering retry attempts to 5. Suggestion is under 3"
70
+ warnings.warn(
71
+ f"num_parse_retries={num_parse_retries} is high. Lowering to 5. Suggestion: <3",
72
+ UserWarning,
73
+ stacklevel=2,
68
74
  )
69
75
  num_parse_retries = 5
70
76
 
71
- ins, res = await branch.chat(
72
- instruction=instruction,
77
+ # Build contexts
78
+ chat_param = ChatParam(
73
79
  guidance=guidance,
74
80
  context=context,
75
- sender=sender,
76
- recipient=recipient,
81
+ sender=sender or branch.user or "user",
82
+ recipient=recipient or branch.id,
77
83
  response_format=response_format,
78
84
  progression=progression,
79
- imodel=imodel,
80
- images=images,
85
+ tool_schemas=[],
86
+ images=images or [],
81
87
  image_detail=image_detail,
82
- plain_content=plain_content,
83
- return_ins_res_message=True,
88
+ plain_content=plain_content or "",
84
89
  include_token_usage_to_model=include_token_usage_to_model,
85
- **kwargs,
90
+ imodel=imodel,
91
+ imodel_kw=kwargs,
92
+ )
93
+
94
+ parse_param = None
95
+ if response_format and not skip_validation:
96
+ from ..parse.parse import get_default_call
97
+
98
+ fuzzy_kw = fuzzy_match_kwargs or {}
99
+ handle_validation = fuzzy_kw.pop("handle_validation", "raise")
100
+
101
+ parse_param = ParseParam(
102
+ response_format=response_format,
103
+ fuzzy_match_params=(
104
+ FuzzyMatchKeysParams(**fuzzy_kw)
105
+ if fuzzy_kw
106
+ else FuzzyMatchKeysParams()
107
+ ),
108
+ handle_validation=handle_validation,
109
+ alcall_params=get_default_call().with_updates(
110
+ retry_attempts=num_parse_retries
111
+ ),
112
+ imodel=parse_model,
113
+ imodel_kw={},
114
+ )
115
+
116
+ return {
117
+ "instruction": instruction or "",
118
+ "chat_param": chat_param,
119
+ "parse_param": parse_param,
120
+ "clear_messages": clear_messages,
121
+ "skip_validation": skip_validation,
122
+ "request_fields": request_fields,
123
+ }
124
+
125
+
126
+ async def communicate(
127
+ branch: "Branch",
128
+ instruction: "JsonValue | Instruction",
129
+ chat_param: ChatParam,
130
+ parse_param: ParseParam | None = None,
131
+ clear_messages: bool = False,
132
+ skip_validation: bool = False,
133
+ request_fields: dict | None = None,
134
+ ) -> Any:
135
+ if clear_messages:
136
+ branch.msgs.clear_messages()
137
+
138
+ from ..chat.chat import chat
139
+
140
+ ins, res = await chat(
141
+ branch, instruction, chat_param, return_ins_res_message=True
86
142
  )
143
+
87
144
  branch.msgs.add_message(instruction=ins)
88
145
  branch.msgs.add_message(assistant_response=res)
89
146
 
90
147
  if skip_validation:
91
148
  return res.response
92
149
 
93
- if response_format is not None:
94
- # Default to raising errors unless explicitly set in fuzzy_match_kwargs
95
- parse_kwargs = {
96
- "handle_validation": "raise", # Default to raising errors
97
- **(fuzzy_match_kwargs or {}),
98
- }
150
+ # Handle response_format with parse
151
+ if parse_param and chat_param.response_format:
152
+ from lionagi.protocols.messages.assistant_response import (
153
+ AssistantResponse,
154
+ )
155
+
156
+ from ..parse.parse import parse
99
157
 
100
158
  try:
101
- return await branch.parse(
102
- text=res.response,
103
- request_type=response_format,
104
- max_retries=num_parse_retries,
105
- **parse_kwargs,
159
+ out, res2 = await parse(
160
+ branch, res.response, parse_param, return_res_message=True
106
161
  )
162
+ if res2 and isinstance(res2, AssistantResponse):
163
+ res.metadata["original_model_response"] = res.model_response
164
+ # model_response is read-only property - update metadata instead
165
+ res.metadata["model_response"] = res2.model_response
166
+ return out
107
167
  except ValueError as e:
108
168
  # Re-raise with more context
109
- logging.error(
110
- f"Failed to parse response '{res.response}' into {response_format}: {e}"
111
- )
112
169
  raise ValueError(
113
- f"Failed to parse model response into {response_format.__name__}: {e}"
170
+ f"Failed to parse model response into {chat_param.response_format}: {e}"
114
171
  ) from e
115
172
 
173
+ # Handle request_fields with fuzzy validation
116
174
  if request_fields is not None:
117
175
  _d = fuzzy_validate_mapping(
118
176
  res.response,
119
177
  request_fields,
120
178
  handle_unmatched="force",
121
- fill_value=UNDEFINED,
179
+ fill_value=Undefined,
122
180
  )
123
- return {k: v for k, v in _d.items() if v != UNDEFINED}
181
+ return {k: v for k, v in _d.items() if v != Undefined}
124
182
 
125
183
  return res.response
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  """
@@ -430,14 +429,14 @@ class DependencyAwareExecutor:
430
429
  if hasattr(branch, "_message_manager") and hasattr(
431
430
  primary_branch, "_message_manager"
432
431
  ):
433
- branch._message_manager.pile.clear()
434
- for msg in primary_branch._message_manager.pile:
432
+ branch._message_manager.messages.clear()
433
+ for msg in primary_branch._message_manager.messages:
435
434
  if hasattr(msg, "clone"):
436
- branch._message_manager.pile.append(
435
+ branch._message_manager.messages.append(
437
436
  msg.clone()
438
437
  )
439
438
  else:
440
- branch._message_manager.pile.append(msg)
439
+ branch._message_manager.messages.append(msg)
441
440
 
442
441
  # Clear the pending flag
443
442
  branch.metadata["pending_context_inheritance"] = False
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from typing import TYPE_CHECKING, Any
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,43 +1,87 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from typing import TYPE_CHECKING
6
5
 
6
+ from ..types import ChatParam, InterpretParam
7
+
7
8
  if TYPE_CHECKING:
9
+ from lionagi.service.imodel import iModel
8
10
  from lionagi.session.branch import Branch
9
11
 
10
12
 
11
- async def interpret(
13
+ def prepare_interpret_kw(
12
14
  branch: "Branch",
13
15
  text: str,
14
16
  domain: str | None = None,
15
17
  style: str | None = None,
16
18
  sample_writing: str | None = None,
17
- interpret_model: str | None = None,
19
+ interpret_model: "iModel | None" = None,
18
20
  **kwargs,
19
21
  ) -> str:
22
+ """Interpret and refine user input into clearer prompts."""
23
+
24
+ # Build InterpretParam
25
+ intp_param = InterpretParam(
26
+ domain=domain or "general",
27
+ style=style or "concise",
28
+ sample_writing=sample_writing or "",
29
+ imodel=interpret_model or branch.chat_model,
30
+ imodel_kw=kwargs,
31
+ )
32
+ return {
33
+ "text": text,
34
+ "intp_param": intp_param,
35
+ }
36
+
37
+
38
+ async def interpret(
39
+ branch: "Branch",
40
+ text: str,
41
+ intp_param: InterpretParam,
42
+ ) -> str:
43
+ """Execute interpretation with context - clean implementation."""
44
+
45
+ from ..chat.chat import chat
46
+
20
47
  instruction = (
21
- "You are given a user's raw instruction or question. Your task is to rewrite it into a clearer,"
48
+ "You are given a user's raw instruction or question. Your task is to rewrite it into a clearer, "
22
49
  "more structured prompt for an LLM or system, making any implicit or missing details explicit. "
23
50
  "Return only the re-written prompt. Do not assume any details not mentioned in the input, nor "
24
51
  "give additional instruction than what is explicitly stated."
25
52
  )
26
- guidance = f"Domain hint: {domain or 'general'}. Desired style: {style or 'concise'}. "
27
- if sample_writing:
28
- guidance += f" Sample writing: {sample_writing}"
29
-
30
- context = [f"User input: {text}"]
31
-
32
- # Default temperature if none provided
33
- kwargs["guidance"] = guidance + "\n" + kwargs.get("guidance", "")
34
- kwargs["instruction"] = instruction + "\n" + kwargs.get("instruction", "")
35
- kwargs["temperature"] = kwargs.get("temperature", 0.1)
36
- if interpret_model:
37
- kwargs["chat_model"] = interpret_model
38
-
39
- refined_prompt = await branch.chat(
40
- context=context,
41
- **kwargs,
53
+
54
+ guidance = (
55
+ f"Domain hint: {intp_param.domain}. Desired style: {intp_param.style}."
42
56
  )
43
- return str(refined_prompt)
57
+ if intp_param.sample_writing:
58
+ guidance += f" Sample writing: {intp_param.sample_writing}"
59
+
60
+ # Build ChatParam
61
+ chat_param = ChatParam(
62
+ guidance=guidance,
63
+ context=[f"User input: {text}"],
64
+ sender=branch.user or "user",
65
+ recipient=branch.id,
66
+ response_format=None,
67
+ progression=None,
68
+ tool_schemas=[],
69
+ images=[],
70
+ image_detail="auto",
71
+ plain_content="",
72
+ include_token_usage_to_model=False,
73
+ imodel=intp_param.imodel,
74
+ imodel_kw={
75
+ **intp_param.imodel_kw,
76
+ "temperature": intp_param.imodel_kw.get("temperature", 0.1),
77
+ },
78
+ )
79
+
80
+ result = await chat(
81
+ branch,
82
+ instruction=instruction,
83
+ chat_param=chat_param,
84
+ return_ins_res_message=False,
85
+ )
86
+
87
+ return str(result)
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0