Ejemplo n.º 1
0
    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()
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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}
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
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())
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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,
    )
Ejemplo n.º 8
0
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"
            },
        ],
    }