メインコンテンツへスキップ

APIテスト

 

APIの利用設定

APIを使用するためには「LM Studio」のサーバー機能を次の様に有効化します。

画面右上の4つのアイコンから「Developer」を選択して設定を開始します。

スクリーンショット 2026-04-09 2.25.30.png

一番上の「Local Server」を選択します。

スクリーンショット 2026-04-09 2.26.15.png

「 + Load Model」ボタンを押して使用するモデルをロードします。

スクリーンショット 2026-04-09 2.29.20.png

「Status Stoppend」スイッチを「ON」としてサーバーを起動します。

スクリーンショット 2026-04-09 2.43.08.png

Developer Logsにログが出て来るので動作を確認してください。

スクリーンショット 2026-04-09 2.33.02.png

テスト用のiOSアプリの制作

LLMサーバーと通信する簡単なチャットアプリを制作します。

アプリは、単純にメッセージを送信して結果を受け取るというシンプルな物です。

スクリーンショット 2026-04-09 2.47.53.png

以下は今回制作したテストアプリのコードです。

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 sendMessage()