Implement VLM Component for Vision Language Model Support

Status Fixed / completed
Maintainer reply None cached
Activity 2 comments · opened Oct 20, 2025 · closed Oct 20, 2025

Priority

HIGH

Context

The VLM (Vision Language Model) Component enables multimodal AI features by processing images alongside text. The iOS SDK has a complete VLM implementation. The Kotlin SDK has only a stub VLMComponent with no actual functionality.

Current State

  • File: src/commonMain/kotlin/com/runanywhere/sdk/components/vlm/VLMComponent.kt (stub only)
  • Status: Empty implementation, no service layer
  • Impact: Cannot support vision-based AI features

iOS Reference Implementation

  • Component: sdk/runanywhere-swift/Sources/RunAnywhere/Components/VLM/VLMComponent.swift
  • Services: sdk/runanywhere-swift/Sources/RunAnywhere/Components/VLM/Services/VLMService.swift

iOS Architecture Pattern

public final class VLMComponent: ModelBasedComponent {
    private var service: VLMService?
    
    public func analyze(image: Data, prompt: String) async throws -> VLMResponse
    public func analyzeStream(images: AsyncStream<Data>, prompts: AsyncStream<String>) -> AsyncStream<VLMResponse>
}

Implementation Plan

1. Complete Component Implementation (commonMain)

// commonMain/kotlin/com/runanywhere/sdk/components/vlm/VLMComponent.kt
class VLMComponent(
    configuration: VLMConfiguration,
    serviceContainer: ServiceContainer? = null
) : BaseComponent<VLMService>(configuration, serviceContainer) {
    
    suspend fun analyze(image: ByteArray, prompt: String): VLMResponse {
        ensureReady()
        return service?.analyze(image, prompt) 
            ?: throw SDKError.ComponentNotReady("VLM service not initialized")
    }
    
    fun analyzeStream(
        images: Flow<ByteArray>,
        prompts: Flow<String>
    ): Flow<VLMResponse> = flow {
        ensureReady()
        images.zip(prompts) { image, prompt ->
            service?.analyze(image, prompt)
        }.collect { response ->
            response?.let { emit(it) }
        }
    }
    
    override suspend fun createService(): VLMService {
        val provider = ModuleRegistry.vlmProvider(configuration.modelId)
        return provider?.createVLMService(configuration)
            ?: throw SDKError.ComponentNotAvailable("No VLM provider for ${configuration.modelId}")
    }
}

// commonMain/kotlin/com/runanywhere/sdk/components/vlm/services/VLMService.kt
interface VLMService {
    suspend fun analyze(image: ByteArray, prompt: String): VLMResponse
    val supportedFormats: List<ImageFormat>
}

// commonMain/kotlin/com/runanywhere/sdk/components/vlm/models/VLMConfiguration.kt
data class VLMConfiguration(
    val modelId: String,
    val maxImageSize: Int = 1024,
    val imageFormat: ImageFormat = ImageFormat.JPEG
) : ComponentConfiguration

data class VLMResponse(
    val description: String,
    val confidence: Float,
    val objects: List<DetectedObject> = emptyList(),
    val processingTimeMs: Long
)

data class DetectedObject(
    val label: String,
    val confidence: Float,
    val boundingBox: BoundingBox? = null
)

data class BoundingBox(
    val x: Float,
    val y: Float,
    val width: Float,
    val height: Float
)

enum class ImageFormat {
    JPEG, PNG, BMP, WEBP
}

2. Platform Implementations

Android (androidMain)
// androidMain/kotlin/com/runanywhere/sdk/components/vlm/services/AndroidVLMService.kt
class AndroidVLMService(
    private val context: Context,
    private val configuration: VLMConfiguration
) : VLMService {
    
    // Use TensorFlow Lite or ML Kit for on-device vision
    private var interpreter: Interpreter? = null
    
    override suspend fun analyze(image: ByteArray, prompt: String): VLMResponse {
        // Load image
        val bitmap = BitmapFactory.decodeByteArray(image, 0, image.size)
        
        // Preprocess image
        val preprocessed = preprocessImage(bitmap)
        
        // Run inference
        val startTime = System.currentTimeMillis()
        val results = runInference(preprocessed, prompt)
        val duration = System.currentTimeMillis() - startTime
        
        return VLMResponse(
            description = results.description,
            confidence = results.confidence,
            objects = results.detectedObjects,
            processingTimeMs = duration
        )
    }
    
    override val supportedFormats = listOf(
        ImageFormat.JPEG,
        ImageFormat.PNG,
        ImageFormat.BMP,
        ImageFormat.WEBP
    )
}
JVM (jvmMain)
// jvmMain/kotlin/com/runanywhere/sdk/components/vlm/services/JvmVLMService.kt
class JvmVLMService(
    private val configuration: VLMConfiguration
) : VLMService {
    
    // Use DJL (Deep Java Library) or ONNX Runtime
    override suspend fun analyze(image: ByteArray, prompt: String): VLMResponse {
        // Implementation using DJL or ONNX
        return VLMResponse(
            description = "JVM VLM implementation",
            confidence = 0.0f,
            objects = emptyList(),
            processingTimeMs = 0L
        )
    }
    
    override val supportedFormats = listOf(
        ImageFormat.JPEG,
        ImageFormat.PNG,
        ImageFormat.BMP
    )
}

3. Provider Registration

// commonMain/kotlin/com/runanywhere/sdk/core/ModuleRegistry.kt
interface VLMServiceProvider {
    suspend fun createVLMService(configuration: VLMConfiguration): VLMService
    fun canHandle(modelId: String?): Boolean
    val name: String
}

object ModuleRegistry {
    private val vlmProviders = CopyOnWriteArrayList<VLMServiceProvider>()
    
    fun registerVLM(provider: VLMServiceProvider) {
        vlmProviders.add(provider)
    }
    
    fun vlmProvider(modelId: String? = null): VLMServiceProvider? {
        return vlmProviders.firstOrNull { it.canHandle(modelId) }
    }
}

Files to Create/Modify

Files to Modify

  • [ ] components/vlm/VLMComponent.kt (replace stub with full implementation)

New Files (commonMain)

  • [ ] components/vlm/services/VLMService.kt
  • [ ] components/vlm/models/VLMConfiguration.kt
  • [ ] components/vlm/models/VLMResponse.kt
  • [ ] components/vlm/models/ImageFormat.kt
  • [ ] components/vlm/providers/VLMServiceProvider.kt

New Files (androidMain)

  • [ ] components/vlm/services/AndroidVLMService.kt

New Files (jvmMain)

  • [ ] components/vlm/services/JvmVLMService.kt

Files to Modify

  • [ ] core/ModuleRegistry.kt (add VLM provider registration)

Dependencies Required

Android

  • TensorFlow Lite: org.tensorflow:tensorflow-lite:2.14.0
  • OR ML Kit: com.google.mlkit:image-labeling:17.0.7

JVM

  • Deep Java Library: ai.djl:api:0.24.0
  • OR ONNX Runtime: com.microsoft.onnxruntime:onnxruntime:1.16.0

Testing Requirements

  • [ ] Unit tests for VLMComponent lifecycle
  • [ ] Integration tests with mock images
  • [ ] Android platform tests with TensorFlow Lite
  • [ ] JVM platform tests
  • [ ] Image format support tests
  • [ ] Stream processing tests
  • [ ] Error handling tests (invalid images)

Success Criteria

  • [ ] VLMComponent can analyze images with text prompts
  • [ ] Android implementation uses TensorFlow Lite or ML Kit
  • [ ] JVM implementation has working inference
  • [ ] All image formats supported
  • [ ] Stream processing works correctly
  • [ ] Proper error handling
  • [ ] Thread-safe provider registration

Estimated Effort

2-3 weeks (120-180 hours)

  • Component implementation: 3-5 days
  • Android implementation: 5-7 days
  • JVM implementation: 5-7 days
  • Testing: 3-5 days

Dependencies

  • Depends on: #142 (thread safety fixes)
  • Depends on: #147 (privacy-first logging)

Related Issues

  • Part of multimodal AI roadmap
  • Enables vision-based features
  • Aligns with iOS VLM architecture

🤖 Generated with Claude Code

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗