Реализация 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-приложения. Стоимость рассчитывается индивидуально.







