Реалізація AI-бота для моніторингу IoT-пристроїв у мобільному додатку
IoT-моніторинг перестав бути просто дашбордом з графіками. Питання «чому температура на датчику 7 виросла на 3 градуса» раніше вимагало відкрити веб-панель, знайти потрібний датчик, побудувати історичний графік, співвідношення з журналом подій. AI-бот у мобільному додатку відповідає на це питання у чаті — й сам тягне дані з потрібних джерел.
Архітектура: LLM + Function Calling + IoT API
Ключовий механізм — Function Calling (Tool Use) в OpenAI GPT-4o або Anthropic Claude 3.5 Sonnet. Модель не має прямого доступу до даних датчиків, але може вззвати функції через API. Мобільний додаток — клієнт, який отримує запит від моделі, виконує виклик до IoT-бекенду та повертає результат.
// Android: обробка tool_calls від GPT-4o
data class ChatMessage(
val role: String, // user, assistant, tool
val content: String? = null,
val toolCalls: List<ToolCall>? = null,
val toolCallId: String? = null,
val name: String? = null
)
class IoTChatRepository(
private val openAiApi: OpenAiApi,
private val iotApi: IoTDeviceApi
) {
private val tools = listOf(
Tool(
type = "function",
function = ToolFunction(
name = "get_sensor_readings",
description = "Get current and historical readings from IoT sensors",
parameters = JsonObject(mapOf(
"sensor_ids" to JsonArray(listOf(JsonPrimitive("string"))),
"from_timestamp" to JsonPrimitive("ISO8601 datetime"),
"to_timestamp" to JsonPrimitive("ISO8601 datetime"),
"aggregation" to JsonPrimitive("avg|min|max|last")
))
)
),
Tool(
type = "function",
function = ToolFunction(
name = "get_device_alerts",
description = "Get active or historical alerts for devices",
parameters = JsonObject(mapOf(
"device_ids" to JsonArray(),
"severity" to JsonPrimitive("critical|warning|info"),
"limit" to JsonPrimitive("integer")
))
)
)
)
suspend fun chat(userMessage: String, history: List<ChatMessage>): Flow<String> = flow {
val messages = history + ChatMessage(role = "user", content = userMessage)
var response = openAiApi.chatCompletion(messages, tools)
// Цикл виконання tool_calls
while (response.toolCalls != null) {
val toolResults = response.toolCalls!!.map { call ->
val result = when (call.function.name) {
"get_sensor_readings" -> iotApi.getSensorReadings(call.function.arguments)
"get_device_alerts" -> iotApi.getAlerts(call.function.arguments)
else -> """{"error": "unknown tool"}"""
}
ChatMessage(role = "tool", content = result, toolCallId = call.id, name = call.function.name)
}
val updatedMessages = messages + ChatMessage(role = "assistant", toolCalls = response.toolCalls) + toolResults
response = openAiApi.chatCompletion(updatedMessages, tools)
}
emit(response.content ?: "")
}
}
Стриминг відповідей та UX чата
GPT-4o підтримує Server-Sent Events для стримінгу. У Retrofit — анотація @Streaming + розбір text/event-stream. Символи з'являються по мірі генерації — не потрібно чекати на повну відповідь. На iOS — URLSession.AsyncBytes.
// iOS: стриминг з OpenAI SSE
func streamResponse(messages: [ChatMessage]) -> AsyncThrowingStream<String, Error> {
AsyncThrowingStream { continuation in
Task {
var request = URLRequest(url: URL(string: "https://api.openai.com/v1/chat/completions")!)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.httpBody = try JSONEncoder().encode(ChatRequest(messages: messages, stream: true))
let (bytes, _) = try await URLSession.shared.bytes(for: request)
for try await line in bytes.lines {
guard line.hasPrefix("data: "), line != "data: [DONE]" else { continue }
let json = line.dropFirst(6)
if let chunk = try? JSONDecoder().decode(StreamChunk.self, from: Data(json.utf8)),
let delta = chunk.choices.first?.delta.content {
continuation.yield(delta)
}
}
continuation.finish()
}
}
}
Контекст та безпека
Системний промпт задає контекст: список пристроїв користувача з іменами та ідентифікаторами, часову зону, одиниці вимірювання. Це дозволяє боту розуміти «датчик у котельні» без явних ID.
Важливо: функції IoT API викликаються від імені поточного користувача з його правами доступу. Бот не може отримати дані пристроїв, до яких у користувача немає доступу — авторизація на рівні бекенду, не на рівні промпта.
Історія чата — останні 20–30 повідомлень у контексті. Старі повідомлення стискаємо суммаризацією: gpt-4o-mini з промптом «Summarize this conversation history briefly» — економія токенів.
Локальна модель як fallback
При відсутності мережі або для зниження витрат на токени — llama.cpp з моделлю Phi-3 Mini або Mistral 7B через android-llamacpp або LLM.swift. Локальна модель не працює з function calling у повному обсязі, але відповідає на базові питання за кешованими даними.
Розробка AI-бота для IoT-моніторингу з Function Calling, стримингом та інтеграцією з IoT API: 4–6 тижнів поверх існуючого IoT-додатку. Стоимість розраховується індивідуально.







