Skip to content

API Calling: Base Classes

We provide base classes for the API Agent implementation to align individual modules.

Abstract base classes

Abstract base classes for API interaction components.

Provides base classes for query builders, fetchers, and interpreters used in API interactions and result processing.

BaseAPIModel

Bases: BaseModel

A base class for all API models.

Includes default fields uuid and method_name.

Source code in biochatter/api_agent/base/agent_abc.py
class BaseAPIModel(BaseModel):
    """A base class for all API models.

    Includes default fields `uuid` and `method_name`.
    """

    uuid: str | None = Field(
        None,
        description="Unique identifier for the model instance",
    )
    model_config = ConfigDict(arbitrary_types_allowed=True)

BaseFetcher

Bases: ABC

Abstract base class for fetchers.

A fetcher is responsible for submitting queries (in systems where submission and fetching are separate) and fetching and saving results of queries. It has to implement a fetch_results() method, which can wrap a multi-step procedure to submit and retrieve. Should implement retry method to account for connectivity issues or processing times.

Source code in biochatter/api_agent/base/agent_abc.py
class BaseFetcher(ABC):
    """Abstract base class for fetchers.

    A fetcher is responsible for submitting queries (in systems where
    submission and fetching are separate) and fetching and saving results of
    queries. It has to implement a `fetch_results()` method, which can wrap a
    multi-step procedure to submit and retrieve. Should implement retry method to
    account for connectivity issues or processing times.
    """

    @abstractmethod
    def fetch_results(
        self,
        query_models: list[BaseModel],
        retries: int | None = 3,
    ):
        """Fetch results by submitting a query.

        Can implement a multi-step procedure if submitting and fetching are
        distinct processes (e.g., in the case of long processing times as in the
        case of BLAST).

        Args:
        ----
            query_models: list of Pydantic models describing the parameterised
                queries

        """

fetch_results(query_models, retries=3) abstractmethod

Fetch results by submitting a query.

Can implement a multi-step procedure if submitting and fetching are distinct processes (e.g., in the case of long processing times as in the case of BLAST).


query_models: list of Pydantic models describing the parameterised
    queries
Source code in biochatter/api_agent/base/agent_abc.py
@abstractmethod
def fetch_results(
    self,
    query_models: list[BaseModel],
    retries: int | None = 3,
):
    """Fetch results by submitting a query.

    Can implement a multi-step procedure if submitting and fetching are
    distinct processes (e.g., in the case of long processing times as in the
    case of BLAST).

    Args:
    ----
        query_models: list of Pydantic models describing the parameterised
            queries

    """

BaseInterpreter

Bases: ABC

Abstract base class for result interpreters.

The interpreter is aware of the nature and structure of the results and can extract and summarise information from them.

Source code in biochatter/api_agent/base/agent_abc.py
class BaseInterpreter(ABC):
    """Abstract base class for result interpreters.

    The interpreter is aware of the nature and structure of the results and can
    extract and summarise information from them.
    """

    @abstractmethod
    def summarise_results(
        self,
        question: str,
        conversation_factory: Callable,
        response_text: str,
    ) -> str:
        """Summarise an answer based on the given parameters.

        Args:
        ----
            question (str): The question that was asked.

            conversation_factory (Callable): A function that creates a
                BioChatter conversation.

            response_text (str): The response.text returned from the request.

        Returns:
        -------
            A summary of the answer.

        Todo:
        ----
            Genericise (remove file path and n_lines parameters, and use a
            generic way to get the results). The child classes should manage the
            specifics of the results.

        """

summarise_results(question, conversation_factory, response_text) abstractmethod

Summarise an answer based on the given parameters.


question (str): The question that was asked.

conversation_factory (Callable): A function that creates a
    BioChatter conversation.

response_text (str): The response.text returned from the request.

A summary of the answer.
Todo:
Genericise (remove file path and n_lines parameters, and use a
generic way to get the results). The child classes should manage the
specifics of the results.
Source code in biochatter/api_agent/base/agent_abc.py
@abstractmethod
def summarise_results(
    self,
    question: str,
    conversation_factory: Callable,
    response_text: str,
) -> str:
    """Summarise an answer based on the given parameters.

    Args:
    ----
        question (str): The question that was asked.

        conversation_factory (Callable): A function that creates a
            BioChatter conversation.

        response_text (str): The response.text returned from the request.

    Returns:
    -------
        A summary of the answer.

    Todo:
    ----
        Genericise (remove file path and n_lines parameters, and use a
        generic way to get the results). The child classes should manage the
        specifics of the results.

    """

BaseQueryBuilder

Bases: ABC

An abstract base class for query builders.

Source code in biochatter/api_agent/base/agent_abc.py
class BaseQueryBuilder(ABC):
    """An abstract base class for query builders."""

    @property
    def structured_output_prompt(self) -> ChatPromptTemplate:
        """Define a structured output prompt template.

        This provides a default implementation for an API agent that can be
        overridden by subclasses to return a ChatPromptTemplate-compatible
        object.
        """
        return ChatPromptTemplate.from_messages(
            [
                (
                    "system",
                    "You are a world class algorithm for extracting information in structured formats.",
                ),
                (
                    "human",
                    "Use the given format to extract information from the following input: {input}",
                ),
                ("human", "Tip: Make sure to answer in the correct format"),
            ],
        )

    @abstractmethod
    def create_runnable(
        self,
        query_parameters: "BaseModel",
        conversation: "Conversation",
    ) -> Callable:
        """Create a runnable object for executing queries.

        Must be implemented by subclasses. Should use the LangChain
        `create_structured_output_runnable` method to generate the Callable.

        Args:
        ----
            query_parameters: A Pydantic data model that specifies the fields of
                the API that should be queried.

            conversation: A BioChatter conversation object.

        Returns:
        -------
            A Callable object that can execute the query.

        """

    @abstractmethod
    def parameterise_query(
        self,
        question: str,
        conversation: "Conversation",
    ) -> list[BaseModel]:
        """Parameterise a query object.

        Parameterises a Pydantic model with the fields of the API based on the
        given question using a BioChatter conversation instance. Must be
        implemented by subclasses.

        Args:
        ----
            question (str): The question to be answered.

            conversation: The BioChatter conversation object containing the LLM
                that should parameterise the query.

        Returns:
        -------
            A list containing one or more parameterised instance(s) of the query
            object (Pydantic BaseModel).

        """

structured_output_prompt property

Define a structured output prompt template.

This provides a default implementation for an API agent that can be overridden by subclasses to return a ChatPromptTemplate-compatible object.

create_runnable(query_parameters, conversation) abstractmethod

Create a runnable object for executing queries.

Must be implemented by subclasses. Should use the LangChain create_structured_output_runnable method to generate the Callable.


query_parameters: A Pydantic data model that specifies the fields of
    the API that should be queried.

conversation: A BioChatter conversation object.

A Callable object that can execute the query.
Source code in biochatter/api_agent/base/agent_abc.py
@abstractmethod
def create_runnable(
    self,
    query_parameters: "BaseModel",
    conversation: "Conversation",
) -> Callable:
    """Create a runnable object for executing queries.

    Must be implemented by subclasses. Should use the LangChain
    `create_structured_output_runnable` method to generate the Callable.

    Args:
    ----
        query_parameters: A Pydantic data model that specifies the fields of
            the API that should be queried.

        conversation: A BioChatter conversation object.

    Returns:
    -------
        A Callable object that can execute the query.

    """

parameterise_query(question, conversation) abstractmethod

Parameterise a query object.

Parameterises a Pydantic model with the fields of the API based on the given question using a BioChatter conversation instance. Must be implemented by subclasses.


question (str): The question to be answered.

conversation: The BioChatter conversation object containing the LLM
    that should parameterise the query.

A list containing one or more parameterised instance(s) of the query
object (Pydantic BaseModel).
Source code in biochatter/api_agent/base/agent_abc.py
@abstractmethod
def parameterise_query(
    self,
    question: str,
    conversation: "Conversation",
) -> list[BaseModel]:
    """Parameterise a query object.

    Parameterises a Pydantic model with the fields of the API based on the
    given question using a BioChatter conversation instance. Must be
    implemented by subclasses.

    Args:
    ----
        question (str): The question to be answered.

        conversation: The BioChatter conversation object containing the LLM
            that should parameterise the query.

    Returns:
    -------
        A list containing one or more parameterised instance(s) of the query
        object (Pydantic BaseModel).

    """

BaseTools

Abstract base class for tools.

Source code in biochatter/api_agent/base/agent_abc.py
class BaseTools:
    """Abstract base class for tools."""

    def make_pydantic_tools(self) -> list[BaseAPIModel]:
        """Uses pydantics create_model to create a list of pydantic tools from a dictionary of parameters"""
        tools = []
        for func_name, tool_params in self.tools_params.items():
            tools.append(create_model(func_name, **tool_params, __base__=BaseAPIModel))
        return tools

make_pydantic_tools()

Uses pydantics create_model to create a list of pydantic tools from a dictionary of parameters

Source code in biochatter/api_agent/base/agent_abc.py
def make_pydantic_tools(self) -> list[BaseAPIModel]:
    """Uses pydantics create_model to create a list of pydantic tools from a dictionary of parameters"""
    tools = []
    for func_name, tool_params in self.tools_params.items():
        tools.append(create_model(func_name, **tool_params, __base__=BaseAPIModel))
    return tools

The API Agent

Base API agent module.

APIAgent

Source code in biochatter/api_agent/base/api_agent.py
class APIAgent:
    def __init__(
        self,
        conversation_factory: Callable,
        query_builder: "BaseQueryBuilder",
        fetcher: "BaseFetcher",
        interpreter: "BaseInterpreter",
    ):
        """API agent class to interact with a tool's API for querying and fetching
        results.  The query fields have to be defined in a Pydantic model
        (`BaseModel`) and used (i.e., parameterised by the LLM) in the query
        builder. Specific API agents are defined in submodules of this directory
        (`api_agent`). The agent's logic is implemented in the `execute` method.

        Attributes
        ----------
            conversation_factory (Callable): A function used to create a
                BioChatter conversation, providing LLM access.

            query_builder (BaseQueryBuilder): An instance of a child of the
                BaseQueryBuilder class.

            result_fetcher (BaseFetcher): An instance of a child of the
                BaseFetcher class.

            result_interpreter (BaseInterpreter): An instance of a child of the
                BaseInterpreter class.

        """
        self.conversation_factory = conversation_factory
        self.query_builder = query_builder
        self.fetcher = fetcher
        self.interpreter = interpreter
        self.final_answer = None

    def parameterise_query(self, question: str) -> list[BaseModel] | None:
        """Use LLM to parameterise a query (a list of Pydantic models) based on the given
        question using a BioChatter conversation instance.
        """
        try:
            conversation = self.conversation_factory()
            return self.query_builder.parameterise_query(question, conversation)
        except Exception as e:
            print(f"Error generating query: {e}")
            return None

    def fetch_results(self, query_models: list[BaseModel]) -> str | None:
        """Fetch the results of the query using the individual API's implementation
        (either single-step or submit-retrieve).

        Args:
        ----
            query_models: list of parameterised query Pydantic models

        """
        try:
            return self.fetcher.fetch_results(query_models, 100)
        except Exception as e:
            print(f"Error fetching results: {e}")
            return None

    def summarise_results(
        self,
        question: str,
        response_text: str,
    ) -> str | None:
        """Summarise the retrieved results to extract the answer to the question."""
        try:
            return self.interpreter.summarise_results(
                question=question,
                conversation_factory=self.conversation_factory,
                response_text=response_text,
            )
        except Exception as e:
            print(f"Error extracting answer: {e}")
            return None

    def execute(self, question: str) -> str | None:
        """Wrapper that uses class methods to execute the API agent logic. Consists
        of 1) query generation, 2) query submission, 3) results fetching, and
        4) answer extraction. The final answer is stored in the final_answer
        attribute.

        Args:
        ----
            question (str): The question to be answered.

        """
        # Generate query
        try:
            query_models = self.parameterise_query(question)
            if not query_models:
                raise ValueError("Failed to generate query.")
        except ValueError as e:
            print(e)

        # Fetch results
        try:
            response_text = self.fetch_results(
                query_models=query_models,
            )
            if not response_text:
                raise ValueError("Failed to fetch results.")
        except ValueError as e:
            print(e)

        # Extract answer from results
        try:
            final_answer = self.summarise_results(question, response_text)
            if not final_answer:
                raise ValueError("Failed to extract answer from results.")
        except ValueError as e:
            print(e)

        self.final_answer = final_answer
        return final_answer

    def get_description(self, tool_name: str, tool_desc: str):
        return f"This API agent interacts with {tool_name}'s API for querying and fetching results. {tool_desc}"

__init__(conversation_factory, query_builder, fetcher, interpreter)

API agent class to interact with a tool's API for querying and fetching results. The query fields have to be defined in a Pydantic model (BaseModel) and used (i.e., parameterised by the LLM) in the query builder. Specific API agents are defined in submodules of this directory (api_agent). The agent's logic is implemented in the execute method.

Attributes
conversation_factory (Callable): A function used to create a
    BioChatter conversation, providing LLM access.

query_builder (BaseQueryBuilder): An instance of a child of the
    BaseQueryBuilder class.

result_fetcher (BaseFetcher): An instance of a child of the
    BaseFetcher class.

result_interpreter (BaseInterpreter): An instance of a child of the
    BaseInterpreter class.
Source code in biochatter/api_agent/base/api_agent.py
def __init__(
    self,
    conversation_factory: Callable,
    query_builder: "BaseQueryBuilder",
    fetcher: "BaseFetcher",
    interpreter: "BaseInterpreter",
):
    """API agent class to interact with a tool's API for querying and fetching
    results.  The query fields have to be defined in a Pydantic model
    (`BaseModel`) and used (i.e., parameterised by the LLM) in the query
    builder. Specific API agents are defined in submodules of this directory
    (`api_agent`). The agent's logic is implemented in the `execute` method.

    Attributes
    ----------
        conversation_factory (Callable): A function used to create a
            BioChatter conversation, providing LLM access.

        query_builder (BaseQueryBuilder): An instance of a child of the
            BaseQueryBuilder class.

        result_fetcher (BaseFetcher): An instance of a child of the
            BaseFetcher class.

        result_interpreter (BaseInterpreter): An instance of a child of the
            BaseInterpreter class.

    """
    self.conversation_factory = conversation_factory
    self.query_builder = query_builder
    self.fetcher = fetcher
    self.interpreter = interpreter
    self.final_answer = None

execute(question)

Wrapper that uses class methods to execute the API agent logic. Consists of 1) query generation, 2) query submission, 3) results fetching, and 4) answer extraction. The final answer is stored in the final_answer attribute.


question (str): The question to be answered.
Source code in biochatter/api_agent/base/api_agent.py
def execute(self, question: str) -> str | None:
    """Wrapper that uses class methods to execute the API agent logic. Consists
    of 1) query generation, 2) query submission, 3) results fetching, and
    4) answer extraction. The final answer is stored in the final_answer
    attribute.

    Args:
    ----
        question (str): The question to be answered.

    """
    # Generate query
    try:
        query_models = self.parameterise_query(question)
        if not query_models:
            raise ValueError("Failed to generate query.")
    except ValueError as e:
        print(e)

    # Fetch results
    try:
        response_text = self.fetch_results(
            query_models=query_models,
        )
        if not response_text:
            raise ValueError("Failed to fetch results.")
    except ValueError as e:
        print(e)

    # Extract answer from results
    try:
        final_answer = self.summarise_results(question, response_text)
        if not final_answer:
            raise ValueError("Failed to extract answer from results.")
    except ValueError as e:
        print(e)

    self.final_answer = final_answer
    return final_answer

fetch_results(query_models)

Fetch the results of the query using the individual API's implementation (either single-step or submit-retrieve).


query_models: list of parameterised query Pydantic models
Source code in biochatter/api_agent/base/api_agent.py
def fetch_results(self, query_models: list[BaseModel]) -> str | None:
    """Fetch the results of the query using the individual API's implementation
    (either single-step or submit-retrieve).

    Args:
    ----
        query_models: list of parameterised query Pydantic models

    """
    try:
        return self.fetcher.fetch_results(query_models, 100)
    except Exception as e:
        print(f"Error fetching results: {e}")
        return None

parameterise_query(question)

Use LLM to parameterise a query (a list of Pydantic models) based on the given question using a BioChatter conversation instance.

Source code in biochatter/api_agent/base/api_agent.py
def parameterise_query(self, question: str) -> list[BaseModel] | None:
    """Use LLM to parameterise a query (a list of Pydantic models) based on the given
    question using a BioChatter conversation instance.
    """
    try:
        conversation = self.conversation_factory()
        return self.query_builder.parameterise_query(question, conversation)
    except Exception as e:
        print(f"Error generating query: {e}")
        return None

summarise_results(question, response_text)

Summarise the retrieved results to extract the answer to the question.

Source code in biochatter/api_agent/base/api_agent.py
def summarise_results(
    self,
    question: str,
    response_text: str,
) -> str | None:
    """Summarise the retrieved results to extract the answer to the question."""
    try:
        return self.interpreter.summarise_results(
            question=question,
            conversation_factory=self.conversation_factory,
            response_text=response_text,
        )
    except Exception as e:
        print(f"Error extracting answer: {e}")
        return None