graphs.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. from builtins import list as _list
  2. from typing import Any, Optional
  3. from uuid import UUID
  4. from shared.api.models import (
  5. WrappedBooleanResponse,
  6. WrappedCommunitiesResponse,
  7. WrappedCommunityResponse,
  8. WrappedEntitiesResponse,
  9. WrappedEntityResponse,
  10. WrappedGenericMessageResponse,
  11. WrappedGraphResponse,
  12. WrappedGraphsResponse,
  13. WrappedRelationshipResponse,
  14. WrappedRelationshipsResponse,
  15. )
  16. class GraphsSDK:
  17. """SDK for interacting with knowledge graphs in the v3 API."""
  18. def __init__(self, client):
  19. self.client = client
  20. def list(
  21. self,
  22. collection_ids: Optional[list[str | UUID]] = None,
  23. offset: Optional[int] = 0,
  24. limit: Optional[int] = 100,
  25. ) -> WrappedGraphsResponse:
  26. """List graphs with pagination and filtering options.
  27. Args:
  28. ids (Optional[list[str | UUID]]): Filter graphs by ids
  29. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  30. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  31. Returns:
  32. WrappedGraphsResponse
  33. """
  34. params: dict = {
  35. "offset": offset,
  36. "limit": limit,
  37. }
  38. if collection_ids:
  39. params["collection_ids"] = collection_ids
  40. response_dict = self.client._make_request(
  41. "GET", "graphs", params=params, version="v3"
  42. )
  43. return WrappedGraphsResponse(**response_dict)
  44. def retrieve(
  45. self,
  46. collection_id: str | UUID,
  47. ) -> WrappedGraphResponse:
  48. """Get detailed information about a specific graph.
  49. Args:
  50. collection_id (str | UUID): Graph ID to retrieve
  51. Returns:
  52. WrappedGraphResponse
  53. """
  54. response_dict = self.client._make_request(
  55. "GET", f"graphs/{str(collection_id)}", version="v3"
  56. )
  57. return WrappedGraphResponse(**response_dict)
  58. def reset(
  59. self,
  60. collection_id: str | UUID,
  61. ) -> WrappedBooleanResponse:
  62. """Deletes a graph and all its associated data.
  63. This endpoint permanently removes the specified graph along with all
  64. entities and relationships that belong to only this graph.
  65. Entities and relationships extracted from documents are not deleted.
  66. Args:
  67. collection_id (str | UUID): Graph ID to reset
  68. Returns:
  69. WrappedBooleanResponse
  70. """
  71. response_dict = self.client._make_request(
  72. "POST", f"graphs/{str(collection_id)}/reset", version="v3"
  73. )
  74. return WrappedBooleanResponse(**response_dict)
  75. def update(
  76. self,
  77. collection_id: str | UUID,
  78. name: Optional[str] = None,
  79. description: Optional[str] = None,
  80. ) -> WrappedGraphResponse:
  81. """Update graph information.
  82. Args:
  83. collection_id (str | UUID): The collection ID corresponding to the graph
  84. name (Optional[str]): Optional new name for the graph
  85. description (Optional[str]): Optional new description for the graph
  86. Returns:
  87. WrappedGraphResponse
  88. """
  89. data: dict[str, Any] = {}
  90. if name is not None:
  91. data["name"] = name
  92. if description is not None:
  93. data["description"] = description
  94. response_dict = self.client._make_request(
  95. "POST",
  96. f"graphs/{str(collection_id)}",
  97. json=data,
  98. version="v3",
  99. )
  100. return WrappedGraphResponse(**response_dict)
  101. def list_entities(
  102. self,
  103. collection_id: str | UUID,
  104. offset: Optional[int] = 0,
  105. limit: Optional[int] = 100,
  106. ) -> WrappedEntitiesResponse:
  107. """List entities in a graph.
  108. Args:
  109. collection_id (str | UUID): Graph ID to list entities from
  110. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  111. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  112. Returns:
  113. WrappedEntitiesResponse
  114. """
  115. params: dict = {
  116. "offset": offset,
  117. "limit": limit,
  118. }
  119. response_dict = self.client._make_request(
  120. "GET",
  121. f"graphs/{str(collection_id)}/entities",
  122. params=params,
  123. version="v3",
  124. )
  125. return WrappedEntitiesResponse(**response_dict)
  126. def get_entity(
  127. self,
  128. collection_id: str | UUID,
  129. entity_id: str | UUID,
  130. ) -> WrappedEntityResponse:
  131. """Get entity information in a graph.
  132. Args:
  133. collection_id (str | UUID): The collection ID corresponding to the graph
  134. entity_id (str | UUID): Entity ID to get from the graph
  135. Returns:
  136. WrappedEntityResponse
  137. """
  138. response_dict = self.client._make_request(
  139. "GET",
  140. f"graphs/{str(collection_id)}/entities/{str(entity_id)}",
  141. version="v3",
  142. )
  143. return WrappedEntityResponse(**response_dict)
  144. def remove_entity(
  145. self,
  146. collection_id: str | UUID,
  147. entity_id: str | UUID,
  148. ) -> WrappedBooleanResponse:
  149. """Remove an entity from a graph.
  150. Args:
  151. collection_id (str | UUID): The collection ID corresponding to the graph
  152. entity_id (str | UUID): Entity ID to remove from the graph
  153. Returns:
  154. WrappedBooleanResponse
  155. """
  156. response_dict = self.client._make_request(
  157. "DELETE",
  158. f"graphs/{str(collection_id)}/entities/{str(entity_id)}",
  159. version="v3",
  160. )
  161. return WrappedBooleanResponse(**response_dict)
  162. def list_relationships(
  163. self,
  164. collection_id: str | UUID,
  165. offset: Optional[int] = 0,
  166. limit: Optional[int] = 100,
  167. ) -> WrappedRelationshipsResponse:
  168. """List relationships in a graph.
  169. Args:
  170. collection_id (str | UUID): The collection ID corresponding to the graph
  171. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  172. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  173. Returns:
  174. WrappedRelationshipsResponse
  175. """
  176. params: dict = {
  177. "offset": offset,
  178. "limit": limit,
  179. }
  180. response_dict = self.client._make_request(
  181. "GET",
  182. f"graphs/{str(collection_id)}/relationships",
  183. params=params,
  184. version="v3",
  185. )
  186. return WrappedRelationshipsResponse(**response_dict)
  187. def get_relationship(
  188. self,
  189. collection_id: str | UUID,
  190. relationship_id: str | UUID,
  191. ) -> WrappedRelationshipResponse:
  192. """Get relationship information in a graph.
  193. Args:
  194. collection_id (str | UUID): The collection ID corresponding to the graph
  195. relationship_id (str | UUID): Relationship ID to get from the graph
  196. Returns:
  197. WrappedRelationshipResponse
  198. """
  199. response_dict = self.client._make_request(
  200. "GET",
  201. f"graphs/{str(collection_id)}/relationships/{str(relationship_id)}",
  202. version="v3",
  203. )
  204. return WrappedRelationshipResponse(**response_dict)
  205. def remove_relationship(
  206. self,
  207. collection_id: str | UUID,
  208. relationship_id: str | UUID,
  209. ) -> WrappedBooleanResponse:
  210. """Remove a relationship from a graph.
  211. Args:
  212. collection_id (str | UUID): The collection ID corresponding to the graph
  213. relationship_id (str | UUID): Relationship ID to remove from the graph
  214. Returns:
  215. WrappedBooleanResponse
  216. """
  217. response_dict = self.client._make_request(
  218. "DELETE",
  219. f"graphs/{str(collection_id)}/relationships/{str(relationship_id)}",
  220. version="v3",
  221. )
  222. return WrappedBooleanResponse(**response_dict)
  223. def build(
  224. self,
  225. collection_id: str | UUID,
  226. settings: Optional[dict] = None,
  227. run_with_orchestration: bool = True,
  228. ) -> WrappedGenericMessageResponse:
  229. """Build a graph.
  230. Args:
  231. collection_id (str | UUID): The collection ID corresponding to the graph
  232. settings (dict): Settings for the build
  233. run_with_orchestration (bool, optional): Whether to run with orchestration. Defaults to True.
  234. Returns:
  235. WrappedGenericMessageResponse
  236. """
  237. data: dict[str, Any] = {
  238. "run_with_orchestration": run_with_orchestration,
  239. }
  240. if settings:
  241. data["settings"] = settings
  242. response_dict = self.client._make_request(
  243. "POST",
  244. f"graphs/{str(collection_id)}/communities/build",
  245. json=data,
  246. version="v3",
  247. )
  248. return WrappedGenericMessageResponse(**response_dict)
  249. def list_communities(
  250. self,
  251. collection_id: str | UUID,
  252. offset: Optional[int] = 0,
  253. limit: Optional[int] = 100,
  254. ) -> WrappedCommunitiesResponse:
  255. """List communities in a graph.
  256. Args:
  257. collection_id (str | UUID): The collection ID corresponding to the graph
  258. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  259. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  260. Returns:
  261. WrappedCommunitiesResponse
  262. """
  263. params: dict = {
  264. "offset": offset,
  265. "limit": limit,
  266. }
  267. response_dict = self.client._make_request(
  268. "GET",
  269. f"graphs/{str(collection_id)}/communities",
  270. params=params,
  271. version="v3",
  272. )
  273. return WrappedCommunitiesResponse(**response_dict)
  274. def get_community(
  275. self,
  276. collection_id: str | UUID,
  277. community_id: str | UUID,
  278. ) -> WrappedCommunityResponse:
  279. """Get community information in a graph.
  280. Args:
  281. collection_id (str | UUID): The collection ID corresponding to the graph
  282. community_id (str | UUID): Community ID to get from the graph
  283. Returns:
  284. WrappedCommunityResponse
  285. """
  286. response_dict = self.client._make_request(
  287. "GET",
  288. f"graphs/{str(collection_id)}/communities/{str(community_id)}",
  289. version="v3",
  290. )
  291. return WrappedCommunityResponse(**response_dict)
  292. def update_community(
  293. self,
  294. collection_id: str | UUID,
  295. community_id: str | UUID,
  296. name: Optional[str] = None,
  297. summary: Optional[str] = None,
  298. findings: Optional[_list[str]] = None,
  299. rating: Optional[int] = None,
  300. rating_explanation: Optional[str] = None,
  301. level: Optional[int] = None,
  302. attributes: Optional[dict] = None,
  303. ) -> WrappedCommunityResponse:
  304. """Update community information.
  305. Args:
  306. collection_id (str | UUID): The collection ID corresponding to the graph
  307. community_id (str | UUID): Community ID to update
  308. name (Optional[str]): Optional new name for the community
  309. summary (Optional[str]): Optional new summary for the community
  310. findings (Optional[list[str]]): Optional new findings for the community
  311. rating (Optional[int]): Optional new rating for the community
  312. rating_explanation (Optional[str]): Optional new rating explanation for the community
  313. level (Optional[int]): Optional new level for the community
  314. attributes (Optional[dict]): Optional new attributes for the community
  315. Returns:
  316. WrappedCommunityResponse
  317. """
  318. data: dict[str, Any] = {}
  319. if name is not None:
  320. data["name"] = name
  321. if summary is not None:
  322. data["summary"] = summary
  323. if findings is not None:
  324. data["findings"] = findings
  325. if rating is not None:
  326. data["rating"] = str(rating)
  327. if rating_explanation is not None:
  328. data["rating_explanation"] = rating_explanation
  329. if level is not None:
  330. data["level"] = level
  331. if attributes is not None:
  332. data["attributes"] = attributes
  333. response_dict = self.client._make_request(
  334. "POST",
  335. f"graphs/{str(collection_id)}/communities/{str(community_id)}",
  336. json=data,
  337. version="v3",
  338. )
  339. return WrappedCommunityResponse(**response_dict)
  340. def delete_community(
  341. self,
  342. collection_id: str | UUID,
  343. community_id: str | UUID,
  344. ) -> WrappedBooleanResponse:
  345. """Remove a community from a graph.
  346. Args:
  347. collection_id (str | UUID): The collection ID corresponding to the graph
  348. community_id (str | UUID): Community ID to remove from the graph
  349. Returns:
  350. WrappedBooleanResponse
  351. """
  352. response_dict = self.client._make_request(
  353. "DELETE",
  354. f"graphs/{str(collection_id)}/communities/{str(community_id)}",
  355. version="v3",
  356. )
  357. return WrappedBooleanResponse(**response_dict)
  358. def pull(
  359. self,
  360. collection_id: str | UUID,
  361. ) -> WrappedBooleanResponse:
  362. """Adds documents to a graph by copying their entities and
  363. relationships.
  364. This endpoint:
  365. 1. Copies document entities to the graphs_entities table
  366. 2. Copies document relationships to the graphs_relationships table
  367. 3. Associates the documents with the graph
  368. When a document is added:
  369. - Its entities and relationships are copied to graph-specific tables
  370. - Existing entities/relationships are updated by merging their properties
  371. - The document ID is recorded in the graph's document_ids array
  372. Documents added to a graph will contribute their knowledge to:
  373. - Graph analysis and querying
  374. - Community detection
  375. - Knowledge graph enrichment
  376. Returns:
  377. WrappedBooleanResponse
  378. """
  379. response_dict = self.client._make_request(
  380. "POST",
  381. f"graphs/{str(collection_id)}/pull",
  382. version="v3",
  383. )
  384. return WrappedBooleanResponse(**response_dict)
  385. def remove_document(
  386. self,
  387. collection_id: str | UUID,
  388. document_id: str | UUID,
  389. ) -> WrappedBooleanResponse:
  390. """Removes a document from a graph and removes any associated entities.
  391. This endpoint:
  392. 1. Removes the document ID from the graph's document_ids array
  393. 2. Optionally deletes the document's copied entities and relationships
  394. The user must have access to both the graph and the document being removed.
  395. Returns:
  396. WrappedBooleanResponse
  397. """
  398. response_dict = self.client._make_request(
  399. "DELETE",
  400. f"graphs/{str(collection_id)}/documents/{str(document_id)}",
  401. version="v3",
  402. )
  403. return WrappedBooleanResponse(**response_dict)
  404. def create_entity(
  405. self,
  406. collection_id: str | UUID,
  407. name: str,
  408. description: str,
  409. category: Optional[str] = None,
  410. metadata: Optional[dict] = None,
  411. ) -> WrappedEntityResponse:
  412. """Creates a new entity in the graph.
  413. Args:
  414. collection_id (str | UUID): The collection ID corresponding to the graph
  415. name (str): The name of the entity to create
  416. description (Optional[str]): The description of the entity
  417. category (Optional[str]): The category of the entity
  418. metadata (Optional[dict]): Additional metadata for the entity
  419. Returns:
  420. WrappedEntityResponse
  421. """
  422. data: dict[str, Any] = {
  423. "name": name,
  424. "description": description,
  425. }
  426. if category is not None:
  427. data["category"] = category
  428. if metadata is not None:
  429. data["metadata"] = metadata
  430. response_dict = self.client._make_request(
  431. "POST",
  432. f"graphs/{str(collection_id)}/entities",
  433. json=data,
  434. version="v3",
  435. )
  436. return WrappedEntityResponse(**response_dict)
  437. def create_relationship(
  438. self,
  439. collection_id: str | UUID,
  440. subject: str,
  441. subject_id: str | UUID,
  442. predicate: str,
  443. object: str,
  444. object_id: str | UUID,
  445. description: str,
  446. weight: Optional[float] = None,
  447. metadata: Optional[dict] = None,
  448. ) -> WrappedRelationshipResponse:
  449. """Creates a new relationship in the graph.
  450. Args:
  451. collection_id (str | UUID): The collection ID corresponding to the graph
  452. subject (str): The subject of the relationship
  453. subject_id (str | UUID): The ID of the subject entity
  454. predicate (str): The predicate/type of the relationship
  455. object (str): The object of the relationship
  456. object_id (str | UUID): The ID of the object entity
  457. description (Optional[str]): Description of the relationship
  458. weight (Optional[float]): Weight/strength of the relationship
  459. metadata (Optional[dict]): Additional metadata for the relationship
  460. Returns:
  461. WrappedRelationshipResponse
  462. """
  463. data: dict[str, Any] = {
  464. "subject": subject,
  465. "subject_id": str(subject_id),
  466. "predicate": predicate,
  467. "object": object,
  468. "object_id": str(object_id),
  469. "description": description,
  470. }
  471. if weight is not None:
  472. data["weight"] = weight
  473. if metadata is not None:
  474. data["metadata"] = metadata
  475. response_dict = self.client._make_request(
  476. "POST",
  477. f"graphs/{str(collection_id)}/relationships",
  478. json=data,
  479. version="v3",
  480. )
  481. return WrappedRelationshipResponse(**response_dict)
  482. def create_community(
  483. self,
  484. collection_id: str | UUID,
  485. name: str,
  486. summary: str,
  487. findings: Optional[_list[str]] = None,
  488. rating: Optional[float] = None,
  489. rating_explanation: Optional[str] = None,
  490. ) -> WrappedCommunityResponse:
  491. """Creates a new community in the graph.
  492. Args:
  493. collection_id (str | UUID): The collection ID corresponding to the graph
  494. name (str): The name of the community
  495. summary (str): A summary description of the community
  496. findings (Optional[list[str]]): List of findings about the community
  497. rating (Optional[float]): Rating between 1 and 10
  498. rating_explanation (Optional[str]): Explanation for the rating
  499. Returns:
  500. WrappedCommunityResponse
  501. """
  502. data: dict[str, Any] = {
  503. "name": name,
  504. "summary": summary,
  505. }
  506. if findings is not None:
  507. data["findings"] = findings
  508. if rating is not None:
  509. data["rating"] = rating
  510. if rating_explanation is not None:
  511. data["rating_explanation"] = rating_explanation
  512. response_dict = self.client._make_request(
  513. "POST",
  514. f"graphs/{str(collection_id)}/communities",
  515. json=data,
  516. version="v3",
  517. )
  518. return WrappedCommunityResponse(**response_dict)