-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add pluggable DevInsCompilerService for switchable compiler core #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package cc.unitmesh.devins.compiler.service | ||
|
|
||
| import cc.unitmesh.devins.compiler.DevInsCompiler | ||
| import cc.unitmesh.devins.compiler.context.CompilerContext | ||
| import cc.unitmesh.devins.compiler.result.DevInsCompiledResult | ||
| import cc.unitmesh.devins.compiler.variable.VariableScope | ||
| import cc.unitmesh.devins.compiler.variable.VariableType | ||
| import cc.unitmesh.devins.filesystem.ProjectFileSystem | ||
|
|
||
| /** | ||
| * 默认的 DevIns 编译器服务实现 | ||
| * | ||
| * 使用 mpp-core 的 DevInsCompiler,基于自定义 AST 解析器。 | ||
| * 适用于 CLI、Desktop、WASM 等跨平台环境。 | ||
| * | ||
| * 特点: | ||
| * - 跨平台支持(JS, WASM, Desktop JVM, Android, iOS) | ||
| * - 基于自定义 DevInsParser 解析 | ||
| * - 命令输出为占位符格式(如 {{FILE_CONTENT:path}}) | ||
| * - 不支持 IDE 特定功能(Symbol 解析、重构等) | ||
| */ | ||
| class DefaultDevInsCompilerService : DevInsCompilerService { | ||
|
|
||
| override suspend fun compile(source: String, fileSystem: ProjectFileSystem): DevInsCompiledResult { | ||
| val context = CompilerContext().apply { | ||
| this.fileSystem = fileSystem | ||
| } | ||
| val compiler = DevInsCompiler(context) | ||
| return compiler.compileFromSource(source) | ||
| } | ||
|
|
||
| override suspend fun compile( | ||
| source: String, | ||
| fileSystem: ProjectFileSystem, | ||
| variables: Map<String, Any> | ||
| ): DevInsCompiledResult { | ||
| val context = CompilerContext().apply { | ||
| this.fileSystem = fileSystem | ||
| } | ||
|
|
||
| // 添加自定义变量 | ||
| variables.forEach { (name, value) -> | ||
| context.variableTable.addVariable( | ||
| name = name, | ||
| varType = inferVariableType(value), | ||
| value = value, | ||
| scope = VariableScope.USER_DEFINED | ||
| ) | ||
| } | ||
|
|
||
| val compiler = DevInsCompiler(context) | ||
| return compiler.compileFromSource(source) | ||
| } | ||
|
|
||
| override fun supportsIdeFeatures(): Boolean = false | ||
|
|
||
| override fun getName(): String = "DefaultDevInsCompilerService (mpp-core)" | ||
|
|
||
| private fun inferVariableType(value: Any): VariableType { | ||
| return when (value) { | ||
| is String -> VariableType.STRING | ||
| is Int, is Long, is Double, is Float -> VariableType.NUMBER | ||
| is Boolean -> VariableType.BOOLEAN | ||
| is List<*> -> VariableType.ARRAY | ||
| is Map<*, *> -> VariableType.OBJECT | ||
| else -> VariableType.UNKNOWN | ||
| } | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||||||||||||||||||
| package cc.unitmesh.devins.compiler.service | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import cc.unitmesh.devins.compiler.result.DevInsCompiledResult | ||||||||||||||||||||
| import cc.unitmesh.devins.filesystem.ProjectFileSystem | ||||||||||||||||||||
| import kotlin.concurrent.Volatile | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * DevIns 编译器服务接口 | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * 提供可切换的编译器核心,支持: | ||||||||||||||||||||
| * - mpp-core 默认实现(跨平台,基于自定义 AST) | ||||||||||||||||||||
| * - IDEA 专用实现(基于 PSI,支持 IDE 功能如 Symbol 解析、重构等) | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * 使用方式: | ||||||||||||||||||||
| * ```kotlin | ||||||||||||||||||||
| * // 在 mpp-idea 中使用 IDEA 编译器 | ||||||||||||||||||||
| * val compilerService = IdeaDevInsCompilerService(project) | ||||||||||||||||||||
| * val llmService = KoogLLMService(config, compilerService = compilerService) | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * // 在 CLI/Desktop 中使用默认编译器 | ||||||||||||||||||||
| * val llmService = KoogLLMService(config) | ||||||||||||||||||||
| * // compilerService 默认为 DefaultDevInsCompilerService | ||||||||||||||||||||
| * ``` | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| interface DevInsCompilerService { | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 编译 DevIns 源代码 | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * @param source DevIns 源代码字符串 | ||||||||||||||||||||
| * @param fileSystem 项目文件系统,用于解析文件路径 | ||||||||||||||||||||
| * @return 编译结果 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| suspend fun compile(source: String, fileSystem: ProjectFileSystem): DevInsCompiledResult | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 编译 DevIns 源代码,带有自定义变量 | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * @param source DevIns 源代码字符串 | ||||||||||||||||||||
| * @param fileSystem 项目文件系统 | ||||||||||||||||||||
| * @param variables 自定义变量映射 | ||||||||||||||||||||
| * @return 编译结果 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| suspend fun compile( | ||||||||||||||||||||
| source: String, | ||||||||||||||||||||
| fileSystem: ProjectFileSystem, | ||||||||||||||||||||
| variables: Map<String, Any> | ||||||||||||||||||||
| ): DevInsCompiledResult | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 检查编译器是否支持 IDE 功能 | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * IDE 功能包括: | ||||||||||||||||||||
| * - Symbol 解析 (/symbol 命令) | ||||||||||||||||||||
| * - 代码重构 (/refactor 命令) | ||||||||||||||||||||
| * - 数据库操作 (/database 命令) | ||||||||||||||||||||
| * - 代码结构分析 (/structure 命令) | ||||||||||||||||||||
| * - 符号使用查找 (/usage 命令) | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * @return true 如果支持 IDE 功能 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| fun supportsIdeFeatures(): Boolean = false | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 获取编译器名称,用于日志和调试 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| fun getName(): String | ||||||||||||||||||||
|
|
||||||||||||||||||||
| companion object { | ||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 全局编译器服务实例 | ||||||||||||||||||||
| * 可以在应用启动时设置为 IDEA 专用实现 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| @Volatile | ||||||||||||||||||||
| private var instance: DevInsCompilerService? = null | ||||||||||||||||||||
|
Comment on lines
+74
to
+75
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 获取当前编译器服务实例 | ||||||||||||||||||||
| * 如果未设置,返回默认实现 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| fun getInstance(): DevInsCompilerService { | ||||||||||||||||||||
| return instance ?: DefaultDevInsCompilerService() | ||||||||||||||||||||
|
||||||||||||||||||||
| return instance ?: DefaultDevInsCompilerService() | |
| if (instance == null) { | |
| synchronized(this) { | |
| if (instance == null) { | |
| instance = DefaultDevInsCompilerService() | |
| } | |
| } | |
| } | |
| return instance!! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,7 @@ import ai.koog.prompt.llm.LLModel | |
| import ai.koog.prompt.params.LLMParams | ||
| import ai.koog.prompt.streaming.StreamFrame | ||
| import cc.unitmesh.agent.logging.getLogger | ||
| import cc.unitmesh.devins.compiler.DevInsCompilerFacade | ||
| import cc.unitmesh.devins.compiler.context.CompilerContext | ||
| import cc.unitmesh.devins.compiler.service.DevInsCompilerService | ||
| import cc.unitmesh.devins.filesystem.EmptyFileSystem | ||
| import cc.unitmesh.devins.filesystem.ProjectFileSystem | ||
| import cc.unitmesh.devins.llm.Message | ||
|
|
@@ -17,29 +16,40 @@ import cc.unitmesh.llm.compression.* | |
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.cancellable | ||
| import kotlinx.coroutines.flow.flow | ||
| import kotlinx.coroutines.flow.onCompletion | ||
| import kotlinx.serialization.json.Json | ||
| import kotlinx.datetime.Clock | ||
|
|
||
| /** | ||
| * LLM 服务 | ||
| * | ||
| * @param config 模型配置 | ||
| * @param compressionConfig 压缩配置 | ||
| * @param compilerService 可选的编译器服务,用于编译 DevIns 命令 | ||
| * 如果不提供,将使用 DevInsCompilerService.getInstance() | ||
| */ | ||
| class KoogLLMService( | ||
| private val config: ModelConfig, | ||
| private val compressionConfig: CompressionConfig = CompressionConfig() | ||
| private val compressionConfig: CompressionConfig = CompressionConfig(), | ||
| private val compilerService: DevInsCompilerService? = null | ||
| ) { | ||
| private val logger = getLogger("KoogLLMService") | ||
|
|
||
| private val executor: SingleLLMPromptExecutor by lazy { | ||
| ExecutorFactory.create(config) | ||
| } | ||
|
|
||
| private val model: LLModel by lazy { | ||
| ModelRegistry.createModel(config.provider, config.modelName) | ||
| ?: ModelRegistry.createGenericModel(config.provider, config.modelName) | ||
| } | ||
|
|
||
| private val compressionService: ChatCompressionService by lazy { | ||
| ChatCompressionService(executor, model, compressionConfig) | ||
| } | ||
|
|
||
|
|
||
| // 获取实际使用的编译器服务 | ||
| private val actualCompilerService: DevInsCompilerService | ||
| get() = compilerService ?: DevInsCompilerService.getInstance() | ||
|
|
||
| // Token 追踪 | ||
| private var lastTokenInfo: TokenInfo = TokenInfo() | ||
| private var messagesSinceLastCompression = 0 | ||
|
|
@@ -125,16 +135,14 @@ class KoogLLMService( | |
| } | ||
|
|
||
| private suspend fun compilePrompt(userPrompt: String, fileSystem: ProjectFileSystem): String { | ||
| val context = CompilerContext().apply { | ||
| this.fileSystem = fileSystem | ||
| } | ||
|
|
||
| val compiledResult = DevInsCompilerFacade.compile(userPrompt, context) | ||
| val compiledResult = actualCompilerService.compile(userPrompt, fileSystem) | ||
|
|
||
| if (compiledResult.hasError) { | ||
| logger.warn { "⚠️ [KoogLLMService] 编译错误: ${compiledResult.errorMessage}" } | ||
| logger.warn { "⚠️ [KoogLLMService] 编译错误 (${actualCompilerService.getName()}): ${compiledResult.errorMessage}" } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When using the IDEA compiler, 🤖 Was this useful? React with 👍 or 👎 |
||
| } | ||
|
|
||
| logger.debug { "📝 [KoogLLMService] 使用编译器: ${actualCompilerService.getName()}, IDE功能: ${actualCompilerService.supportsIdeFeatures()}" } | ||
|
|
||
| return compiledResult.output | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| package cc.unitmesh.devins.compiler.service | ||
|
|
||
| import cc.unitmesh.devins.filesystem.EmptyFileSystem | ||
| import kotlinx.coroutines.test.runTest | ||
| import kotlin.test.AfterTest | ||
| import kotlin.test.Test | ||
| import kotlin.test.assertEquals | ||
| import kotlin.test.assertFalse | ||
| import kotlin.test.assertNotNull | ||
| import kotlin.test.assertTrue | ||
|
|
||
| class DevInsCompilerServiceTest { | ||
|
|
||
| @AfterTest | ||
| fun tearDown() { | ||
| DevInsCompilerService.reset() | ||
| } | ||
|
|
||
| @Test | ||
| fun `getInstance returns DefaultDevInsCompilerService when not set`() { | ||
| val service = DevInsCompilerService.getInstance() | ||
| assertNotNull(service) | ||
| assertEquals("DefaultDevInsCompilerService (mpp-core)", service.getName()) | ||
| assertFalse(service.supportsIdeFeatures()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `setInstance allows custom implementation`() { | ||
| val customService = object : DevInsCompilerService { | ||
| override suspend fun compile(source: String, fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem) = | ||
| cc.unitmesh.devins.compiler.result.DevInsCompiledResult(output = "custom: $source") | ||
|
|
||
| override suspend fun compile( | ||
| source: String, | ||
| fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem, | ||
| variables: Map<String, Any> | ||
| ) = compile(source, fileSystem) | ||
|
|
||
| override fun supportsIdeFeatures() = true | ||
| override fun getName() = "CustomCompilerService" | ||
| } | ||
|
|
||
| DevInsCompilerService.setInstance(customService) | ||
|
|
||
| val service = DevInsCompilerService.getInstance() | ||
| assertEquals("CustomCompilerService", service.getName()) | ||
| assertTrue(service.supportsIdeFeatures()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `reset restores default implementation`() { | ||
| val customService = object : DevInsCompilerService { | ||
| override suspend fun compile(source: String, fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem) = | ||
| cc.unitmesh.devins.compiler.result.DevInsCompiledResult(output = "custom") | ||
|
|
||
| override suspend fun compile( | ||
| source: String, | ||
| fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem, | ||
| variables: Map<String, Any> | ||
| ) = compile(source, fileSystem) | ||
|
|
||
| override fun supportsIdeFeatures() = true | ||
| override fun getName() = "CustomCompilerService" | ||
| } | ||
|
|
||
| DevInsCompilerService.setInstance(customService) | ||
| assertEquals("CustomCompilerService", DevInsCompilerService.getInstance().getName()) | ||
|
|
||
| DevInsCompilerService.reset() | ||
| assertEquals("DefaultDevInsCompilerService (mpp-core)", DevInsCompilerService.getInstance().getName()) | ||
| } | ||
| } | ||
|
|
||
| class DefaultDevInsCompilerServiceTest { | ||
|
|
||
| @Test | ||
| fun `compile returns output for simple text`() = runTest { | ||
| val service = DefaultDevInsCompilerService() | ||
| val result = service.compile("Hello World", EmptyFileSystem()) | ||
|
|
||
| assertEquals("Hello World", result.output) | ||
| assertFalse(result.hasError) | ||
| } | ||
|
|
||
| @Test | ||
| fun `compile handles DevIns commands`() = runTest { | ||
| val service = DefaultDevInsCompilerService() | ||
| // /file command should produce placeholder in mpp-core implementation | ||
| val result = service.compile("/file:test.kt", EmptyFileSystem()) | ||
|
|
||
| // The mpp-core compiler outputs placeholders for commands | ||
| assertNotNull(result.output) | ||
| } | ||
|
|
||
| @Test | ||
| fun `supportsIdeFeatures returns false`() { | ||
| val service = DefaultDevInsCompilerService() | ||
| assertFalse(service.supportsIdeFeatures()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `getName returns correct name`() { | ||
| val service = DefaultDevInsCompilerService() | ||
| assertEquals("DefaultDevInsCompilerService (mpp-core)", service.getName()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `compile with variables works`() = runTest { | ||
| val service = DefaultDevInsCompilerService() | ||
| val variables = mapOf( | ||
| "name" to "test", | ||
| "count" to 42, | ||
| "enabled" to true | ||
| ) | ||
|
|
||
| val result = service.compile("Hello \$name", EmptyFileSystem(), variables) | ||
| assertNotNull(result.output) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
contentThresholdforAnalysisAgenthas been increased from 5000 to 15000 characters without documentation. This is a significant change (3x increase) that affects when content analysis is triggered. Consider:The change may be related to "Enhanced content analysis capacity" mentioned in the PR description, but the reasoning should be clear in the code.