lionagi 0.17.11__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 (52) hide show
  1. lionagi/libs/schema/minimal_yaml.py +98 -0
  2. lionagi/ln/types.py +32 -5
  3. lionagi/models/field_model.py +9 -0
  4. lionagi/operations/ReAct/ReAct.py +474 -237
  5. lionagi/operations/ReAct/utils.py +3 -0
  6. lionagi/operations/act/act.py +206 -0
  7. lionagi/operations/chat/chat.py +130 -114
  8. lionagi/operations/communicate/communicate.py +101 -42
  9. lionagi/operations/flow.py +4 -4
  10. lionagi/operations/interpret/interpret.py +65 -20
  11. lionagi/operations/operate/operate.py +212 -106
  12. lionagi/operations/parse/parse.py +170 -142
  13. lionagi/operations/select/select.py +78 -17
  14. lionagi/operations/select/utils.py +1 -1
  15. lionagi/operations/types.py +119 -23
  16. lionagi/protocols/generic/log.py +3 -2
  17. lionagi/protocols/messages/__init__.py +27 -0
  18. lionagi/protocols/messages/action_request.py +86 -184
  19. lionagi/protocols/messages/action_response.py +73 -131
  20. lionagi/protocols/messages/assistant_response.py +130 -159
  21. lionagi/protocols/messages/base.py +26 -18
  22. lionagi/protocols/messages/instruction.py +281 -625
  23. lionagi/protocols/messages/manager.py +112 -62
  24. lionagi/protocols/messages/message.py +87 -197
  25. lionagi/protocols/messages/system.py +52 -123
  26. lionagi/protocols/types.py +0 -2
  27. lionagi/service/connections/endpoint.py +0 -8
  28. lionagi/service/connections/providers/oai_.py +29 -94
  29. lionagi/service/connections/providers/ollama_.py +3 -2
  30. lionagi/service/hooks/hooked_event.py +2 -2
  31. lionagi/service/third_party/claude_code.py +3 -2
  32. lionagi/service/third_party/openai_models.py +433 -0
  33. lionagi/session/branch.py +170 -178
  34. lionagi/session/session.py +3 -9
  35. lionagi/tools/file/reader.py +2 -2
  36. lionagi/version.py +1 -1
  37. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
  38. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/RECORD +41 -49
  39. lionagi/operations/_act/act.py +0 -86
  40. lionagi/protocols/messages/templates/README.md +0 -28
  41. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  42. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  43. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  44. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  45. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  46. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  47. lionagi/service/connections/providers/types.py +0 -28
  48. lionagi/service/third_party/openai_model_names.py +0 -198
  49. lionagi/service/types.py +0 -58
  50. /lionagi/operations/{_act → act}/__init__.py +0 -0
  51. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
  52. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,144 +1,73 @@
1
1
  # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ from dataclasses import dataclass
4
5
  from datetime import datetime
5
- from typing import Any, NoReturn
6
+ from typing import Any
6
7
 
7
- from pydantic import JsonValue
8
- from typing_extensions import Self, override
8
+ from pydantic import Field, field_validator
9
9
 
10
10
  from .base import SenderRecipient
11
- from .message import MessageRole, RoledMessage, Template, jinja_env
11
+ from .message import MessageContent, MessageRole, RoledMessage
12
12
 
13
- __all__ = ("System",)
14
13
 
14
+ @dataclass(slots=True)
15
+ class SystemContent(MessageContent):
16
+ """Content for system messages.
15
17
 
16
- def format_system_content(
17
- system_datetime: bool | str | None,
18
- system_message: str,
19
- ) -> dict:
18
+ Fields:
19
+ system_message: Main system instruction text
20
+ system_datetime: Optional datetime string
20
21
  """
21
- Insert optional datetime string into the system message content.
22
22
 
23
- Args:
24
- system_datetime (bool|str|None):
25
- If True, embed current time. If str, use as time. If None, omit.
26
- system_message (str):
27
- The main system message text.
28
-
29
- Returns:
30
- dict: The combined system content.
31
- """
32
- content: dict = {"system_message": system_message}
33
- if system_datetime:
34
- if isinstance(system_datetime, str):
35
- content["system_datetime"] = system_datetime
36
- else:
37
- content["system_datetime"] = datetime.now().isoformat(
38
- timespec="minutes"
39
- )
40
- return content
41
-
42
-
43
- class System(RoledMessage):
44
- """
45
- A specialized message that sets a *system-level* context or policy.
46
- Usually the first in a conversation, instructing the AI about general
47
- constraints or identity.
48
- """
49
-
50
- template: str | Template | None = jinja_env.get_template(
51
- "system_message.jinja2"
23
+ system_message: str = (
24
+ "You are a helpful AI assistant. Let's think step by step."
52
25
  )
26
+ system_datetime: str | None = None
53
27
 
54
- @override
55
- @classmethod
56
- def create(
57
- cls,
58
- system_message="You are a helpful AI assistant. Let's think step by step.",
59
- system_datetime=None,
60
- sender: SenderRecipient = None,
61
- recipient: SenderRecipient = None,
62
- template=None,
63
- system: Any = None,
64
- **kwargs,
65
- ) -> Self:
66
- """
67
- Construct a system message with optional datetime annotation.
68
-
69
- Args:
70
- system_message (str):
71
- The main text instructing the AI about behavior/identity.
72
- system_datetime (bool|str, optional):
73
- If True or str, embed a time reference. If str, it is used directly.
74
- sender (SenderRecipient, optional):
75
- Typically `MessageRole.SYSTEM`.
76
- recipient (SenderRecipient, optional):
77
- Typically `MessageRole.ASSISTANT`.
78
- template (Template|str|None):
79
- An optional custom template for rendering.
80
- system (Any):
81
- Alias for `system_message` (deprecated).
82
- **kwargs:
83
- Additional content merged into the final dict.
28
+ @property
29
+ def rendered(self) -> str:
30
+ """Render system message with optional datetime."""
31
+ parts = []
32
+ if self.system_datetime:
33
+ parts.append(f"System Time: {self.system_datetime}")
34
+ parts.append(self.system_message)
35
+ return "\n\n".join(parts)
84
36
 
85
- Returns:
86
- System: A newly created system-level message.
87
- """
88
- if system and system_message:
89
- raise ValueError(
90
- "Cannot provide both system and system_message arguments."
91
- "as they are alias, and `system` is deprecated"
92
- )
93
- system_message = system_message or system
94
-
95
- content = format_system_content(
96
- system_datetime=system_datetime, system_message=system_message
37
+ @classmethod
38
+ def from_dict(cls, data: dict[str, Any]) -> "SystemContent":
39
+ """Construct SystemContent from dictionary."""
40
+ system_message = data.get(
41
+ "system_message",
42
+ cls.__dataclass_fields__["system_message"].default,
97
43
  )
98
- content.update(kwargs)
99
- params = {
100
- "role": MessageRole.SYSTEM,
101
- "content": content,
102
- "sender": sender or MessageRole.SYSTEM,
103
- "recipient": recipient or MessageRole.ASSISTANT,
104
- }
105
- if template:
106
- params["template"] = template
107
- return cls(**params)
44
+ system_datetime = data.get("system_datetime")
108
45
 
109
- def update(
110
- self,
111
- system_message: JsonValue = None,
112
- sender: SenderRecipient = None,
113
- recipient: SenderRecipient = None,
114
- system_datetime: bool | str = None,
115
- template: Template | str | None = None,
116
- **kwargs,
117
- ) -> NoReturn:
118
- """
119
- Adjust fields of this system message.
46
+ # Handle datetime generation
47
+ if system_datetime is True:
48
+ system_datetime = datetime.now().isoformat(timespec="minutes")
49
+ elif system_datetime is False or system_datetime is None:
50
+ system_datetime = None
120
51
 
121
- Args:
122
- system_message (JsonValue):
123
- New system message text.
124
- sender (SenderRecipient):
125
- Updated sender or role.
126
- recipient (SenderRecipient):
127
- Updated recipient or role.
128
- system_datetime (bool|str):
129
- If set, embed new datetime info.
130
- template (Template|str|None):
131
- New template override.
132
- **kwargs:
133
- Additional fields for self.content.
134
- """
135
- if any([system_message, system_message]):
136
- self.content = format_system_content(
137
- system_datetime=system_datetime, system_message=system_message
138
- )
139
- super().update(
140
- sender=sender, recipient=recipient, template=template, **kwargs
52
+ return cls(
53
+ system_message=system_message, system_datetime=system_datetime
141
54
  )
142
55
 
143
56
 
144
- # File: lionagi/protocols/messages/system.py
57
+ class System(RoledMessage):
58
+ """System-level message setting context or policy for the conversation."""
59
+
60
+ role: MessageRole = MessageRole.SYSTEM
61
+ content: SystemContent = Field(default_factory=SystemContent)
62
+ sender: SenderRecipient | None = MessageRole.SYSTEM
63
+ recipient: SenderRecipient | None = MessageRole.ASSISTANT
64
+
65
+ @field_validator("content", mode="before")
66
+ def _validate_content(cls, v):
67
+ if v is None:
68
+ return SystemContent()
69
+ if isinstance(v, dict):
70
+ return SystemContent.from_dict(v)
71
+ if isinstance(v, SystemContent):
72
+ return v
73
+ raise TypeError("content must be dict or SystemContent instance")
@@ -25,7 +25,6 @@ from .mail.manager import MailManager
25
25
  from .messages.base import (
26
26
  MESSAGE_FIELDS,
27
27
  MessageField,
28
- MessageFlag,
29
28
  MessageRole,
30
29
  validate_sender_recipient,
31
30
  )
@@ -82,7 +81,6 @@ __all__ = (
82
81
  "PackageCategory",
83
82
  "MESSAGE_FIELDS",
84
83
  "MessageField",
85
- "MessageFlag",
86
84
  "MessageRole",
87
85
  "validate_sender_recipient",
88
86
  "ActionRequest",
@@ -122,10 +122,7 @@ class Endpoint:
122
122
  # Validate the filtered payload
123
123
  payload = self.config.validate_payload(filtered_payload)
124
124
  else:
125
- # If no request_options, we still need to remove obvious non-API params
126
- # These are parameters that are never part of any API payload
127
125
  non_api_params = {
128
- "task",
129
126
  "provider",
130
127
  "base_url",
131
128
  "endpoint",
@@ -133,13 +130,9 @@ class Endpoint:
133
130
  "api_key",
134
131
  "queue_capacity",
135
132
  "capacity_refresh_time",
136
- "interval",
137
- "limit_requests",
138
- "limit_tokens",
139
133
  "invoke_with_endpoint",
140
134
  "extra_headers",
141
135
  "headers",
142
- "cache_control",
143
136
  "include_token_usage_to_model",
144
137
  "chat_model",
145
138
  "imodel",
@@ -148,7 +141,6 @@ class Endpoint:
148
141
  "aggregation_count",
149
142
  "action_strategy",
150
143
  "parse_model",
151
- "reason",
152
144
  "actions",
153
145
  "return_operative",
154
146
  "operative_model",
@@ -18,45 +18,21 @@ from pydantic import BaseModel
18
18
  from lionagi.config import settings
19
19
  from lionagi.service.connections.endpoint import Endpoint
20
20
  from lionagi.service.connections.endpoint_config import EndpointConfig
21
- from lionagi.service.third_party.openai_model_names import (
22
- REASONING_MODELS,
23
- is_reasoning_model,
24
- )
25
-
26
- __all__ = (
27
- "OpenaiChatEndpoint",
28
- "OpenaiResponseEndpoint",
29
- "OpenaiEmbedEndpoint",
30
- "OpenrouterChatEndpoint",
31
- "GroqChatEndpoint",
32
- "OPENAI_CHAT_ENDPOINT_CONFIG",
33
- "OPENAI_RESPONSE_ENDPOINT_CONFIG",
34
- "OPENAI_EMBEDDING_ENDPOINT_CONFIG",
35
- "OPENROUTER_CHAT_ENDPOINT_CONFIG",
36
- "OPENROUTER_GEMINI_ENDPOINT_CONFIG",
37
- "GROQ_CHAT_ENDPOINT_CONFIG",
38
- "REASONING_MODELS",
39
- "REASONING_NOT_SUPPORT_PARAMS",
21
+ from lionagi.service.third_party.openai_models import (
22
+ OpenAIChatCompletionsRequest,
40
23
  )
41
24
 
42
25
 
43
- def _get_openai_config(**kwargs):
44
- """Create OpenAI endpoint configuration with defaults."""
45
- config = dict(
46
- name="openai_chat",
47
- provider="openai",
48
- base_url="https://api.openai.com/v1",
49
- endpoint="chat/completions",
50
- kwargs={"model": settings.OPENAI_DEFAULT_MODEL},
51
- api_key=settings.OPENAI_API_KEY or "dummy-key-for-testing",
52
- auth_type="bearer",
53
- content_type="application/json",
54
- method="POST",
55
- requires_tokens=True,
56
- # NOTE: OpenAI models have incorrect role literals, only use for param validation
57
- # request_options=CreateChatCompletionRequest,
58
- )
59
- config.update(kwargs)
26
+ def _get_oai_config(**kw):
27
+ config = {
28
+ "provider": "openai",
29
+ "base_url": "https://api.openai.com/v1",
30
+ "auth_type": "bearer",
31
+ "content_type": "application/json",
32
+ "method": "POST",
33
+ "api_key": settings.OPENAI_API_KEY or "dummy-key-for-testing",
34
+ }
35
+ config.update(kw)
60
36
  return EndpointConfig(**config)
61
37
 
62
38
 
@@ -72,8 +48,7 @@ def _get_openrouter_config(**kwargs):
72
48
  auth_type="bearer",
73
49
  content_type="application/json",
74
50
  method="POST",
75
- # NOTE: OpenRouter uses OpenAI-compatible format
76
- # request_options=CreateChatCompletionRequest,
51
+ request_options=OpenAIChatCompletionsRequest,
77
52
  )
78
53
  config.update(kwargs)
79
54
  return EndpointConfig(**config)
@@ -91,48 +66,21 @@ def _get_groq_config(**kwargs):
91
66
  auth_type="bearer",
92
67
  content_type="application/json",
93
68
  method="POST",
69
+ request_options=OpenAIChatCompletionsRequest,
94
70
  )
95
71
  config.update(kwargs)
96
72
  return EndpointConfig(**config)
97
73
 
98
74
 
99
- # OpenAI endpoints
100
- OPENAI_CHAT_ENDPOINT_CONFIG = _get_openai_config()
101
-
102
- OPENAI_RESPONSE_ENDPOINT_CONFIG = _get_openai_config(
103
- name="openai_response",
104
- endpoint="responses",
105
- )
106
-
107
- OPENAI_EMBEDDING_ENDPOINT_CONFIG = _get_openai_config(
108
- name="openai_embed",
109
- endpoint="embeddings",
110
- kwargs={"model": "text-embedding-3-small"},
111
- )
112
-
113
- # OpenRouter endpoints
114
- OPENROUTER_CHAT_ENDPOINT_CONFIG = _get_openrouter_config()
115
-
116
- OPENROUTER_GEMINI_ENDPOINT_CONFIG = _get_openrouter_config(
117
- name="openrouter_gemini",
118
- kwargs={"model": "google/gemini-2.5-flash"},
119
- )
120
-
121
- # Groq endpoints
122
- GROQ_CHAT_ENDPOINT_CONFIG = _get_groq_config()
123
-
124
- REASONING_NOT_SUPPORT_PARAMS = (
125
- "temperature",
126
- "top_p",
127
- "logit_bias",
128
- "logprobs",
129
- "top_logprobs",
130
- )
131
-
132
-
133
75
  class OpenaiChatEndpoint(Endpoint):
134
76
  def __init__(self, config=None, **kwargs):
135
- config = config or _get_openai_config()
77
+ config = config or _get_oai_config(
78
+ name="oai_chat",
79
+ endpoint="chat/completions",
80
+ request_options=OpenAIChatCompletionsRequest,
81
+ kwargs={"model": settings.OPENAI_DEFAULT_MODEL},
82
+ requires_tokens=True,
83
+ )
136
84
  super().__init__(config, **kwargs)
137
85
 
138
86
  def create_payload(
@@ -145,34 +93,20 @@ class OpenaiChatEndpoint(Endpoint):
145
93
  payload, headers = super().create_payload(
146
94
  request, extra_headers, **kwargs
147
95
  )
148
-
149
- # Handle reasoning models
150
- model = payload.get("model")
151
- if (
152
- model
153
- and is_reasoning_model(model)
154
- and not model.startswith("gpt-5")
155
- ):
156
- # Remove unsupported parameters for reasoning models
157
- for param in REASONING_NOT_SUPPORT_PARAMS:
158
- payload.pop(param, None)
159
-
160
- # Convert system role to developer role for reasoning models
161
- if "messages" in payload and payload["messages"]:
162
- if payload["messages"][0].get("role") == "system":
163
- payload["messages"][0]["role"] = "developer"
164
- else:
165
- # Remove reasoning_effort for non-reasoning models
166
- payload.pop("reasoning_effort", None)
96
+ # Convert system role to developer role for reasoning models
97
+ if "messages" in payload and payload["messages"]:
98
+ if payload["messages"][0].get("role") == "system":
99
+ payload["messages"][0]["role"] = "developer"
167
100
 
168
101
  return (payload, headers)
169
102
 
170
103
 
171
104
  class OpenaiResponseEndpoint(Endpoint):
172
105
  def __init__(self, config=None, **kwargs):
173
- config = config or _get_openai_config(
106
+ config = config or _get_oai_config(
174
107
  name="openai_response",
175
108
  endpoint="responses",
109
+ requires_tokens=True,
176
110
  )
177
111
  super().__init__(config, **kwargs)
178
112
 
@@ -191,9 +125,10 @@ class GroqChatEndpoint(Endpoint):
191
125
 
192
126
  class OpenaiEmbedEndpoint(Endpoint):
193
127
  def __init__(self, config=None, **kwargs):
194
- config = config or _get_openai_config(
128
+ config = config or _get_oai_config(
195
129
  name="openai_embed",
196
130
  endpoint="embeddings",
197
131
  kwargs={"model": "text-embedding-3-small"},
132
+ requires_tokens=True,
198
133
  )
199
134
  super().__init__(config, **kwargs)
@@ -24,6 +24,8 @@ _HAS_OLLAMA = is_import_installed("ollama")
24
24
 
25
25
  def _get_ollama_config(**kwargs):
26
26
  """Create Ollama endpoint configuration with defaults."""
27
+ from ...third_party.openai_models import OpenAIChatCompletionsRequest
28
+
27
29
  config = dict(
28
30
  name="ollama_chat",
29
31
  provider="ollama",
@@ -36,8 +38,7 @@ def _get_ollama_config(**kwargs):
36
38
  content_type="application/json",
37
39
  auth_type="none", # No authentication
38
40
  default_headers={}, # No auth headers needed
39
- # NOTE: Not using request_options due to OpenAI model role literal issues
40
- # request_options=CreateChatCompletionRequest,
41
+ request_options=OpenAIChatCompletionsRequest,
41
42
  )
42
43
  config.update(kwargs)
43
44
  return EndpointConfig(**config)
@@ -47,7 +47,7 @@ class HookedEvent(Event):
47
47
  raise h_ev._exit_cause or RuntimeError(
48
48
  "Pre-invocation hook requested exit without a cause"
49
49
  )
50
- await global_hook_logger.alog(Log.create(h_ev))
50
+ await global_hook_logger.alog(h_ev)
51
51
 
52
52
  response = await self._invoke()
53
53
 
@@ -57,7 +57,7 @@ class HookedEvent(Event):
57
57
  raise h_ev._exit_cause or RuntimeError(
58
58
  "Post-invocation hook requested exit without a cause"
59
59
  )
60
- await global_hook_logger.alog(Log.create(h_ev))
60
+ await global_hook_logger.alog(h_ev)
61
61
 
62
62
  self.execution.response = response
63
63
  self.execution.status = EventStatus.COMPLETED
@@ -18,12 +18,11 @@ from pathlib import Path
18
18
  from textwrap import shorten
19
19
  from typing import Any, Literal
20
20
 
21
- from json_repair import repair_json
22
21
  from pydantic import BaseModel, Field, field_validator, model_validator
23
22
 
24
23
  from lionagi import ln
25
24
  from lionagi.libs.schema.as_readable import as_readable
26
- from lionagi.utils import is_coro_func, is_import_installed
25
+ from lionagi.utils import is_coro_func
27
26
 
28
27
  HAS_CLAUDE_CODE_CLI = False
29
28
  CLAUDE_CLI = None
@@ -415,6 +414,8 @@ async def _ndjson_from_cli(request: ClaudeCodeRequest):
415
414
  • Robust against braces inside strings (uses json.JSONDecoder.raw_decode)
416
415
  • Falls back to `json_repair.repair_json` when necessary.
417
416
  """
417
+ from json_repair import repair_json
418
+
418
419
  workspace = request.cwd()
419
420
  workspace.mkdir(parents=True, exist_ok=True)
420
421