如何使用OpenAI API × Flask 建立網路服務-以範例為例說明-2

Sean Yeh
Python Everywhere -from Beginner to Advanced
24 min readOct 30, 2023

--

Yokohama, Japan, photo by Sean Yeh

在上一篇文章裡,我們已經簡單地示範如何取得和運行OpenAI提供的「Quickstart Sample App」範例程式。這是一個用Python和Flask打造的範例專案。Flask這個框架本身非常具有親和力,稍作介紹大家就能快上手。

在這篇文章中,我們要再進一層的剖析這個「Quickstart Sample App」範例,一探程式碼的奧妙,看看OpenAI API在Flask Web應用中是如何運作的。

先從檔案的外觀開始,請先打開GitHub裡面的範例專案,您會發現有好幾個資料夾和檔案。大致來說,這些程式碼可以分為兩大塊:

  • 一是Flask Web框架的部分。
  • 二是與OpenAI API的連結設定。 當然,除了這兩個主要部份外,還有一些用來設定開發環境的檔案,比如requirements.txtenv.example.gitignore等等,這部分在上一篇已經提過,就不再贅述。

說到開發Web應用,Python有不少選項,包括但不限於Django、Flask、Pyramid、Bottle、Tornado等。但一般來說,Django和Flask是比較受歡迎的選項。Django功能全面但較為「沉重」,而Flask則簡潔明、輕量多了。也正因如此,OpenAI選擇使用Flask作為其「Quickstart Sample App」的開發框架。

接下來,我們就從Flask Web框架這個角度來展開這篇文章的探討。希望這樣能讓您更深入了解如何利用Python和OpenAI API來打造你自己的Web應用。

一窺Flask Web框架的精妙

Flask首度問世於2010年,承繼了多個網頁框架的優勢,並建立在Jinja2樣板引擎和Werkzeug WSGI的基石之上。儘管Flask被定義為「微框架」( microframework),它不僅程式碼結構簡單,擴充性也相當出色。在預設的情況下,Flask運用了Jinja2樣板引擎,讓您能迅速地打造一個小巧但功能完善的網路應用。若想要更深入了解建置Flask Web應用程式的話,這篇文章值得一讀。

值得一提的是,用Flask來開發Web應用程式真的很簡單。即使您是Flask的新手,只要對Python有基礎的了解,很快就能輕鬆上手並建立出您自己的Web應用程式。

這樣的設計使得Flask成為Python界裡,年輕開發者經常首選的Web框架。這也解釋了為何OpenAI會選擇它作為示例程式「Quickstart Sample App」的基礎架構。

Flask起手式

Flask程式碼的基本「起手式」如下,這基本上是您開始撰寫一個Flask應用程式時的初始樣板:

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route(路徑)
def index():
return render_template(樣板名稱, 選項)

上面的程式碼看起來不是很複雜,對吧?不過,如果還是不大理解,我們接下來還是會一行一行分析這些程式碼。

匯入必要套件

from flask import Flask, render_template, request

首先,我們透過import指令,匯入了Flask框架的三個主要套件Flaskrender_templaterequest

  • Flask:是Flask框架的主體核心類別。
  • render_template:負責處理樣板運算與繪製的函式。
  • request:負責用來接收和處理從使用者端發出的HTTP請求。

這三個套件可說是Flask應用最基礎的三個套件。

初始化Flask應用

完成套件的匯入之後,接下來我們初始化一個Flask的實體,這時候通常會將__name__作為參數傳遞給Flask實體。最後,這個實體被指定給變數app,供後續的路由和處理函式使用。

app = Flask(__name__)

配置路由(Routing)

在Flask中,路由的配置佔有重要角色。簡單來說,路由就是將一個網址(路徑)對應到一個特定的函式。它透過在函式前加上@app.route()裝飾器(Decorator)來實現。如下:

@app.route(路徑)

這樣一來,當使用者瀏覽到該路徑時,其對應的函式(或稱為「處理器」、「Handler」)就會被觸發。

@app.route(路徑)
def index():
return render_template(樣板名稱, 選項)

樣板算繪

最後,我們使用render_template函式,將頁面算繪並呈現給使用者。在這個函式中,我們可以傳入多種參數,除了包括樣板名稱之外,還有其他變數。

由於Flask已內建Jinja2樣板引擎,這使得Python與前端間的資料交換變得極為便利。

希望這篇簡要的解析,能夠讓您對Flask有更加清晰的了解。即便是剛剛接觸Flask的您,應該也能迅速上手,開發出一個簡單的Web應用程式。

簡化版本的應用程式

在您正式探究「Quickstart Sample App」這個範例之前,讓我們先透過另一個較簡易的版本來釐清概要。我們可以稱這個為「精簡啟動應用程式(Simplified Sample App)」。這個版本的目的是為了讓您更迅速地掌握核心概念,而不會被過多的細節困擾。

首先,在您的專案資料夾中,請建立一個名為app.py的Python檔案。在這個檔案中,您將需要貼上指定的程式碼(如下:app.py for Simplified Sample App)。特別要注意的是,您必須在 api_key的指定區域輸入您自己的OpenAI API金鑰。

# app.py for Simplified Sample App

from flask import Flask, render_template, request
import openai

api_key = "....你的API金鑰...."
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', question=None, result=None)
@app.route('/', methods=['POST'])
def submit():
prompt = request.form['prompt']
result = access_openai(prompt)
return render_template('index.html', question=prompt, result=result)
def access_openai(prompt_value):
openai.api_key = api_key
response = openai.Completion.create(model="text-davinci-003", prompt=prompt_value, max_tokens=100, n=2, stop=None, temperature=0.5)
return response.choices[0].text.strip()

這份程式碼是以Python語言撰寫,結合了Flask這個網頁框架,還有OpenAI的API來執行自然語言處理(NLP)任務。接著,我們會逐一介紹程式碼各部分的功能與作用。

這個「精簡啟動應用程式」,是利用先前介紹過的「程式碼起手式」加以擴展而成。而對比於「Quickstart Sample App」這個範例,則是較為精簡。程式碼的複雜度可以說是介於兩者之間。

# 程式碼起手式

from flask import Flask, render_template, request

app = Flask(__name__)
@app.route(路徑)
def index():
return render_template(樣板名稱, 選項)

如此一來,您不僅是快速上手,也能對在Python程式中使用OpenAI API有更扎實的了解。接下來,我們就一段一段來看看吧。

匯入必要的模組

首先,在這個「精簡啟動應用程式」中,我們匯入了必要的模組。這些模組除了多加了一個openai模組之外,其他的都與「程式碼起手式」一樣。

from flask import Flask, render_template, request
import openai

設定OpenAI的API金鑰

這是「程式碼起手式」所沒有的部分。我們在這裡將OpenAI API的金鑰指定給變數api_key。金鑰是以「sk-」為開頭的一串字串,唯有透過這個金鑰才能讓您能使用OpenAI的服務。

api_key = "....你的API金鑰...."

初始化Flask實體

與前面一樣,我們初始化一個Flask的實體。

app = Flask(__name__)

定義首頁的路由

與「程式碼起手式」類似,路由裝飾器定義為@app.route('/'),可以讓使用者瀏覽網站的首頁時,執行下面的index()函式。

@app.route('/')
def index():
return render_template('index.html', question=None, result=None)

index()函式,我們使用render_template這個函式來返回樣板檔案。在這裡使用index.html 作為首頁的樣板。同時我們也知道,在render_template中,可以加入第二個參數。 我們加入了questionresult參數,透過這參數可以指定要傳給樣板的變數和值。並將questionresult兩個參數設為None。(question=None, result=None

以上程式碼為「程式碼起手式」的改寫與擴展。接下來,我們看到另外兩個函式submitaccess_openai

定義submit送出功能路由

這個函式的作用在於將使用者輸入的prompt指令透過Post的方式傳送到OpenAI。 由@app.route('/', methods=['POST'])可以看出,如果首頁有表單被submit送出時,會以POST的方式觸發此路由。在函式中prompt = request.form['prompt']: 這一行可以從表單中取出名為prompt的資料。在這一行result = access_openai(prompt): 我們透過將剛剛取出的prompt傳入access_openai函式裡面,並取出回傳的結果給result變數。最後透過render_template將結果返回到樣板檔案index.html中,並帶入questionresult參數。

@app.route('/', methods=['POST'])
def submit():
prompt = request.form['prompt']
result = access_openai(prompt)
return render_template('index.html', question=prompt, result=result)

定義連接OpenAI API的函式

在前面的submit函式中,我們將prompt的值傳入access_openai函式。但是access_openai函式尚未被定義,在此我們要定義access_openai函式。至於這個函式的內容,我們先暫且不進去細看。大家只要先將這個函式理解為只要傳入prompt指令,就會返回GPT結果的一個百寶箱。

def access_openai(prompt_value):
openai.api_key = api_key
response = openai.Completion.create(model="text-davinci-003", prompt=prompt_value, max_tokens=100, n=2, stop=None, temperature=0.5)
return response.choices[0].text.strip()

定義首頁樣板檔案

接著,我們需要準備應用程式裡頭要用的樣版檔案index.html。在目前的專案資料夾裡面,再開一個叫「templates」的資料夾。在這個「templates」資料夾裡,建立一個index.html檔。在render_template內傳入index.html的話,就會讀到templates資料夾裡面的index.html檔。這是Flask 「Jinja2」樣版引擎功能,透過Jinja2,你就能用普通的.html副檔名來儲存檔案。

對於樣版引擎的問題有興趣的朋友,可以參考以下文章的相關說明。

index.html

回過頭來,我們來看看這個首頁的樣板(index.html)。該檔案的內容如下:

<!DOCTYPE html>
<html lang="zh-Tw">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>GPT簡單範例</title>
<link href="<https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css>" rel="stylesheet" crossorigin="anonymous">
</head>
<body class="container">
<h1 class="display-6 py-2">簡單範例</h1>
<form method="POST" action="/">
<div class="m-3">
<label for="prompt" class="form-label">Prompt:</label>
<textarea id="prompt" name="prompt" class="form-control" rows="3"></textarea>
</div>
<div class="m-3">
<input type="submit" value="送出" class="btn btn-primary">
</div>
</form>
{% if question != None %}
<div class="mx-3">
<p class="d-inline-flex gap-1">
<a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">您的問題</a>
</p>
<div class="collapse" id="collapseExample">
<div class="card card-body">
<p>{{ question }}</p>
</div>
</div>
</div>
{% endif %}
{% if result != None %}
<div class="card m-3">
<div class="card-body">
<h5 class="card-title">GPT的回答:</h5>
<p class="card-text">{{ result }}</p>
</div>
</div>
{% endif %}
<script src="<https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js>" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>

上面的html使用了Bootstrap 5.3.2版作為CSS的框架。就整個頁面來說,就屬textarea與input比較重要。

<textarea id="prompt" name="prompt" class="form-control" rows="3"></textarea>

<input type="submit" value="送出" class="btn btn-primary">

透過這個textarea讓使用者可以輸入prompt命令,再使用input type="submit"的標籤作為按鈕來驅動將命令傳輸到openAI。

然後透過{{question}}來顯示使用者剛剛的問問題。當然,前提是使用者有問問題:

{% if question != None %}
<p>{{ question }}</p>
{% endif %}

此外,同時透過 {{result}} 把答案顯示在畫面的下方,一樣是附條件的顯示:

{% if result != None %}
<div class="card m-3">
<div class="card-body">
<h5 class="card-title">GPT的回答:</h5>
<p class="card-text">{{ result }}</p>
</div>
</div>
{% endif %}

完成上面的index.html之後,就可以啟動伺服器實際上執行看看。在命令提示字元或終端機裡面,切換到專案的資料夾(也就是app.py所在的地方),然後執行下面的命令:

flask run

這樣一來,app.py 就會作為應用程式跑起來。啟動之後,用網路瀏覽器去 http://localhost:5000 (或者是 http://127.0.0.1:5000 )看看。

GPT簡單範例

您會看到一個很簡單,只有一個輸入框的表單。在這裡填入您的prompt指令,然後點擊「送出」按鈕,系統就會連接到OpenAI API,問題和結果會顯示在表單的下方。

總結來說,這個簡單的Flask網站,讓使用者輸入一個問題或提示,然後使用OpenAI的API來生成一個相關的回答或內容。

探究範例App:Quickstart Sample App

接下來,我們來詳細介紹OpenAI提供的快速上手範例「Quickstart Sample App」,就從主程式app.py開始聊吧。這個檔案大致上可以分成幾個部份,我們一個一個來看:

第1部分: 匯入必要套件

首先,在這個範例App裡頭,除了匯入Flask框架的基本模組(「程式碼起手式」中flask的必要模組),也加入了一個openai模組。不只如此,這個範例還多匯入了redirecturl_for,主要是為了讓網站有更多功能,像是頁面跳轉之類的。另外,還有osdotenv這兩個工具套件,主要是為了保護OpenAI的API金鑰。下面是這部分的程式碼:

#精簡啟動應用程式:

# app.py for Simplified Sample App

from flask import Flask, render_template, request
import openai

#Quickstart Sample App:

# app.py for Quickstart Sample App

import os
from flask import Flask, render_template, request, redirect, url_for
import openai
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

第2部分: 設定OpenAI的API金鑰

在這裡需要設定OpenAI的API金鑰。有別於「精簡啟動應用程式」中直接將OpenAI API的金鑰存在app.py的檔案裡面,「Quickstart Sample App」則利用環境變數.env檔案來儲存API的金鑰。因此,我們在PART 1的地方需要匯入os套件與dotenv套件。

這裡主要是要設定OpenAI的API金鑰。不像快速起手版App「精簡啟動應用程式」那樣直接把API金鑰塞在app.py檔裡,這個快速上手範例「Quickstart Sample App」比較聰明,選擇用.env檔來安全收藏這把金鑰。所以,在第一部分的程式碼裡頭,我們就需要先匯入osdotenv這兩個工具套件。以下是這部分的程式碼:

#精簡啟動應用程式:

api_key = "....你的API金鑰...."

#Quickstart Sample App:

openai.api_key = os.getenv("OPENAI_API_KEY")

第3部分: 初始化Flask實體

這裡其實蠻簡單的,不管是快速起手版App「精簡啟動應用程式」還是快速上手範例「Quickstart Sample App」,都是要先初始化一個Flask的實體。總之,就是要先把Flask準備好。

app = Flask(__name__)

第4部分:

在這一區塊裡,快速起手版App「精簡啟動應用程式」可分為A、B、C三大部分,而快速上手範例「Quickstart Sample App」則簡化為1跟2兩個大區塊。

精簡啟動應用程式
Quickstart Sample App

在快速起手版App「精簡啟動應用程式」中,我們把首頁拆成A跟B兩個功能,A負責處理GET請求,B則是POST。C就是B的進階擴充。但在快速上手範例「Quickstart Sample App」裡,這三個部分被簡化成了一個,就是第1區塊,然後再加個第2區塊作為第1區塊的補充。

那第2區塊到底是幹嘛的呢?大家看一下裡面的generate_prompt()這個函式,會發現它會接收一個叫animal的變數,然後返回一大串文字字串。字串只有一個地方可以放變數,就是Animal: {}這。仔細一看,其實就是指令(prompt)啦。

沒錯,我們就是要把這個指令透過API傳到OpenAI的模型去,然後把結果顯示在網頁上。使用者只需要輸入動物(animal)名稱就好,其他的程式會幫你搞定。這樣使用者就不用像跟ChatGPT互動時那樣,得輸入一大堆指令來保證結果的品質了。

此外,關於取得openai 返回結果的部分,我們在下一篇文章會提到。至於首頁樣板ndex.html的部分,由於兩者的差異性不大,而且這個範疇屬於視覺處理的部分,不在這裡贅述。以下是這部分的程式碼:

#精簡啟動應用程式:

# app.py for Simplified Sample App


# PART A: Define the home route
@app.route('/')
def index():
return render_template('index.html', question=None, result=None)


# PART B: Define the submit route
@app.route('/', methods=['POST'])
def submit():
prompt = request.form['prompt']
result = access_openai(prompt)
return render_template('index.html', question=prompt, result=result)


# Part C: Define the access_openai function
def access_openai(prompt_value):
openai.api_key = api_key
response = openai.Completion.create(model="text-davinci-003",
prompt=prompt_value, max_tokens=100, n=2, stop=None,
temperature=0.5)
return response.choices[0].text.strip()

# Quickstart Sample App:

# app.py for Quickstart Sample App

#Part 1: Define the home route, get prompt,and access openai
@app.route("/", methods=("GET", "POST"))
def index():
if request.method == "POST":
animal = request.form["animal"]
response = openai.Completion.create(model="text-davinci-003",
prompt=generate_prompt(animal), temperature=0.6,
)
return redirect(url_for("index", result=response.choices[0].text))
result = request.args.get("result")
return render_template("index.html", result=result)


#Part 2: Define the generate_prompt function
def generate_prompt(animal):
return """Suggest three names for an animal that is a superhero.
Animal: Cat
Names: Captain Sharpclaw, Agent Fluffball, The Incredible Feline
Animal: Dog
Names: Ruff the Protector, Wonder Canine, Sir Barks-a-Lot
Animal: {}
Names:""".format(animal.capitalize())

結語

總括來說,從一開始填表單、送出表單,並串接OpenAI的API,再到最後將結果傳回來顯示在網頁上,整體來說應該都不算難懂吧。因為Flask本來就是個簡單易用的框架,所以您會發現拿它來開發網站或是搭配OpenAI API真的是方便。下一篇文章,我們會繼續聊還沒講完的部分,也就是怎麼拿到OpenAI回傳的資料。敬請期待。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。