🧩

[iOS]ChatGPTのFunctions Callingを試す

2023/10/15に公開

個人開発者として英作文アプリを作成しており、その中でChatGPTを利用しています。

その中でもFunction Callingを利用し、JSONスキーマに対応したレスポンスを取得することにしました。
メモ程度の備忘録として、簡単な使い方を記載していきます。

今回の例では、特定の質問に対して、英作文を行い、そのレビューをChatGPTに任せるというものになります。

なお、OSSとしてMacPawが提供しているパッケージを利用していきますので、細かい利用方法などは下記をご覧ください。

https://github.com/MacPaw/OpenAI

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を用いたサンプルを紹介しました。
どなたかの参考になれば幸いです。

拙作ではございますが、英作文の練習をたくさんするためのアプリを作成しましたのでよろしくお願いいたします。

https://apps.apple.com/us/app/tensakuai/id6466396999

GitHubで編集を提案

Discussion