lionagi 0.14.8__py3-none-any.whl → 0.14.9__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.
- lionagi/_errors.py +120 -11
- lionagi/_types.py +0 -6
- lionagi/config.py +3 -1
- lionagi/models/operable_model.py +8 -3
- lionagi/operations/flow.py +0 -1
- lionagi/protocols/generic/event.py +2 -0
- lionagi/protocols/generic/log.py +26 -10
- lionagi/protocols/operatives/step.py +1 -1
- lionagi/protocols/types.py +9 -1
- lionagi/service/__init__.py +22 -1
- lionagi/service/connections/api_calling.py +57 -2
- lionagi/service/connections/endpoint_config.py +1 -1
- lionagi/service/connections/header_factory.py +4 -2
- lionagi/service/connections/match_endpoint.py +10 -10
- lionagi/service/connections/providers/anthropic_.py +5 -2
- lionagi/service/connections/providers/claude_code_.py +13 -17
- lionagi/service/connections/providers/claude_code_cli.py +51 -16
- lionagi/service/connections/providers/exa_.py +5 -3
- lionagi/service/connections/providers/oai_.py +116 -81
- lionagi/service/connections/providers/ollama_.py +38 -18
- lionagi/service/connections/providers/perplexity_.py +36 -14
- lionagi/service/connections/providers/types.py +30 -0
- lionagi/service/hooks/__init__.py +25 -0
- lionagi/service/hooks/_types.py +52 -0
- lionagi/service/hooks/_utils.py +85 -0
- lionagi/service/hooks/hook_event.py +67 -0
- lionagi/service/hooks/hook_registry.py +221 -0
- lionagi/service/imodel.py +120 -34
- lionagi/service/third_party/claude_code.py +715 -0
- lionagi/service/third_party/openai_model_names.py +198 -0
- lionagi/service/third_party/pplx_models.py +16 -8
- lionagi/service/types.py +21 -0
- lionagi/session/branch.py +1 -4
- lionagi/tools/base.py +1 -3
- lionagi/utils.py +8 -2
- lionagi/version.py +1 -1
- {lionagi-0.14.8.dist-info → lionagi-0.14.9.dist-info}/METADATA +2 -2
- {lionagi-0.14.8.dist-info → lionagi-0.14.9.dist-info}/RECORD +40 -36
- lionagi/service/connections/providers/_claude_code/__init__.py +0 -3
- lionagi/service/connections/providers/_claude_code/models.py +0 -244
- lionagi/service/connections/providers/_claude_code/stream_cli.py +0 -359
- lionagi/service/third_party/openai_models.py +0 -18241
- {lionagi-0.14.8.dist-info → lionagi-0.14.9.dist-info}/WHEEL +0 -0
- {lionagi-0.14.8.dist-info → lionagi-0.14.9.dist-info}/licenses/LICENSE +0 -0
lionagi/_errors.py
CHANGED
@@ -2,28 +2,137 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from typing import Any, ClassVar
|
8
|
+
|
9
|
+
__all__ = (
|
10
|
+
"LionError",
|
11
|
+
"ValidationError",
|
12
|
+
"NotFoundError",
|
13
|
+
"ExistsError",
|
14
|
+
"ObservationError",
|
15
|
+
"ResourceError",
|
16
|
+
"RateLimitError",
|
17
|
+
"IDError",
|
18
|
+
"RelationError",
|
19
|
+
"OperationError",
|
20
|
+
"ExecutionError",
|
21
|
+
"ItemNotFoundError",
|
22
|
+
"ItemExistsError",
|
23
|
+
)
|
24
|
+
|
5
25
|
|
6
26
|
class LionError(Exception):
|
7
|
-
|
27
|
+
default_message: ClassVar[str] = "LionAGI error"
|
28
|
+
default_status_code: ClassVar[int] = 500
|
29
|
+
__slots__ = ("message", "details", "status_code")
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
message: str | None = None,
|
34
|
+
*,
|
35
|
+
details: dict[str, Any] | None = None,
|
36
|
+
status_code: int | None = None,
|
37
|
+
cause: Exception | None = None,
|
38
|
+
):
|
39
|
+
super().__init__(message or self.default_message)
|
40
|
+
if cause:
|
41
|
+
self.__cause__ = cause # preserves traceback
|
42
|
+
self.message = message or self.default_message
|
43
|
+
self.details = details or {}
|
44
|
+
self.status_code = status_code or type(self).default_status_code
|
45
|
+
|
46
|
+
def to_dict(self, *, include_cause: bool = False) -> dict[str, Any]:
|
47
|
+
data = {
|
48
|
+
"error": self.__class__.__name__,
|
49
|
+
"message": self.message,
|
50
|
+
"status_code": self.status_code,
|
51
|
+
**({"details": self.details} if self.details else {}),
|
52
|
+
}
|
53
|
+
if include_cause and (cause := self.get_cause()):
|
54
|
+
data["cause"] = repr(cause)
|
55
|
+
return data
|
56
|
+
|
57
|
+
def get_cause(self) -> Exception | None:
|
58
|
+
"""Get the cause of this error, if any."""
|
59
|
+
return self.__cause__ if hasattr(self, "__cause__") else None
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def from_value(
|
63
|
+
cls,
|
64
|
+
value: Any,
|
65
|
+
*,
|
66
|
+
expected: str | None = None,
|
67
|
+
message: str | None = None,
|
68
|
+
cause: Exception | None = None,
|
69
|
+
**extra: Any,
|
70
|
+
):
|
71
|
+
"""Create a ValidationError from a value with optional expected type and message."""
|
72
|
+
details = {
|
73
|
+
"value": value,
|
74
|
+
"type": type(value).__name__,
|
75
|
+
**({"expected": expected} if expected else {}),
|
76
|
+
**extra,
|
77
|
+
}
|
78
|
+
return cls(message=message, details=details, cause=cause)
|
8
79
|
|
9
80
|
|
10
|
-
class
|
11
|
-
|
81
|
+
class ValidationError(LionError):
|
82
|
+
"""Exception raised when validation fails."""
|
12
83
|
|
84
|
+
default_message = "Validation failed"
|
85
|
+
default_status_code = 422
|
86
|
+
__slots__ = ()
|
13
87
|
|
14
|
-
class ItemExistsError(LionError):
|
15
|
-
pass
|
16
88
|
|
89
|
+
class NotFoundError(LionError):
|
90
|
+
"""Exception raised when an item is not found."""
|
17
91
|
|
18
|
-
|
19
|
-
|
92
|
+
default_message = "Item not found"
|
93
|
+
default_status_code = 404
|
94
|
+
__slots__ = ()
|
20
95
|
|
21
96
|
|
22
|
-
class
|
23
|
-
|
97
|
+
class ExistsError(LionError):
|
98
|
+
"""Exception raised when an item already exists."""
|
99
|
+
|
100
|
+
default_message = "Item already exists"
|
101
|
+
default_status_code = 409
|
102
|
+
__slots__ = ()
|
103
|
+
|
104
|
+
|
105
|
+
class ObservationError(LionError):
|
106
|
+
"""Exception raised when an observation fails."""
|
107
|
+
|
108
|
+
default_message = "Observation failed"
|
109
|
+
default_status_code = 500
|
110
|
+
__slots__ = ()
|
111
|
+
|
112
|
+
|
113
|
+
class ResourceError(LionError):
|
114
|
+
"""Exception raised when resource access fails."""
|
115
|
+
|
116
|
+
default_message = "Resource error"
|
117
|
+
default_status_code = 429
|
118
|
+
__slots__ = ()
|
24
119
|
|
25
120
|
|
26
121
|
class RateLimitError(LionError):
|
122
|
+
__slots__ = ("retry_after",) # one extra attr
|
123
|
+
default_message = "Rate limit exceeded"
|
124
|
+
default_status_code = 429
|
125
|
+
|
126
|
+
def __init__(self, retry_after: float, **kw):
|
127
|
+
super().__init__(**kw)
|
128
|
+
object.__setattr__(self, "retry_after", retry_after)
|
129
|
+
|
130
|
+
|
131
|
+
class IDError(LionError):
|
132
|
+
pass
|
133
|
+
|
134
|
+
|
135
|
+
class RelationError(LionError):
|
27
136
|
pass
|
28
137
|
|
29
138
|
|
@@ -35,5 +144,5 @@ class ExecutionError(LionError):
|
|
35
144
|
pass
|
36
145
|
|
37
146
|
|
38
|
-
|
39
|
-
|
147
|
+
ItemNotFoundError = NotFoundError
|
148
|
+
ItemExistsError = ExistsError
|
lionagi/_types.py
CHANGED
lionagi/config.py
CHANGED
@@ -63,12 +63,14 @@ class AppSettings(BaseSettings, frozen=True):
|
|
63
63
|
GROQ_API_KEY: SecretStr | None = None
|
64
64
|
ANTHROPIC_API_KEY: SecretStr | None = None
|
65
65
|
|
66
|
+
OPENAI_DEFAULT_MODEL: str = "gpt-4.1-mini"
|
67
|
+
|
66
68
|
# defaults models
|
67
69
|
LIONAGI_EMBEDDING_PROVIDER: str = "openai"
|
68
70
|
LIONAGI_EMBEDDING_MODEL: str = "text-embedding-3-small"
|
69
71
|
|
70
72
|
LIONAGI_CHAT_PROVIDER: str = "openai"
|
71
|
-
LIONAGI_CHAT_MODEL: str = "gpt-4.1-
|
73
|
+
LIONAGI_CHAT_MODEL: str = "gpt-4.1-mini"
|
72
74
|
|
73
75
|
# default storage
|
74
76
|
LIONAGI_AUTO_STORE_EVENT: bool = False
|
lionagi/models/operable_model.py
CHANGED
@@ -263,7 +263,7 @@ class OperableModel(HashableModel):
|
|
263
263
|
Dictionary mapping field names to FieldInfo objects,
|
264
264
|
excluding the extra_fields field itself
|
265
265
|
"""
|
266
|
-
a = {**self.model_fields, **self.extra_fields}
|
266
|
+
a = {**type(self).model_fields, **self.extra_fields}
|
267
267
|
a.pop("extra_fields", None)
|
268
268
|
a.pop("extra_field_models", None) # Exclude internal field tracking
|
269
269
|
return a
|
@@ -358,7 +358,12 @@ class OperableModel(HashableModel):
|
|
358
358
|
for k, v in kwargs.items():
|
359
359
|
self.field_setattr(field_name, k, v)
|
360
360
|
else:
|
361
|
-
|
361
|
+
_kwargs = {
|
362
|
+
k: v
|
363
|
+
for k, v in kwargs.items()
|
364
|
+
if k not in ("name", "annotation", "validator_kwargs")
|
365
|
+
}
|
366
|
+
self.extra_fields[field_name] = Field(**_kwargs)
|
362
367
|
|
363
368
|
# Handle no explicit defined field
|
364
369
|
if not field_obj and not kwargs:
|
@@ -478,7 +483,7 @@ class OperableModel(HashableModel):
|
|
478
483
|
raise KeyError(f"Field {field_name} not found in object fields.")
|
479
484
|
|
480
485
|
if str(attr).strip("s").lower() == "annotation":
|
481
|
-
return self.model_fields[field_name].annotation
|
486
|
+
return type(self).model_fields[field_name].annotation
|
482
487
|
|
483
488
|
field_obj = all_fields[field_name]
|
484
489
|
|
lionagi/operations/flow.py
CHANGED
@@ -349,7 +349,6 @@ class DependencyAwareExecutor:
|
|
349
349
|
and branch.metadata
|
350
350
|
and branch.metadata.get("pending_context_inheritance")
|
351
351
|
):
|
352
|
-
|
353
352
|
primary_dep_id = branch.metadata.get("inherit_from_operation")
|
354
353
|
if primary_dep_id and primary_dep_id in self.results:
|
355
354
|
# Find the primary dependency's branch
|
lionagi/protocols/generic/log.py
CHANGED
@@ -13,18 +13,19 @@ from pydantic import BaseModel, Field, PrivateAttr, field_validator
|
|
13
13
|
|
14
14
|
from lionagi.utils import create_path, to_dict
|
15
15
|
|
16
|
-
from .._concepts import Manager
|
17
16
|
from .element import Element
|
18
17
|
from .pile import Pile
|
19
18
|
|
20
19
|
__all__ = (
|
20
|
+
"DataLoggerConfig",
|
21
21
|
"LogManagerConfig",
|
22
22
|
"Log",
|
23
|
+
"DataLogger",
|
23
24
|
"LogManager",
|
24
25
|
)
|
25
26
|
|
26
27
|
|
27
|
-
class
|
28
|
+
class DataLoggerConfig(BaseModel):
|
28
29
|
persist_dir: str | Path = "./data/logs"
|
29
30
|
subfolder: str | None = None
|
30
31
|
file_prefix: str | None = None
|
@@ -101,7 +102,7 @@ class Log(Element):
|
|
101
102
|
return cls(content=content)
|
102
103
|
|
103
104
|
|
104
|
-
class
|
105
|
+
class DataLogger:
|
105
106
|
"""
|
106
107
|
Manages a collection of logs, optionally auto-dumping them
|
107
108
|
to CSV or JSON when capacity is reached or at program exit.
|
@@ -111,7 +112,7 @@ class LogManager(Manager):
|
|
111
112
|
self,
|
112
113
|
*,
|
113
114
|
logs: Any = None,
|
114
|
-
_config:
|
115
|
+
_config: DataLoggerConfig = None,
|
115
116
|
**kwargs,
|
116
117
|
):
|
117
118
|
"""
|
@@ -128,7 +129,7 @@ class LogManager(Manager):
|
|
128
129
|
clear_after_dump: Whether to clear logs after saving.
|
129
130
|
"""
|
130
131
|
if _config is None:
|
131
|
-
_config =
|
132
|
+
_config = DataLoggerConfig(**kwargs)
|
132
133
|
|
133
134
|
if isinstance(logs, dict):
|
134
135
|
self.logs = Pile.from_dict(logs)
|
@@ -188,8 +189,15 @@ class LogManager(Manager):
|
|
188
189
|
if do_clear:
|
189
190
|
self.logs.clear()
|
190
191
|
except Exception as e:
|
191
|
-
|
192
|
-
|
192
|
+
# Check if it's a JSON serialization error with complex objects
|
193
|
+
if "JSON serializable" in str(e):
|
194
|
+
logging.debug(f"Could not serialize logs to JSON: {e}")
|
195
|
+
# Don't raise for JSON serialization issues during dumps
|
196
|
+
if clear is not False:
|
197
|
+
self.logs.clear() # Still clear if requested
|
198
|
+
else:
|
199
|
+
logging.error(f"Failed to dump logs: {e}")
|
200
|
+
raise
|
193
201
|
|
194
202
|
async def adump(
|
195
203
|
self,
|
@@ -222,16 +230,24 @@ class LogManager(Manager):
|
|
222
230
|
try:
|
223
231
|
self.dump(clear=self._config.clear_after_dump)
|
224
232
|
except Exception as e:
|
225
|
-
|
233
|
+
# Only log debug level for JSON serialization errors during exit
|
234
|
+
# These are non-critical and often occur with complex objects
|
235
|
+
if "JSON serializable" in str(e):
|
236
|
+
logging.debug(f"Could not serialize logs to JSON: {e}")
|
237
|
+
else:
|
238
|
+
logging.error(f"Failed to save logs on exit: {e}")
|
226
239
|
|
227
240
|
@classmethod
|
228
241
|
def from_config(
|
229
|
-
cls, config:
|
230
|
-
) ->
|
242
|
+
cls, config: DataLoggerConfig, logs: Any = None
|
243
|
+
) -> DataLogger:
|
231
244
|
"""
|
232
245
|
Construct a LogManager from a LogManagerConfig.
|
233
246
|
"""
|
234
247
|
return cls(_config=config, logs=logs)
|
235
248
|
|
236
249
|
|
250
|
+
LogManagerConfig = DataLoggerConfig
|
251
|
+
LogManager = DataLogger
|
252
|
+
|
237
253
|
# File: lionagi/protocols/generic/log.py
|
@@ -168,7 +168,7 @@ class Step:
|
|
168
168
|
ACTION_REQUESTS_FIELD,
|
169
169
|
]
|
170
170
|
)
|
171
|
-
if "reason" in operative.response_model.model_fields:
|
171
|
+
if "reason" in type(operative.response_model).model_fields:
|
172
172
|
field_models.extend([REASON_FIELD])
|
173
173
|
|
174
174
|
operative = Step._create_response_type(
|
lionagi/protocols/types.py
CHANGED
@@ -18,7 +18,13 @@ from .forms.flow import FlowDefinition, FlowStep
|
|
18
18
|
from .forms.report import BaseForm, Form, Report
|
19
19
|
from .generic.element import ID, Element, IDError, IDType, validate_order
|
20
20
|
from .generic.event import Event, EventStatus, Execution
|
21
|
-
from .generic.log import
|
21
|
+
from .generic.log import (
|
22
|
+
DataLogger,
|
23
|
+
DataLoggerConfig,
|
24
|
+
Log,
|
25
|
+
LogManager,
|
26
|
+
LogManagerConfig,
|
27
|
+
)
|
22
28
|
from .generic.pile import Pile, to_list_type
|
23
29
|
from .generic.processor import Executor, Processor
|
24
30
|
from .generic.progression import Progression, prog
|
@@ -107,4 +113,6 @@ __all__ = (
|
|
107
113
|
"FunctionCalling",
|
108
114
|
"ToolRef",
|
109
115
|
"MailManager",
|
116
|
+
"DataLogger",
|
117
|
+
"DataLoggerConfig",
|
110
118
|
)
|
lionagi/service/__init__.py
CHANGED
@@ -1 +1,22 @@
|
|
1
|
-
from .
|
1
|
+
from .connections.api_calling import APICalling
|
2
|
+
from .connections.endpoint import Endpoint, EndpointConfig
|
3
|
+
from .hooks import *
|
4
|
+
from .imodel import iModel
|
5
|
+
from .manager import iModelManager
|
6
|
+
from .rate_limited_processor import RateLimitedAPIExecutor
|
7
|
+
from .token_calculator import TokenCalculator
|
8
|
+
|
9
|
+
__all__ = (
|
10
|
+
"APICalling",
|
11
|
+
"Endpoint",
|
12
|
+
"EndpointConfig",
|
13
|
+
"RateLimitedAPIExecutor",
|
14
|
+
"TokenCalculator",
|
15
|
+
"iModel",
|
16
|
+
"iModelManager",
|
17
|
+
"HookEventTypes",
|
18
|
+
"HookDict",
|
19
|
+
"AssosiatedEventInfo",
|
20
|
+
"HookEvent",
|
21
|
+
"HookRegistry",
|
22
|
+
)
|
@@ -5,10 +5,13 @@
|
|
5
5
|
import asyncio
|
6
6
|
import logging
|
7
7
|
|
8
|
-
from
|
8
|
+
from anyio import get_cancelled_exc_class
|
9
|
+
from pydantic import Field, PrivateAttr, model_validator
|
9
10
|
from typing_extensions import Self
|
10
11
|
|
11
12
|
from lionagi.protocols.generic.event import Event, EventStatus
|
13
|
+
from lionagi.protocols.types import Log
|
14
|
+
from lionagi.service.hooks import HookEvent, HookEventTypes, global_hook_logger
|
12
15
|
from lionagi.service.token_calculator import TokenCalculator
|
13
16
|
|
14
17
|
from .endpoint import Endpoint
|
@@ -51,6 +54,9 @@ class APICalling(Event):
|
|
51
54
|
exclude=True,
|
52
55
|
)
|
53
56
|
|
57
|
+
_pre_invoke_hook_event: HookEvent = PrivateAttr(None)
|
58
|
+
_post_invoke_hook_event: HookEvent = PrivateAttr(None)
|
59
|
+
|
54
60
|
@model_validator(mode="after")
|
55
61
|
def _validate_streaming(self) -> Self:
|
56
62
|
"""Validate streaming configuration and add token usage if requested."""
|
@@ -162,6 +168,13 @@ class APICalling(Event):
|
|
162
168
|
|
163
169
|
try:
|
164
170
|
self.execution.status = EventStatus.PROCESSING
|
171
|
+
if h_ev := self._pre_invoke_hook_event:
|
172
|
+
await h_ev.invoke()
|
173
|
+
if h_ev._should_exit:
|
174
|
+
raise h_ev._exit_cause or RuntimeError(
|
175
|
+
"Pre-invocation hook requested exit without a cause"
|
176
|
+
)
|
177
|
+
await global_hook_logger.alog(Log.create(h_ev))
|
165
178
|
|
166
179
|
# Make the API call with skip_payload_creation=True since payload is already prepared
|
167
180
|
response = await self.endpoint.call(
|
@@ -171,10 +184,18 @@ class APICalling(Event):
|
|
171
184
|
extra_headers=self.headers if self.headers else None,
|
172
185
|
)
|
173
186
|
|
187
|
+
if h_ev := self._post_invoke_hook_event:
|
188
|
+
await h_ev.invoke()
|
189
|
+
if h_ev._should_exit:
|
190
|
+
raise h_ev._exit_cause or RuntimeError(
|
191
|
+
"Post-invocation hook requested exit without a cause"
|
192
|
+
)
|
193
|
+
await global_hook_logger.alog(Log.create(h_ev))
|
194
|
+
|
174
195
|
self.execution.response = response
|
175
196
|
self.execution.status = EventStatus.COMPLETED
|
176
197
|
|
177
|
-
except
|
198
|
+
except get_cancelled_exc_class():
|
178
199
|
self.execution.error = "API call cancelled"
|
179
200
|
self.execution.status = EventStatus.FAILED
|
180
201
|
raise
|
@@ -228,3 +249,37 @@ class APICalling(Event):
|
|
228
249
|
def response(self):
|
229
250
|
"""Get the response from the execution."""
|
230
251
|
return self.execution.response if self.execution else None
|
252
|
+
|
253
|
+
def create_pre_invoke_hook(
|
254
|
+
self,
|
255
|
+
hook_registry,
|
256
|
+
exit_hook: bool = None,
|
257
|
+
hook_timeout: float = 30.0,
|
258
|
+
hook_params: dict = None,
|
259
|
+
):
|
260
|
+
h_ev = HookEvent(
|
261
|
+
hook_type=HookEventTypes.PreInvokation,
|
262
|
+
event_like=self,
|
263
|
+
registry=hook_registry,
|
264
|
+
exit=exit_hook,
|
265
|
+
timeout=hook_timeout,
|
266
|
+
params=hook_params or {},
|
267
|
+
)
|
268
|
+
self._pre_invoke_hook_event = h_ev
|
269
|
+
|
270
|
+
def create_post_invoke_hook(
|
271
|
+
self,
|
272
|
+
hook_registry,
|
273
|
+
exit_hook: bool = None,
|
274
|
+
hook_timeout: float = 30.0,
|
275
|
+
hook_params: dict = None,
|
276
|
+
):
|
277
|
+
h_ev = HookEvent(
|
278
|
+
hook_type=HookEventTypes.PostInvokation,
|
279
|
+
event_like=self,
|
280
|
+
registry=hook_registry,
|
281
|
+
exit=exit_hook,
|
282
|
+
timeout=hook_timeout,
|
283
|
+
params=hook_params or {},
|
284
|
+
)
|
285
|
+
self._post_invoke_hook_event = h_ev
|
@@ -32,7 +32,7 @@ class EndpointConfig(BaseModel):
|
|
32
32
|
endpoint_params: list[str] | None = None
|
33
33
|
method: str = "POST"
|
34
34
|
params: dict[str, str] = Field(default_factory=dict)
|
35
|
-
content_type: str = "application/json"
|
35
|
+
content_type: str | None = "application/json"
|
36
36
|
auth_type: AUTH_TYPES = "bearer"
|
37
37
|
default_headers: dict = {}
|
38
38
|
request_options: B | None = None
|
@@ -27,11 +27,13 @@ class HeaderFactory:
|
|
27
27
|
@staticmethod
|
28
28
|
def get_header(
|
29
29
|
auth_type: AUTH_TYPES,
|
30
|
-
content_type: str = "application/json",
|
30
|
+
content_type: str | None = "application/json",
|
31
31
|
api_key: str | SecretStr | None = None,
|
32
32
|
default_headers: dict[str, str] | None = None,
|
33
33
|
) -> dict[str, str]:
|
34
|
-
dict_ =
|
34
|
+
dict_ = {}
|
35
|
+
if content_type is not None:
|
36
|
+
dict_ = HeaderFactory.get_content_type_header(content_type)
|
35
37
|
|
36
38
|
if auth_type == "none":
|
37
39
|
# No authentication needed
|
@@ -16,49 +16,49 @@ def match_endpoint(
|
|
16
16
|
if "chat" in endpoint:
|
17
17
|
from .providers.oai_ import OpenaiChatEndpoint
|
18
18
|
|
19
|
-
return OpenaiChatEndpoint(**kwargs)
|
19
|
+
return OpenaiChatEndpoint(None, **kwargs)
|
20
20
|
if "response" in endpoint:
|
21
21
|
from .providers.oai_ import OpenaiResponseEndpoint
|
22
22
|
|
23
|
-
return OpenaiResponseEndpoint(**kwargs)
|
23
|
+
return OpenaiResponseEndpoint(None, **kwargs)
|
24
24
|
if provider == "openrouter" and "chat" in endpoint:
|
25
25
|
from .providers.oai_ import OpenrouterChatEndpoint
|
26
26
|
|
27
|
-
return OpenrouterChatEndpoint(**kwargs)
|
27
|
+
return OpenrouterChatEndpoint(None, **kwargs)
|
28
28
|
if provider == "ollama" and "chat" in endpoint:
|
29
29
|
from .providers.ollama_ import OllamaChatEndpoint
|
30
30
|
|
31
|
-
return OllamaChatEndpoint(**kwargs)
|
31
|
+
return OllamaChatEndpoint(None, **kwargs)
|
32
32
|
if provider == "exa" and "search" in endpoint:
|
33
33
|
from .providers.exa_ import ExaSearchEndpoint
|
34
34
|
|
35
|
-
return ExaSearchEndpoint(**kwargs)
|
35
|
+
return ExaSearchEndpoint(None, **kwargs)
|
36
36
|
if provider == "anthropic" and (
|
37
37
|
"messages" in endpoint or "chat" in endpoint
|
38
38
|
):
|
39
39
|
from .providers.anthropic_ import AnthropicMessagesEndpoint
|
40
40
|
|
41
|
-
return AnthropicMessagesEndpoint(**kwargs)
|
41
|
+
return AnthropicMessagesEndpoint(None, **kwargs)
|
42
42
|
if provider == "groq" and "chat" in endpoint:
|
43
43
|
from .providers.oai_ import GroqChatEndpoint
|
44
44
|
|
45
|
-
return GroqChatEndpoint(**kwargs)
|
45
|
+
return GroqChatEndpoint(None, **kwargs)
|
46
46
|
if provider == "perplexity" and "chat" in endpoint:
|
47
47
|
from .providers.perplexity_ import PerplexityChatEndpoint
|
48
48
|
|
49
|
-
return PerplexityChatEndpoint(**kwargs)
|
49
|
+
return PerplexityChatEndpoint(None, **kwargs)
|
50
50
|
if provider == "claude_code":
|
51
51
|
if "cli" in endpoint:
|
52
52
|
from .providers.claude_code_cli import ClaudeCodeCLIEndpoint
|
53
53
|
|
54
|
-
return ClaudeCodeCLIEndpoint(**kwargs)
|
54
|
+
return ClaudeCodeCLIEndpoint(None, **kwargs)
|
55
55
|
|
56
56
|
if "query" in endpoint or "code" in endpoint:
|
57
57
|
from lionagi.service.connections.providers.claude_code_ import (
|
58
58
|
ClaudeCodeEndpoint,
|
59
59
|
)
|
60
60
|
|
61
|
-
return ClaudeCodeEndpoint(**kwargs)
|
61
|
+
return ClaudeCodeEndpoint(None, **kwargs)
|
62
62
|
|
63
63
|
from .providers.oai_ import OpenaiChatEndpoint
|
64
64
|
|
@@ -9,7 +9,7 @@ from lionagi.service.connections.endpoint import Endpoint
|
|
9
9
|
from lionagi.service.connections.endpoint_config import EndpointConfig
|
10
10
|
from lionagi.service.third_party.anthropic_models import CreateMessageRequest
|
11
11
|
|
12
|
-
|
12
|
+
_get_config = lambda: EndpointConfig(
|
13
13
|
name="anthropic_messages",
|
14
14
|
provider="anthropic",
|
15
15
|
base_url="https://api.anthropic.com/v1",
|
@@ -22,13 +22,16 @@ ANTHROPIC_MESSAGES_ENDPOINT_CONFIG = EndpointConfig(
|
|
22
22
|
request_options=CreateMessageRequest,
|
23
23
|
)
|
24
24
|
|
25
|
+
ANTHROPIC_MESSAGES_ENDPOINT_CONFIG = _get_config() # backward compatibility
|
26
|
+
|
25
27
|
|
26
28
|
class AnthropicMessagesEndpoint(Endpoint):
|
27
29
|
def __init__(
|
28
30
|
self,
|
29
|
-
config: EndpointConfig =
|
31
|
+
config: EndpointConfig = None,
|
30
32
|
**kwargs,
|
31
33
|
):
|
34
|
+
config = config or _get_config()
|
32
35
|
super().__init__(config, **kwargs)
|
33
36
|
|
34
37
|
def create_payload(
|
@@ -12,12 +12,14 @@ from pydantic import BaseModel
|
|
12
12
|
from lionagi.libs.schema.as_readable import as_readable
|
13
13
|
from lionagi.service.connections.endpoint import Endpoint
|
14
14
|
from lionagi.service.connections.endpoint_config import EndpointConfig
|
15
|
-
from lionagi.utils import
|
15
|
+
from lionagi.utils import to_dict, to_list
|
16
16
|
|
17
|
-
from .
|
17
|
+
from ...third_party.claude_code import (
|
18
18
|
CLAUDE_CODE_OPTION_PARAMS,
|
19
|
+
HAS_CLAUDE_CODE_SDK,
|
19
20
|
ClaudeCodeRequest,
|
20
21
|
ClaudePermission,
|
22
|
+
stream_cc_sdk_events,
|
21
23
|
)
|
22
24
|
|
23
25
|
__all__ = (
|
@@ -27,24 +29,27 @@ __all__ = (
|
|
27
29
|
"ClaudeCodeEndpoint",
|
28
30
|
)
|
29
31
|
|
30
|
-
HAS_CLAUDE_CODE_SDK = is_import_installed("claude_code_sdk")
|
31
32
|
|
32
33
|
# --------------------------------------------------------------------------- SDK endpoint
|
33
|
-
|
34
|
+
|
35
|
+
_get_config = lambda: EndpointConfig(
|
34
36
|
name="claude_code",
|
35
37
|
provider="claude_code",
|
36
38
|
base_url="internal",
|
37
39
|
endpoint="query",
|
38
|
-
api_key="dummy",
|
39
40
|
request_options=ClaudeCodeRequest,
|
40
41
|
timeout=3000,
|
42
|
+
api_key="dummy-key",
|
41
43
|
)
|
42
44
|
|
43
45
|
|
46
|
+
ENDPOINT_CONFIG = _get_config() # backward compatibility
|
47
|
+
|
48
|
+
|
44
49
|
class ClaudeCodeEndpoint(Endpoint):
|
45
50
|
"""Direct Python-SDK (non-CLI) endpoint - unchanged except for bug-fixes."""
|
46
51
|
|
47
|
-
def __init__(self, config: EndpointConfig =
|
52
|
+
def __init__(self, config: EndpointConfig = None, **kwargs):
|
48
53
|
if not HAS_CLAUDE_CODE_SDK:
|
49
54
|
raise ImportError(
|
50
55
|
"claude_code_sdk is not installed. "
|
@@ -56,6 +61,7 @@ class ClaudeCodeEndpoint(Endpoint):
|
|
56
61
|
DeprecationWarning,
|
57
62
|
)
|
58
63
|
|
64
|
+
config = config or _get_config()
|
59
65
|
super().__init__(config=config, **kwargs)
|
60
66
|
|
61
67
|
def create_payload(self, request: dict | BaseModel, **kwargs):
|
@@ -64,16 +70,9 @@ class ClaudeCodeEndpoint(Endpoint):
|
|
64
70
|
req_obj = ClaudeCodeRequest.create(messages=messages, **req_dict)
|
65
71
|
return {"request": req_obj}, {}
|
66
72
|
|
67
|
-
def _stream_claude_code(self, request: ClaudeCodeRequest):
|
68
|
-
from claude_code_sdk import query as sdk_query
|
69
|
-
|
70
|
-
return sdk_query(
|
71
|
-
prompt=request.prompt, options=request.as_claude_options()
|
72
|
-
)
|
73
|
-
|
74
73
|
async def stream(self, request: dict | BaseModel, **kwargs):
|
75
74
|
payload, _ = self.create_payload(request, **kwargs)
|
76
|
-
async for chunk in
|
75
|
+
async for chunk in stream_cc_sdk_events(payload["request"]):
|
77
76
|
yield chunk
|
78
77
|
|
79
78
|
def _parse_claude_code_response(self, responses: list) -> dict:
|
@@ -204,9 +203,6 @@ class ClaudeCodeEndpoint(Endpoint):
|
|
204
203
|
|
205
204
|
responses.append(chunk)
|
206
205
|
|
207
|
-
# 3. Parse the responses into a clean format
|
208
|
-
return self._parse_claude_code_response(responses)
|
209
|
-
|
210
206
|
|
211
207
|
def _display_message(chunk, theme):
|
212
208
|
from claude_code_sdk import types as cc_types
|