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,208 +1,178 @@
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
 
4
+ from dataclasses import dataclass
5
5
  from typing import Any
6
6
 
7
- from pydantic import BaseModel
7
+ from pydantic import BaseModel, field_validator
8
8
 
9
- from lionagi.utils import copy
9
+ from .base import SenderRecipient
10
+ from .message import MessageContent, MessageRole, RoledMessage
10
11
 
11
- from .base import MessageRole, SenderRecipient
12
- from .message import MessageRole, RoledMessage, Template, jinja_env
13
12
 
13
+ def parse_assistant_response(
14
+ response: BaseModel | list[BaseModel] | dict | str | Any,
15
+ ) -> tuple[str, dict | list[dict]]:
16
+ """Parse various AI model response formats into text and raw data.
14
17
 
15
- def prepare_assistant_response(
16
- assistant_response: BaseModel | list[BaseModel] | dict | str | Any, /
17
- ) -> dict:
18
- assistant_response = (
19
- [assistant_response]
20
- if not isinstance(assistant_response, list)
21
- else assistant_response
22
- )
18
+ Supports:
19
+ - Anthropic format (content field)
20
+ - OpenAI chat completions (choices field)
21
+ - OpenAI responses API (output field)
22
+ - Claude Code (result field)
23
+ - Raw strings
24
+
25
+ Returns:
26
+ tuple: (extracted_text, raw_model_response)
27
+ """
28
+ responses = [response] if not isinstance(response, list) else response
23
29
 
24
30
  text_contents = []
25
31
  model_responses = []
26
32
 
27
- for i in assistant_response:
28
- if isinstance(i, BaseModel):
29
- i = i.model_dump(exclude_none=True, exclude_unset=True)
33
+ for item in responses:
34
+ if isinstance(item, BaseModel):
35
+ item = item.model_dump(exclude_none=True, exclude_unset=True)
30
36
 
31
- model_responses.append(i)
37
+ model_responses.append(item)
32
38
 
33
- if isinstance(i, dict):
34
- # anthropic standard
35
- if "content" in i:
36
- content = i["content"]
39
+ if isinstance(item, dict):
40
+ # Anthropic standard
41
+ if "content" in item:
42
+ content = item["content"]
37
43
  content = (
38
44
  [content] if not isinstance(content, list) else content
39
45
  )
40
- for j in content:
41
- if isinstance(j, dict):
42
- if j.get("type") == "text":
43
- text_contents.append(j["text"])
44
- elif isinstance(j, str):
45
- text_contents.append(j)
46
-
47
- # openai chat completions standard
48
- elif "choices" in i:
49
- choices = i["choices"]
46
+ for c in content:
47
+ if isinstance(c, dict) and c.get("type") == "text":
48
+ text_contents.append(c["text"])
49
+ elif isinstance(c, str):
50
+ text_contents.append(c)
51
+
52
+ # OpenAI chat completions standard
53
+ elif "choices" in item:
54
+ choices = item["choices"]
50
55
  choices = (
51
56
  [choices] if not isinstance(choices, list) else choices
52
57
  )
53
- for j in choices:
54
- if "message" in j:
55
- text_contents.append(j["message"]["content"] or "")
56
- elif "delta" in j:
57
- text_contents.append(j["delta"]["content"] or "")
58
-
59
- # openai responses API standard
60
- elif "output" in i:
61
- output = i["output"]
58
+ for choice in choices:
59
+ if "message" in choice:
60
+ text_contents.append(
61
+ choice["message"].get("content") or ""
62
+ )
63
+ elif "delta" in choice:
64
+ text_contents.append(
65
+ choice["delta"].get("content") or ""
66
+ )
67
+
68
+ # OpenAI responses API standard
69
+ elif "output" in item:
70
+ output = item["output"]
62
71
  output = [output] if not isinstance(output, list) else output
63
- for item in output:
64
- if isinstance(item, dict):
65
- if item.get("type") == "message":
66
- # Extract content from message
67
- content = item.get("content", [])
68
- if isinstance(content, list):
69
- for c in content:
70
- if (
71
- isinstance(c, dict)
72
- and c.get("type") == "output_text"
73
- ):
74
- text_contents.append(c.get("text", ""))
75
- elif isinstance(c, str):
76
- text_contents.append(c)
77
-
78
- # claude code standard
79
- elif "result" in i:
80
- text_contents.append(i["result"])
81
-
82
- elif isinstance(i, str):
83
- text_contents.append(i)
84
-
85
- text_contents = "".join(text_contents)
86
- model_responses = (
72
+ for out in output:
73
+ if isinstance(out, dict) and out.get("type") == "message":
74
+ content = out.get("content", [])
75
+ if isinstance(content, list):
76
+ for c in content:
77
+ if (
78
+ isinstance(c, dict)
79
+ and c.get("type") == "output_text"
80
+ ):
81
+ text_contents.append(c.get("text", ""))
82
+ elif isinstance(c, str):
83
+ text_contents.append(c)
84
+
85
+ # Claude Code standard
86
+ elif "result" in item:
87
+ text_contents.append(item["result"])
88
+
89
+ elif isinstance(item, str):
90
+ text_contents.append(item)
91
+
92
+ text = "".join(text_contents)
93
+ model_response = (
87
94
  model_responses[0] if len(model_responses) == 1 else model_responses
88
95
  )
89
- return {
90
- "assistant_response": text_contents,
91
- "model_response": model_responses,
92
- }
93
96
 
97
+ return text, model_response
94
98
 
95
- class AssistantResponse(RoledMessage):
99
+
100
+ @dataclass(slots=True)
101
+ class AssistantResponseContent(MessageContent):
102
+ """Content for assistant responses.
103
+
104
+ Fields:
105
+ assistant_response: Extracted text from the model
96
106
  """
97
- A message representing the AI assistant's reply, typically
98
- from a model or LLM call. If the raw model output is available,
99
- it's placed in `metadata["model_response"]`.
107
+
108
+ assistant_response: str = ""
109
+
110
+ @property
111
+ def rendered(self) -> str:
112
+ """Render assistant response as plain text."""
113
+ return self.assistant_response
114
+
115
+ @classmethod
116
+ def from_dict(cls, data: dict[str, Any]) -> "AssistantResponseContent":
117
+ """Construct AssistantResponseContent from dictionary."""
118
+ assistant_response = data.get("assistant_response", "")
119
+ return cls(assistant_response=assistant_response)
120
+
121
+
122
+ class AssistantResponse(RoledMessage):
123
+ """Message representing an AI assistant's reply.
124
+
125
+ The raw model output is stored in metadata["model_response"].
100
126
  """
101
127
 
102
- template: Template | str | None = jinja_env.get_template(
103
- "assistant_response.jinja2"
104
- )
128
+ role: MessageRole = MessageRole.ASSISTANT
129
+ content: AssistantResponseContent
130
+ recipient: SenderRecipient | None = MessageRole.USER
131
+
132
+ @field_validator("content", mode="before")
133
+ def _validate_content(cls, v):
134
+ if v is None:
135
+ return AssistantResponseContent()
136
+ if isinstance(v, dict):
137
+ return AssistantResponseContent.from_dict(v)
138
+ if isinstance(v, AssistantResponseContent):
139
+ return v
140
+ raise TypeError(
141
+ "content must be dict or AssistantResponseContent instance"
142
+ )
105
143
 
106
144
  @property
107
145
  def response(self) -> str:
108
- """Get or set the text portion of the assistant's response."""
109
- return copy(self.content["assistant_response"])
110
-
111
- @response.setter
112
- def response(self, value: str) -> None:
113
- self.content["assistant_response"] = value
146
+ """Access the text response from the assistant."""
147
+ return self.content.assistant_response
114
148
 
115
149
  @property
116
150
  def model_response(self) -> dict | list[dict]:
117
- """
118
- Access the underlying model's raw data, if available.
119
-
120
- Returns:
121
- dict or list[dict]: The stored model output data.
122
- """
123
- return copy(self.metadata.get("model_response", {}))
151
+ """Access the underlying model's raw data from metadata."""
152
+ return self.metadata.get("model_response", {})
124
153
 
125
154
  @classmethod
126
- def create(
155
+ def from_response(
127
156
  cls,
128
- assistant_response: BaseModel | list[BaseModel] | dict | str | Any,
157
+ response: BaseModel | list[BaseModel] | dict | str | Any,
129
158
  sender: SenderRecipient | None = None,
130
159
  recipient: SenderRecipient | None = None,
131
- template: Template | str | None = None,
132
- **kwargs,
133
160
  ) -> "AssistantResponse":
134
- """
135
- Build an AssistantResponse from arbitrary assistant data.
161
+ """Create AssistantResponse from raw model output.
136
162
 
137
163
  Args:
138
- assistant_response:
139
- A pydantic model, list, dict, or string representing
140
- an LLM or system response.
141
- sender (SenderRecipient | None):
142
- The ID or role denoting who sends this response.
143
- recipient (SenderRecipient | None):
144
- The ID or role to receive it.
145
- template (Template | str | None):
146
- Optional custom template.
147
- **kwargs:
148
- Additional content key-value pairs.
164
+ response: Raw model output in any supported format
165
+ sender: Message sender
166
+ recipient: Message recipient
149
167
 
150
168
  Returns:
151
- AssistantResponse: The constructed instance.
169
+ AssistantResponse with parsed content and metadata
152
170
  """
153
- content = prepare_assistant_response(assistant_response)
154
- model_response = content.pop("model_response", {})
155
- content.update(kwargs)
156
- params = {
157
- "content": content,
158
- "role": MessageRole.ASSISTANT,
159
- "recipient": recipient or MessageRole.USER,
160
- }
161
- if sender:
162
- params["sender"] = sender
163
- if template:
164
- params["template"] = template
165
- if model_response:
166
- params["metadata"] = {"model_response": model_response}
167
- return cls(**params)
168
-
169
- def update(
170
- self,
171
- assistant_response: (
172
- BaseModel | list[BaseModel] | dict | str | Any
173
- ) = None,
174
- sender: SenderRecipient | None = None,
175
- recipient: SenderRecipient | None = None,
176
- template: Template | str | None = None,
177
- **kwargs,
178
- ):
179
- """
180
- Update this AssistantResponse with new data or fields.
171
+ text, model_response = parse_assistant_response(response)
181
172
 
182
- Args:
183
- assistant_response:
184
- Additional or replaced assistant model output.
185
- sender (SenderRecipient | None):
186
- Updated sender.
187
- recipient (SenderRecipient | None):
188
- Updated recipient.
189
- template (Template | str | None):
190
- Optional new template.
191
- **kwargs:
192
- Additional content updates for `self.content`.
193
- """
194
- if assistant_response:
195
- content = prepare_assistant_response(assistant_response)
196
- self.content.update(content)
197
- super().update(
198
- sender=sender, recipient=recipient, template=template, **kwargs
173
+ return cls(
174
+ content=AssistantResponseContent(assistant_response=text),
175
+ sender=sender,
176
+ recipient=recipient or MessageRole.USER,
177
+ metadata={"model_response": model_response},
199
178
  )
200
-
201
- def as_context(self) -> dict:
202
- return f"""
203
- Response: {self.response or "Not available"}
204
- Summary: {self.model_response.get("summary") or "Not available"}
205
- """.strip()
206
-
207
-
208
- # File: lionagi/protocols/messages/assistant_response.py
@@ -1,18 +1,17 @@
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 enum import Enum
6
5
  from typing import Any, TypeAlias
7
6
 
8
- from ..generic.element import ID, IDError, IDType, Observable
7
+ from ..generic.element import ID, Element, IDError, IDType, Observable
9
8
 
10
9
  __all__ = (
11
10
  "MessageRole",
12
- "MessageFlag",
13
11
  "MessageField",
14
12
  "MESSAGE_FIELDS",
15
13
  "validate_sender_recipient",
14
+ "serialize_sender_recipient",
16
15
  )
17
16
 
18
17
 
@@ -28,15 +27,6 @@ class MessageRole(str, Enum):
28
27
  ACTION = "action"
29
28
 
30
29
 
31
- class MessageFlag(str, Enum):
32
- """
33
- Internal flags for certain message states, e.g., clones or loads.
34
- """
35
-
36
- MESSAGE_CLONE = "MESSAGE_CLONE"
37
- MESSAGE_LOAD = "MESSAGE_LOAD"
38
-
39
-
40
30
  SenderRecipient: TypeAlias = IDType | MessageRole | str
41
31
  """
42
32
  A union type indicating that a sender or recipient could be:
@@ -76,7 +66,7 @@ def validate_sender_recipient(value: Any, /) -> SenderRecipient:
76
66
  Raises:
77
67
  ValueError: If the input cannot be recognized as a role or ID.
78
68
  """
79
- if isinstance(value, MessageRole | MessageFlag):
69
+ if isinstance(value, MessageRole):
80
70
  return value
81
71
 
82
72
  if isinstance(value, IDType):
@@ -91,13 +81,30 @@ def validate_sender_recipient(value: Any, /) -> SenderRecipient:
91
81
  if value in ["system", "user", "unset", "assistant", "action"]:
92
82
  return MessageRole(value)
93
83
 
94
- if value in ["MESSAGE_CLONE", "MESSAGE_LOAD"]:
95
- return MessageFlag(value)
84
+ # Accept plain strings (user names, identifiers, etc)
85
+ if isinstance(value, str):
86
+ # Try to parse as ID first, but allow plain strings as fallback
87
+ try:
88
+ return ID.get_id(value)
89
+ except IDError:
90
+ return value
96
91
 
97
- try:
98
- return ID.get_id(value)
99
- except IDError as e:
100
- raise ValueError("Invalid sender or recipient") from e
92
+ raise ValueError("Invalid sender or recipient")
93
+
94
+
95
+ def serialize_sender_recipient(value: Any) -> str | None:
96
+ if not value:
97
+ return None
98
+ # Check instance types first before enum membership
99
+ if isinstance(value, Element):
100
+ return str(value.id)
101
+ if isinstance(value, IDType):
102
+ return str(value)
103
+ if isinstance(value, MessageRole):
104
+ return value.value
105
+ if isinstance(value, str):
106
+ return value
107
+ return str(value)
101
108
 
102
109
 
103
110
  # File: lionagi/protocols/messages/base.py