basion-agent 0.4.0__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 (41) hide show
  1. basion_agent/__init__.py +62 -0
  2. basion_agent/agent.py +360 -0
  3. basion_agent/agent_state_client.py +149 -0
  4. basion_agent/app.py +502 -0
  5. basion_agent/artifact.py +58 -0
  6. basion_agent/attachment_client.py +153 -0
  7. basion_agent/checkpoint_client.py +169 -0
  8. basion_agent/checkpointer.py +16 -0
  9. basion_agent/cli.py +139 -0
  10. basion_agent/conversation.py +103 -0
  11. basion_agent/conversation_client.py +86 -0
  12. basion_agent/conversation_message.py +48 -0
  13. basion_agent/exceptions.py +36 -0
  14. basion_agent/extensions/__init__.py +1 -0
  15. basion_agent/extensions/langgraph.py +526 -0
  16. basion_agent/extensions/pydantic_ai.py +180 -0
  17. basion_agent/gateway_client.py +531 -0
  18. basion_agent/gateway_pb2.py +73 -0
  19. basion_agent/gateway_pb2_grpc.py +101 -0
  20. basion_agent/heartbeat.py +84 -0
  21. basion_agent/loki_handler.py +355 -0
  22. basion_agent/memory.py +73 -0
  23. basion_agent/memory_client.py +155 -0
  24. basion_agent/message.py +333 -0
  25. basion_agent/py.typed +0 -0
  26. basion_agent/streamer.py +184 -0
  27. basion_agent/structural/__init__.py +6 -0
  28. basion_agent/structural/artifact.py +94 -0
  29. basion_agent/structural/base.py +71 -0
  30. basion_agent/structural/stepper.py +125 -0
  31. basion_agent/structural/surface.py +90 -0
  32. basion_agent/structural/text_block.py +96 -0
  33. basion_agent/tools/__init__.py +19 -0
  34. basion_agent/tools/container.py +46 -0
  35. basion_agent/tools/knowledge_graph.py +306 -0
  36. basion_agent-0.4.0.dist-info/METADATA +880 -0
  37. basion_agent-0.4.0.dist-info/RECORD +41 -0
  38. basion_agent-0.4.0.dist-info/WHEEL +5 -0
  39. basion_agent-0.4.0.dist-info/entry_points.txt +2 -0
  40. basion_agent-0.4.0.dist-info/licenses/LICENSE +21 -0
  41. basion_agent-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,306 @@
1
+ """Knowledge Graph Tool for querying biomedical knowledge graphs."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import aiohttp
7
+
8
+
9
+ @dataclass
10
+ class Entity:
11
+ """Base entity from knowledge graph."""
12
+
13
+ id: str
14
+ name: str
15
+ entity_type: str
16
+
17
+ @classmethod
18
+ def from_dict(cls, data: Dict[str, Any]) -> "Entity":
19
+ return cls(
20
+ id=str(data.get("id", "")),
21
+ name=data.get("name") or data.get("label") or data.get("symbol", ""),
22
+ entity_type=data.get("type", "unknown"),
23
+ )
24
+
25
+
26
+ @dataclass
27
+ class Edge:
28
+ """Relationship edge in knowledge graph."""
29
+
30
+ source_id: str
31
+ source_type: str
32
+ target_id: str
33
+ target_type: str
34
+ relation_type: str
35
+
36
+ @classmethod
37
+ def from_dict(cls, data: Dict[str, Any]) -> "Edge":
38
+ return cls(
39
+ source_id=str(data.get("source_id", "")),
40
+ source_type=data.get("source_type", ""),
41
+ target_id=str(data.get("target_id", "")),
42
+ target_type=data.get("target_type", ""),
43
+ relation_type=data.get("relation_type", ""),
44
+ )
45
+
46
+
47
+ @dataclass
48
+ class SimilarDisease:
49
+ """Disease similarity result."""
50
+
51
+ disease_id: str
52
+ disease_name: str
53
+ similarity_score: float
54
+ shared_count: int
55
+
56
+ @classmethod
57
+ def from_dict(cls, data: Dict[str, Any]) -> "SimilarDisease":
58
+ return cls(
59
+ disease_id=str(data.get("disease_id", "")),
60
+ disease_name=data.get("disease_name", ""),
61
+ similarity_score=float(data.get("similarity_score", 0)),
62
+ shared_count=int(data.get("shared_count", 0)),
63
+ )
64
+
65
+
66
+ @dataclass
67
+ class PathStep:
68
+ """Step in shortest path."""
69
+
70
+ node_id: str
71
+ node_name: str
72
+ node_type: str
73
+ relation: Optional[str] = None
74
+
75
+ @classmethod
76
+ def from_dict(cls, data: Dict[str, Any]) -> "PathStep":
77
+ return cls(
78
+ node_id=str(data.get("node_id", "")),
79
+ node_name=data.get("node_name", ""),
80
+ node_type=data.get("node_type", ""),
81
+ relation=data.get("relation"),
82
+ )
83
+
84
+
85
+ class KnowledgeGraphTool:
86
+ """
87
+ Tool for querying biomedical knowledge graphs.
88
+
89
+ Usage:
90
+ # Initialize with gateway URL
91
+ kg = KnowledgeGraphTool(base_url="http://gateway:8080/s/knowledge-graph")
92
+
93
+ # Or from gateway client
94
+ kg = KnowledgeGraphTool.from_gateway(gateway_client)
95
+
96
+ # Query diseases
97
+ diseases = await kg.search_diseases(name="Huntington")
98
+
99
+ # Find similar diseases
100
+ similar = await kg.find_similar_diseases("Huntington Disease", limit=10)
101
+
102
+ # Get entity connections
103
+ edges = await kg.get_entity_network("BRCA1", "protein")
104
+
105
+ # Find path between entities
106
+ path = await kg.find_shortest_path("BRCA1", "protein", "Breast Cancer", "disease")
107
+ """
108
+
109
+ def __init__(self, base_url: str):
110
+ self.base_url = base_url.rstrip("/")
111
+ self._session: Optional[aiohttp.ClientSession] = None
112
+
113
+ @classmethod
114
+ def from_gateway(cls, gateway_client) -> "KnowledgeGraphTool":
115
+ """Create from GatewayClient instance."""
116
+ return cls(gateway_client.knowledge_graph_url)
117
+
118
+ async def _get_session(self) -> aiohttp.ClientSession:
119
+ if self._session is None or self._session.closed:
120
+ timeout = aiohttp.ClientTimeout(total=30.0)
121
+ self._session = aiohttp.ClientSession(timeout=timeout)
122
+ return self._session
123
+
124
+ async def close(self):
125
+ """Close the HTTP session."""
126
+ if self._session and not self._session.closed:
127
+ await self._session.close()
128
+
129
+ async def _get(self, path: str, params: Optional[Dict] = None) -> Any:
130
+ session = await self._get_session()
131
+ url = f"{self.base_url}{path}"
132
+ async with session.get(url, params=params) as response:
133
+ response.raise_for_status()
134
+ return await response.json()
135
+
136
+ # === Entity Lookup Methods ===
137
+
138
+ async def search_diseases(
139
+ self,
140
+ name: Optional[str] = None,
141
+ orphacode: Optional[str] = None,
142
+ omim: Optional[str] = None,
143
+ limit: int = 20,
144
+ ) -> List[Dict]:
145
+ """Search for diseases by name or codes."""
146
+ params: Dict[str, Any] = {"limit": limit}
147
+ if name:
148
+ params["label"] = name
149
+ if orphacode:
150
+ params["orphacode"] = orphacode
151
+ if omim:
152
+ params["omim"] = omim
153
+ return await self._get("/diseases/", params)
154
+
155
+ async def get_disease(self, disease_id: int) -> Dict:
156
+ """Get disease by ID."""
157
+ return await self._get(f"/diseases/{disease_id}")
158
+
159
+ async def search_proteins(
160
+ self,
161
+ symbol: Optional[str] = None,
162
+ ensembl_id: Optional[str] = None,
163
+ limit: int = 20,
164
+ ) -> List[Dict]:
165
+ """Search for proteins/genes."""
166
+ params: Dict[str, Any] = {"limit": limit}
167
+ if symbol:
168
+ params["symbol"] = symbol
169
+ if ensembl_id:
170
+ params["ensembl_id"] = ensembl_id
171
+ return await self._get("/proteins/", params)
172
+
173
+ async def search_phenotypes(
174
+ self,
175
+ name: Optional[str] = None,
176
+ hpo_id: Optional[str] = None,
177
+ limit: int = 20,
178
+ ) -> List[Dict]:
179
+ """Search for phenotypes (HPO terms)."""
180
+ params: Dict[str, Any] = {"limit": limit}
181
+ if name:
182
+ params["name"] = name
183
+ if hpo_id:
184
+ params["hpo_id"] = hpo_id
185
+ return await self._get("/phenotypes/", params)
186
+
187
+ async def search_drugs(
188
+ self,
189
+ name: Optional[str] = None,
190
+ limit: int = 20,
191
+ ) -> List[Dict]:
192
+ """Search for drugs."""
193
+ params: Dict[str, Any] = {"limit": limit}
194
+ if name:
195
+ params["name"] = name
196
+ return await self._get("/drugs/", params)
197
+
198
+ async def search_pathways(
199
+ self,
200
+ name: Optional[str] = None,
201
+ limit: int = 20,
202
+ ) -> List[Dict]:
203
+ """Search for biological pathways."""
204
+ params: Dict[str, Any] = {"limit": limit}
205
+ if name:
206
+ params["name"] = name
207
+ return await self._get("/pathways/", params)
208
+
209
+ # === Similarity Analysis ===
210
+
211
+ async def find_similar_diseases(
212
+ self,
213
+ disease_name: str,
214
+ limit: int = 10,
215
+ ) -> List[SimilarDisease]:
216
+ """Find diseases similar by shared phenotypes."""
217
+ data = await self._get(
218
+ "/diseases/similarity",
219
+ params={"disease_name": disease_name, "limit": limit},
220
+ )
221
+ return [SimilarDisease.from_dict(d) for d in data.get("similar_diseases", [])]
222
+
223
+ async def find_similar_diseases_by_genes(
224
+ self,
225
+ disease_name: str,
226
+ limit: int = 10,
227
+ ) -> List[SimilarDisease]:
228
+ """Find diseases similar by shared genes."""
229
+ data = await self._get(
230
+ "/diseases/similarity-by-genes",
231
+ params={"disease_name": disease_name, "limit": limit},
232
+ )
233
+ return [SimilarDisease.from_dict(d) for d in data.get("similar_diseases", [])]
234
+
235
+ # === Graph Traversal ===
236
+
237
+ async def get_entity_network(
238
+ self,
239
+ entity_name: str,
240
+ entity_type: str,
241
+ ) -> List[Edge]:
242
+ """
243
+ Get all connections for an entity.
244
+
245
+ Args:
246
+ entity_name: Name of the entity (e.g., "BRCA1", "Huntington Disease")
247
+ entity_type: One of: protein, phenotype, disease, pathway,
248
+ molecular_function, cellular_component, biological_process
249
+ """
250
+ data = await self._get(
251
+ "/edges/network",
252
+ params={"entity_name": entity_name, "entity_type": entity_type},
253
+ )
254
+ return [Edge.from_dict(e) for e in data]
255
+
256
+ async def k_hop_traversal(
257
+ self,
258
+ entity_name: str,
259
+ entity_type: str,
260
+ k: int = 2,
261
+ limit_edges: int = 100,
262
+ ) -> Dict:
263
+ """
264
+ Perform k-hop BFS traversal from entity.
265
+
266
+ Returns subgraph with nodes and edges within k hops.
267
+ """
268
+ return await self._get(
269
+ "/graph/k-hop",
270
+ params={
271
+ "entity_name": entity_name,
272
+ "entity_type": entity_type,
273
+ "k": k,
274
+ "limit_edges": limit_edges,
275
+ },
276
+ )
277
+
278
+ async def find_shortest_path(
279
+ self,
280
+ start_name: str,
281
+ start_type: str,
282
+ end_name: str,
283
+ end_type: str,
284
+ max_hops: int = 5,
285
+ ) -> List[PathStep]:
286
+ """
287
+ Find shortest path between two entities (Mechanism of Action).
288
+
289
+ Args:
290
+ start_name: Starting entity name
291
+ start_type: Starting entity type
292
+ end_name: Target entity name
293
+ end_type: Target entity type
294
+ max_hops: Maximum path length
295
+ """
296
+ data = await self._get(
297
+ "/paths/shortest",
298
+ params={
299
+ "start_name": start_name,
300
+ "start_type": start_type,
301
+ "end_name": end_name,
302
+ "end_type": end_type,
303
+ "max_hops": max_hops,
304
+ },
305
+ )
306
+ return [PathStep.from_dict(s) for s in data.get("path", [])]