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.
- basion_agent/__init__.py +62 -0
- basion_agent/agent.py +360 -0
- basion_agent/agent_state_client.py +149 -0
- basion_agent/app.py +502 -0
- basion_agent/artifact.py +58 -0
- basion_agent/attachment_client.py +153 -0
- basion_agent/checkpoint_client.py +169 -0
- basion_agent/checkpointer.py +16 -0
- basion_agent/cli.py +139 -0
- basion_agent/conversation.py +103 -0
- basion_agent/conversation_client.py +86 -0
- basion_agent/conversation_message.py +48 -0
- basion_agent/exceptions.py +36 -0
- basion_agent/extensions/__init__.py +1 -0
- basion_agent/extensions/langgraph.py +526 -0
- basion_agent/extensions/pydantic_ai.py +180 -0
- basion_agent/gateway_client.py +531 -0
- basion_agent/gateway_pb2.py +73 -0
- basion_agent/gateway_pb2_grpc.py +101 -0
- basion_agent/heartbeat.py +84 -0
- basion_agent/loki_handler.py +355 -0
- basion_agent/memory.py +73 -0
- basion_agent/memory_client.py +155 -0
- basion_agent/message.py +333 -0
- basion_agent/py.typed +0 -0
- basion_agent/streamer.py +184 -0
- basion_agent/structural/__init__.py +6 -0
- basion_agent/structural/artifact.py +94 -0
- basion_agent/structural/base.py +71 -0
- basion_agent/structural/stepper.py +125 -0
- basion_agent/structural/surface.py +90 -0
- basion_agent/structural/text_block.py +96 -0
- basion_agent/tools/__init__.py +19 -0
- basion_agent/tools/container.py +46 -0
- basion_agent/tools/knowledge_graph.py +306 -0
- basion_agent-0.4.0.dist-info/METADATA +880 -0
- basion_agent-0.4.0.dist-info/RECORD +41 -0
- basion_agent-0.4.0.dist-info/WHEEL +5 -0
- basion_agent-0.4.0.dist-info/entry_points.txt +2 -0
- basion_agent-0.4.0.dist-info/licenses/LICENSE +21 -0
- 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", [])]
|