APIテスト
APIの利用設定
APIを使用するために「LM Studio」のサーバー機能を次の様に有効化します。
画面右上の4つのアイコンから「Developer」を選択して設定を開始します。
一番上の「Local Server」を選択します。
「 + Load Model」ボタンを押して使用するモデルをロードします。
「Status Stoppend」スイッチを「ON」としてサーバーを起動します。
Developer Logsにログが出て来るので動作を確認してください。
テスト用のiOSアプリの制作
LLMサーバーと通信する簡単なチャットアプリを制作します。
アプリは、単純にメッセージを送信して結果を受け取るというシンプルな物です。
以下は今回制作したテストアプリのコードです。
iOS 26.0でビルドしてにiOS 26.4のシミュレータで動作確認しています。
全ソースコード
//
// ContentView.swift
// ML ClientTest
//
// Created by Masahiko Tani on 2026/04/05.
//
import SwiftUI
import Foundation
struct ContentView: View {
@State private var message = "" // 入力テキストを保持するステート変数
@State private var chatMessages: [ContentView.ChatMessage] = []
var body: some View {
VStack {
// テキストフィールド (入力欄)
TextField("Enter message...", text: $message)
.padding()
// 送信ボタン
Button("送信") {
sendMessage()
}
.padding()
// チャットメッセージの表示
ScrollView { // 縦にスクロールできるようにScrollViewを追加
LazyVStack { // 効率的な表示のためにLazyVStackを使用
ForEach(chatMessages) { message in
// let markdown = message.text
// Text(try! AttributedString(markdown: markdown))
Text(.init(message.text)) // markdown
}
}
}
}
}
func sendMessage() {
guard !message.isEmpty else { return } // 空のメッセージは送信しない
let url = URL(string: "http://127.0.0.1:1234/api/v1/chat")! // LM StudioのAPIエンドポイント
var request = URLRequest(url: url)
request.httpMethod = "POST" // POSTメソッドを使用
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let chat = ChatRequest(input: message)
do {
request.httpBody = try JSONEncoder().encode(chat)
} catch {
// エンコード失敗時はここで処理
print("JSONエンコードに失敗しました: \(error)")
}
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
handleServerResponse(data: data)
// let json = try JSONDecoder().decode(String.self, from: data!) // サーバーからのJSON文字列をデコード
// DispatchQueue.main.async {
// let message = ContentView.ChatMessage(text: "You: \(json)", isUser: true)
// self.chatMessages.append(message)
// }
} catch {
DispatchQueue.main.async {
let errorMsg = ContentView.ChatMessage(
text: "Error: \(error.localizedDescription)",
isUser: false // エラーは「Bot」側として扱う
)
self.chatMessages.append(errorMsg)
debugPrint(errorMsg.text)
}
}
}
task.resume()
// 入力テキストをクリア
message = ""
}
// data はサーバーから受け取った `Data`(URLSession などで取得)
func handleServerResponse(data: Data?) {
guard let data = data else { return }
do {
// ① JSON を `ChatResponse` にデコード
let chatResp = try JSONDecoder().decode(ChatResponse.self, from: data)
// ② type が "message" の要素を取得
if let messageOutput = chatResp.output.first(where: { $0.type == "message" }) {
let serverMessage = messageOutput.content
// ③ UI スレッドへ渡す
DispatchQueue.main.async {
let message = ContentView.ChatMessage(text: "You: \(serverMessage)", isUser: true)
self.chatMessages.append(message)
}
} else {
// "message" が見つからない場合のフォールバック
let errorMsg = ContentView.ChatMessage(
text: "⚠️ 返答に message がありませんでした",
isUser: false // エラーは「Bot」側として扱う
)
self.chatMessages.append(errorMsg)
debugPrint(errorMsg.text)
}
} catch {
DispatchQueue.main.async {
let errorMsg = ContentView.ChatMessage(
text: "Error: \(error.localizedDescription)",
isUser: false // エラーは「Bot」側として扱う
)
self.chatMessages.append(errorMsg)
debugPrint(errorMsg.text)
}
}
}
struct ChatMessage: Identifiable {
let id = UUID()
let text: String
let isUser: Bool // true なら「You」、false なら「Bot」
}
// 2. JSON オブジェクトに包む
struct ChatRequest: Codable {
let input: String
// let temperature: number // 任意(デフォルト 1.0) -生成のランダム性を調整 (0〜2)
// let max_tokens: integer // 任意(デフォルト 512) -生成テキストの最大トークン数
var model: String = "openai/gpt-oss-20b"
}
/// 最外層
struct ChatResponse: Codable {
let model_instance_id: String
let output: [ChatOutput]
let stats: ChatStats
let response_id: String
}
/// output 配列の要素
struct ChatOutput: Codable {
let type: String // "reasoning" / "message"
let content: String
}
/// stats の情報(必要なら取り込めます)
struct ChatStats: Codable {
let input_tokens: Int
let total_output_tokens: Int
let reasoning_output_tokens: Int
let tokens_per_second: Double
let time_to_first_token_seconds: Double
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
コードの簡単な説明
bodyでは大まかなUIを定義しています。
テキストフィールド (入力欄)/送信ボタン/チャットメッセージの表示などです。
var body: some View
送信ボタンが押されるとsendMessageが呼ばれ、入力されたメッセージをJSONエンコードしてhttpのPOSTで送ります。送信後はfunc handleServerResponse(data: Data?)で受信します。
func sendMessage()
「LM Studio」のサーバーログの例です。
クライアントからメッセージを受け取り推論してレスポンス生成し返しています。
2026-04-09 02:47:37 [DEBUG]
Received request: POST to /api/v1/chat with body {
"model": "openai/gpt-oss-20b",
"input": "こんにちは"
}
2026-04-09 02:47:37 [INFO]
[openai/gpt-oss-20b] Running api/v1/chat on history with 1 message.
2026-04-09 02:47:38 [INFO]
[openai/gpt-oss-20b] Prompt processing progress: 100.0%
2026-04-09 02:47:38 [INFO]
[openai/gpt-oss-20b] Prompt processing progress: 100.0%
2026-04-09 02:47:38 [INFO]
[openai/gpt-oss-20b] Generated response: {
"model_instance_id": "openai/gpt-oss-20b",
"output": [
{
"type": "reasoning",
"content": "Need greet in Japanese."
},
{
"type": "message",
"content": "こんにちは!今日はどんなことに興味がありますか?お手伝いできることがあれば教えてくださいね。"
}
],
"stats": {
"input_tokens": 68,
"total_output_tokens": 42,
"reasoning_output_tokens": 5,
"tokens_per_second": 75.68517600625522,
"time_to_first_token_seconds": 1.129
},
"response_id": "resp_850cca25c267606a71f269820dafb2bd6a99b2e5a6d8f81f"
}
コードの説明を追加
制作したアプリのプログラムに関して追加説明をします。
まず、JSONとかチャットの為にstructを複数定義しています。
- ChatMessage : やり取りのメッセージ格納用
- ChatRequest :LLMサーバーのAPIに対してJSONを組み立てるメッセージ格納用
- ChatResponse :サーバーからのレスポンスの格納用JSONフォーマット
- ChatOutput :ChatResponseの出力用JSONフォーマット
- ChatStats : ChatResponseのstats の情報用のJSONフォーマット
ChatMessageは、送信/受信/表示などに使用されています。
ChatRequestは「/api/v1」用のJSONを組み立てています。現在は、input/modelが必須のバラメータの様です
ChatResponseは、サーバーからのレスポンスでChatOutputとChatStatsを含みます。推論したモデル名/ChatOutput/ChatStatsを含みます。
ChatOutputは、reasoning/messageの2つの内容を含みます。messageは実際の推論結果となります。
ChatStatsは、トークンの利用状態とか処理時間などです。





