Check in before major engine ovberhaul to use the Google Mediapipe LLM framework.
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-02-28T03:12:27.182833316Z">
|
<DropdownSelection timestamp="2026-02-28T04:08:30.769945596Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=461ed66e" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/home/michael/.android/avd/Pixel_8_API_35.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package net.mmanningau.alice
|
package net.mmanningau.alice
|
||||||
|
|
||||||
import com.llamatik.library.platform.LlamaBridge // The correct Llamatik import!
|
import com.llamatik.library.platform.LlamaBridge
|
||||||
|
import dev.langchain4j.agent.tool.ToolExecutionRequest
|
||||||
|
import dev.langchain4j.agent.tool.ToolSpecification
|
||||||
import dev.langchain4j.data.message.AiMessage
|
import dev.langchain4j.data.message.AiMessage
|
||||||
import dev.langchain4j.data.message.ChatMessage
|
import dev.langchain4j.data.message.ChatMessage
|
||||||
import dev.langchain4j.data.message.SystemMessage
|
import dev.langchain4j.data.message.SystemMessage
|
||||||
|
import dev.langchain4j.data.message.ToolExecutionResultMessage
|
||||||
import dev.langchain4j.data.message.UserMessage
|
import dev.langchain4j.data.message.UserMessage
|
||||||
import dev.langchain4j.model.chat.ChatLanguageModel
|
import dev.langchain4j.model.chat.ChatLanguageModel
|
||||||
import dev.langchain4j.model.output.Response
|
import dev.langchain4j.model.output.Response
|
||||||
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
import dev.langchain4j.agent.tool.ToolSpecification
|
|
||||||
|
|
||||||
class LlamaCppAdapter(private val modelPath: String) : ChatLanguageModel {
|
class LlamaCppAdapter(private val modelPath: String) : ChatLanguageModel {
|
||||||
|
|
||||||
@@ -21,43 +24,92 @@ class LlamaCppAdapter(private val modelPath: String) : ChatLanguageModel {
|
|||||||
if (!modelFile.exists()) {
|
if (!modelFile.exists()) {
|
||||||
throw IllegalStateException("Model file not found at: $modelPath. Please download a model first.")
|
throw IllegalStateException("Model file not found at: $modelPath. Please download a model first.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boot the native C++ backend via Llamatik's Kotlin bridge
|
|
||||||
LlamaBridge.initGenerateModel(modelPath)
|
LlamaBridge.initGenerateModel(modelPath)
|
||||||
isEngineLoaded = true
|
isEngineLoaded = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generate(messages: List<ChatMessage>): Response<AiMessage> {
|
override fun generate(messages: List<ChatMessage>): Response<AiMessage> {
|
||||||
getOrInitEngine()
|
return generate(messages, emptyList())
|
||||||
|
|
||||||
// 1. Translation IN: Format specifically for Qwen 2.5 (ChatML)
|
|
||||||
val promptBuilder = java.lang.StringBuilder()
|
|
||||||
for (message in messages) {
|
|
||||||
when (message) {
|
|
||||||
is SystemMessage -> promptBuilder.append("<|im_start|>system\n${message.text()}<|im_end|>\n")
|
|
||||||
is UserMessage -> promptBuilder.append("<|im_start|>user\n${message.text()}<|im_end|>\n")
|
|
||||||
is AiMessage -> promptBuilder.append("<|im_start|>assistant\n${message.text()}<|im_end|>\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Prompt the AI to start generating its response
|
|
||||||
promptBuilder.append("<|im_start|>assistant\n")
|
|
||||||
|
|
||||||
// 2. Execution: Run it on the local hardware using Llamatik
|
|
||||||
val responseText = LlamaBridge.generate(promptBuilder.toString())
|
|
||||||
|
|
||||||
// 3. Translation OUT: Clean up any trailing ChatML tags the engine might leave behind
|
|
||||||
val cleanResponse = responseText.replace("<|im_end|>", "").trim()
|
|
||||||
|
|
||||||
return Response.from(AiMessage(cleanResponse))
|
|
||||||
}
|
}
|
||||||
// This catches LangChain4j when it tries to send tools to our local engine
|
|
||||||
|
// Advanced generator (With tools)
|
||||||
override fun generate(
|
override fun generate(
|
||||||
messages: List<ChatMessage>,
|
messages: List<ChatMessage>,
|
||||||
toolSpecifications: List<ToolSpecification>
|
toolSpecifications: List<ToolSpecification>
|
||||||
): Response<AiMessage> {
|
): Response<AiMessage> {
|
||||||
// For Phase 1, we simply ignore the tools and route it to our standard text generator
|
getOrInitEngine()
|
||||||
// so we can prove the local GPU engine is successfully generating tokens!
|
|
||||||
return generate(messages)
|
val promptBuilder = java.lang.StringBuilder()
|
||||||
|
|
||||||
|
// 1. Build the Fool-Proof System Prompt
|
||||||
|
val toolsPrompt = java.lang.StringBuilder()
|
||||||
|
if (toolSpecifications.isNotEmpty()) {
|
||||||
|
toolsPrompt.append("\n\n# AVAILABLE TOOLS\n")
|
||||||
|
for (tool in toolSpecifications) {
|
||||||
|
toolsPrompt.append("- ${tool.name()}: ${tool.description()}\n")
|
||||||
|
}
|
||||||
|
toolsPrompt.append("\nCRITICAL INSTRUCTION: To use a tool, you MUST reply with a JSON object. Do NOT use Python parentheses.\n")
|
||||||
|
toolsPrompt.append("Correct Example:\n{\"name\": \"get_battery_level\", \"arguments\": {}}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Format the Chat History
|
||||||
|
for (message in messages) {
|
||||||
|
when (message) {
|
||||||
|
is SystemMessage -> {
|
||||||
|
val content = message.text() + toolsPrompt.toString()
|
||||||
|
promptBuilder.append("<|im_start|>system\n$content<|im_end|>\n")
|
||||||
|
}
|
||||||
|
is UserMessage -> promptBuilder.append("<|im_start|>user\n${message.text()}<|im_end|>\n")
|
||||||
|
is ToolExecutionResultMessage -> {
|
||||||
|
promptBuilder.append("<|im_start|>user\nTool [${message.toolName()}] returned: ${message.text()}<|im_end|>\n")
|
||||||
|
}
|
||||||
|
is AiMessage -> {
|
||||||
|
if (message.hasToolExecutionRequests()) {
|
||||||
|
val request = message.toolExecutionRequests()[0]
|
||||||
|
promptBuilder.append("<|im_start|>assistant\n{\"name\": \"${request.name()}\", \"arguments\": ${request.arguments()}}<|im_end|>\n")
|
||||||
|
} else {
|
||||||
|
val cleanText = message.text()?.replace(Regex("Calling tool:.*?\\.\\.\\."), "")?.trim() ?: ""
|
||||||
|
if (cleanText.isNotBlank()) {
|
||||||
|
promptBuilder.append("<|im_start|>assistant\n$cleanText<|im_end|>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promptBuilder.append("<|im_start|>assistant\n")
|
||||||
|
|
||||||
|
// 3. Execution
|
||||||
|
val responseText = LlamaBridge.generate(promptBuilder.toString()).replace("<|im_end|>", "").trim()
|
||||||
|
|
||||||
|
// 4. Parse the Output (Regex Hunter)
|
||||||
|
if (toolSpecifications.isNotEmpty()) {
|
||||||
|
// Hunt for the pattern "name": "something" regardless of surrounding brackets
|
||||||
|
val nameRegex = Regex("\"name\"\\s*:\\s*\"([^\"]+)\"")
|
||||||
|
val match = nameRegex.find(responseText.replace("'", "\"")) // Sanitize single quotes first
|
||||||
|
|
||||||
|
if (match != null) {
|
||||||
|
val toolName = match.groupValues[1]
|
||||||
|
|
||||||
|
// Try to extract arguments if they exist, otherwise default to empty JSON
|
||||||
|
var argumentsJson = "{}"
|
||||||
|
val argRegex = Regex("\"arguments\"\\s*:\\s*(\\{.*?\\})")
|
||||||
|
val argMatch = argRegex.find(responseText.replace("'", "\""))
|
||||||
|
if (argMatch != null) {
|
||||||
|
argumentsJson = argMatch.groupValues[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = ToolExecutionRequest.builder()
|
||||||
|
.id(UUID.randomUUID().toString())
|
||||||
|
.name(toolName)
|
||||||
|
.arguments(argumentsJson)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return Response.from(AiMessage.from("Calling tool: $toolName...", listOf(request)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Standard Text Output
|
||||||
|
return Response.from(AiMessage(responseText))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user