lionagi 0.0.112__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 (94) hide show
  1. lionagi/__init__.py +3 -3
  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 +8 -2
  9. lionagi/core/instruction_sets.py +1 -3
  10. lionagi/core/messages.py +2 -2
  11. lionagi/core/sessions.py +174 -27
  12. lionagi/datastore/__init__.py +1 -0
  13. lionagi/loader/__init__.py +9 -4
  14. lionagi/loader/chunker.py +157 -0
  15. lionagi/loader/reader.py +124 -0
  16. lionagi/objs/__init__.py +7 -0
  17. lionagi/objs/messenger.py +163 -0
  18. lionagi/objs/tool_registry.py +247 -0
  19. lionagi/schema/__init__.py +11 -0
  20. lionagi/schema/base_schema.py +239 -0
  21. lionagi/schema/base_tool.py +9 -0
  22. lionagi/schema/data_logger.py +94 -0
  23. lionagi/services/__init__.py +14 -0
  24. lionagi/{service_/oai.py → services/base_api_service.py} +49 -82
  25. lionagi/{endpoint/base_endpoint.py → services/chatcompletion.py} +19 -22
  26. lionagi/services/oai.py +34 -0
  27. lionagi/services/openrouter.py +32 -0
  28. lionagi/{service_/service_utils.py → services/service_objs.py} +0 -1
  29. lionagi/structure/__init__.py +7 -0
  30. lionagi/structure/relationship.py +128 -0
  31. lionagi/structure/structure.py +160 -0
  32. lionagi/tests/test_flatten_util.py +426 -0
  33. lionagi/tools/__init__.py +0 -5
  34. lionagi/tools/coder.py +1 -0
  35. lionagi/tools/scorer.py +1 -0
  36. lionagi/tools/validator.py +1 -0
  37. lionagi/utils/__init__.py +46 -20
  38. lionagi/utils/api_util.py +86 -0
  39. lionagi/utils/call_util.py +347 -0
  40. lionagi/utils/flat_util.py +540 -0
  41. lionagi/utils/io_util.py +102 -0
  42. lionagi/utils/load_utils.py +190 -0
  43. lionagi/utils/sys_util.py +191 -0
  44. lionagi/utils/tool_util.py +92 -0
  45. lionagi/utils/type_util.py +81 -0
  46. lionagi/version.py +1 -1
  47. {lionagi-0.0.112.dist-info → lionagi-0.0.113.dist-info}/METADATA +37 -13
  48. lionagi-0.0.113.dist-info/RECORD +84 -0
  49. lionagi/endpoint/chat_completion.py +0 -20
  50. lionagi/endpoint/endpoint_utils.py +0 -0
  51. lionagi/llm_configs.py +0 -21
  52. lionagi/loader/load_utils.py +0 -161
  53. lionagi/schema.py +0 -275
  54. lionagi/service_/__init__.py +0 -6
  55. lionagi/service_/base_service.py +0 -48
  56. lionagi/service_/openrouter.py +0 -1
  57. lionagi/services.py +0 -1
  58. lionagi/tools/tool_utils.py +0 -75
  59. lionagi/utils/sys_utils.py +0 -799
  60. lionagi-0.0.112.dist-info/RECORD +0 -67
  61. /lionagi/{core/responses.py → datastore/chroma.py} +0 -0
  62. /lionagi/{endpoint/assistants.py → datastore/deeplake.py} +0 -0
  63. /lionagi/{endpoint/audio.py → datastore/elasticsearch.py} +0 -0
  64. /lionagi/{endpoint/embeddings.py → datastore/lantern.py} +0 -0
  65. /lionagi/{endpoint/files.py → datastore/pinecone.py} +0 -0
  66. /lionagi/{endpoint/fine_tuning.py → datastore/postgres.py} +0 -0
  67. /lionagi/{endpoint/images.py → datastore/qdrant.py} +0 -0
  68. /lionagi/{endpoint/messages.py → schema/base_condition.py} +0 -0
  69. /lionagi/{service_ → services}/anthropic.py +0 -0
  70. /lionagi/{service_ → services}/anyscale.py +0 -0
  71. /lionagi/{service_ → services}/azure.py +0 -0
  72. /lionagi/{service_ → services}/bedrock.py +0 -0
  73. /lionagi/{service_ → services}/everlyai.py +0 -0
  74. /lionagi/{service_ → services}/gemini.py +0 -0
  75. /lionagi/{service_ → services}/gpt4all.py +0 -0
  76. /lionagi/{service_ → services}/huggingface.py +0 -0
  77. /lionagi/{service_ → services}/litellm.py +0 -0
  78. /lionagi/{service_ → services}/localai.py +0 -0
  79. /lionagi/{service_ → services}/mistralai.py +0 -0
  80. /lionagi/{service_ → services}/ollama.py +0 -0
  81. /lionagi/{service_ → services}/openllm.py +0 -0
  82. /lionagi/{service_ → services}/perplexity.py +0 -0
  83. /lionagi/{service_ → services}/predibase.py +0 -0
  84. /lionagi/{service_ → services}/rungpt.py +0 -0
  85. /lionagi/{service_ → services}/vllm.py +0 -0
  86. /lionagi/{service_ → services}/xinference.py +0 -0
  87. /lionagi/{endpoint → tests}/__init__.py +0 -0
  88. /lionagi/{endpoint/models.py → tools/planner.py} +0 -0
  89. /lionagi/{endpoint/moderations.py → tools/prompter.py} +0 -0
  90. /lionagi/{endpoint/runs.py → tools/sandbox.py} +0 -0
  91. /lionagi/{endpoint/threads.py → tools/summarizer.py} +0 -0
  92. {lionagi-0.0.112.dist-info → lionagi-0.0.113.dist-info}/LICENSE +0 -0
  93. {lionagi-0.0.112.dist-info → lionagi-0.0.113.dist-info}/WHEEL +0 -0
  94. {lionagi-0.0.112.dist-info → lionagi-0.0.113.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,94 @@
1
+ from collections import deque
2
+ from typing import List, Optional
3
+ from ..utils.sys_util import create_path
4
+ from ..utils.io_util import to_csv
5
+
6
+
7
+ class DataLogger:
8
+ """
9
+ A class for logging data entries and exporting them as CSV files.
10
+
11
+ This class provides functionality to log data entries in a deque and
12
+ supports exporting the logged data to a CSV file. The DataLogger can
13
+ be configured to use a specific directory for saving files.
14
+
15
+ Attributes:
16
+ dir (Optional[str]):
17
+ The default directory where CSV files will be saved.
18
+ log (deque):
19
+ A deque object that stores the logged data entries.
20
+
21
+ Methods:
22
+ __call__:
23
+ Adds an entry to the log.
24
+ to_csv:
25
+ Exports the logged data to a CSV file and clears the log.
26
+ set_dir:
27
+ Sets the default directory for saving CSV files.
28
+ """
29
+
30
+ def __init__(self, dir= None, log: list = None) -> None:
31
+ """
32
+ Initializes the DataLogger with an optional directory and initial log.
33
+
34
+ Parameters:
35
+ dir (Optional[str]):
36
+ The directory where CSV files will be saved. Defaults to None.
37
+ log (Optional[List]):
38
+ An initial list of log entries. Defaults to an empty list.
39
+ """
40
+ self.dir = dir
41
+ self.log = deque(log) if log else deque()
42
+
43
+ def __call__(self, entry):
44
+ """
45
+ Adds a new entry to the log.
46
+
47
+ Parameters:
48
+ entry:
49
+ The data entry to be added to the log.
50
+ """
51
+ self.log.append(entry)
52
+
53
+ def to_csv(self, filename: str, dir: Optional[str] = None, verbose: bool = True,
54
+ timestamp: bool = True, dir_exist_ok: bool = True, file_exist_ok: bool = False) -> None:
55
+ """
56
+ Exports the logged data to a CSV file and optionally clears the log.
57
+
58
+ Parameters:
59
+ filename (str):
60
+ The name of the CSV file.
61
+ dir (Optional[str]):
62
+ The directory to save the file. Defaults to the instance's dir attribute.
63
+ verbose (bool):
64
+ If True, prints a message upon completion. Defaults to True.
65
+ timestamp (bool):
66
+ If True, appends a timestamp to the filename. Defaults to True.
67
+ dir_exist_ok (bool):
68
+ If True, will not raise an error if the directory already exists. Defaults to True.
69
+ file_exist_ok (bool):
70
+ If True, overwrites the file if it exists. Defaults to False.
71
+
72
+ Side Effects:
73
+ Clears the log after saving the CSV file.
74
+ Prints a message indicating the save location and number of logs saved if verbose is True.
75
+ """
76
+ dir = dir or self.dir
77
+ filepath = create_path(
78
+ dir=dir, filename=filename, timestamp=timestamp, dir_exist_ok=dir_exist_ok)
79
+ to_csv(list(self.log), filepath, file_exist_ok=file_exist_ok)
80
+ n_logs = len(list(self.log))
81
+ self.log = deque()
82
+ if verbose:
83
+ print(f"{n_logs} logs saved to {filepath}")
84
+
85
+ def set_dir(self, dir: str) -> None:
86
+ """
87
+ Sets the default directory for saving CSV files.
88
+
89
+ Parameters:
90
+ dir (str):
91
+ The directory to be set as the default for saving files.
92
+ """
93
+ self.dir = dir
94
+
@@ -0,0 +1,14 @@
1
+ from .chatcompletion import ChatCompletion
2
+ from .base_api_service import BaseAPIService, BaseAPIRateLimiter
3
+ from .oai import OpenAIService
4
+ from .openrouter import OpenRouterService
5
+
6
+
7
+
8
+ __all__ = [
9
+ "BaseAPIService",
10
+ "OpenAIService",
11
+ "OpenRouterService",
12
+ "ChatCompletion",
13
+ "BaseAPIRateLimiter"
14
+ ]
@@ -1,55 +1,18 @@
1
- import os
2
- import dotenv
1
+ import re
3
2
  import asyncio
4
- import logging
3
+ import os
5
4
  import tiktoken
5
+ import logging
6
6
  import aiohttp
7
- from typing import Optional, NoReturn, Dict, Any
8
-
9
- from .base_service import BaseAPIService
10
-
11
- dotenv.load_dotenv()
12
-
13
- from .base_service import BaseAPIService
14
- from .service_utils import StatusTracker, AsyncQueue, RateLimiter
15
-
7
+ from typing import Generator, NoReturn, Dict, Any, Optional
8
+ from .service_objs import BaseService, RateLimiter, StatusTracker, AsyncQueue
16
9
 
17
-
18
- class OpenAIRateLimiter(RateLimiter):
19
- """
20
- A specialized RateLimiter for managing requests to the OpenAI API.
21
-
22
- Extends the generic RateLimiter to enforce specific rate-limiting rules and limits
23
- as required by the OpenAI API. This includes maximum requests and tokens per minute
24
- and replenishing these limits at regular intervals.
25
-
26
- Attributes:
27
- max_requests_per_minute (int):
28
- Maximum number of requests allowed per minute.
29
- max_tokens_per_minute (int):
30
- Maximum number of tokens allowed per minute.
31
-
32
- Methods:
33
- rate_limit_replenisher:
34
- Coroutine to replenish rate limits over time.
35
- calculate_num_token:
36
- Calculates the required tokens for a request.
37
- """
10
+ class BaseAPIRateLimiter(RateLimiter):
38
11
 
39
12
  def __init__(
40
13
  self, max_requests_per_minute: int, max_tokens_per_minute: int
41
14
  ) -> None:
42
- """
43
- Initializes the rate limiter with specific limits for OpenAI API.
44
-
45
- Parameters:
46
- max_requests_per_minute (int): The maximum number of requests allowed per minute.
47
-
48
- max_tokens_per_minute (int): The maximum number of tokens that can accumulate per minute.
49
- """
50
15
  super().__init__(max_requests_per_minute, max_tokens_per_minute)
51
- if not os.getenv('env_readthedocs'):
52
- self.rate_limit_replenisher_task = asyncio.create_task(self.rate_limit_replenisher())
53
16
 
54
17
  @classmethod
55
18
  async def create(
@@ -160,48 +123,53 @@ class OpenAIRateLimiter(RateLimiter):
160
123
  raise NotImplementedError(
161
124
  f'API endpoint "{api_endpoint}" not implemented in this script'
162
125
  )
163
-
164
-
165
- class OpenAIService(BaseAPIService):
166
- """
167
- Service class for interacting with the OpenAI API.
168
126
 
169
- This class provides methods for calling OpenAI's API endpoints, handling the responses,
170
- and managing rate limits and asynchronous tasks associated with API calls.
171
127
 
172
- Attributes:
173
- base_url (str): The base URL for OpenAI's API.
174
-
175
- Methods:
176
- call_api: Call an API endpoint with a payload and handle the response.
177
- """
128
+ class BaseAPIService(BaseService):
129
+
130
+ def __init__(self, api_key: str = None,
131
+ status_tracker = None,
132
+ queue = None, endpoint=None, schema=None,
133
+ ratelimiter=None, max_requests_per_minute=None, max_tokens_per_minute=None) -> None:
134
+ self.api_key = api_key
135
+ self.status_tracker = status_tracker or StatusTracker()
136
+ self.queue = queue or AsyncQueue()
137
+ self.endpoint=endpoint
138
+ self.schema = schema
139
+ self.rate_limiter = ratelimiter(max_requests_per_minute, max_tokens_per_minute)
140
+
141
+ @staticmethod
142
+ def api_methods(http_session, method="post"):
143
+ if method not in ["post", "delete", "head", "options", "patch"]:
144
+ raise ValueError("Invalid request, method must be in ['post', 'delete', 'head', 'options', 'patch']")
145
+ elif method == "post":
146
+ return http_session.post
147
+ elif method == "delete":
148
+ return http_session.delete
149
+ elif method == "head":
150
+ return http_session.head
151
+ elif method == "options":
152
+ return http_session.options
153
+ elif method == "patch":
154
+ return http_session.patch
155
+
156
+ @staticmethod
157
+ def api_endpoint_from_url(request_url: str) -> str:
158
+ match = re.search(r"^https://[^/]+/v\d+/(.+)$", request_url)
159
+ if match:
160
+ return match.group(1)
161
+ else:
162
+ return ""
178
163
 
179
- base_url = "https://api.openai.com/v1/"
164
+ @staticmethod
165
+ def task_id_generator_function() -> Generator[int, None, None]:
166
+ task_id = 0
167
+ while True:
168
+ yield task_id
169
+ task_id += 1
180
170
 
181
- def __init__(
182
- self,
183
- api_key: str = None,
184
- token_encoding_name: str = "cl100k_base",
185
- max_attempts: int = 3,
186
- max_requests_per_minute: int = 500,
187
- max_tokens_per_minute: int = 150_000,
188
- ratelimiter = OpenAIRateLimiter ,
189
- status_tracker = None,
190
- queue = None,
191
- ):
192
- super().__init__(
193
- api_key = api_key or os.getenv("OPENAI_API_KEY"),
194
- status_tracker = status_tracker or StatusTracker(),
195
- queue = queue or AsyncQueue(),
196
- ratelimiter=ratelimiter,
197
- max_requests_per_minute=max_requests_per_minute,
198
- max_tokens_per_minute=max_tokens_per_minute),
199
- self.token_encoding_name=token_encoding_name
200
- self.max_attempts = max_attempts
201
-
202
-
203
171
  async def _call_api(self, http_session, endpoint_, method="post", payload: Dict[str, any] =None) -> Optional[Dict[str, any]]:
204
- endpoint_ = self.api_endpoint_from_url(self.base_url+endpoint_)
172
+ endpoint_ = self.api_endpoint_from_url("https://api.openai.com/v1/"+endpoint_)
205
173
 
206
174
  while True:
207
175
  if self.rate_limiter.available_request_capacity < 1 or self.rate_limiter.available_token_capacity < 10: # Minimum token count
@@ -244,7 +212,7 @@ class OpenAIService(BaseAPIService):
244
212
  else:
245
213
  await asyncio.sleep(1)
246
214
 
247
- async def serve(self, payload, endpoint_="chat/completions", method="post"):
215
+ async def _serve(self, payload, endpoint_="chat/completions", method="post"):
248
216
 
249
217
  async def call_api():
250
218
  async with aiohttp.ClientSession() as http_session:
@@ -256,5 +224,4 @@ class OpenAIService(BaseAPIService):
256
224
  except Exception as e:
257
225
  self.status_tracker.num_tasks_failed += 1
258
226
  raise e
259
-
260
227
 
@@ -1,5 +1,6 @@
1
1
  import abc
2
2
 
3
+
3
4
  class BaseEndpoint(abc.ABC):
4
5
  endpoint: str = abc.abstractproperty()
5
6
 
@@ -16,17 +17,6 @@ class BaseEndpoint(abc.ABC):
16
17
  """
17
18
  pass
18
19
 
19
- # @abc.abstractmethod
20
- # async def call_api(self, session, **kwargs):
21
- # """
22
- # Make a call to the API endpoint and process the response.
23
-
24
- # Parameters:
25
- # session: The aiohttp client session.
26
- # **kwargs: Additional keyword arguments for configuration.
27
- # """
28
- # pass
29
-
30
20
  @abc.abstractmethod
31
21
  def process_response(self, response):
32
22
  """
@@ -38,14 +28,21 @@ class BaseEndpoint(abc.ABC):
38
28
  pass
39
29
 
40
30
 
41
-
42
-
43
- # @abc.abstractmethod
44
- # def handle_error(self, error):
45
- # """
46
- # Handle any errors that occur during the API call.
47
-
48
- # Parameters:
49
- # error: The error to handle.
50
- # """
51
- # pass
31
+ class ChatCompletion(BaseEndpoint):
32
+ endpoint: str = "chat/completion"
33
+
34
+ @classmethod
35
+ def create_payload(scls, messages, llmconfig, schema, **kwargs):
36
+ config = {**llmconfig, **kwargs}
37
+ payload = {"messages": messages}
38
+ for key in schema['required']:
39
+ payload.update({key: config[key]})
40
+
41
+ for key in schema['optional']:
42
+ if bool(config[key]) is True and str(config[key]).lower() != "none":
43
+ payload.update({key: config[key]})
44
+ return payload
45
+
46
+ def process_response(self, session, payload, completion):
47
+ ...
48
+
@@ -0,0 +1,34 @@
1
+ from os import getenv
2
+ import dotenv
3
+ from .base_api_service import BaseAPIService, BaseAPIRateLimiter
4
+
5
+ dotenv.load_dotenv()
6
+
7
+ class OpenAIService(BaseAPIService):
8
+
9
+ base_url = "https://api.openai.com/v1/"
10
+
11
+ def __init__(
12
+ self,
13
+ api_key: str = None,
14
+ token_encoding_name: str = "cl100k_base",
15
+ max_attempts: int = 3,
16
+ max_requests_per_minute: int = 500,
17
+ max_tokens_per_minute: int = 150_000,
18
+ ratelimiter = BaseAPIRateLimiter ,
19
+ status_tracker = None,
20
+ queue = None,
21
+ ):
22
+ super().__init__(
23
+ api_key = api_key or getenv("OPENAI_API_KEY"),
24
+ status_tracker = status_tracker,
25
+ queue = queue,
26
+ ratelimiter=ratelimiter,
27
+ max_requests_per_minute=max_requests_per_minute,
28
+ max_tokens_per_minute=max_tokens_per_minute),
29
+ self.token_encoding_name=token_encoding_name
30
+ self.max_attempts = max_attempts
31
+
32
+ async def serve(self, payload, endpoint_="chat/completions", method="post"):
33
+ return await self._serve(payload=payload, endpoint_=endpoint_, method=method)
34
+
@@ -0,0 +1,32 @@
1
+ from os import getenv
2
+ from .base_api_service import BaseAPIService, BaseAPIRateLimiter
3
+
4
+ class OpenRouterService(BaseAPIService):
5
+ _key_scheme = "OPENROUTER_API_KEY"
6
+
7
+ base_url = "https://openrouter.ai/api/v1/"
8
+
9
+ def __init__(
10
+ self,
11
+ api_key: str = None,
12
+ token_encoding_name: str = "cl100k_base",
13
+ max_attempts: int = 3,
14
+ max_requests_per_minute: int = 500,
15
+ max_tokens_per_minute: int = 150_000,
16
+ ratelimiter = BaseAPIRateLimiter ,
17
+ status_tracker = None,
18
+ queue = None,
19
+ ):
20
+ super().__init__(
21
+ api_key = api_key or getenv(self._key_scheme),
22
+ status_tracker = status_tracker,
23
+ queue = queue,
24
+ ratelimiter=ratelimiter,
25
+ max_requests_per_minute=max_requests_per_minute,
26
+ max_tokens_per_minute=max_tokens_per_minute),
27
+ self.token_encoding_name=token_encoding_name
28
+ self.max_attempts = max_attempts
29
+
30
+ async def serve(self, payload, endpoint_="chat/completions"):
31
+ return await self._serve(payload=payload, endpoint_=endpoint_)
32
+
@@ -280,4 +280,3 @@ class RateLimiter(ABC):
280
280
  ...
281
281
 
282
282
 
283
-
@@ -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
+