Skip to content

Commit c6daa30

Browse files
authored
Merge pull request #794 from jeffmaury/GH-562
feat: add function calling recipe
2 parents e67cfbe + f421121 commit c6daa30

File tree

14 files changed

+645
-0
lines changed

14 files changed

+645
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
SHELL := /bin/bash
2+
APP ?= function_calling
3+
PORT ?= 8501
4+
5+
include ../../common/Makefile.common
6+
7+
RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin)
8+
RELATIVE_MODELS_PATH := ../../../models
9+
RELATIVE_TESTS_PATH := ../tests
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Function Calling Application
2+
3+
This recipe helps developers start building their own custom function calling enabled chat applications. It consists of two main components: the Model Service and the AI Application.
4+
5+
There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile).
6+
7+
The AI Application will connect to the Model Service via its OpenAI compatible API. The recipe relies on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with the Model Service and uses [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the chat application below.
8+
9+
![](/assets/chatbot_ui.png)
10+
11+
12+
## Try the Function Calling Application
13+
14+
The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Function Calling` and follow the instructions to start the application.
15+
16+
# Build the Application
17+
18+
The rest of this document will explain how to build and run the application from the terminal, and will
19+
go into greater detail on how each container in the Pod above is built, run, and
20+
what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application.
21+
22+
23+
This application requires a model, a model service and an AI inferencing application.
24+
25+
* [Quickstart](#quickstart)
26+
* [Download a model](#download-a-model)
27+
* [Build the Model Service](#build-the-model-service)
28+
* [Deploy the Model Service](#deploy-the-model-service)
29+
* [Build the AI Application](#build-the-ai-application)
30+
* [Deploy the AI Application](#deploy-the-ai-application)
31+
* [Interact with the AI Application](#interact-with-the-ai-application)
32+
* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image)
33+
34+
35+
## Quickstart
36+
To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command
37+
builds the application's metadata and generates Kubernetes YAML at `./build/chatbot.yaml` to spin up a Pod that can then be launched locally.
38+
Try it with:
39+
40+
```
41+
make quadlet
42+
podman kube play build/chatbot.yaml
43+
```
44+
45+
This will take a few minutes if the model and model-server container images need to be downloaded.
46+
The Pod is named `chatbot`, so you may use [Podman](https://podman.io) to manage the Pod and its containers:
47+
48+
```
49+
podman pod list
50+
podman ps
51+
```
52+
53+
Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. However, if you started the app via the podman desktop UI, a random port will be assigned instead of `8501`. Please use the AI App Details `Open AI App` button to access it instead.
54+
Please refer to the section below for more details about [interacting with the chatbot application](#interact-with-the-ai-application).
55+
56+
To stop and remove the Pod, run:
57+
58+
```
59+
podman pod stop chatbot
60+
podman pod rm chatbot
61+
```
62+
63+
## Download a model
64+
65+
If you are just getting started, we recommend using [granite-7b-lab](https://huggingface.co/instructlab/granite-7b-lab). This is a well
66+
performant mid-sized model with an apache-2.0 license. In order to use it with our Model Service we need it converted
67+
and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of
68+
ways to get a GGUF version of granite-7b-lab, but the simplest is to download a pre-converted one from
69+
[huggingface.co](https://huggingface.co) here: https://huggingface.co/instructlab/granite-7b-lab-GGUF.
70+
71+
The recommended model can be downloaded using the code snippet below:
72+
73+
```bash
74+
cd ../../../models
75+
curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf
76+
cd ../recipes/natural_language_processing/chatbot
77+
```
78+
79+
_A full list of supported open models is forthcoming._
80+
81+
82+
## Build the Model Service
83+
84+
The complete instructions for building and deploying the Model Service can be found in the
85+
[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md).
86+
87+
The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/).
88+
89+
```bash
90+
# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes
91+
make build
92+
```
93+
Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build.
94+
95+
## Deploy the Model Service
96+
97+
The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults:
98+
99+
```bash
100+
# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes
101+
make run
102+
```
103+
104+
## Build the AI Application
105+
106+
The AI Application can be built from the make command:
107+
108+
```bash
109+
# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes)
110+
make build
111+
```
112+
113+
## Deploy the AI Application
114+
115+
Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following:
116+
117+
```bash
118+
# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes)
119+
make run
120+
```
121+
122+
## Interact with the AI Application
123+
124+
Everything should now be up an running with the chat application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled chatbot applications.
125+
126+
## Embed the AI Application in a Bootable Container Image
127+
128+
To build a bootable container image that includes this sample chatbot workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE=<your_bootc_image> bootc`.
129+
130+
Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option.
131+
132+
```bash
133+
make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc
134+
```
135+
136+
Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable.
137+
138+
```
139+
make ARCH=x86_64 bootc
140+
```
141+
142+
The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built
143+
with the chatbot application, it's as simple as ssh-ing into the bootc system and running:
144+
145+
```bash
146+
bootc switch quay.io/ai-lab/chatbot-bootc:latest
147+
```
148+
149+
Upon a reboot, you'll see that the chatbot service is running on the system. Check on the service with:
150+
151+
```bash
152+
ssh user@bootc-system-ip
153+
sudo systemctl status chatbot
154+
```
155+
156+
### What are bootable containers?
157+
158+
What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI?
159+
160+
That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than
161+
at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system.
162+
Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization
163+
tools. Might I suggest [podman](https://podman.io/)?
164+
165+
Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI
166+
image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think
167+
factories or appliances. Who doesn't want to add a little AI to their appliance, am I right?
168+
169+
Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime!
170+
171+
#### Creating bootable disk images
172+
173+
You can convert a bootc image to a bootable disk image using the
174+
[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image.
175+
176+
This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images.
177+
178+
Default image types can be set via the DISK_TYPE Makefile variable.
179+
180+
`make bootc-image-builder DISK_TYPE=ami`
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: v1.0
2+
application:
3+
type: language
4+
name: Function_Calling_Streamlit
5+
description: Function calling a remote service with a model service in a web frontend.
6+
containers:
7+
- name: llamacpp-server
8+
contextdir: ../../../model_servers/llamacpp_python
9+
containerfile: ./base/Containerfile
10+
model-service: true
11+
backend:
12+
- llama-cpp
13+
arch:
14+
- arm64
15+
- amd64
16+
ports:
17+
- 8001
18+
image: quay.io/ai-lab/llamacpp_python:latest
19+
- name: streamlit-function-calling-app
20+
contextdir: app
21+
containerfile: Containerfile
22+
arch:
23+
- arm64
24+
- amd64
25+
ports:
26+
- 8501
27+
image: quay.io/ai-lab/function-calling:latest
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM registry.access.redhat.com/ubi9/python-311:1-72.1722518949
2+
WORKDIR /function-call
3+
COPY requirements.txt .
4+
RUN pip install --upgrade pip
5+
RUN pip install --no-cache-dir --upgrade -r /function-call/requirements.txt
6+
COPY *.py .
7+
EXPOSE 8501
8+
ENTRYPOINT [ "streamlit", "run", "app.py" ]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from langchain_openai import ChatOpenAI
2+
from langchain_core.prompts import ChatPromptTemplate
3+
from langchain_core.pydantic_v1 import BaseModel, Field
4+
from langchain_core.output_parsers import PydanticToolsParser
5+
import streamlit as st
6+
import requests
7+
import time
8+
import json
9+
import os
10+
11+
model_service = os.getenv("MODEL_ENDPOINT",
12+
"http://localhost:8001")
13+
model_service = f"{model_service}/v1"
14+
15+
@st.cache_resource(show_spinner=False)
16+
def checking_model_service():
17+
start = time.time()
18+
print("Checking Model Service Availability...")
19+
ready = False
20+
while not ready:
21+
try:
22+
request_cpp = requests.get(f'{model_service}/models')
23+
request_ollama = requests.get(f'{model_service[:-2]}api/tags')
24+
if request_cpp.status_code == 200:
25+
server = "Llamacpp_Python"
26+
ready = True
27+
elif request_ollama.status_code == 200:
28+
server = "Ollama"
29+
ready = True
30+
except:
31+
pass
32+
time.sleep(1)
33+
print(f"{server} Model Service Available")
34+
print(f"{time.time()-start} seconds")
35+
return server
36+
37+
def get_models():
38+
try:
39+
response = requests.get(f"{model_service[:-2]}api/tags")
40+
return [i["name"].split(":")[0] for i in
41+
json.loads(response.content)["models"]]
42+
except:
43+
return None
44+
45+
with st.spinner("Checking Model Service Availability..."):
46+
server = checking_model_service()
47+
48+
def enableInput():
49+
st.session_state["input_disabled"] = False
50+
51+
def disableInput():
52+
st.session_state["input_disabled"] = True
53+
54+
st.title("💬 Function calling")
55+
if "input_disabled" not in st.session_state:
56+
enableInput()
57+
58+
model_name = os.getenv("MODEL_NAME", "")
59+
60+
if server == "Ollama":
61+
models = get_models()
62+
with st.sidebar:
63+
model_name = st.radio(label="Select Model",
64+
options=models)
65+
66+
class getWeather(BaseModel):
67+
"""Get the current weather in a given latitude and longitude."""
68+
69+
latitude: float = Field(description="The latitude of a place")
70+
longitude: float = Field(description="The longitude of a place")
71+
72+
#https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m
73+
def retrieve(self):
74+
return requests.get("https://api.open-meteo.com/v1/forecast", params={'latitude': self.latitude, 'longitude': self.longitude, 'hourly': 'temperature_2m'}).json();
75+
76+
llm = ChatOpenAI(base_url=model_service,
77+
api_key="sk-no-key-required",
78+
model=model_name,
79+
streaming=False, verbose=False).bind_tools(tools=[getWeather], tool_choice='auto')
80+
81+
SYSTEM_MESSAGE="""
82+
You are a helpful assistant.
83+
You can call functions with appropriate input when necessary.
84+
"""
85+
86+
87+
prompt = ChatPromptTemplate.from_messages([
88+
("system", SYSTEM_MESSAGE),
89+
("user", "What's the weather like in {input} ?")
90+
])
91+
92+
chain = prompt | llm | PydanticToolsParser(tools=[getWeather])
93+
94+
st.markdown("""
95+
This demo application will ask the LLM for the weather in the city given in the input field and
96+
specify a tool that can get weather information given a latitude and longitude. The weather information
97+
retrieval is implemented using open-meteo.com.
98+
""")
99+
container = st.empty()
100+
101+
if prompt := st.chat_input(placeholder="Enter the city name:", disabled=not input):
102+
with container:
103+
st.write("Calling LLM")
104+
response = chain.invoke(prompt)
105+
with container:
106+
st.write("Retrieving weather information")
107+
temperatures = list(map(lambda r: r.retrieve(), response))
108+
print(temperatures[0])
109+
with container:
110+
st.line_chart(temperatures[0]['hourly'], x='time', y='temperature_2m')
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
langchain==0.2.3
2+
langchain-openai==0.1.7
3+
langchain-community==0.2.4
4+
streamlit==1.34.0
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Example: an AI powered sample application is embedded as a systemd service
2+
# via Podman quadlet files in /usr/share/containers/systemd
3+
#
4+
# from recipes/natural_language_processing/chatbot, run
5+
# 'make bootc'
6+
7+
FROM quay.io/centos-bootc/centos-bootc:stream9
8+
ARG SSHPUBKEY
9+
10+
# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your
11+
# public key into the image, allowing root access via ssh.
12+
RUN set -eu; mkdir -p /usr/ssh && \
13+
echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \
14+
echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys
15+
16+
ARG RECIPE=function_calling
17+
ARG MODEL_IMAGE=quay.io/ai-lab/granite-7b-lab:latest
18+
ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest
19+
ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest
20+
ARG TARGETARCH
21+
22+
# Include growfs service
23+
COPY build/usr/lib /usr/lib
24+
COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart
25+
26+
# Add quadlet files to setup system to automatically run AI application on boot
27+
COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd
28+
29+
# Because images are prepulled, no need for .image quadlet
30+
# If commenting out the pulls below, uncomment this to track the images
31+
# so the systemd service will wait for the images with the service startup
32+
# COPY build/${RECIPE}.image /usr/share/containers/systemd
33+
34+
# Setup /usr/lib/containers/storage as an additional store for images.
35+
# Remove once the base images have this set by default.
36+
RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \
37+
/etc/containers/storage.conf
38+
39+
# Added for running as an OCI Container to prevent Overlay on Overlay issues.
40+
VOLUME /var/lib/containers
41+
42+
# Prepull the model, model_server & application images to populate the system.
43+
# Comment the pull commands to keep bootc image smaller.
44+
# The quadlet .image file added above pulls following images with service startup
45+
46+
RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE}
47+
RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE}
48+
RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE}
49+
50+
RUN podman system reset --force 2>/dev/null

0 commit comments

Comments
 (0)