Introducing the FlowVerse with a Simple Q&A Flow¶
Prerequisites: setting up your API keys (see setting_up_aiFlows.md), Atomic Flow Tutorial
This guide introduces the FlowVerse via an example: minimalQA. The guide is organized in two sections:
Section 1: What’s the FlowVerse?
Section 2: Crafting a Simple Q&A Flow with the ChatFlowModule
By the Tutorial’s End, I Will Have…¶
Gained an understanding of the FlowVerse and its significance
Acquired the skills to retrieve flows from the FlowVerse
Successfully developed my initial flow by incorporating a FlowVerse flow
Created a Simple Q&A flow capable of managing user-assistant interactions with a Language Model (LLM) through an API
Familiarized myself with the fundamental parameters of the
ChatAtomicFlow
Section 1: What’s the FlowVerse ?¶
The FlowVerse is the hub of flows created and shared by our amazing community for everyone to use! These flows are usually shared on Hugging Face with the intention of being reused by others. Explore our Flows on the FlowVerse here!
Section 2: Crafting a Simple Q&A Flow with the ChatFlowModule¶
In this section, we’ll guide you through the creation of a simple Q&A flow — a single user-assitant interaction with a LLM. We’ll achieve this by leveraging the ChatAtomicFlow from the ChatFlowModule in the FlowVerse. The ChatAtomicFlow seamlessly interfaces with an LLM through an API, generating textual responses for textual input. Powered by the LiteLLM library in the backend, ChatAtomicFlow supports various API providers; explore the full list here.
For an in-depth understanding of ChatAtomicFlow, refer to its FlowCard (README).
Note that all the code referenced from this point onwards can be found here
Let’s dive in without further delay!
First thing to do is to fetch the ChatFlowModule from the FlowVerse (see run_qa_flow.py to see all the code):
from aiflows import flow_verse
# ~~~ Load Flow dependecies from FlowVerse ~~~
dependencies = [
{"url": "aiflows/ChatFlowModule", "revision": "297c90d08087d9ff3139521f11d1a48d7dc63ed4"},
]
flow_verse.sync_dependencies(dependencies)
Let’s break this down:
dependenciesis a list of dictionaries (in this case, there’s only one) indicating which FlowModules we want to pull from the FlowVerse. The dictionary contains two key-value pairs:url: Specifies the URL where the flow can be found on Hugging Face. Here, the URL isaiflows/ChatFlowModule, whereaiflowsis the name of our organization on Hugging Face (or the username of a user hosting their flow on Hugging Face), andChatFlowModuleis the name of the FlowModule containing theChatAtomicFlowon the FlowVerse. Note that theurlis literally the address of theChatFlowModuleon Hugging Face (excluding the https://huggingface.co/). So if you type https://huggingface.co/aiflows/ChatFlowModule in your browser, you will find the Flow.revision: Represents the revision id (i.e., the full commit hash) of the commit we want to fetch. Note that if you setrevisiontomain, it will fetch the latest commit on the main branch.
Now that we’ve fetched the ChatAtomicFlowModule from the FlowVerse, we can start creating our Flow.
The configuration for our flow is available in simpleQA.yaml. We will now break it down into chunks and explain its various parameters. Note that the flow is instantiated from its default configuration, so we are only defining the parameters we wish to override here. The default configuration can be found here
Let’s start with the input and output interface:
input_interface: # Connector between the "input data" and the Flow
_target_: aiflows.interfaces.KeyInterface
additional_transformations:
- _target_: aiflows.data_transformations.KeyMatchInput # Pass the input parameters specified by the flow
output_interface: # Connector between the Flow's output and the caller
_target_: aiflows.interfaces.KeyInterface
keys_to_rename:
api_output: answer # Rename the api_output to answer
input_interfacespecifies the expected keys in the input data dictionary passed to our flow.output_interfaceoutlines the expected keys in the output data dictionary produced by our flow.
Now let’s look at the flow’s configuration:
flow: # Overrides the ChatAtomicFlow config
_target_: flow_modules.aiflows.ChatFlowModule.ChatAtomicFlow.instantiate_from_default_config
name: "SimpleQA_Flow"
description: "A flow that answers questions."
The
_target_parameter specifies the instantiation method for our flow. In this instance, we’re using it to instantiate theChatAtomicFlowfrom its default configuration filenameanddescription: self-explanatory parameters
# ~~~ Input interface specification ~~~
input_interface_non_initialized:
- "question"
The
input_interface_non_initializedparameter in our configuration specifies the keys expected in the input data dictionary when theChatAtomicFlowis called for the first time (i.e., when the system prompt is constructed). Essentially, it serves a role similar to the regularinput_interface. The distinction becomes apparent when you require different inputs for the initial query compared to subsequent queries. For instance, in ReAct, the first time you query the LLM, the input is provided by a human, such as a question. In subsequent queries, the input comes from the execution of a tool (e.g. a query to wikipedia). In ReAct’s case, these two scenarios are distinguished byChatAtomicFlow’sinput_interface_non_initializedandinput_interface_initializedparameters. For this tutorial, as we’re creating a simple Q&A flow performing a single user-assistant interaction with an LLM, we never useinput_interface_initialized(which is why it’s not defined in the configuration).
# ~~~ backend model parameters ~~
backend:
_target_: aiflows.backends.llm_lite.LiteLLMBackend
api_infos: ???
model_name:
openai: "gpt-3.5-turbo"
azure: "azure/gpt-4"
# ~~~ generation_parameters ~~
n: 1
max_tokens: 3000
temperature: 0.3
top_p: 0.2
frequency_penalty: 0
presence_penalty: 0
backendis a dictionary containing parameters specific to the LLM. These parameters include:api_infosYour API information (which will be passed later for privacy reasons).model_nameA dictionary with key-item pairs, where keys correspond to thebackend_usedattribute of theApiInfoclass for the chosen backend, and values represent the desired model for that backend. Model selection depends on the providedapi_infos. Additional models can be added for different backends, following LiteLLM’s naming conventions (refer to LiteLLM’s supported providers and model names here). For instance, with an Anthropic API key and a desire to use “claude-2,” one would check Anthropic’s model details here. As “claude-2” is named the same in LiteLLM, themodel_namedictionary would be updated as follows:backend: _target_: aiflows.backends.llm_lite.LiteLLMBackend api_infos: ??? model_name: openai: "gpt-3.5-turbo" azure: "azure/gpt-4" anthropic: "claude-2"
n,max_tokens,top_p,frequency_penalty,presence_penaltyare generation parameters for LiteLLM’s completion function (refer to all possible generation parameters here).
# ~~~ Prompt specification ~~~
system_message_prompt_template:
_target_: aiflows.prompt_template.JinjaPrompt
template: |2-
You are a helpful chatbot that truthfully answers questions.
input_variables: []
partial_variables: {}
init_human_message_prompt_template:
_target_: aiflows.prompt_template.JinjaPrompt
template: |2-
Answer the following question: {{question}}
input_variables: ["question"]
partial_variables: {}
system_message_prompt_template: This is the system prompt template passed to the LLM.init_human_message_prompt_template: This is the user prompt template passed to the LLM the first time the flow is called. It includes the following parameters:templateThe prompt template in Jinja format.input_variablesThe input variables of the prompt. For instance, in our case, the prompttemplateis “Answer the following question: {{question}},” and ourinput_variablesis “question.” Before querying the LLM, the prompttemplateis rendered by placing the input variable “question” in the placeholder “{{question}}” of the prompttemplate. It’s worth noting thatinput_interface_non_initialized == input_variables. This alignment is intentional, as they are passed as input_variables to theinit_human_message_prompt_templateto render thetemplate
Now that our configuration file is set up, we can proceed to call our flow. Begin by configuring your API information. Below is an example using an OpenAI key, along with examples for other API providers (in comment):
# ~~~ Set the API information ~~~
# OpenAI backend
api_information = [ApiInfo(backend_used="openai", api_key=os.getenv("OPENAI_API_KEY"))]
# # Azure backend
# api_information = [ApiInfo(backend_used = "azure",
# api_base = os.getenv("AZURE_API_BASE"),
# api_key = os.getenv("AZURE_OPENAI_KEY"),
# api_version = os.getenv("AZURE_API_VERSION") )]
# # Anthropic backend
#api_information = [ApiInfo(backend_used= "anthropic",api_key = os.getenv("ANTHROPIC_API_KEY"))]
Next, load the YAML configuration, insert your API information, and define the flow_with_interfaces dictionary:
cfg_path = os.path.join(root_dir, "simpleQA.yaml")
cfg = read_yaml_file(cfg_path)
# put api information in config (done like this for privacy reasons)
cfg["flow"]["backend"]["api_infos"] = api_information
# ~~~ Instantiate the Flow ~~~
flow_with_interfaces = {
"flow": hydra.utils.instantiate(cfg["flow"], _recursive_=False, _convert_="partial"),
"input_interface": (
None
if cfg.get("input_interface", None) is None
else hydra.utils.instantiate(cfg["input_interface"], _recursive_=False)
),
"output_interface": (
None
if cfg.get("output_interface", None) is None
else hydra.utils.instantiate(cfg["output_interface"], _recursive_=False)
),
}
Finally, run the flow with FlowLauncher.
# ~~~ Get the data ~~~
data = {"id": 0, "question": "Who was the NBA champion in 2023?"} # This can be a list of samples
# ~~~ Run inference ~~~
path_to_output_file = None
# path_to_output_file = "output.jsonl" # Uncomment this line to save the output to disk
_, outputs = FlowLauncher.launch(
flow_with_interfaces=flow_with_interfaces, data=data, path_to_output_file=path_to_output_file
)
# ~~~ Print the output ~~~
flow_output_data = outputs[0]
print(flow_output_data)
The full example is available here and can be executed as follows:
cd examples/minimal\ QA/
python run_qa_flow.py
Upon running, the answer is similar to the following:
[{'answer': "I'm sorry, but as an AI language model, I don't have access to real-time information or the ability to predict future events. As of now, I cannot provide you with the answer to who the NBA champion was in 2023. I recommend checking reliable sports news sources or conducting an internet search for the most up-to-date information."}]
To learn how to obtain information on the 2023 NBA Champion using Flows, refer to the next tutorial ReAct, a Flow that provides ChatAtomicFlow to tools like search engines!
Additionally, the minimal QA folder contains other examples using ChatAtomicFlow such as:
Running a Flow with Demonstrations (encouraging the LLM to finshis answers with “my sire”). To run:
cd examples/minimal\ QA/ python run_qa_flow_w_demonstrations.py
Running the Simple Q&A flow in a multithreaded fashion in order answer multiple questions with mulitple API_keys or providers. To run:
cd examples/minimal\ QA/ python run_qa_flow_multithreaded.py
Next Tutorial: ReAct Tutorial