Files
sleepy_agent_ios/SleepyAgent/Inference/Bridge/LlmEngineBridge.mm
T
sleepy 45d43f2645 Add Objective-C++ bridge for LiteRT-LM integration
- LlmEngineBridge.h/.mm: Objective-C++ wrapper around LiteRT-LM C++ API
- SleepyAgent-Bridging-Header.h: Swift bridging header
- Updated LlmEngine.swift to use the bridge
- Added LITERT_INTEGRATION.md with detailed research findings

Based on analysis of Google's litert-samples repository:
- Google uses C++ bridge pattern for iOS (confirmed in image_segmentation example)
- MediaPipe has working Swift API but is deprecated
- LiteRT-LM Swift APIs are 'coming soon'

The bridge pattern matches how Google AI Edge Gallery iOS app is likely implemented
2026-04-06 14:54:06 +02:00

274 lines
8.1 KiB
Plaintext

//
// LlmEngineBridge.mm
// SleepyAgent
//
// Objective-C++ implementation of LiteRT-LM bridge
//
#import "LlmEngineBridge.h"
#include <memory>
#include <string>
#include <vector>
// TODO: Include actual LiteRT-LM headers when available
// These are placeholder includes - replace with actual paths
// #include "litert_lm/engine.h"
// #include "litert_lm/conversation.h"
// #include "litert_lm/content.h"
NSString *const kLiteRTLMErrorDomain = @"com.sleepyagent.litert.lm";
// MARK: - Private Interface
@interface LlmEngineBridge () {
// TODO: Replace with actual LiteRT-LM C++ types
// std::unique_ptr<litert::lm::Engine> _engine;
// std::unique_ptr<litert::lm::Conversation> _conversation;
// Stub: Just track state for now
BOOL _isInitialized;
NSString *_modelPath;
LiteRTAccelerator _accelerator;
NSMutableArray<NSDictionary *> *_history;
}
@end
// MARK: - Response Stream Implementation
@interface LiteRTResponseStream () {
NSMutableArray<NSString *> *_chunks;
NSInteger _currentIndex;
BOOL _isComplete;
}
@end
@implementation LiteRTResponseStream
- (instancetype)init {
self = [super init];
if (self) {
_chunks = [NSMutableArray array];
_currentIndex = 0;
_isComplete = NO;
}
return self;
}
- (void)addChunk:(NSString *)chunk {
[_chunks addObject:chunk];
}
- (void)markComplete {
_isComplete = YES;
}
- (nullable NSString *)nextChunk {
if (_currentIndex < _chunks.count) {
return _chunks[_currentIndex++];
}
return nil;
}
- (BOOL)hasMore {
return _currentIndex < _chunks.count || !_isComplete;
}
- (void)close {
_chunks = nil;
_isComplete = YES;
}
@end
// MARK: - LlmEngineBridge Implementation
@implementation LlmEngineBridge
- (nullable instancetype)initWithModelPath:(NSString *)modelPath
accelerator:(LiteRTAccelerator)accelerator
error:(NSError **)error {
self = [super init];
if (self) {
_modelPath = [modelPath copy];
_accelerator = accelerator;
_history = [NSMutableArray array];
// Check if model file exists
if (![[NSFileManager defaultManager] fileExistsAtPath:modelPath]) {
if (error) {
*error = [NSError errorWithDomain:kLiteRTLMErrorDomain
code:404
userInfo:@{NSLocalizedDescriptionKey:
[NSString stringWithFormat:@"Model file not found: %@", modelPath]}];
}
return nil;
}
// TODO: Initialize actual LiteRT-LM engine
//
// Example implementation:
// auto config = litert::lm::EngineConfig{
// .model_path = [modelPath UTF8String],
// .max_num_tokens = 8192
// };
//
// auto accel = (accelerator == LiteRTAcceleratorMetal)
// ? litert::HwAccelerators::kGpu
// : litert::HwAccelerators::kCpu;
//
// auto result = litert::lm::Engine::Create(config, accel);
// if (!result.ok()) {
// if (error) {
// *error = [NSError errorWithDomain:kLiteRTLMErrorDomain
// code:1
// userInfo:@{NSLocalizedDescriptionKey:
// @(result.status().message().data())}];
// }
// return nil;
// }
// _engine = std::move(*result);
//
// // Create conversation for KV cache
// auto conv_config = litert::lm::ConversationConfig{};
// auto conv_result = _engine->CreateConversation(conv_config);
// if (conv_result.ok()) {
// _conversation = std::move(*conv_result);
// }
// Stub: Simulate successful initialization
_isInitialized = YES;
}
return self;
}
- (nullable NSString *)generateResponse:(NSString *)prompt
error:(NSError **)error {
if (!_isInitialized) {
if (error) {
*error = [NSError errorWithDomain:kLiteRTLMErrorDomain
code:2
userInfo:@{NSLocalizedDescriptionKey: @"Engine not initialized"}];
}
return nil;
}
// TODO: Implement actual generation with LiteRT-LM
//
// Example:
// auto contents = litert::lm::Contents::FromText([prompt UTF8String]);
// auto response = _conversation->SendMessage(contents);
// if (response.ok()) {
// return [NSString stringWithUTF8String:response->text().c_str()];
// } else {
// if (error) {
// *error = [NSError errorWithDomain:kLiteRTLMErrorDomain
// code:3
// userInfo:@{NSLocalizedDescriptionKey:
// @(response.status().message().data())}];
// }
// return nil;
// }
// Stub: Return placeholder response
return [NSString stringWithFormat:@"[STUB] LiteRT-LM Swift APIs are coming soon. "
@"This is a placeholder response for prompt: %@", prompt];
}
- (nullable LiteRTResponseStream *)generateResponseStream:(NSString *)prompt
error:(NSError **)error {
if (!_isInitialized) {
if (error) {
*error = [NSError errorWithDomain:kLiteRTLMErrorDomain
code:2
userInfo:@{NSLocalizedDescriptionKey: @"Engine not initialized"}];
}
return nil;
}
LiteRTResponseStream *stream = [[LiteRTResponseStream alloc] init];
// TODO: Implement actual streaming with LiteRT-LM
//
// Example:
// auto contents = litert::lm::Contents::FromText([prompt UTF8String]);
// auto async_response = _conversation->SendMessageAsync(contents);
//
// for (const auto& chunk : async_response) {
// [stream addChunk:@(chunk.text().c_str())];
// }
// [stream markComplete];
// Stub: Simulate streaming with placeholder
NSArray *words = @[@"LiteRT-LM", @"on", @"iOS", @"requires", @"C++",
@"integration.", @"Swift", @"APIs", @"are", @"'coming",
@"soon'", @"per", @"Google.", @"See", @"LITERT_INTEGRATION.md",
@"for", @"details."];
for (NSString *word in words) {
[stream addChunk:[word stringByAppendingString:@" "]];
}
[stream markComplete];
return stream;
}
- (void)addToHistory:(NSString *)message
role:(NSString *)role {
[_history addObject:@{@"role": role, @"message": message}];
// TODO: Add to LiteRT-LM conversation history
// if (_conversation) {
// auto role_str = [role isEqualToString:@"user"]
// ? litert::lm::Role::kUser
// : litert::lm::Role::kAssistant;
// _conversation->AddMessage(role_str, [message UTF8String]);
// }
}
- (void)clearHistory {
[_history removeAllObjects];
// TODO: Reset LiteRT-LM conversation
// if (_engine) {
// auto conv_config = litert::lm::ConversationConfig{};
// auto conv_result = _engine->CreateConversation(conv_config);
// if (conv_result.ok()) {
// _conversation = std::move(*conv_result);
// }
// }
}
- (BOOL)isReady {
return _isInitialized; // && _engine != nullptr;
}
- (NSInteger)maxTokens {
return 16384; // Default, could query from model
}
- (NSInteger)estimateTokens:(NSString *)text {
// Rough estimation: ~4 characters per token
return text.length / 4;
// TODO: Use actual tokenizer
// if (_engine) {
// return _engine->EstimateTokens([text UTF8String]);
// }
}
- (void)close {
// TODO: Release C++ resources
// _conversation.reset();
// _engine.reset();
_isInitialized = NO;
_modelPath = nil;
[_history removeAllObjects];
}
- (void)dealloc {
[self close];
}
@end