lionagi 0.8.2__py3-none-any.whl → 0.8.7__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/service/imodel.py CHANGED
@@ -5,6 +5,8 @@
5
5
  import os
6
6
  import warnings
7
7
 
8
+ from pydantic import BaseModel
9
+
8
10
  from .endpoints.base import APICalling, EndPoint
9
11
  from .endpoints.match_endpoint import match_endpoint
10
12
  from .endpoints.rate_limited_processor import RateLimitedAPIExecutor
@@ -266,6 +268,15 @@ class iModel:
266
268
  """
267
269
  return self.kwargs.get("model", "")
268
270
 
271
+ @property
272
+ def request_options(self) -> type[BaseModel] | None:
273
+ """type[BaseModel] | None: The request options model for the endpoint.
274
+
275
+ Returns:
276
+ The request options model if available; otherwise, None.
277
+ """
278
+ return self.endpoint.request_options
279
+
269
280
  def to_dict(self):
270
281
  kwargs = self.kwargs
271
282
  if "kwargs" in self.kwargs:
@@ -1,10 +1,8 @@
1
- from typing import TYPE_CHECKING, Literal
1
+ from typing import Literal
2
2
 
3
3
  from lionagi.service.endpoints.base import EndPoint
4
4
 
5
- if TYPE_CHECKING:
6
- from .models import ExaSearchRequest
7
-
5
+ from .models import ExaSearchRequest
8
6
 
9
7
  CATEGORY_OPTIONS = Literal[
10
8
  "article",
@@ -21,6 +19,7 @@ CATEGORY_OPTIONS = Literal[
21
19
  ]
22
20
 
23
21
  SEARCH_CONFIG = {
22
+ "name": "search_exa",
24
23
  "provider": "exa",
25
24
  "base_url": "https://api.exa.ai",
26
25
  "endpoint": "search",
@@ -47,6 +46,7 @@ SEARCH_CONFIG = {
47
46
  "type", # keyword, neural, auto
48
47
  "useAutoPrompt",
49
48
  },
49
+ "request_options": ExaSearchRequest,
50
50
  }
51
51
 
52
52
 
@@ -4,7 +4,10 @@
4
4
 
5
5
  from lionagi.service.endpoints.chat_completion import ChatCompletionEndPoint
6
6
 
7
+ from .models import PerplexityChatCompletionRequest
8
+
7
9
  CHAT_COMPLETION_CONFIG = {
10
+ "name": "search_perplexity",
8
11
  "provider": "perplexity",
9
12
  "base_url": "https://api.perplexity.ai",
10
13
  "endpoint": "chat/completions",
@@ -31,6 +34,7 @@ CHAT_COMPLETION_CONFIG = {
31
34
  "frequency_penalty",
32
35
  },
33
36
  "allowed_roles": ["user", "assistant"],
37
+ "request_options": PerplexityChatCompletionRequest,
34
38
  }
35
39
 
36
40
 
@@ -0,0 +1,144 @@
1
+ from enum import Enum
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel, Field, model_validator
5
+
6
+
7
+ class PerplexityRole(str, Enum):
8
+ """Roles allowed in Perplexity's messages."""
9
+
10
+ system = "system"
11
+ user = "user"
12
+ assistant = "assistant"
13
+
14
+
15
+ class PerplexityMessage(BaseModel):
16
+ """
17
+ A single message in the conversation.
18
+ `role` can be 'system', 'user', or 'assistant'.
19
+ `content` is the text for that conversation turn.
20
+ """
21
+
22
+ role: PerplexityRole = Field(
23
+ ...,
24
+ description="The role of the speaker. Must be system, user, or assistant.",
25
+ )
26
+ content: str = Field(..., description="The text content of this message.")
27
+
28
+
29
+ class PerplexityChatCompletionRequest(BaseModel):
30
+ """
31
+ Represents the request body for Perplexity's Chat Completions endpoint.
32
+ Endpoint: POST https://api.perplexity.ai/chat/completions
33
+ """
34
+
35
+ model: str = Field(
36
+ ...,
37
+ description="The model name, e.g. 'llama-3.1-sonar-small-128k-online'.",
38
+ )
39
+ messages: list[PerplexityMessage] = Field(
40
+ ..., description="A list of messages forming the conversation so far."
41
+ )
42
+
43
+ # Optional parameters
44
+ frequency_penalty: float | None = Field(
45
+ default=None,
46
+ gt=0,
47
+ description=(
48
+ "Multiplicative penalty > 0. Values > 1.0 penalize repeated tokens more strongly. "
49
+ "Value=1.0 means no penalty. Incompatible with presence_penalty."
50
+ ),
51
+ )
52
+ presence_penalty: float | None = Field(
53
+ default=None,
54
+ ge=-2.0,
55
+ le=2.0,
56
+ description=(
57
+ "Penalizes tokens that have appeared so far (range -2 to 2). "
58
+ "Positive values encourage talking about new topics. Incompatible with frequency_penalty."
59
+ ),
60
+ )
61
+ max_tokens: int | None = Field(
62
+ default=None,
63
+ description=(
64
+ "Maximum number of completion tokens. If omitted, model generates tokens until it "
65
+ "hits stop or context limit."
66
+ ),
67
+ )
68
+ return_images: bool | None = Field(
69
+ default=None,
70
+ description="If True, attempt to return images (closed beta feature).",
71
+ )
72
+ return_related_questions: bool | None = Field(
73
+ default=None,
74
+ description="If True, attempt to return related questions (closed beta feature).",
75
+ )
76
+ search_domain_filter: list[Any] | None = Field(
77
+ default=None,
78
+ description=(
79
+ "List of domains to limit or exclude in the online search. Example: ['example.com', '-twitter.com']. "
80
+ "Supports up to 3 entries. (Closed beta feature.)"
81
+ ),
82
+ )
83
+ search_recency_filter: str | None = Field(
84
+ default=None,
85
+ description=(
86
+ "Returns search results within a specified time interval: 'month', 'week', 'day', or 'hour'."
87
+ ),
88
+ )
89
+ stream: bool | None = Field(
90
+ default=None,
91
+ description=(
92
+ "If True, response is returned incrementally via Server-Sent Events (SSE)."
93
+ ),
94
+ )
95
+ temperature: float | None = Field(
96
+ default=None,
97
+ ge=0.0,
98
+ lt=2.0,
99
+ description=(
100
+ "Controls randomness of sampling, range [0, 2). Higher => more random. "
101
+ "Defaults to 0.2."
102
+ ),
103
+ )
104
+ top_k: int | None = Field(
105
+ default=None,
106
+ ge=0,
107
+ le=2048,
108
+ description=(
109
+ "Top-K filtering. 0 disables top-k filtering. If set, only the top K tokens are considered. "
110
+ "We recommend altering either top_k or top_p, but not both."
111
+ ),
112
+ )
113
+ top_p: float | None = Field(
114
+ default=None,
115
+ ge=0.0,
116
+ le=1.0,
117
+ description=(
118
+ "Nucleus sampling threshold. We recommend altering either top_k or top_p, but not both."
119
+ ),
120
+ )
121
+
122
+ @model_validator(mode="before")
123
+ def validate_penalties(cls, values):
124
+ """
125
+ Disallow using both frequency_penalty != 1.0 and presence_penalty != 0.0 at once,
126
+ since the docs say they're incompatible.
127
+ """
128
+ freq_pen = values.get("frequency_penalty", 1.0)
129
+ pres_pen = values.get("presence_penalty", 0.0)
130
+
131
+ # The doc states frequency_penalty is incompatible with presence_penalty.
132
+ # We'll enforce that if presence_penalty != 0, frequency_penalty must be 1.0
133
+ # or vice versa. Adjust logic as needed.
134
+ if pres_pen != 0.0 and freq_pen != 1.0:
135
+ raise ValueError(
136
+ "presence_penalty is incompatible with frequency_penalty. "
137
+ "Please use only one: either presence_penalty=0 with freq_pen !=1, "
138
+ "or presence_penalty!=0 with freq_pen=1."
139
+ )
140
+ return values
141
+
142
+ def to_dict(self) -> dict:
143
+ """Return a dict suitable for JSON serialization and sending to Perplexity API."""
144
+ return self.model_dump(exclude_none=True)
lionagi/session/branch.py CHANGED
@@ -45,8 +45,10 @@ from lionagi.protocols.types import (
45
45
  SenderRecipient,
46
46
  System,
47
47
  )
48
+ from lionagi.service.endpoints.base import EndPoint
48
49
  from lionagi.service.types import iModel, iModelManager
49
50
  from lionagi.settings import Settings
51
+ from lionagi.tools.base import LionTool
50
52
  from lionagi.utils import UNDEFINED, alcall, bcall, copy
51
53
 
52
54
  if TYPE_CHECKING:
@@ -204,7 +206,9 @@ class Branch(Element, Communicatable, Relational):
204
206
  )
205
207
 
206
208
  # --- ActionManager ---
207
- self._action_manager = ActionManager(tools)
209
+ self._action_manager = ActionManager()
210
+ if tools:
211
+ self.register_tools(tools)
208
212
 
209
213
  # --- LogManager ---
210
214
  if log_config:
@@ -345,6 +349,29 @@ class Branch(Element, Communicatable, Relational):
345
349
 
346
350
  return branch_clone
347
351
 
352
+ def _register_tool(self, tools: FuncTool | LionTool, update: bool = False):
353
+ if isinstance(tools, type) and issubclass(tools, LionTool):
354
+ tools = tools()
355
+ if isinstance(tools, LionTool):
356
+ tools = tools.to_tool()
357
+ self._action_manager.register_tool(tools, update=update)
358
+
359
+ def register_tools(
360
+ self, tools: FuncTool | list[FuncTool] | LionTool, update: bool = False
361
+ ):
362
+ """
363
+ Registers one or more tools in the ActionManager.
364
+
365
+ Args:
366
+ tools (FuncTool | list[FuncTool] | LionTool):
367
+ A single tool or a list of tools to register.
368
+ update (bool, optional):
369
+ If `True`, updates existing tools with the same name.
370
+ """
371
+ tools = [tools] if not isinstance(tools, list) else tools
372
+ for tool in tools:
373
+ self._register_tool(tool, update=update)
374
+
348
375
  # -------------------------------------------------------------------------
349
376
  # Conversion / Serialization
350
377
  # -------------------------------------------------------------------------
@@ -532,6 +559,59 @@ class Branch(Element, Communicatable, Relational):
532
559
  for key in self.mailbox.pending_ins:
533
560
  self.receive(key)
534
561
 
562
+ def connect(
563
+ self,
564
+ provider: str = None,
565
+ base_url: str = None,
566
+ endpoint: str | EndPoint = "chat",
567
+ endpoint_params: list[str] | None = None,
568
+ api_key: str = None,
569
+ queue_capacity: int = 100,
570
+ capacity_refresh_time: float = 60,
571
+ interval: float | None = None,
572
+ limit_requests: int = None,
573
+ limit_tokens: int = None,
574
+ invoke_with_endpoint: bool = False,
575
+ imodel: iModel = None,
576
+ name: str = None,
577
+ request_options: type[BaseModel] = None,
578
+ description: str = None,
579
+ update: bool = False,
580
+ ):
581
+ if not imodel:
582
+ imodel = iModel(
583
+ provider=provider,
584
+ base_url=base_url,
585
+ endpoint=endpoint,
586
+ endpoint_params=endpoint_params,
587
+ api_key=api_key,
588
+ queue_capacity=queue_capacity,
589
+ capacity_refresh_time=capacity_refresh_time,
590
+ interval=interval,
591
+ limit_requests=limit_requests,
592
+ limit_tokens=limit_tokens,
593
+ invoke_with_endpoint=invoke_with_endpoint,
594
+ )
595
+
596
+ if not update and name in self.tools:
597
+ raise ValueError(f"Tool with name '{name}' already exists.")
598
+
599
+ async def _connect(**kwargs):
600
+ """connect to an api endpoint"""
601
+ api_call = await imodel.invoke(**kwargs)
602
+ self._log_manager.log(Log.create(api_call))
603
+ return api_call.response
604
+
605
+ _connect.__name__ = name or imodel.endpoint.name
606
+ if description:
607
+ _connect.__doc__ = description
608
+
609
+ tool = Tool(
610
+ func_callable=_connect,
611
+ request_options=request_options or imodel.request_options,
612
+ )
613
+ self._action_manager.register_tools(tool, update=update)
614
+
535
615
  # -------------------------------------------------------------------------
536
616
  # Dictionary Conversion
537
617
  # -------------------------------------------------------------------------
@@ -824,6 +904,11 @@ class Branch(Element, Communicatable, Relational):
824
904
  actions: bool = False,
825
905
  reason: bool = False,
826
906
  action_kwargs: dict = None,
907
+ action_strategy: Literal[
908
+ "sequential", "concurrent", "batch"
909
+ ] = "concurrent",
910
+ action_batch_size: int = None,
911
+ verbose_action: bool = False,
827
912
  field_models: list[FieldModel] = None,
828
913
  exclude_fields: list | dict | None = None,
829
914
  request_params: ModelParams = None,
@@ -898,6 +983,12 @@ class Branch(Element, Communicatable, Relational):
898
983
  If `True`, signals that the LLM should provide chain-of-thought or reasoning (where applicable).
899
984
  action_kwargs (dict | None, optional):
900
985
  Additional parameters for the `branch.act()` call if tools are invoked.
986
+ action_strategy (Literal["sequential","concurrent","batch"], optional):
987
+ The strategy for invoking tools (default: "concurrent").
988
+ action_batch_size (int, optional):
989
+ The batch size for concurrent tool invocation if `action_strategy="batch"`.
990
+ verbose_action (bool, optional):
991
+ If `True`, logs detailed information about tool invocation.
901
992
  field_models (list[FieldModel] | None, optional):
902
993
  Field-level definitions or overrides for the model schema.
903
994
  exclude_fields (list|dict|None, optional):
@@ -955,6 +1046,9 @@ class Branch(Element, Communicatable, Relational):
955
1046
  actions=actions,
956
1047
  reason=reason,
957
1048
  action_kwargs=action_kwargs,
1049
+ action_strategy=action_strategy,
1050
+ action_batch_size=action_batch_size,
1051
+ verbose_action=verbose_action,
958
1052
  field_models=field_models,
959
1053
  exclude_fields=exclude_fields,
960
1054
  request_params=request_params,
@@ -1082,6 +1176,7 @@ class Branch(Element, Communicatable, Relational):
1082
1176
  self,
1083
1177
  action_request: ActionRequest | BaseModel | dict,
1084
1178
  suppress_errors: bool = False,
1179
+ verbose_action: bool = False,
1085
1180
  ) -> ActionResponse:
1086
1181
  """
1087
1182
  Internal method to invoke a tool (action) asynchronously.
@@ -1097,13 +1192,19 @@ class Branch(Element, Communicatable, Relational):
1097
1192
  """
1098
1193
  from lionagi.operations._act.act import _act
1099
1194
 
1100
- return await _act(self, action_request, suppress_errors)
1195
+ return await _act(
1196
+ branch=self,
1197
+ action_request=action_request,
1198
+ suppress_errors=suppress_errors,
1199
+ verbose_action=verbose_action,
1200
+ )
1101
1201
 
1102
1202
  async def act(
1103
1203
  self,
1104
1204
  action_request: list | ActionRequest | BaseModel | dict,
1105
1205
  *,
1106
1206
  strategy: Literal["concurrent", "sequential", "batch"] = "concurrent",
1207
+ verbose_action: bool = False,
1107
1208
  batch_size: int = None,
1108
1209
  suppress_errors: bool = True,
1109
1210
  sanitize_input: bool = False,
@@ -1129,6 +1230,10 @@ class Branch(Element, Communicatable, Relational):
1129
1230
  action_request (list|ActionRequest|BaseModel|dict):
1130
1231
  A single or list of action requests, each requiring
1131
1232
  `function` and `arguments`.
1233
+ strategy (Literal["concurrent","sequential","batch"]):
1234
+ The execution strategy to use.
1235
+ verbose_action (bool):
1236
+ If True, log detailed information about the action.
1132
1237
  suppress_errors (bool):
1133
1238
  If True, log errors instead of raising exceptions.
1134
1239
  sanitize_input (bool):
@@ -1175,6 +1280,7 @@ class Branch(Element, Communicatable, Relational):
1175
1280
  case "concurrent":
1176
1281
  return await self._concurrent_act(
1177
1282
  action_request,
1283
+ verbose_action=verbose_action,
1178
1284
  suppress_errors=suppress_errors,
1179
1285
  sanitize_input=sanitize_input,
1180
1286
  unique_input=unique_input,
@@ -1195,11 +1301,13 @@ class Branch(Element, Communicatable, Relational):
1195
1301
  case "sequential":
1196
1302
  return await self._sequential_act(
1197
1303
  action_request,
1304
+ verbose_action=verbose_action,
1198
1305
  suppress_errors=suppress_errors,
1199
1306
  )
1200
1307
  case "batch":
1201
1308
  return await self._batch_act(
1202
1309
  action_request,
1310
+ verbose_action=verbose_action,
1203
1311
  batch_size=batch_size or 1,
1204
1312
  max_concurrent=max_concurrent,
1205
1313
  suppress_errors=suppress_errors,
@@ -1230,6 +1338,7 @@ class Branch(Element, Communicatable, Relational):
1230
1338
  self,
1231
1339
  action_request: ActionRequest | BaseModel | dict,
1232
1340
  suppress_errors: bool = True,
1341
+ verbose_action: bool = False,
1233
1342
  ) -> list:
1234
1343
  action_request = (
1235
1344
  action_request
@@ -1239,7 +1348,11 @@ class Branch(Element, Communicatable, Relational):
1239
1348
  results = []
1240
1349
  for req in action_request:
1241
1350
  results.append(
1242
- await self._act(req, suppress_errors=suppress_errors)
1351
+ await self._act(
1352
+ req,
1353
+ verbose_action=verbose_action,
1354
+ suppress_errors=suppress_errors,
1355
+ )
1243
1356
  )
1244
1357
  return results
1245
1358
 
@@ -1480,6 +1593,10 @@ class Branch(Element, Communicatable, Relational):
1480
1593
  self,
1481
1594
  instruct: Instruct | dict[str, Any],
1482
1595
  interpret: bool = False,
1596
+ interpret_domain: str | None = None,
1597
+ interpret_style: str | None = None,
1598
+ interpret_sample: str | None = None,
1599
+ interpret_kwargs: dict | None = None,
1483
1600
  tools: Any = None,
1484
1601
  tool_schemas: Any = None,
1485
1602
  response_format: type[BaseModel] = None,
@@ -1488,6 +1605,7 @@ class Branch(Element, Communicatable, Relational):
1488
1605
  response_kwargs: dict | None = None,
1489
1606
  return_analysis: bool = False,
1490
1607
  analysis_model: iModel | None = None,
1608
+ verbose: bool = False,
1491
1609
  **kwargs,
1492
1610
  ):
1493
1611
  """
@@ -1506,6 +1624,14 @@ class Branch(Element, Communicatable, Relational):
1506
1624
  interpret (bool, optional):
1507
1625
  If `True`, first interprets (`branch.interpret`) the instructions to refine them
1508
1626
  before proceeding. Defaults to `False`.
1627
+ interpret_domain (str | None, optional):
1628
+ Optional domain hint for the interpretation step.
1629
+ interpret_style (str | None, optional):
1630
+ Optional style hint for the interpretation step.
1631
+ interpret_sample (str | None, optional):
1632
+ Optional sample hint for the interpretation step.
1633
+ interpret_kwargs (dict | None, optional):
1634
+ Additional arguments for the interpretation step.
1509
1635
  tools (Any, optional):
1510
1636
  Tools to be made available for the ReAct process. If omitted or `None`,
1511
1637
  and if no `tool_schemas` are provided, it defaults to `True` (all tools).
@@ -1554,6 +1680,10 @@ class Branch(Element, Communicatable, Relational):
1554
1680
  self,
1555
1681
  instruct,
1556
1682
  interpret=interpret,
1683
+ interpret_domain=interpret_domain,
1684
+ interpret_style=interpret_style,
1685
+ interpret_sample=interpret_sample,
1686
+ interpret_kwargs=interpret_kwargs,
1557
1687
  tools=tools,
1558
1688
  tool_schemas=tool_schemas,
1559
1689
  response_format=response_format,
@@ -1562,6 +1692,8 @@ class Branch(Element, Communicatable, Relational):
1562
1692
  response_kwargs=response_kwargs,
1563
1693
  return_analysis=return_analysis,
1564
1694
  analysis_model=analysis_model,
1695
+ verbose_action=verbose,
1696
+ verbose_analysis=verbose,
1565
1697
  **kwargs,
1566
1698
  )
1567
1699
 
File without changes
lionagi/tools/base.py ADDED
@@ -0,0 +1,12 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from lionagi.operatives.action.tool import Tool
4
+
5
+
6
+ class LionTool(ABC):
7
+ is_lion_system_tool: bool = True
8
+ system_tool_name: str
9
+
10
+ @abstractmethod
11
+ def to_tool(self) -> Tool:
12
+ pass