async def _query( self, query: Query, priority: float = 0, # lowest goes first timeout: Optional[float] = 60.0, ) -> ReasonerResponse: """Queue up a query for batching and return when completed""" if self.worker is None: raise RuntimeError( "Cannot send a request until a worker is running - enter the context" ) # TODO figure out a way to remove this conversion query = Query.parse_obj(query) response_queue = asyncio.Queue() qgraphs = get_canonical_qgraphs(query.message.query_graph) for qgraph in qgraphs: subquery = Query(message=Message(query_graph=qgraph)) # Queue query for processing request_id = str(uuid.uuid1()) await self.request_queue.put( ( (priority, next(self.counter)), (request_id, subquery, response_queue), ) ) combined_output = ReasonerResponse.parse_obj( { "message": { "knowledge_graph": {"nodes": {}, "edges": {}}, "results": [], } } ) for _ in qgraphs: # Wait for response output: Union[ReasonerResponse, Exception] = await asyncio.wait_for( response_queue.get(), timeout=None, ) if isinstance(output, Exception): raise output output.message.query_graph = None combined_output.message.update(output.message) combined_output.message.query_graph = query.message.query_graph return combined_output.dict()
async def sync_query( query: Query = Body(..., example=EXAMPLE), redis_client: Redis = Depends(get_redis_client), ) -> dict: """Handle synchronous query.""" # parse requested workflow query_dict = query.dict() workflow = query_dict.get("workflow", None) or [{"id": "lookup"}] if not isinstance(workflow, list): raise HTTPException(400, "workflow must be a list") if not len(workflow) == 1: raise HTTPException(400, "workflow must contain exactly 1 operation") if "id" not in workflow[0]: raise HTTPException(400, "workflow must have property 'id'") if workflow[0]["id"] == "filter_results_top_n": max_results = workflow[0]["parameters"]["max_results"] if max_results < len(query_dict["message"]["results"]): query_dict["message"]["results"] = query_dict["message"][ "results"][:max_results] return query_dict if not workflow[0]["id"] == "lookup": raise HTTPException(400, "operations must have id 'lookup'") query_results = await lookup(query_dict, redis_client) # Return results return JSONResponse(query_results)
def knowledge_graph_one_hop( obj: Query = Body(..., example=KG_ONEHOP_EXAMPLE), reasoner: bool = True, verbose: bool = False, conn=Depends(get_db), api_key: APIKey = Depends(get_api_key), ) -> Dict: """Query the ICEES clinical reasoner for knowledge graph one hop.""" if obj.get("workflow", [{"id": "lookup"}]) != [{"id": "lookup"}]: raise HTTPException( 400, "The only supported workflow is a single 'lookup' operation") return_value = knowledgegraph.one_hop(conn, obj, verbose=verbose) return_value = { "message": { "query_graph": return_value.pop("query_graph"), "knowledge_graph": return_value.pop("knowledge_graph", None), "results": return_value.pop("results", None), }, "workflow": [ { "id": "lookup" }, ], **return_value, } if reasoner: return return_value return {"return value": return_value}
async def answer_question(query: Query, ) -> Response: """Get results for query graph.""" query = query.dict(exclude_unset=True) workflow = query.get("workflow", [{"id": "lookup"}]) if len(workflow) > 1: raise HTTPException( 400, "Binder does not support workflows of length >1") operation = workflow[0] qgraph = query["message"]["query_graph"] if operation["id"] == "lookup": async with KnowledgeProvider(database_file, **kwargs) as kp: kgraph, results = await kp.get_results(qgraph) elif operation["id"] == "bind": kgraph = query["message"]["knowledge_graph"] knodes = [{ "id": knode_id, "category": knode.get("categories", ["biolink:NamedThing"])[0], } for knode_id, knode in kgraph["nodes"].items()] kedges = [{ "id": kedge_id, "subject": kedge["subject"], "predicate": kedge["predicate"], "object": kedge["object"], } for kedge_id, kedge in kgraph["edges"].items()] async with KnowledgeProvider(":memory:", **kwargs) as kp: await add_data(kp.db, knodes, kedges) kgraph, results = await kp.get_results(qgraph) else: raise HTTPException(400, f"Unsupported operation {operation}") response = { "message": { "knowledge_graph": kgraph, "results": results, "query_graph": qgraph, } } return Response.parse_obj(response)
async def lookup( request: ReasonerQuery = Body(..., example=load_example("query")), ) -> Response: """Look up answers to the question.""" trapi_query = request.dict( by_alias=True, exclude_unset=True, ) try: await map_identifiers(trapi_query) except KeyError: pass async with httpx.AsyncClient() as client: response = await client.post( f"{settings.robokop_kg}/query", json=trapi_query, timeout=None, ) if response.status_code != 200: raise HTTPException(500, f"Failed doing lookup: {response.text}") response = await client.post( f"{settings.aragorn_ranker}/omnicorp_overlay", json=response.json(), timeout=None, ) if response.status_code != 200: raise HTTPException(500, f"Failed doing overlay: {response.text}") response = await client.post( f"{settings.aragorn_ranker}/weight_correctness", json=response.json(), timeout=None, ) if response.status_code != 200: raise HTTPException(500, f"Failed doing weighting: {response.text}") response = await client.post( f"{settings.aragorn_ranker}/score", json=response.json(), timeout=None, ) if response.status_code != 200: raise HTTPException(500, f"Failed doing scoring: {response.text}") return Response(**response.json())
def post_reasoner_predict(request_body: Query = Body( ..., example=TRAPI_EXAMPLE)) -> Query: """Get predicted associations for a given ReasonerAPI query. :param request_body: The ReasonerStdAPI query in JSON :return: Predictions as a ReasonerStdAPI Message """ query_graph = request_body.message.query_graph.dict(exclude_none=True) if len(query_graph["edges"]) == 0: return { "message": { 'knowledge_graph': { 'nodes': {}, 'edges': {} }, 'query_graph': query_graph, 'results': [] } } # return ({"status": 400, "title": "Bad Request", "detail": "No edges", "type": "about:blank" }, 400) if len(query_graph["edges"]) > 1: # Currently just return a empty result if multi-edges query return { "message": { 'knowledge_graph': { 'nodes': {}, 'edges': {} }, 'query_graph': query_graph, 'results': [] } } # return ({"status": 501, "title": "Not Implemented", "detail": "Multi-edges queries not yet implemented", "type": "about:blank" }, 501) # reasonerapi_response = resolve_trapi_query(request_body.dict(exclude_none=True), app=app) reasonerapi_response = resolve_trapi_query( request_body.dict(exclude_none=True), app) return JSONResponse(reasonerapi_response) or ('Not found', 404)
async def run_workflow( request: ReasonerQuery = Body(..., example=load_example("query")), ) -> Response: """Run workflow.""" request_dict = request.dict(exclude_unset=True, ) message = request_dict["message"] workflow = request_dict["workflow"] logger = gen_logger() qgraph = message["query_graph"] kgraph = {"nodes": {}, "edges": {}} if "knowledge_graph" in message.keys(): if "nodes" in message["knowledge_graph"].keys(): kgraph = message["knowledge_graph"] async with httpx.AsyncClient(verify=False, timeout=60.0) as client: for operation in workflow: service_operation_responses = [] for service in SERVICES[operation["id"]]: url = service["url"] service_name = service["title"] logger.debug( f"Requesting operation '{operation}' from {service_name}..." ) try: response = await post_safely( url, { "message": message, "workflow": [ operation, ], "submitter": "Workflow Runner", }, client=client, timeout=60.0, logger=logger, service_name=service_name, ) logger.debug( f"Received operation '{operation}' from {service_name}..." ) try: response = await post_safely( NORMALIZER_URL + "/response", { "message": response["message"], "submitter": "Workflow Runner" }, client=client, timeout=60.0, logger=logger, service_name="node_normalizer") except RuntimeError as e: logger.warning({"error": str(e)}) service_operation_responses.append(response) except RuntimeError as e: logger.warning({"error": str(e)}) if not OPERATIONS[operation["id"]]["unique"] and len( service_operation_responses) == 1: # We only need one successful response for non-unique operations break logger.debug( f"Merging {len(service_operation_responses)} responses for '{operation}'..." ) m = Message( query_graph=QueryGraph.parse_obj(qgraph), knowledge_graph=KnowledgeGraph.parse_obj(kgraph), ) for response in service_operation_responses: response["message"]["query_graph"] = qgraph m.update(Message.parse_obj(response["message"])) message = m.dict() return Response( message=message, workflow=workflow, logs=logger.handlers[0].store, )
def query(obj: Query = Body(..., example=KG_ONEHOP_EXAMPLE), ) -> Dict: """Solve a one-hop TRAPI query.""" if obj.get("workflow", [{"id": "lookup"}]) != [{"id": "lookup"}]: raise HTTPException( 400, "The only supported workflow is a single 'lookup' operation") qgraph = copy.deepcopy(obj["message"]["query_graph"]) normalize_qgraph(qgraph) if len(qgraph["nodes"]) != 2: raise NotImplementedError("Number of nodes in query graph must be 2") if len(qgraph["edges"]) != 1: raise NotImplementedError("Number of edges in query graph must be 1") qedge_id, qedge = next(iter(qgraph["edges"].items())) if ("biolink:correlated_with" not in qedge["predicates"] and "biolink:has_real_world_evidence_of_association_with" not in qedge["predicates"]): return { "message": { "query_graph": qgraph, "knowledge_graph": { "nodes": {}, "edges": {} }, "results": [], } } source_qid = qedge["subject"] source_qnode = qgraph["nodes"][source_qid] target_qid = qedge["object"] target_qnode = qgraph["nodes"][target_qid] # features = correlations[0] source_features = features_from_node(source_qnode) target_features = features_from_node(target_qnode) kedge_pairs = [ tuple(sorted([source_feature, target_feature])) for source_feature in source_features for target_feature in target_features ] kgraph = { "nodes": {}, "edges": {}, } results = [] for pair in kedge_pairs: if pair not in correlations: continue p_value = correlations[pair] source_feature, target_feature = pair # note the source and target may be flipped, which is okay source_kid, source_knode = knode(source_feature) target_kid, target_knode = knode(target_feature) kgraph["nodes"].update({ source_kid: source_knode, target_kid: target_knode, }) kedges = knowledgegraph.knowledge_graph_edges(source_kid, target_kid, p_value=p_value) kgraph["edges"].update(kedges) results.append({ "node_bindings": { source_qid: [{ "id": source_kid }], target_qid: [{ "id": target_kid }], }, "edge_bindings": { qedge_id: [{ "id": kedge_id, } for kedge_id in kedges] }, "score": p_value, "score_name": "p value" }) return { "message": { "query_graph": obj["message"]["query_graph"], # Return unmodified "knowledge_graph": kgraph, "results": results, }, "workflow": [ { "id": "lookup" }, ], }