lionagi 0.0.208__py3-none-any.whl → 0.0.210__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. lionagi/__init__.py +4 -6
  2. lionagi/api_service/base_endpoint.py +65 -0
  3. lionagi/api_service/base_rate_limiter.py +121 -0
  4. lionagi/api_service/base_service.py +146 -0
  5. lionagi/api_service/chat_completion.py +6 -0
  6. lionagi/api_service/embeddings.py +6 -0
  7. lionagi/api_service/payload_package.py +47 -0
  8. lionagi/api_service/status_tracker.py +29 -0
  9. lionagi/core/__init__.py +5 -9
  10. lionagi/core/branch.py +1191 -0
  11. lionagi/core/flow.py +423 -0
  12. lionagi/core/{instruction_set/instruction_set.py → instruction_set.py} +3 -3
  13. lionagi/core/session.py +872 -0
  14. lionagi/schema/__init__.py +5 -8
  15. lionagi/schema/base_schema.py +821 -0
  16. lionagi/{_services → services}/base_service.py +4 -4
  17. lionagi/{_services → services}/oai.py +4 -4
  18. lionagi/structures/graph.py +1 -1
  19. lionagi/structures/relationship.py +1 -1
  20. lionagi/structures/structure.py +1 -1
  21. lionagi/tools/tool_manager.py +0 -163
  22. lionagi/tools/tool_util.py +2 -1
  23. lionagi/utils/__init__.py +7 -14
  24. lionagi/utils/api_util.py +63 -2
  25. lionagi/utils/core_utils.py +338 -0
  26. lionagi/utils/sys_util.py +3 -3
  27. lionagi/version.py +1 -1
  28. {lionagi-0.0.208.dist-info → lionagi-0.0.210.dist-info}/METADATA +28 -29
  29. lionagi-0.0.210.dist-info/RECORD +56 -0
  30. lionagi/_services/anthropic.py +0 -79
  31. lionagi/_services/anyscale.py +0 -0
  32. lionagi/_services/azure.py +0 -1
  33. lionagi/_services/bedrock.py +0 -0
  34. lionagi/_services/everlyai.py +0 -0
  35. lionagi/_services/gemini.py +0 -0
  36. lionagi/_services/gpt4all.py +0 -0
  37. lionagi/_services/huggingface.py +0 -0
  38. lionagi/_services/litellm.py +0 -33
  39. lionagi/_services/localai.py +0 -0
  40. lionagi/_services/openllm.py +0 -0
  41. lionagi/_services/openrouter.py +0 -44
  42. lionagi/_services/perplexity.py +0 -0
  43. lionagi/_services/predibase.py +0 -0
  44. lionagi/_services/rungpt.py +0 -0
  45. lionagi/_services/vllm.py +0 -0
  46. lionagi/_services/xinference.py +0 -0
  47. lionagi/agents/planner.py +0 -1
  48. lionagi/agents/prompter.py +0 -1
  49. lionagi/agents/scorer.py +0 -1
  50. lionagi/agents/summarizer.py +0 -1
  51. lionagi/agents/validator.py +0 -1
  52. lionagi/bridge/__init__.py +0 -22
  53. lionagi/bridge/langchain.py +0 -195
  54. lionagi/bridge/llama_index.py +0 -266
  55. lionagi/core/branch/__init__.py +0 -0
  56. lionagi/core/branch/branch.py +0 -841
  57. lionagi/core/branch/cluster.py +0 -1
  58. lionagi/core/branch/conversation.py +0 -787
  59. lionagi/core/core_util.py +0 -0
  60. lionagi/core/flow/__init__.py +0 -0
  61. lionagi/core/flow/flow.py +0 -19
  62. lionagi/core/flow/flow_util.py +0 -62
  63. lionagi/core/instruction_set/__init__.py +0 -0
  64. lionagi/core/messages/__init__.py +0 -0
  65. lionagi/core/sessions/__init__.py +0 -0
  66. lionagi/core/sessions/session.py +0 -504
  67. lionagi/datastores/__init__.py +0 -1
  68. lionagi/datastores/chroma.py +0 -1
  69. lionagi/datastores/deeplake.py +0 -1
  70. lionagi/datastores/elasticsearch.py +0 -1
  71. lionagi/datastores/lantern.py +0 -1
  72. lionagi/datastores/pinecone.py +0 -1
  73. lionagi/datastores/postgres.py +0 -1
  74. lionagi/datastores/qdrant.py +0 -1
  75. lionagi/loaders/__init__.py +0 -18
  76. lionagi/loaders/chunker.py +0 -166
  77. lionagi/loaders/load_util.py +0 -240
  78. lionagi/loaders/reader.py +0 -122
  79. lionagi/models/__init__.py +0 -0
  80. lionagi/models/base_model.py +0 -0
  81. lionagi/models/imodel.py +0 -53
  82. lionagi/schema/async_queue.py +0 -158
  83. lionagi/schema/base_condition.py +0 -1
  84. lionagi/schema/base_node.py +0 -422
  85. lionagi/schema/base_tool.py +0 -44
  86. lionagi/schema/data_logger.py +0 -126
  87. lionagi/schema/data_node.py +0 -88
  88. lionagi/schema/status_tracker.py +0 -37
  89. lionagi/tests/test_utils/test_encrypt_util.py +0 -323
  90. lionagi/utils/encrypt_util.py +0 -283
  91. lionagi/utils/url_util.py +0 -55
  92. lionagi-0.0.208.dist-info/RECORD +0 -106
  93. lionagi/{agents → api_service}/__init__.py +0 -0
  94. lionagi/core/{branch/branch_manager.py → branch_manager.py} +0 -0
  95. lionagi/core/{messages/messages.py → messages.py} +3 -3
  96. /lionagi/{_services → services}/__init__.py +0 -0
  97. /lionagi/{_services → services}/mistralai.py +0 -0
  98. /lionagi/{_services → services}/mlx_service.py +0 -0
  99. /lionagi/{_services → services}/ollama.py +0 -0
  100. /lionagi/{_services → services}/services.py +0 -0
  101. /lionagi/{_services → services}/transformers.py +0 -0
  102. {lionagi-0.0.208.dist-info → lionagi-0.0.210.dist-info}/LICENSE +0 -0
  103. {lionagi-0.0.208.dist-info → lionagi-0.0.210.dist-info}/WHEEL +0 -0
  104. {lionagi-0.0.208.dist-info → lionagi-0.0.210.dist-info}/top_level.txt +0 -0
@@ -1,841 +0,0 @@
1
- import json
2
- import pandas as pd
3
-
4
- from typing import Any, Callable, Dict, List, Optional, Union
5
- from collections import deque
6
- from dotenv import load_dotenv
7
-
8
- from lionagi.utils import as_dict, get_flattened_keys, alcall, lcall, to_list
9
- from lionagi.utils.sys_util import is_same_dtype
10
- from lionagi.schema import Tool
11
- from lionagi._services.base_service import StatusTracker, BaseService
12
- from lionagi._services.oai import OpenAIService
13
- from lionagi._services.openrouter import OpenRouterService
14
-
15
- from lionagi.configs.oai_configs import oai_schema
16
- from lionagi.configs.openrouter_configs import openrouter_schema
17
- from lionagi.tools.tool_manager import ToolManager
18
-
19
- from ..messages.messages import Instruction, System
20
- from ..instruction_set.instruction_set import InstructionSet
21
-
22
- from .conversation import Conversation, validate_messages
23
- from .branch_manager import Request
24
-
25
- load_dotenv()
26
-
27
-
28
- class Branch(Conversation):
29
- """
30
- Manages a conversation branch within the application, handling messages, instruction sets,
31
- tool registrations, and service interactions for a single conversation flow. Extends the
32
- Conversation class to provide specialized functionalities like message handling, tool
33
- management, and integration with external services.
34
-
35
- Attributes:
36
- messages (pd.DataFrame): Dataframe storing conversation messages.
37
- instruction_sets (Dict[str, InstructionSet]): Dictionary mapping instruction set names to their instances.
38
- tool_manager (ToolManager): Manages tools available within the conversation.
39
- status_tracker (StatusTracker): Tracks the status of various tasks within the conversation.
40
- name (Optional[str]): Identifier for the branch.
41
- pending_ins (Dict): Dictionary storing incoming requests.
42
- pending_outs (deque): Queue for outgoing requests.
43
- service (BaseService): Service instance for interaction with external services.
44
- llmconfig (Dict): Configuration for language model interactions.
45
-
46
- Methods:
47
- __init__(self, name=None, messages=None, instruction_sets=None, tool_manager=None,
48
- service=None, llmconfig=None):
49
- Initializes a new Branch instance with optional configurations.
50
-
51
- clone(self) -> 'Branch':
52
- Creates a deep copy of the current Branch instance.
53
-
54
- merge_branch(self, branch: 'Branch', update: True):
55
- Merges another branch into the current Branch instance.
56
-
57
- send(self, to_name: str, title: str, package: Any):
58
- Sends a request package to a specified recipient.
59
-
60
- receive(self, from_name: str, messages=True, tool=True, service=True, llmconfig=True):
61
- Processes and integrates received request packages based on their titles.
62
-
63
- receive_all(self):
64
- Processes all pending incoming requests from all senders.
65
-
66
- call_chatcompletion(self, sender=None, with_sender=False, **kwargs):
67
- Asynchronously calls the chat completion service with the current message queue.
68
-
69
- chat(self, instruction: Union[Instruction, str], context=None, sender=None, system=None,
70
- tools=False, out=True, invoke=True, **kwargs) -> Any:
71
- Asynchronously handles a chat interaction within the branch.
72
-
73
- ReAct(self, instruction: Union[Instruction, str], context=None, sender=None, system=None,
74
- tools=None, num_rounds=1, **kwargs):
75
- Performs a sequence of reasoning and action based on the given instruction over multiple rounds.
76
-
77
- auto_followup(self, instruction: Union[Instruction, str], context=None, sender=None,
78
- system=None, tools=False, max_followup=3, out=True, **kwargs) -> None:
79
- Automatically performs follow-up actions until a specified condition is met or the maximum number of follow-ups is reached.
80
-
81
- Note:
82
- This class is designed to be used within an asynchronous environment, where methods like
83
- `chat`, `ReAct`, and `auto_followup` are particularly useful for handling complex conversation flows.
84
- """
85
-
86
- def __init__(
87
- self,
88
- name: Optional[str] = None,
89
- messages: Optional[pd.DataFrame] = None,
90
- instruction_sets: Optional[Dict[str, InstructionSet]] = None,
91
- tool_manager: Optional[ToolManager] = None,
92
- service : Optional[BaseService] = None,
93
- llmconfig: Optional[Dict] = None,
94
- ):
95
- """
96
- Initializes a new Branch instance.
97
-
98
- Args:
99
- name (Optional[str]): Name of the branch, providing an identifier within the conversational system. Defaults to None.
100
- messages (Optional[pd.DataFrame]): A pandas DataFrame containing the conversation's messages. Initializes with an empty DataFrame if None. Defaults to None.
101
- instruction_sets (Optional[Dict[str, InstructionSet]]): Dictionary mapping instruction set names to InstructionSet objects for conversation flow management. Defaults to {}.
102
- tool_manager (Optional[ToolManager]): Manages tools within the branch. Creates a new instance if None. Defaults to None.
103
- service (Optional[BaseService]): Interacts with external services. Initializes a default service based on branch configuration if None. Defaults to None.
104
- llmconfig (Optional[Dict]): Configuration for language model interactions. Sets up default configuration based on the service type if None. Defaults to None.
105
- """
106
- super().__init__()
107
- self.messages = (
108
- messages
109
- if messages is not None
110
- else pd.DataFrame(
111
- columns=["node_id", "role", "sender", "timestamp", "content"]
112
- )
113
- )
114
- self.instruction_sets = instruction_sets if instruction_sets else {}
115
- self.tool_manager = tool_manager if tool_manager else ToolManager()
116
- self.status_tracker = StatusTracker()
117
- self._add_service(service, llmconfig)
118
- self.name = name
119
- self.pending_ins = {}
120
- self.pending_outs = deque()
121
-
122
- @property
123
- def chat_messages(self):
124
- return self._to_chatcompletion_message()
125
-
126
- @property
127
- def chat_messages_with_sender(self):
128
- return self._to_chatcompletion_message(with_sender=True)
129
-
130
- @property
131
- def messages_describe(self) -> Dict[str, Any]:
132
- return {
133
- "total_messages": len(self.messages),
134
- "summary_by_role": self._info(),
135
- "summary_by_sender": self._info(use_sender=True),
136
- "instruction_sets": self.instruction_sets,
137
- "registered_tools": self.tool_manager.registry,
138
- "messages": [
139
- msg.to_dict() for _, msg in self.messages.iterrows()
140
- ],
141
- }
142
-
143
- @property
144
- def has_tools(self) -> bool:
145
- return self.tool_manager.registry != {}
146
-
147
- # ----- tool manager methods ----- #
148
-
149
- def register_tools(self, tools: Union[Tool, List[Tool]]):
150
- """
151
- Registers a tool or a list of tools with the branch's tool manager.
152
-
153
- This makes the tools available for use within the conversation.
154
-
155
- Args:
156
- tools (Union[Tool, List[Tool]]): A single Tool instance or a list of Tool instances to be registered.
157
-
158
- Examples:
159
- >>> branch.register_tools(tool)
160
- >>> branch.register_tools([tool1, tool2])
161
- """
162
- if not isinstance(tools, list):
163
- tools = [tools]
164
- self.tool_manager.register_tools(tools=tools)
165
-
166
- def delete_tool(self, tools: Union[Tool, List[Tool], str, List[str]], verbose=True) -> bool:
167
- """
168
- Deletes one or more tools from the branch's tool manager registry.
169
-
170
- This can be done using either tool instances or their names.
171
-
172
- Args:
173
- tools (Union[Tool, List[Tool], str, List[str]]): A single Tool instance, a list of Tool instances, a tool name, or a list of tool names to be deleted.
174
- verbose (bool): If True, prints a success message upon successful deletion. Defaults to True.
175
-
176
- Returns:
177
- bool: True if the tool(s) were successfully deleted, False otherwise.
178
-
179
- Examples:
180
- >>> branch.delete_tool("tool_name")
181
- >>> branch.delete_tool(["tool_name1", "tool_name2"])
182
- >>> branch.delete_tool(tool_instance)
183
- >>> branch.delete_tool([tool_instance1, tool_instance2])
184
- """
185
- if isinstance(tools, list):
186
- if is_same_dtype(tools, str):
187
- for tool in tools:
188
- if tool in self.tool_manager.registry:
189
- self.tool_manager.registry.pop(tool)
190
- if verbose:
191
- print("tools successfully deleted")
192
- return True
193
- elif is_same_dtype(tools, Tool):
194
- for tool in tools:
195
- if tool.name in self.tool_manager.registry:
196
- self.tool_manager.registry.pop(tool.name)
197
- if verbose:
198
- print("tools successfully deleted")
199
- return True
200
- if verbose:
201
- print("tools deletion failed")
202
- return False
203
-
204
- # ----- branch manipulation ----- #
205
- def clone(self) -> 'Branch':
206
- """
207
- Creates a deep copy of the current Branch instance.
208
-
209
- This method duplicates the Branch's state, including its messages, instruction sets, and tool registrations, but creates a new ToolManager instance for the cloned branch.
210
-
211
- Returns:
212
- Branch: A new Branch instance that is a deep copy of the current instance.
213
-
214
- Examples:
215
- >>> cloned_branch = branch.clone()
216
- """
217
- cloned = Branch(
218
- messages=self.messages.copy(),
219
- instruction_sets=self.instruction_sets.copy(),
220
- tool_manager=ToolManager()
221
- )
222
- tools = [
223
- tool for tool in self.tool_manager.registry.values()]
224
-
225
- cloned.register_tools(tools)
226
-
227
- return cloned
228
-
229
- def merge_branch(self, branch: 'Branch', update: bool = True):
230
- """
231
- Merges another branch into the current Branch instance.
232
-
233
- Incorporates messages, instruction sets, and tool registrations from the specified branch. Optionally updates existing instruction sets and tools if duplicates are found.
234
-
235
- Args:
236
- branch (Branch): The branch to merge into the current branch.
237
- update (bool): If True, existing instruction sets and tools are updated with those from the merged branch. Defaults to True.
238
-
239
- Examples:
240
- >>> branch.merge_branch(another_branch)
241
- """
242
-
243
- message_copy = branch.messages.copy()
244
- self.messages = self.messages.merge(message_copy, how='outer')
245
-
246
- if update:
247
- self.instruction_sets.update(branch.instruction_sets)
248
- self.tool_manager.registry.update(
249
- branch.tool_manager.registry
250
- )
251
- else:
252
- for key, value in branch.instruction_sets.items():
253
- if key not in self.instruction_sets:
254
- self.instruction_sets[key] = value
255
-
256
- for key, value in branch.tool_manager.registry.items():
257
- if key not in self.tool_manager.registry:
258
- self.tool_manager.registry[key] = value
259
-
260
-
261
- # ----- intra-branch communication methods ----- #
262
- def send(self, to_name, title, package):
263
- """
264
- Sends a request package to a specified recipient.
265
-
266
- Packages are queued in `pending_outs` for dispatch. The function doesn't immediately send the package but prepares it for delivery.
267
-
268
- Args:
269
- to_name (str): The name of the recipient branch.
270
- title (str): The title or category of the request (e.g., 'messages', 'tool', 'service', 'llmconfig').
271
- package (Any): The actual data or object to be sent, its expected type depends on the title.
272
-
273
- Examples:
274
- >>> branch.send("another_branch", "messages", message_dataframe)
275
- >>> branch.send("service_branch", "service", service_config)
276
- """
277
- request = Request(from_name=self.name, to_name=to_name, title=title, request=package)
278
- self.pending_outs.append(request)
279
-
280
- def receive(self, from_name, messages=True, tool=True, service=True, llmconfig=True):
281
- """
282
- Processes and integrates received request packages based on their titles.
283
-
284
- Handles incoming requests by updating the branch's state with the received data. It can selectively process requests based on the type specified by the `title` of the request.
285
-
286
- Args:
287
- from_name (str): The name of the sender whose packages are to be processed.
288
- messages (bool): If True, processes 'messages' requests. Defaults to True.
289
- tool (bool): If True, processes 'tool' requests. Defaults to True.
290
- service (bool): If True, processes 'service' requests. Defaults to True.
291
- llmconfig (bool): If True, processes 'llmconfig' requests. Defaults to True.
292
-
293
- Raises:
294
- ValueError: If no package is found from the specified sender, or if any of the packages have an invalid format.
295
-
296
- Examples:
297
- >>> branch.receive("another_branch")
298
- """
299
- skipped_requests = deque()
300
- if from_name not in self.pending_ins:
301
- raise ValueError(f'No package from {from_name}')
302
- while self.pending_ins[from_name]:
303
- request = self.pending_ins[from_name].popleft()
304
-
305
- if request.title == 'messages' and messages:
306
- if not isinstance(request.request, pd.DataFrame):
307
- raise ValueError('Invalid messages format')
308
- validate_messages(request.request)
309
- self.messages = self.messages.merge(request.request, how='outer')
310
- continue
311
-
312
- elif request.title == 'tool' and tool:
313
- if not isinstance(request.request, Tool):
314
- raise ValueError('Invalid tool format')
315
- self.tool_manager.register_tools([request.request])
316
-
317
- elif request.title == 'service' and service:
318
- if not isinstance(request.request, BaseService):
319
- raise ValueError('Invalid service format')
320
- self.service = request.request
321
-
322
- elif request.title == 'llmconfig' and llmconfig:
323
- if not isinstance(request.request, dict):
324
- raise ValueError('Invalid llmconfig format')
325
- self.llmconfig.update(request.request)
326
-
327
- else:
328
- skipped_requests.append(request)
329
-
330
- self.pending_ins[from_name] = skipped_requests
331
-
332
- def receive_all(self):
333
- """
334
- Processes all pending incoming requests from all senders.
335
-
336
- This method iterates through all senders with pending requests and processes each using the `receive` method. It ensures that all queued incoming data is integrated into the branch's state.
337
-
338
- Examples:
339
- >>> branch.receive_all()
340
- """
341
- for key in list(self.pending_ins.keys()):
342
- self.receive(key)
343
-
344
-
345
- # ----- service methods ----- #
346
-
347
- async def call_chatcompletion(self, sender=None, with_sender=False, **kwargs):
348
- """
349
- Asynchronously calls the chat completion service with the current message queue.
350
-
351
- This method prepares the messages for chat completion, sends the request to the configured service, and handles the response. The method supports additional keyword arguments that are passed directly to the service.
352
-
353
- Args:
354
- sender (Optional[str]): The name of the sender to be included in the chat completion request. Defaults to None.
355
- with_sender (bool): If True, includes the sender's name in the messages. Defaults to False.
356
- **kwargs: Arbitrary keyword arguments passed directly to the chat completion service.
357
-
358
- Examples:
359
- >>> await branch.call_chatcompletion()
360
- """
361
- messages = self.chat_messages if not with_sender else self.chat_messages_with_sender
362
- payload, completion = await self.service.serve_chat(
363
- messages=messages, **kwargs)
364
- if "choices" in completion:
365
- add_msg_config = {"response":completion['choices'][0]}
366
- if sender is not None:
367
- add_msg_config["sender"] = sender
368
-
369
- self.add_message(**add_msg_config)
370
- self.status_tracker.num_tasks_succeeded += 1
371
- else:
372
- self.status_tracker.num_tasks_failed += 1
373
-
374
- # ----- chat methods ----- #
375
-
376
- async def chat(
377
- self,
378
- instruction: Union[Instruction, str],
379
- context: Optional[Any] = None,
380
- sender: Optional[str] = None,
381
- system: Optional[Union[System, str, Dict[str, Any]]] = None,
382
- tools: Union[bool, Tool, List[Tool], str, List[str]] = False,
383
- out: bool = True,
384
- invoke: bool = True,
385
- **kwargs
386
- ) -> Any:
387
- """
388
- Asynchronously handles a chat interaction within the branch.
389
-
390
- This method adds a new message based on the provided instruction, optionally using specified tools, and processes the chat completion.
391
-
392
- Args:
393
- instruction (Union[Instruction, str]): The instruction or query to process.
394
- context (Optional[Any]): Additional context for the chat completion request. Defaults to None.
395
- sender (Optional[str]): The name of the sender. Defaults to None.
396
- system (Optional[Union[System, str, Dict[str, Any]]]): System message or configuration. Defaults to None.
397
- tools (Union[bool, Tool, List[Tool], str, List[str]]): Specifies if and which tools to use in the chat. Defaults to False.
398
- out (bool): If True, the output of the chat completion is returned. Defaults to True.
399
- invoke (bool): If True, invokes any action as determined by the chat completion. Defaults to True.
400
- **kwargs: Arbitrary keyword arguments for further customization.
401
-
402
- Returns:
403
- Any: The result of the chat interaction, which could be varied based on the input and configuration.
404
-
405
- Examples:
406
- >>> result = await branch.chat("How's the weather?")
407
- """
408
-
409
- if system:
410
- self.change_first_system_message(system)
411
- self.add_message(instruction=instruction, context=context, sender=sender)
412
-
413
- if 'tool_parsed' in kwargs:
414
- kwargs.pop('tool_parsed')
415
- tool_kwarg = {'tools': tools}
416
- kwargs = {**tool_kwarg, **kwargs}
417
- else:
418
- if tools and self.has_tools:
419
- kwargs = self.tool_manager._tool_parser(tools=tools, **kwargs)
420
-
421
- config = {**self.llmconfig, **kwargs}
422
- if sender is not None:
423
- config.update({"sender": sender})
424
-
425
- await self.call_chatcompletion(**config)
426
-
427
- async def _output():
428
- content_ = as_dict(self.messages.content.iloc[-1])
429
- if invoke:
430
- try:
431
- tool_uses = content_
432
- func_calls = lcall(
433
- [as_dict(i) for i in tool_uses["action_list"]],
434
- self.tool_manager.get_function_call
435
- )
436
-
437
- outs = await alcall(func_calls, self.tool_manager.invoke)
438
- outs = to_list(outs, flatten=True)
439
-
440
- for out_, f in zip(outs, func_calls):
441
- self.add_message(
442
- response={
443
- "function": f[0],
444
- "arguments": f[1],
445
- "output": out_
446
- }
447
- )
448
- except:
449
- pass
450
- if out:
451
- if (
452
- len(content_.items()) == 1
453
- and len(get_flattened_keys(content_)) == 1
454
- ):
455
- key = get_flattened_keys(content_)[0]
456
- return content_[key]
457
- return content_
458
-
459
- return await _output()
460
-
461
- async def ReAct(
462
- self,
463
- instruction: Union[Instruction, str],
464
- context = None,
465
- sender = None,
466
- system = None,
467
- tools = None,
468
- num_rounds: int = 1,
469
- **kwargs
470
- ):
471
- """
472
- Performs a sequence of reasoning and action based on the given instruction over multiple rounds.
473
-
474
- In each round, the method reflects on the task, devises an action plan using available tools, and invokes the necessary tool usage to execute the plan.
475
-
476
- Args:
477
- instruction (Union[Instruction, str]): The initial task or question to start the reasoning and action process.
478
- context: Optional context to influence the reasoning process. Defaults to None.
479
- sender (Optional[str]): The name of the sender initiating the ReAct process. Defaults to None.
480
- system: Optional system message or configuration to be considered during the process. Defaults to None.
481
- tools: Specifies the tools to be considered for action plans. Defaults to None.
482
- num_rounds (int): The number of reasoning-action rounds to be performed. Defaults to 1.
483
- **kwargs: Arbitrary keyword arguments for further customization.
484
-
485
- Returns:
486
- The final output after completing the specified number of reasoning-action rounds.
487
-
488
- Examples:
489
- >>> await branch.ReAct("Prepare a report on recent sales trends.", num_rounds=2)
490
- """
491
- if tools is not None:
492
- if isinstance(tools, list) and isinstance(tools[0], Tool):
493
- self.register_tools(tools)
494
-
495
- if self.tool_manager.registry == {}:
496
- raise ValueError("No tools found, You need to register tools for ReAct (reason-action)")
497
-
498
- else:
499
- kwargs = self.tool_manager._tool_parser(tools=True, **kwargs)
500
-
501
- out = ''
502
- i = 0
503
- while i < num_rounds:
504
- prompt = f"""you have {(num_rounds-i)*2} step left in current task. if available, integrate previous tool responses. perform reasoning and prepare action plan according to available tools only, apply divide and conquer technique.
505
- """
506
- instruct = {"Notice": prompt}
507
-
508
- if i == 0:
509
- instruct["Task"] = instruction
510
- out = await self.chat(
511
- instruction=instruct, context=context,
512
- system=system, sender=sender, **kwargs
513
- )
514
-
515
- elif i >0:
516
- out = await self.chat(
517
- instruction=instruct, sender=sender, **kwargs
518
- )
519
-
520
- prompt = f"""
521
- you have {(num_rounds-i)*2-1} step left in current task, invoke tool usage to perform actions
522
- """
523
- out = await self.chat(prompt, tool_choice="auto", tool_parsed=True, sender=sender, **kwargs)
524
-
525
- i += 1
526
- if not self._is_invoked():
527
- return out
528
-
529
-
530
- if self._is_invoked():
531
- prompt = """
532
- present the final result to user
533
- """
534
- return await self.chat(prompt, sender=sender, tool_parsed=True, **kwargs)
535
- else:
536
- return out
537
-
538
- # async def auto_ReAct(
539
- # self,
540
- # instruction: Union[Instruction, str],
541
- # context = None,
542
- # sender = None,
543
- # system = None,
544
- # tools = None,
545
- # max_rounds: int = 1,
546
-
547
- # fallback: Optional[Callable] = None,
548
- # fallback_kwargs: Optional[Dict] = None,
549
- # **kwargs
550
- # ):
551
- # if tools is not None:
552
- # if isinstance(tools, list) and isinstance(tools[0], Tool):
553
- # self.register_tools(tools)
554
-
555
- # if self.tool_manager.registry == {}:
556
- # raise ValueError("No tools found, You need to register tools for ReAct (reason-action)")
557
-
558
- # else:
559
- # kwargs = self.tool_manager._tool_parser(tools=True, **kwargs)
560
-
561
- # i = 0
562
- # while i < max_rounds:
563
- # prompt = f"""
564
- # you have {(max_rounds-i)*2} step left in current task. reflect, perform
565
- # reason for action plan according to available tools only, apply divide and conquer technique, retain from invoking functions
566
- # """
567
- # instruct = {"Notice": prompt}
568
-
569
- # if i == 0:
570
- # instruct["Task"] = instruction
571
- # await self.chat(
572
- # instruction=instruct, context=context,
573
- # system=system, out=False, sender=sender, **kwargs
574
- # )
575
-
576
- # elif i >0:
577
- # await self.chat(
578
- # instruction=instruct, out=False, sender=sender, **kwargs
579
- # )
580
-
581
- # prompt = f"""
582
- # you have {(max_rounds-i)*2-1} step left in current task, invoke tool usage to perform the action
583
- # """
584
- # await self.chat(prompt, tool_choice="auto", tool_parsed=True, out=False,sender=sender, **kwargs)
585
-
586
- # i += 1
587
-
588
- # if self._is_invoked():
589
- # if fallback is not None:
590
- # if asyncio.iscoroutinefunction(fallback):
591
- # return await fallback(**fallback_kwargs)
592
- # else:
593
- # return fallback(**fallback_kwargs)
594
- # prompt = """
595
- # present the final result to user
596
- # """
597
- # return await self.chat(prompt, sender=sender, tool_parsed=True, **kwargs)
598
-
599
- async def auto_followup(
600
- self,
601
- instruction: Union[Instruction, str],
602
- context = None,
603
- sender = None,
604
- system = None,
605
- tools: Union[bool, Tool, List[Tool], str, List[str], List[Dict]] = False,
606
- max_followup: int = 3,
607
- out=True,
608
- **kwargs
609
- ) -> None:
610
-
611
- """
612
- Automatically performs follow-up actions until a specified condition is met or the maximum number of follow-ups is reached.
613
-
614
- This method allows for iterative refinement and follow-up based on the instruction, using available tools and considering feedback from each step.
615
-
616
- Args:
617
- instruction (Union[Instruction, str]): The instruction to initiate the follow-up process.
618
- context: Optional context relevant to the follow-up actions. Defaults to None.
619
- sender (Optional[str]): The name of the sender. Defaults to None.
620
- system: Optional system configuration affecting the follow-up process. Defaults to None.
621
- tools (Union[bool, Tool, List[Tool], str, List[str], List[Dict]]): Specifies the tools to be used during follow-up actions. Defaults to False.
622
- max_followup (int): The maximum number of follow-up iterations. Defaults to 3.
623
- out (bool): If True, the final result is returned. Defaults to True.
624
- **kwargs: Arbitrary keyword arguments for additional customization.
625
-
626
- Returns:
627
- The final result after all follow-up actions are completed, if `out` is True.
628
-
629
- Examples:
630
- >>> await branch.auto_followup("Update the database with new entries.", max_followup=2)
631
- """
632
-
633
- if self.tool_manager.registry != {} and tools:
634
- kwargs = self.tool_manager._tool_parser(tools=tools, **kwargs)
635
-
636
- n_tries = 0
637
- while (max_followup - n_tries) > 0:
638
- prompt = f"""
639
- In the current task you are allowed a maximum of another {max_followup-n_tries} followup chats.
640
- if further actions are needed, invoke tools usage. If you are done, present the final result
641
- to user without further tool usage
642
- """
643
- if n_tries > 0:
644
- _out = await self.chat(prompt, sender=sender, tool_choice="auto", tool_parsed=True, **kwargs)
645
- n_tries += 1
646
-
647
- if not self._is_invoked():
648
- return _out if out else None
649
-
650
- elif n_tries == 0:
651
- instruct = {"notice": prompt, "task": instruction}
652
- out = await self.chat(
653
- instruct, context=context, system=system, sender=sender, tool_choice="auto",
654
- tool_parsed=True, **kwargs
655
- )
656
- n_tries += 1
657
-
658
- if not self._is_invoked():
659
- return _out if out else None
660
-
661
- if self._is_invoked():
662
- """
663
- In the current task, you are at your last step, present the final result to user
664
- """
665
- return await self.chat(instruction, sender=sender, tool_parsed=True, **kwargs)
666
-
667
- # async def followup(
668
- # self,
669
- # instruction: Union[Instruction, str],
670
- # context = None,
671
- # sender = None,
672
- # system = None,
673
- # tools: Union[bool, Tool, List[Tool], str, List[str], List[Dict]] = False,
674
- # max_followup: int = 3,
675
- # out=True,
676
- # **kwargs
677
- # ) -> None:
678
-
679
- # """
680
- # auto tool usages until LLM decides done. Then presents final results.
681
- # """
682
-
683
- # if self.tool_manager.registry != {} and tools:
684
- # kwargs = self.tool_manager._tool_parser(tools=tools, **kwargs)
685
-
686
- # n_tries = 0
687
- # while (max_followup - n_tries) > 0:
688
- # prompt = f"""
689
- # In the current task you are allowed a maximum of another {max_followup-n_tries} followup chats.
690
- # if further actions are needed, invoke tools usage. If you are done, present the final result
691
- # to user without further tool usage.
692
- # """
693
- # if n_tries > 0:
694
- # _out = await self.chat(prompt, sender=sender, tool_choice="auto", tool_parsed=True, **kwargs)
695
- # n_tries += 1
696
-
697
- # if not self._is_invoked():
698
- # return _out if out else None
699
-
700
- # elif n_tries == 0:
701
- # instruct = {"notice": prompt, "task": instruction}
702
- # out = await self.chat(
703
- # instruct, context=context, system=system, sender=sender, tool_choice="auto",
704
- # tool_parsed=True, **kwargs
705
- # )
706
- # n_tries += 1
707
-
708
- # if not self._is_invoked():
709
- # return _out if out else None
710
-
711
- def _add_service(self, service, llmconfig):
712
- service = service or OpenAIService()
713
- self.service=service
714
- if llmconfig:
715
- self.llmconfig = llmconfig
716
- else:
717
- if isinstance(service, OpenAIService):
718
- self.llmconfig = oai_schema["chat/completions"]["config"]
719
- elif isinstance(service, OpenRouterService):
720
- self.llmconfig = openrouter_schema["chat/completions"]["config"]
721
- else:
722
- self.llmconfig = {}
723
-
724
-
725
- def _to_chatcompletion_message(self, with_sender=False) -> List[Dict[str, Any]]:
726
- message = []
727
-
728
- for _, row in self.messages.iterrows():
729
- content_ = row['content']
730
- if content_.startswith('Sender'):
731
- content_ = content_.split(':', 1)[1]
732
-
733
- if isinstance(content_, str):
734
- try:
735
- content_ = json.dumps(as_dict(content_))
736
- except Exception as e:
737
- raise ValueError(f"Error in serealizing, {row['node_id']} {content_}: {e}")
738
-
739
- out = {"role": row['role'], "content": content_}
740
- if with_sender:
741
- out['content'] = f"Sender {row['sender']}: {content_}"
742
-
743
- message.append(out)
744
- return message
745
-
746
-
747
- def _is_invoked(self) -> bool:
748
- """
749
- Check if the conversation has been invoked with an action response.
750
-
751
- Returns:
752
- bool: True if the conversation has been invoked, False otherwise.
753
-
754
- """
755
- content = self.messages.iloc[-1]['content']
756
- try:
757
- if (
758
- as_dict(content)['action_response'].keys() >= {'function', 'arguments', 'output'}
759
- ):
760
- return True
761
- except:
762
- return False
763
-
764
-
765
-
766
-
767
- # def add_instruction_set(self, name: str, instruction_set: InstructionSet):
768
- # """
769
- # Add an instruction set to the conversation.
770
- #
771
- # Args:
772
- # name (str): The name of the instruction set.
773
- # instruction_set (InstructionSet): The instruction set to add.
774
- #
775
- # Examples:
776
- # >>> branch.add_instruction_set("greet", InstructionSet(instructions=["Hello", "Hi"]))
777
- # """
778
- # self.instruction_sets[name] = instruction_set
779
-
780
- # def remove_instruction_set(self, name: str) -> bool:
781
- # """
782
- # Remove an instruction set from the conversation.
783
- #
784
- # Args:
785
- # name (str): The name of the instruction set to remove.
786
- #
787
- # Returns:
788
- # bool: True if the instruction set was removed, False otherwise.
789
- #
790
- # Examples:
791
- # >>> branch.remove_instruction_set("greet")
792
- # True
793
- # """
794
- # return self.instruction_sets.pop(name)
795
-
796
- # async def instruction_set_auto_followup(
797
- # self,
798
- # instruction_set: InstructionSet,
799
- # num: Union[int, List[int]] = 3,
800
- # **kwargs
801
- # ) -> None:
802
- # """
803
- # Automatically perform follow-up chats for an entire instruction set.
804
- #
805
- # This method asynchronously conducts follow-up chats for each instruction in the provided instruction set,
806
- # handling tool invocations as specified.
807
- #
808
- # Args:
809
- # instruction_set (InstructionSet): The instruction set to process.
810
- # num (Union[int, List[int]]): The maximum number of follow-up chats to perform for each instruction,
811
- # or a list of maximum numbers corresponding to each instruction.
812
- # **kwargs: Additional keyword arguments to pass to the chat completion service.
813
- #
814
- # Raises:
815
- # ValueError: If the length of `num` as a list does not match the number of instructions in the set.
816
- #
817
- # Examples:
818
- # >>> instruction_set = InstructionSet(instructions=["What's the weather?", "And for tomorrow?"])
819
- # >>> await branch.instruction_set_auto_followup(instruction_set)
820
- # """
821
- #
822
- # if isinstance(num, List):
823
- # if len(num) != instruction_set.instruct_len:
824
- # raise ValueError(
825
- # 'Unmatched auto_followup num size and instructions set size'
826
- # )
827
- # current_instruct_node = instruction_set.get_instruction_by_id(
828
- # instruction_set.first_instruct
829
- # )
830
- # for i in range(instruction_set.instruct_len):
831
- # num_ = num if isinstance(num, int) else num[i]
832
- # tools = instruction_set.get_tools(current_instruct_node)
833
- # if tools:
834
- # await self.auto_followup(
835
- # current_instruct_node, num=num_, tools=tools, self=self, **kwargs
836
- # )
837
- # else:
838
- # await self.chat(current_instruct_node)
839
- # current_instruct_node = instruction_set.get_next_instruction(
840
- # current_instruct_node
841
- # )