lionagi 0.0.111__py3-none-any.whl → 0.0.113__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 (91) hide show
  1. lionagi/__init__.py +7 -2
  2. lionagi/bridge/__init__.py +7 -0
  3. lionagi/bridge/langchain.py +131 -0
  4. lionagi/bridge/llama_index.py +157 -0
  5. lionagi/configs/__init__.py +7 -0
  6. lionagi/configs/oai_configs.py +49 -0
  7. lionagi/configs/openrouter_config.py +49 -0
  8. lionagi/core/__init__.py +15 -0
  9. lionagi/{session/conversation.py → core/conversations.py} +10 -17
  10. lionagi/core/flows.py +1 -0
  11. lionagi/core/instruction_sets.py +1 -0
  12. lionagi/{session/message.py → core/messages.py} +5 -5
  13. lionagi/core/sessions.py +262 -0
  14. lionagi/datastore/__init__.py +1 -0
  15. lionagi/datastore/chroma.py +1 -0
  16. lionagi/datastore/deeplake.py +1 -0
  17. lionagi/datastore/elasticsearch.py +1 -0
  18. lionagi/datastore/lantern.py +1 -0
  19. lionagi/datastore/pinecone.py +1 -0
  20. lionagi/datastore/postgres.py +1 -0
  21. lionagi/datastore/qdrant.py +1 -0
  22. lionagi/loader/__init__.py +12 -0
  23. lionagi/loader/chunker.py +157 -0
  24. lionagi/loader/reader.py +124 -0
  25. lionagi/objs/__init__.py +7 -0
  26. lionagi/objs/messenger.py +163 -0
  27. lionagi/objs/tool_registry.py +247 -0
  28. lionagi/schema/__init__.py +11 -0
  29. lionagi/schema/base_condition.py +1 -0
  30. lionagi/schema/base_schema.py +239 -0
  31. lionagi/schema/base_tool.py +9 -0
  32. lionagi/schema/data_logger.py +94 -0
  33. lionagi/services/__init__.py +14 -0
  34. lionagi/services/anthropic.py +1 -0
  35. lionagi/services/anyscale.py +0 -0
  36. lionagi/services/azure.py +1 -0
  37. lionagi/{api/oai_service.py → services/base_api_service.py} +74 -148
  38. lionagi/services/bedrock.py +0 -0
  39. lionagi/services/chatcompletion.py +48 -0
  40. lionagi/services/everlyai.py +0 -0
  41. lionagi/services/gemini.py +0 -0
  42. lionagi/services/gpt4all.py +0 -0
  43. lionagi/services/huggingface.py +0 -0
  44. lionagi/services/litellm.py +1 -0
  45. lionagi/services/localai.py +0 -0
  46. lionagi/services/mistralai.py +0 -0
  47. lionagi/services/oai.py +34 -0
  48. lionagi/services/ollama.py +1 -0
  49. lionagi/services/openllm.py +0 -0
  50. lionagi/services/openrouter.py +32 -0
  51. lionagi/services/perplexity.py +0 -0
  52. lionagi/services/predibase.py +0 -0
  53. lionagi/services/rungpt.py +0 -0
  54. lionagi/services/service_objs.py +282 -0
  55. lionagi/services/vllm.py +0 -0
  56. lionagi/services/xinference.py +0 -0
  57. lionagi/structure/__init__.py +7 -0
  58. lionagi/structure/relationship.py +128 -0
  59. lionagi/structure/structure.py +160 -0
  60. lionagi/tests/__init__.py +0 -0
  61. lionagi/tests/test_flatten_util.py +426 -0
  62. lionagi/tools/__init__.py +0 -0
  63. lionagi/tools/coder.py +1 -0
  64. lionagi/tools/planner.py +1 -0
  65. lionagi/tools/prompter.py +1 -0
  66. lionagi/tools/sandbox.py +1 -0
  67. lionagi/tools/scorer.py +1 -0
  68. lionagi/tools/summarizer.py +1 -0
  69. lionagi/tools/validator.py +1 -0
  70. lionagi/utils/__init__.py +46 -8
  71. lionagi/utils/api_util.py +63 -416
  72. lionagi/utils/call_util.py +347 -0
  73. lionagi/utils/flat_util.py +540 -0
  74. lionagi/utils/io_util.py +102 -0
  75. lionagi/utils/load_utils.py +190 -0
  76. lionagi/utils/sys_util.py +85 -660
  77. lionagi/utils/tool_util.py +82 -199
  78. lionagi/utils/type_util.py +81 -0
  79. lionagi/version.py +1 -1
  80. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/METADATA +44 -15
  81. lionagi-0.0.113.dist-info/RECORD +84 -0
  82. lionagi/api/__init__.py +0 -8
  83. lionagi/api/oai_config.py +0 -16
  84. lionagi/session/__init__.py +0 -7
  85. lionagi/session/session.py +0 -380
  86. lionagi/utils/doc_util.py +0 -331
  87. lionagi/utils/log_util.py +0 -86
  88. lionagi-0.0.111.dist-info/RECORD +0 -20
  89. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/LICENSE +0 -0
  90. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/WHEEL +0 -0
  91. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,282 @@
1
+ import asyncio
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, Dict, NoReturn
5
+
6
+
7
+ # should be fine ------------------------------------------------------------------
8
+ @dataclass
9
+ class StatusTracker:
10
+ """
11
+ Class for keeping track of various task statuses.
12
+
13
+ This class serves as a simple way to monitor different types of task
14
+ outcomes and errors within a system. It uses dataclasses for easy
15
+ creation and management of state.
16
+
17
+ Attributes:
18
+ num_tasks_started:
19
+ The number of tasks that have been initiated.
20
+ num_tasks_in_progress:
21
+ The number of tasks currently being processed.
22
+ num_tasks_succeeded:
23
+ The number of tasks that have completed successfully.
24
+ num_tasks_failed:
25
+ The number of tasks that have failed.
26
+ num_rate_limit_errors:
27
+ The number of tasks that failed due to rate limiting.
28
+ num_api_errors:
29
+ The number of tasks that failed due to API errors.
30
+ num_other_errors:
31
+ The number of tasks that failed due to other errors.
32
+ """
33
+ num_tasks_started: int = 0
34
+ num_tasks_in_progress: int = 0
35
+ num_tasks_succeeded: int = 0
36
+ num_tasks_failed: int = 0
37
+ num_rate_limit_errors: int = 0
38
+ num_api_errors: int = 0
39
+ num_other_errors: int = 0
40
+
41
+
42
+ class AsyncQueue:
43
+ """
44
+ A queue class that handles asynchronous operations using asyncio.
45
+
46
+ This class provides an asynchronous queue that can enqueue items, process them
47
+ asynchronously, and support graceful shutdowns. It is designed to facilitate
48
+ concurrent task processing in an orderly and controlled manner.
49
+
50
+ Attributes:
51
+ queue (asyncio.Queue):
52
+ A queue to hold items for asynchronous processing.
53
+ _stop_event (asyncio.Event):
54
+ An event to signal when the queue should stop processing.
55
+
56
+ Methods:
57
+ enqueue(item):
58
+ Add an item to the queue for processing.
59
+ dequeue():
60
+ Remove and return an item from the queue.
61
+ join():
62
+ Wait until all items in the queue have been processed.
63
+ stop():
64
+ Signal to stop processing new items in the queue.
65
+ stopped():
66
+ Check if the queue has been signaled to stop.
67
+ process_requests(func):
68
+ Process items using a provided function.
69
+ """
70
+
71
+ def __init__(self) -> None:
72
+ """
73
+ Initializes an AsyncQueue object with an empty asyncio Queue and a stop event.
74
+ """
75
+ self.queue = asyncio.Queue()
76
+ self._stop_event = asyncio.Event()
77
+
78
+ async def enqueue(self, item: Any) -> None:
79
+ """
80
+ Asynchronously add an item to the queue for processing.
81
+
82
+ Parameters:
83
+ item (Any): The item to be added to the queue.
84
+
85
+ Example:
86
+ >>> async_queue = AsyncQueue()
87
+ >>> asyncio.run(async_queue.enqueue('Task 1'))
88
+ """
89
+ await self.queue.put(item)
90
+
91
+ async def dequeue(self) -> Any:
92
+ """
93
+ Asynchronously remove and return an item from the queue.
94
+
95
+ If the queue is empty, this method will wait until an item is available.
96
+
97
+ Returns:
98
+ Any: The next item from the queue.
99
+
100
+ Example:
101
+ >>> async_queue = AsyncQueue()
102
+ >>> asyncio.run(async_queue.enqueue('Task 1'))
103
+ >>> asyncio.run(async_queue.dequeue())
104
+ 'Task 1'
105
+ """
106
+ return await self.queue.get()
107
+
108
+ async def join(self) -> None:
109
+ """
110
+ Asynchronously wait until all items in the queue have been processed.
111
+
112
+ This method blocks until every item that has been enqueued is processed,
113
+ ensuring that all tasks are completed.
114
+
115
+ Example:
116
+ >>> async_queue = AsyncQueue()
117
+ >>> asyncio.run(async_queue.enqueue('Task 1'))
118
+ >>> asyncio.run(async_queue.join()) # This will block until 'Task 1' is processed.
119
+ """
120
+ await self.queue.join()
121
+
122
+ async def stop(self) -> None:
123
+ """
124
+ Signal the queue to stop processing new items.
125
+
126
+ Once called, the queue will not process any new items after the current ones
127
+ are completed, allowing for a graceful shutdown.
128
+
129
+ Example:
130
+ >>> async_queue = AsyncQueue()
131
+ >>> asyncio.run(async_queue.stop()) # This signals the queue to stop processing.
132
+ """
133
+ self._stop_event.set()
134
+
135
+ def stopped(self) -> bool:
136
+ """
137
+ Check if the queue has been signaled to stop processing.
138
+
139
+ Returns:
140
+ bool: True if a stop has been signaled, False otherwise.
141
+
142
+ Example:
143
+ >>> async_queue = AsyncQueue()
144
+ >>> asyncio.run(async_queue.stop())
145
+ >>> async_queue.stopped()
146
+ True
147
+ """
148
+ return self._stop_event.is_set()
149
+
150
+ async def process_requests(self, func: Callable[[Any], Any]) -> None:
151
+ """
152
+ Asynchronously process items from the queue using the provided function.
153
+
154
+ Continuously dequeues items and applies the given function to each.
155
+ The processing stops when the queue is signaled to stop or a sentinel value (`None`) is dequeued.
156
+
157
+ Parameters:
158
+ func (Callable[[Any], Any]): A coroutine function to process items from the queue.
159
+
160
+ Example:
161
+ >>> async def sample_processing(task):
162
+ ... print("Processing:", task)
163
+ >>> async_queue = AsyncQueue()
164
+ >>> asyncio.run(async_queue.enqueue('Task 1'))
165
+ >>> asyncio.run(async_queue.process_requests(sample_processing))
166
+ Processing: Task 1
167
+ """
168
+ while not self.stopped():
169
+ item = await self.dequeue()
170
+ if item is None: # Using `None` as a sentinel value to cease processing.
171
+ await self.stop()
172
+ break
173
+ await func(item)
174
+
175
+
176
+ class BaseService(ABC):
177
+
178
+ @abstractmethod
179
+ def __init__(self) -> None:
180
+ ...
181
+
182
+ @abstractmethod
183
+ async def serve(self) -> Any:
184
+ ...
185
+
186
+
187
+
188
+ class RateLimiter(ABC):
189
+ """
190
+ An abstract base class for rate limiting mechanisms.
191
+
192
+ This class defines a structure for rate limiters, which are used to control the frequency
193
+ of requests sent to or received from a network interface controller or an API.
194
+
195
+ Attributes:
196
+ max_requests_per_minute (int):
197
+ Maximum number of requests permitted per minute.
198
+ max_tokens_per_minute (int):
199
+ Maximum number of tokens that can accumulate per minute.
200
+ available_request_capacity (int):
201
+ Current number of available request slots.
202
+ available_token_capacity (int):
203
+ Current number of available tokens.
204
+
205
+ Methods:
206
+ rate_limit_replenisher:
207
+ Coroutine to replenish rate limits over time.
208
+ calculate_num_token:
209
+ Method to calculate required tokens for a request.
210
+ """
211
+
212
+ def __init__(self, max_requests_per_minute: int, max_tokens_per_minute: int) -> None:
213
+ """
214
+ Initializes the RateLimiter with specified maximum request and token limits.
215
+
216
+ Parameters:
217
+ max_requests_per_minute (int): Maximum requests allowed per minute.
218
+
219
+ max_tokens_per_minute (int): Maximum tokens allowed to accumulate per minute.
220
+
221
+ Example:
222
+ >>> class MyRateLimiter(RateLimiter):
223
+ ... async def rate_limit_replenisher(self) -> NoReturn:
224
+ ... # Implementation for rate replenishment.
225
+ ... def calculate_num_token(self, payload: Dict[str, Any], api_endpoint: str) -> int:
226
+ ... # Implementation for token calculation.
227
+ ...
228
+ >>> limiter = MyRateLimiter(100, 200)
229
+ """
230
+ self.max_requests_per_minute = max_requests_per_minute
231
+ self.max_tokens_per_minute = max_tokens_per_minute
232
+ self.available_request_capacity = max_requests_per_minute
233
+ self.available_token_capacity = max_tokens_per_minute
234
+
235
+ @abstractmethod
236
+ async def rate_limit_replenisher(self) -> NoReturn:
237
+ """
238
+ Asynchronously replenishes rate limit capacities.
239
+
240
+ This coroutine should be implemented to periodically restore `available_request_capacity`
241
+ and `available_token_capacity` according to specific rules defined in subclasses.
242
+
243
+ Example:
244
+ >>> class MyRateLimiter(RateLimiter):
245
+ ... async def rate_limit_replenisher(self) -> NoReturn:
246
+ ... while True:
247
+ ... # Replenishment logic here
248
+ ...
249
+ >>> limiter = MyRateLimiter(100, 200)
250
+ """
251
+
252
+ ...
253
+
254
+ @abstractmethod
255
+ def calculate_num_token(self, payload: Dict[str, Any], api_endpoint: str) -> int:
256
+ """
257
+ Calculates required tokens for a request.
258
+
259
+ Subclasses should implement this method to determine the number of tokens needed based
260
+ on the request payload and target endpoint.
261
+
262
+ Parameters:
263
+ payload (Dict[str, Any]): Payload of the request.
264
+
265
+ api_endpoint (str): Target API endpoint for the request.
266
+
267
+ Returns:
268
+ int: Calculated number of tokens required for the request.
269
+
270
+ Example:
271
+ >>> class MyRateLimiter(RateLimiter):
272
+ ... def calculate_num_token(self, payload: Dict[str, Any], api_endpoint: str) -> int:
273
+ ... return len(payload.get('data', '')) // 10
274
+ ...
275
+ >>> limiter = MyRateLimiter(100, 200)
276
+ >>> limiter.calculate_num_token({'data': '12345'}, 'api/send')
277
+ 0
278
+ """
279
+
280
+ ...
281
+
282
+
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ from .relationship import Relationship
2
+ from .structure import Structure
3
+
4
+ __all__=[
5
+ "Relationship",
6
+ "Structure"
7
+ ]
@@ -0,0 +1,128 @@
1
+ from pydantic import Field
2
+ from typing import Dict, Optional, Any
3
+ from ..schema.base_schema import BaseNode
4
+
5
+
6
+ class Relationship(BaseNode):
7
+ """
8
+ Relationship class represents a relationship between two nodes in a graph.
9
+
10
+ Inherits from BaseNode and adds functionality to manage conditions and relationships
11
+ between source and target nodes.
12
+
13
+ Attributes:
14
+ source_node_id (str): The identifier of the source node.
15
+ target_node_id (str): The identifier of the target node.
16
+ condition (Dict[str, Any]): A dictionary representing conditions for the relationship.
17
+ """
18
+
19
+ source_node_id: str
20
+ target_node_id: str
21
+ condition: dict = Field(default={})
22
+
23
+ def add_condition(self, condition: Dict[str, Any]) -> None:
24
+ """
25
+ Adds a condition to the relationship.
26
+
27
+ Parameters:
28
+ condition (Dict[str, Any]): The condition to be added.
29
+ """
30
+ self.condition.update(condition)
31
+
32
+ def remove_condition(self, condition_key: str) -> Any:
33
+ """
34
+ Removes a condition from the relationship.
35
+
36
+ Parameters:
37
+ condition_key (str): The key of the condition to be removed.
38
+
39
+ Returns:
40
+ Any: The value of the removed condition.
41
+
42
+ Raises:
43
+ KeyError: If the condition key is not found.
44
+ """
45
+ if condition_key not in self.condition.keys():
46
+ raise KeyError(f'condition {condition_key} is not found')
47
+ return self.condition.pop(condition_key)
48
+
49
+ def condition_exists(self, condition_key: str) -> bool:
50
+ """
51
+ Checks if a condition exists in the relationship.
52
+
53
+ Parameters:
54
+ condition_key (str): The key of the condition to check.
55
+
56
+ Returns:
57
+ bool: True if the condition exists, False otherwise.
58
+ """
59
+ if condition_key in self.condition.keys():
60
+ return True
61
+ else:
62
+ return False
63
+
64
+ def get_condition(self, condition_key: Optional[str] = None) -> Any:
65
+ """
66
+ Retrieves a specific condition or all conditions of the relationship.
67
+
68
+ Parameters:
69
+ condition_key (Optional[str]): The key of the specific condition. Defaults to None.
70
+
71
+ Returns:
72
+ Any: The requested condition or all conditions if no key is provided.
73
+
74
+ Raises:
75
+ ValueError: If the specified condition key does not exist.
76
+ """
77
+ if condition_key is None:
78
+ return self.condition
79
+ if self.condition_exists(condition_key=condition_key):
80
+ return self.condition[condition_key]
81
+ else:
82
+ raise ValueError(f"Condition {condition_key} does not exist")
83
+
84
+ def _source_existed(self, obj: Dict[str, Any]) -> bool:
85
+ """
86
+ Checks if the source node exists in a given object.
87
+
88
+ Parameters:
89
+ obj (Dict[str, Any]): The object to check.
90
+
91
+ Returns:
92
+ bool: True if the source node exists, False otherwise.
93
+ """
94
+ return self.source_node_id in obj.keys()
95
+
96
+ def _target_existed(self, obj: Dict[str, Any]) -> bool:
97
+ """
98
+ Checks if the target node exists in a given object.
99
+
100
+ Parameters:
101
+ obj (Dict[str, Any]): The object to check.
102
+
103
+ Returns:
104
+ bool: True if the target node exists, False otherwise.
105
+ """
106
+ return self.target_node_id in obj.keys()
107
+
108
+ def _is_in(self, obj: Dict[str, Any]) -> bool:
109
+ """
110
+ Validates the existence of both source and target nodes in a given object.
111
+
112
+ Parameters:
113
+ obj (Dict[str, Any]): The object to check.
114
+
115
+ Returns:
116
+ bool: True if both nodes exist.
117
+
118
+ Raises:
119
+ ValueError: If either the source or target node does not exist.
120
+ """
121
+ if self._source_existed(obj) and self._target_existed(obj):
122
+ return True
123
+
124
+ elif self._source_existed(obj):
125
+ raise ValueError(f"Target node {self.source_node_id} does not exist")
126
+ else :
127
+ raise ValueError(f"Source node {self.target_node_id} does not exist")
128
+
@@ -0,0 +1,160 @@
1
+ from typing import TypeVar, Dict, Optional, Any, Type, Union, List
2
+ from pydantic import Field
3
+ from ..schema.base_schema import BaseNode
4
+ from .relationship import Relationship
5
+
6
+ T = TypeVar('T', bound='BaseNode')
7
+ R = TypeVar('R', bound='Relationship')
8
+
9
+
10
+ class Structure(BaseNode):
11
+ """
12
+ Represents the structure of a graph consisting of nodes and relationships.
13
+ """
14
+ nodes: Dict[str, T] = Field(default_factory=dict)
15
+ relationships: Dict[str, R] = Field(default_factory=dict)
16
+ node_relationships: Dict[str, Dict[str, Dict[str, str]]] = Field(default_factory=dict)
17
+
18
+ def add_node(self, node: T) -> None:
19
+ """
20
+ Adds a node to the structure.
21
+
22
+ Args:
23
+ node (T): The node instance to be added.
24
+ """
25
+ self.nodes[node.id_] = node
26
+ self.node_relationships[node.id_] = {'in': {}, 'out': {}}
27
+
28
+ def add_relationship(self, relationship: R) -> None:
29
+ """
30
+ Adds a relationship to the structure.
31
+
32
+ Args:
33
+ relationship (R): The relationship instance to be added.
34
+ """
35
+ id_, source_, target_ = (
36
+ relationship.id_, relationship.source_node_id, relationship.target_node_id
37
+ )
38
+
39
+ self.relationships.update({id_ : relationship})
40
+ self.node_relationships[source_]['out'].update({id_ : target_})
41
+ self.node_relationships[target_]['in'].update({id_ : source_})
42
+
43
+ # type can be dict or list
44
+ @staticmethod
45
+ def _typed_return(type: Type[Union[Dict, List]],
46
+ obj: Optional[Dict[str, Any]] = None
47
+ ) -> Union[Dict[str, Any], List[Any]]:
48
+ """
49
+ Returns the object in the specified type format.
50
+
51
+ Args:
52
+ type (Type[Union[Dict, List]]): The type to return the object as (dict or list).
53
+
54
+ obj (Optional[Dict[str, Any]]): The object to be converted.
55
+
56
+ Returns:
57
+ Union[Dict[str, Any], List[Any]]: The object in the specified type format.
58
+ """
59
+ if type is list:
60
+ return list(obj.values())
61
+ return obj
62
+
63
+ def get_relationships(self, type: Type = dict) -> Union[Dict[str, R], List[R]]:
64
+ """
65
+ Returns the relationships in the specified type format.
66
+
67
+ Args:
68
+ type (Type): The type to return the relationships as (dict or list).
69
+
70
+ Returns:
71
+ Union[Dict[str, R], List[R]]: The relationships in the specified type format.
72
+ """
73
+ return self._typed_return(self.relationships, type=type)
74
+
75
+ def get_node_relationships(self, id_: str, in_out: str, type: Type = dict
76
+ ) -> Union[Dict[str, str], List[str]]:
77
+ """
78
+ Returns the relationships of a node in the specified type format.
79
+
80
+ Args:
81
+ id_ (str): The ID of the node.
82
+
83
+ in_out (str): 'in' for incoming relationships, 'out' for outgoing relationships.
84
+
85
+ type (Type): The type to return the relationships as (dict or list).
86
+
87
+ Returns:
88
+ Union[Dict[str, str], List[str]]: The relationships of the node in the specified type format.
89
+ """
90
+ node_relationships = self.node_relationships[id_][in_out]
91
+ return self._typed_return(node_relationships, type=type)
92
+
93
+ def node_exist(self, node: Union[T, str]) -> bool:
94
+ """
95
+ Checks if a node exists in the structure.
96
+
97
+ Args:
98
+ node (Union[T, str]): The node instance or node ID to check for existence.
99
+
100
+ Returns:
101
+ bool: True if the node exists, False otherwise.
102
+ """
103
+
104
+ return node.id_ in self.nodes.keys()
105
+
106
+ def relationship_exist(self, relationship: R) -> bool:
107
+ """
108
+ Checks if a relationship exists in the structure.
109
+
110
+ Args:
111
+ relationship (R): The relationship instance to check for existence.
112
+
113
+ Returns:
114
+ bool: True if the relationship exists, False otherwise.
115
+ """
116
+ return relationship.id_ in self.relationships.keys()
117
+
118
+ def remove_node_relationships(self, relationship_dict: Dict[str, str], in_out: str) -> None:
119
+ """
120
+ Removes relationships of a node from the structure.
121
+
122
+ Args:
123
+ relationship_dict (Dict[str, str]): A dictionary of relationship IDs to node IDs.
124
+
125
+ in_out (str): 'in' to remove incoming relationships, 'out' to remove outgoing relationships.
126
+ """
127
+ for relationship_id, node_id in relationship_dict.items():
128
+ self.node_relationships[node_id][in_out].pop(relationship_id)
129
+ self.relationships.pop(relationship_id)
130
+
131
+ def remove_node(self, node: Union[T, str]) -> None:
132
+ """
133
+ Removes a node and its associated relationships from the structure.
134
+
135
+ Args:
136
+ node (Union[T, str]): The node instance or node ID to be removed.
137
+ """
138
+ node_id = node if isinstance(node, str) else node.id_
139
+ out_ = self.get_node_relationships(node_id, 'out')
140
+ in_ = self.get_node_relationships(node_id, 'in')
141
+
142
+ self.remove_node_relationships(out_, 'in')
143
+ self.remove_node_relationships(in_, 'out')
144
+ self.node_relationships.pop(node_id)
145
+
146
+ def remove_relationship(self, relationship: R) -> None:
147
+ """
148
+ Removes a relationship from the structure.
149
+
150
+ Args:
151
+ relationship (R): The relationship instance to be removed.
152
+ """
153
+ id_, source_, target_ = (relationship.id_,
154
+ relationship.source_node_id,
155
+ relationship.target_node_id)
156
+
157
+ self.node_relationships[source_]['out'].pop(id_)
158
+ self.node_relationships[target_]['in'].pop(id_)
159
+ self.relationships.pop(id_)
160
+
File without changes