[iOS]ChatGPTのFunctions Callingを試す
個人開発者として英作文アプリを作成しており、その中でChatGPTを利用しています。
その中でもFunction Calling
を利用し、JSONスキーマに対応したレスポンスを取得することにしました。
メモ程度の備忘録として、簡単な使い方を記載していきます。
今回の例では、特定の質問に対して、英作文を行い、そのレビューをChatGPTに任せるというものになります。
なお、OSSとしてMacPawが提供しているパッケージを利用していきますので、細かい利用方法などは下記をご覧ください。
Chat API
まずは、基礎となるChat APIの利用です。
モデルはgpt-3.5-turbo-0613
を利用し、単純にクエリを作成します。
import OpenAI
func getFeedback() async throws -> ChatResult {
let query = ChatQuery(
model: .gpt3_5Turbo0613,
messages: [
.init(role: .system, content: "You're a college instructor of academic English"),
.init(role: .user, content: "Review the following answer: \(answer) to the question: \(question)"),
]
)
let result = try await openAI.chats(query: query)
return result
}
ChatQuery
にはさまざまなパラメータを渡すことができ、トークン数の上限(maxTokens
)や出力の乱数加減(Temperature
)など、必要に応じて指定することもできます。
利用する質問、回答を準備します。どちらもChatGPTに生成させました。
let question = "Share your all-time favorite movie and explain why it holds a special place in your heart"
let answer = "My all-time favorite movie is 'The Shawshank Redemption.' Its timeless tale of hope and friendship, coupled with compelling characters and powerful storytelling, never ceases to inspire. The film's message of resilience and the redemptive power of friendship resonates deeply with me, making it an enduring cinematic masterpiece."
上記の変数とともにメソッドを呼んだ結果として、ChatResult
型のレスポンスを受けることが可能です。
適宜デコードし、必要な部分を使っていきましょう。
(lldb) po result
▿ ChatResult
- id : "chatcmpl-89uUsHELRZVQscZKGirisIFbBal92"
- object : "chat.completion"
- created : 1697372818.0
- model : "gpt-3.5-turbo-0613"
▿ choices : 1 element
▿ 0 : Choice
- index : 0
▿ message : Chat
- role : OpenAI.Chat.Role.assistant
▿ content : Optional<String>
- some : "The answer provided is excellent. It effectively conveys the personal sentiment towards the film \"The Shawshank Redemption\" and provides specific reasons why it holds a special place in the writer\'s heart.\n\nThe response begins by clearly stating that \"The Shawshank Redemption\" is the writer\'s all-time favorite movie. This declaration immediately grabs attention and sets the tone for the rest of the answer.\n\nThe writer then proceeds to explain why the movie is special to them. They highlight the timeless tale of hope and friendship as a key element that captivates them. By emphasizing the film\'s compelling characters and powerful storytelling, they make a strong case for why it is deserving of admiration.\n\nFurthermore, the writer expresses how the film\'s message of resilience and the redemptive power of friendship resonates deeply with them. This personal connection shows that the movie has influenced their perspective and left a lasting impact. The usage of phrases like \"enduring cinematic masterpiece\" demonstrates the writer\'s passion and enthusiasm for the film.\n\nOverall, the answer is well-structured, engaging, and effectively communicates why \"The Shawshank Redemption\" holds a special place in the writer\'s heart."
- name : nil
- functionCall : nil
▿ finishReason : Optional<String>
- some : "stop"
▿ usage : Optional<Usage>
▿ some : Usage
- promptTokens : 107
- completionTokens : 232
- totalTokens : 339
let content = result.choices.first?.message.content
などとすれば欲しいテキストは取得できそうです。
チャットアプリのようなテキストをただ表示する場合は良さそうです。
しかし、動的なUIを作成したりよりよいUXを考えてみると、構造化されている方が便利でしょう。
例として、下記のFeedback型を取得したいと仮定します。
struct Feedback: Decodable {
var overall: String
var revision: String
var suggestions: [String]
var compliments: [String]
}
上記のテキストをどうやっても変換するのは難しそうです。
そんな時に、便利なのがFunction Calling
です。
Function Calling
いよいよ本題です。
ChatQuery
の初期パラメータにfunctions
を追加しましょう。
let query = ChatQuery(
// ...
functions: functions
)
ChatFunctionDeclaration
に必要なのはname
, description
, parameters
の3つです。
-
name
: 呼び出される関数名。a-z、A-Z、0-9、またはアンダースコアとダッシュを含むもので、最大長は64。 -
description
: その関数が何をするのかの説明。 -
parameters
: JSON Schemaオブジェクトとして記述される。
parameters
に独自のJSONSchema
型を適用することで思い通りの型を取得することができます。
オブジェクト型(object
)を指定したらparameters
でさらに変数名と、対応する型とその説明を記述していきます。
必ず返して欲しい変数にはrequired
の配列で指定します。
let functions = [
ChatFunctionDeclaration(
name: "parse_feedback_into_json",
description: "Get the result as json file",
parameters: JSONSchema(
type: .object,
properties: [
"overall": .init(type: .string, description: "a summary assessment of the entire piece of writing, offering a holistic view of its quality and effectiveness."),
"revision": .init(type: .string, description: "Revision of the review to overcome the problems without adding new information or argument to the text."),"compliments": .init(type: .array, description: "Positive feedback acknowledging strengths in the user's writing.", items: .init(type: .string)),
"suggestions": .init(type: .array, description: "Problems in the answer and explain all of these problems.", items: .init(type: .string)),
],
required: ["overall", "revision", "suggestions", "compliments"]
)
)
]
上記の各description
はChatGPTが命令を理解するためにそれぞれ利用していますので、複数パターンを試し、推敲することをお勧めいたします。
functions
が配列であることからも条件によって複数の利用が可能であることが伺えます。
効果的な利用方法が思いつかなかったのでどなたか試した場合には教えて頂けると幸いです。
さて、準備が整ったところでAPIを叩き、得られたJSONをデコードしていきましょう。
guard let arguments = result.choices.first?.message.functionCall?.arguments else { return /* error handling */ }
let decoder = JSONDecoder()
let data = arguments.data(using: .utf8)!
let feedback = try decoder.decode(Feedback.self, from: data)
今回得られたfeedbackは以下のようなものになりました。
(lldb) po response
▿ Feedback
- overall : "Well-written with a clear explanation of why the movie holds a special place in the writer\'s heart"
- revision : "My all-time favorite movie is \'The Shawshank Redemption.\' Its timeless tale of hope and friendship, coupled with compelling characters and powerful storytelling, never ceases to inspire. The film\'s message of resilience and the redemptive power of friendship resonates deeply with me, making it an enduring cinematic masterpiece."
- suggestions : 0 elements
▿ compliments : 2 elements
- 0 : "Great choice of movie"
- 1 : "Well-expressed description of the movie\'s qualities"
懸念点
Streamを利用していない場合、やはりレスポンスが返ってくるまでの時間が一般的なアプリよりも長くなってしまいます。
また、条件が判然としませんが、Function Calling
がうまく機能せず、JSONスキーマがarguments
に格納されず、前述したcontent
にテキスト形式で返ってくることがあります。
現状では対策が打てておらず、テキストをそのまま利用することで対応しています。
どなたか何かご存知であれば教えていただきたいです。
全体コード
getFeedback関数
import OpenAI
func getFeedback() async throws -> Feedback {
let functions = [
ChatFunctionDeclaration(
name: "parse_feedback_into_json",
description: "Get the result as json file",
parameters: JSONSchema(
type: .object,
properties: [
"overall": .init(type: .string, description: "a summary assessment of the entire piece of writing, offering a holistic view of its quality and effectiveness."),
"revision": .init(type: .string, description: "Revision of the review to overcome the problems without adding new information or argument to the text."),
"compliments": .init(type: .array, description: "Positive feedback acknowledging strengths in the user's writing.", items: .init(type: .string)),
"suggestions": .init(type: .array, description: "Problems in the answer and explain all of these problems.", items: .init(type: .string)),
],
required: ["overall", "revision", "suggestions", "compliments"]
)
)
]
let query = ChatQuery(
model: .gpt3_5Turbo0613,
messages: [
.init(role: .system, content: "You're a college instructor of academic English"),
.init(role: .user, content: "Review the following answer: \(answer) to the question: \(question) "),
],
functions: functions
)
let result = try await openAI.chats(query: query)
guard let arguments = result.choices.first?.message.functionCall?.arguments else { return /* error handling */ }
let decoder = JSONDecoder()
let data = arguments.data(using: .utf8)!
let feedback = try decoder.decode(Feedback.self, from: data)
return feedback
}
宣伝
簡単ではありますが、iOSアプリでFunction Calling
を用いたサンプルを紹介しました。
どなたかの参考になれば幸いです。
拙作ではございますが、英作文の練習をたくさんするためのアプリを作成しましたのでよろしくお願いいたします。
Discussion