From 375042b2b3eb1336be5b2b49137c2e79c6fb5610 Mon Sep 17 00:00:00 2001 From: Andrew Charneski Date: Fri, 6 Sep 2024 09:22:17 -0400 Subject: [PATCH] 1.2.0 (#98) * 1.2.0 * wip * wip * wip * wip * 1.2.0 * Update ActorTestAppServer.kt * Update ActorTestAppServer.kt --- README.md | 6 +- core/build.gradle.kts | 2 +- .../skyenet/core/actors/ActorSystem.kt | 61 +-- .../skyenet/core/actors/BaseActor.kt | 3 +- .../skyenet/core/actors/CodingActor.kt | 9 +- .../skyenet/core/actors/ParsedActor.kt | 3 +- .../core/actors/opt/ActorOptimization.kt | 205 ---------- .../skyenet/core/actors/opt/Expectation.kt | 71 ---- .../actors/record/CodingActorInterceptor.kt | 45 --- .../actors/record/ImageActorInterceptor.kt | 37 -- .../actors/record/ParsedActorInterceptor.kt | 46 --- .../actors/record/SimpleActorInterceptor.kt | 30 -- .../record/TextToSpeechActorInterceptor.kt | 34 -- .../skyenet/core/actors/test/ActorTestBase.kt | 69 ---- .../core/actors/test/CodingActorTestBase.kt | 23 -- .../core/actors/test/ImageActorTestBase.kt | 13 - .../skyenet/core/platform/ClientManager.kt | 85 ++-- .../skyenet/core/platform/HSQLUsageManager.kt | 2 +- .../skyenet/core/platform/file/DataStorage.kt | 2 +- .../simiacryptus/skyenet/core/util/Ears.kt | 3 +- .../skyenet/core/actors/ActorOptTest.kt | 71 ---- gradle.properties | 2 +- webui/build.gradle.kts | 2 +- .../diff/AddApplyFileDiffLinks.kt | 34 +- .../diff/AddShellExecutionLinks.kt | 2 +- .../simiacryptus/diff/FileValidationUtils.kt | 3 +- .../simiacryptus/diff/IterativePatchUtil.kt | 2 +- .../skyenet/apps/coding/CodingAgent.kt | 5 +- .../skyenet/apps/coding/ShellToolAgent.kt | 5 +- .../skyenet/apps/general/CmdPatchApp.kt | 4 +- .../skyenet/apps/general/CommandPatchApp.kt | 4 +- .../skyenet/apps/general/PatchApp.kt | 17 +- .../skyenet/apps/general/PlanAheadApp.kt | 60 ++- .../skyenet/apps/general/WebDevApp.kt | 3 +- .../skyenet/apps/plan/AbstractAnalysisTask.kt | 37 +- .../skyenet/apps/plan/AbstractTask.kt | 64 ++- .../skyenet/apps/plan/CodeOptimizationTask.kt | 44 +- .../skyenet/apps/plan/CodeReviewTask.kt | 6 +- .../skyenet/apps/plan/CommandAutoFixTask.kt | 49 +-- .../apps/plan/DisabledTaskException.kt | 3 + .../skyenet/apps/plan/DocumentationTask.kt | 30 +- .../skyenet/apps/plan/FileModificationTask.kt | 38 +- .../skyenet/apps/plan/ForeachTask.kt | 53 ++- .../skyenet/apps/plan/InquiryTask.kt | 36 +- .../apps/plan/PerformanceAnalysisTask.kt | 6 +- .../skyenet/apps/plan/PlanCoordinator.kt | 381 ++++++++---------- .../skyenet/apps/plan/PlanProcessingState.kt | 15 + .../skyenet/apps/plan/PlanSettings.kt | 30 ++ .../skyenet/apps/plan/PlanTask.kt | 18 - .../skyenet/apps/plan/PlanUtil.kt | 165 ++++++++ .../skyenet/apps/plan/PlanningTask.kt | 157 +++++--- .../skyenet/apps/plan/RefactorTask.kt | 6 +- .../skyenet/apps/plan/RunShellCommandTask.kt | 46 ++- .../skyenet/apps/plan/SecurityAuditTask.kt | 6 +- .../skyenet/apps/plan/Settings.kt | 102 ----- .../skyenet/apps/plan/TaskType.kt | 94 ++++- .../skyenet/apps/plan/TestGenerationTask.kt | 6 +- .../webui/application/ApplicationDirectory.kt | 2 +- .../skyenet/webui/chat/ChatSocketManager.kt | 3 +- .../skyenet/webui/servlet/FileServlet.kt | 2 +- .../skyenet/webui/servlet/ProxyHttpServlet.kt | 4 +- .../skyenet/webui/session/SessionTask.kt | 3 + .../webui/session/SocketManagerBase.kt | 12 +- .../skyenet/webui/test/CodingActorTestApp.kt | 3 +- .../skyenet/webui/test/ImageActorTestApp.kt | 3 +- .../skyenet/webui/test/ParsedActorTestApp.kt | 3 +- .../skyenet/webui/test/SimpleActorTestApp.kt | 3 +- webui/src/main/resources/application/main.js | 10 +- .../skyenet/webui/ActorTestAppServer.kt | 10 +- 69 files changed, 989 insertions(+), 1424 deletions(-) delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/ActorOptimization.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/Expectation.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/CodingActorInterceptor.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ImageActorInterceptor.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ParsedActorInterceptor.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/SimpleActorInterceptor.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/TextToSpeechActorInterceptor.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ActorTestBase.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/CodingActorTestBase.kt delete mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ImageActorTestBase.kt delete mode 100644 core/src/test/java/com/simiacryptus/skyenet/core/actors/ActorOptTest.kt create mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt create mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt create mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt delete mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt create mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt delete mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt diff --git a/README.md b/README.md index 5fcb1a5b..b4744299 100644 --- a/README.md +++ b/README.md @@ -76,18 +76,18 @@ Maven: com.simiacryptus skyenet-webui - 1.0.68 + 1.1.0 ``` Gradle: ```groovy -implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.0.68' +implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.1.0' ``` ```kotlin -implementation("com.simiacryptus:skyenet:1.0.68") +implementation("com.simiacryptus:skyenet:1.1.0") ``` ### 🌟 To Use diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ff7388f9..aa5e70c9 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -33,7 +33,7 @@ val hsqldb_version = "2.7.2" dependencies { - implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.0.68") + implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.1.0") implementation(group = "org.hsqldb", name = "hsqldb", version = hsqldb_version) implementation("org.apache.commons:commons-text:1.11.0") diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt index 58af7933..7275f62d 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt @@ -1,12 +1,9 @@ package com.simiacryptus.skyenet.core.actors -import com.simiacryptus.skyenet.core.actors.record.* import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.StorageInterface import com.simiacryptus.skyenet.core.platform.User -import com.simiacryptus.skyenet.core.util.FunctionWrapper -import com.simiacryptus.skyenet.core.util.JsonFunctionRecorder open class ActorSystem>( val actors: Map>, @@ -16,61 +13,5 @@ open class ActorSystem>( ) { protected val pool by lazy { ApplicationServices.clientManager.getPool(session, user) } - private val actorMap = mutableMapOf>() - - fun getActor(actor: T): BaseActor<*, *> { - return synchronized(actorMap) { - actorMap.computeIfAbsent(actor) { innerActor -> - try { - val wrapper = getWrapper(actor.name) - when (val baseActor = actors[actor.name]) { - null -> throw RuntimeException("No actor for $actor") - is SimpleActor -> SimpleActorInterceptor( - inner = baseActor, - functionInterceptor = wrapper - ) - - is ParsedActor<*> -> ParsedActorInterceptor( - inner = baseActor, - functionInterceptor = wrapper - ) - - is CodingActor -> CodingActorInterceptor( - inner = baseActor, - functionInterceptor = wrapper - ) - - is ImageActor -> ImageActorInterceptor( - inner = baseActor, - functionInterceptor = wrapper - ) - - is TextToSpeechActor -> TextToSpeechActorInterceptor( - inner = baseActor, - functionInterceptor = wrapper - ) - - else -> throw RuntimeException("Unknown actor type: ${baseActor.javaClass}") - } - } catch (e: Throwable) { - val baseActor = actors[actor.name]!! - log.warn("Error creating actor $actor, returning $baseActor", e) - baseActor - } - } - } - } - - private val wrapperMap = mutableMapOf() - private fun getWrapper(name: String) = synchronized(wrapperMap) { - wrapperMap.getOrPut(name) { - FunctionWrapper(JsonFunctionRecorder( - dataStorage.getDataDir(user, session).resolve("actors/$name").apply { mkdirs() } - )) - } - } - - companion object { - private val log = org.slf4j.LoggerFactory.getLogger(ActorSystem::class.java) - } + fun getActor(actor: T) = actors.get(actor.name)!! } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt index 6c5918b0..22399203 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt @@ -2,6 +2,7 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAIModel @@ -15,7 +16,7 @@ abstract class BaseActor( ) { abstract fun respond(input: I, api: API, vararg messages: ApiModel.ChatMessage): R open fun response(vararg input: ApiModel.ChatMessage, model: OpenAIModel = this.model, api: API) = - (api as OpenAIClient).chat( + (api as ChatClient).chat( ApiModel.ChatRequest( messages = ArrayList(input.toList()), temperature = temperature, diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt index d9220a4d..aa497f84 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt @@ -2,6 +2,7 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel.* +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.describe.TypeDescriber @@ -136,7 +137,7 @@ open class CodingActor( var result = CodeResultImpl( *messages, input = input, - api = (api as OpenAIClient) + api = (api as ChatClient) ) if (!input.autoEvaluate) return result for (i in 0..input.fixIterations) try { @@ -206,7 +207,7 @@ open class CodingActor( inner class CodeResultImpl( vararg val messages: ChatMessage, private val input: CodeRequest, - private val api: OpenAIClient, + private val api: ChatClient, private val givenCode: String? = null, private val givenResponse: String? = null, ) : CodeResult { @@ -309,7 +310,7 @@ open class CodingActor( } private fun fixCommand( - api: OpenAIClient, + api: ChatClient, previousCode: String, error: Throwable, vararg promptMessages: ChatMessage, @@ -345,7 +346,7 @@ open class CodingActor( model = model ) - private fun chat(api: OpenAIClient, request: ChatRequest, model: OpenAITextModel) = + private fun chat(api: ChatClient, request: ChatRequest, model: OpenAITextModel) = api.chat(request.copy(model = model.modelName, temperature = temperature), model) .choices.first().message?.content.orEmpty().trim() diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt index a86699a4..ec7686fb 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt @@ -2,6 +2,7 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.describe.TypeDescriber @@ -76,7 +77,7 @@ open class ParsedActor( """.trimMargin() for (i in 0 until deserializerRetries) { try { - val content = (api as OpenAIClient).chat( + val content = (api as ChatClient).chat( ApiModel.ChatRequest( messages = listOf( ApiModel.ChatMessage(role = ApiModel.Role.system, content = prompt.toContentList()), diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/ActorOptimization.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/ActorOptimization.kt deleted file mode 100644 index d9b7848d..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/ActorOptimization.kt +++ /dev/null @@ -1,205 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.opt - -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.OpenAIClient -import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.jopenai.proxy.ChatProxy -import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.skyenet.core.actors.BaseActor -import com.simiacryptus.skyenet.core.actors.opt.ActorOptimization.GeneticApi.Prompt -import org.slf4j.LoggerFactory -import kotlin.math.ceil -import kotlin.math.ln -import kotlin.math.max -import kotlin.math.pow - -open class ActorOptimization( - val api: OpenAIClient, - val model: ChatModels, - private val mutationRate: Double = 0.5, - private val mutatonTypes: Map = mapOf( - "Rephrase" to 1.0, - "Randomize" to 1.0, - "Summarize" to 1.0, - "Expand" to 1.0, - "Reorder" to 1.0, - "Remove Duplicate" to 1.0, - ) -) { - - data class TestCase( - val userMessages: List, - val expectations: List, - val retries: Int = 3 - ) - - open fun , T : Any> runGeneticGenerations( - prompts: List, - testCases: List, - actorFactory: (String) -> BaseActor, - resultMapper: (T) -> String, - selectionSize: Int = defaultSelectionSize(prompts), - populationSize: Int = defaultPositionSize(selectionSize, prompts), - generations: Int = 3 - ): List { - var topPrompts = regenerate(prompts, populationSize) - for (generation in 0..generations) { - val scores = topPrompts.map { prompt -> - prompt to testCases.map { testCase -> - val actor = actorFactory(prompt) - val answer = actor.respond( - input = listOf(actor.prompt) as I, api = api, *(listOf( - ApiModel.ChatMessage( - role = ApiModel.Role.system, - content = actor.prompt.toContentList() - ), - ) + testCase.userMessages).toTypedArray() - ) - testCase.expectations.map { it.score(api, resultMapper(answer)) }.average() - }.average() - } - scores.sortedByDescending { it.second }.forEach { - log.info("""Scored ${it.second}: ${it.first.replace("\n", "\\n")}""") - } - if (generation == generations) { - log.info("Final generation: ${topPrompts.first()}") - break - } else { - val survivors = scores.sortedByDescending { it.second }.take(selectionSize).map { it.first } - topPrompts = regenerate(survivors, populationSize) - log.info("Generation $generation: ${topPrompts.first()}") - } - } - return topPrompts - } - - private fun defaultPositionSize(selectionSize: Int, systemPrompts: List) = - max(max(selectionSize, 5), systemPrompts.size) - - private fun defaultSelectionSize(systemPrompts: List) = - max(ceil(ln((systemPrompts.size + 1).toDouble()) / ln(2.0)), 3.0) - .toInt() - - open fun regenerate(progenetors: List, desiredCount: Int): List { - val result = listOf().toMutableList() - result += progenetors - while (result.size < desiredCount) { - if (progenetors.size == 1) { - val selected = progenetors.first() - val mutated = mutate(selected) - result += mutated - } else if (progenetors.size == 0) { - throw RuntimeException("No survivors") - } else { - val a = progenetors.random() - var b: String - do { - b = progenetors.random() - } while (a == b) - val child = recombine(a, b) - result += child - } - } - return result - } - - open fun recombine(a: String, b: String): String { - val temperature = 0.3 - for (retry in 0..3) { - try { - val child = geneticApi(temperature.pow(1.0 / (retry + 1))).recombine(Prompt(a), Prompt(b)).prompt - if (child.contentEquals(a) || child.contentEquals(b)) { - log.info("Recombine failure: retry $retry") - continue - } - log.info( - "Recombined Prompts\n\t${ - a.replace("\n", "\n\t") - }\n\t-- + --\n\t${ - b.replace("\n", "\n\t") - }\n\t-- -> --\n\t${child.replace("\n", "\n\t")}" - ) - if (Math.random() < mutationRate) { - return mutate(child) - } else { - return child - } - } catch (e: Exception) { - log.warn("Failed to recombine $a + $b", e) - } - } - return mutate(a) - } - - open fun mutate(selected: String): String { - val temperature = 0.3 - for (retry in 0..10) { - try { - val directive = getMutationDirective() - val mutated = geneticApi(temperature.pow(1.0 / (retry + 1))).mutate(Prompt(selected), directive).prompt - if (mutated.contentEquals(selected)) { - log.info("Mutate failure $retry ($directive): ${selected.replace("\n", "\\n")}") - continue - } - log.info( - "Mutated Prompt\n\t${selected.replace("\n", "\n\t")}\n\t-- -> --\n\t${ - mutated.replace( - "\n", - "\n\t" - ) - }" - ) - return mutated - } catch (e: Exception) { - log.warn("Failed to mutate $selected", e) - } - } - throw RuntimeException("Failed to mutate $selected") - } - - open fun getMutationDirective(): String { - val fate = mutatonTypes.values.sum() * Math.random() - var cumulative = 0.0 - for ((key, value) in mutatonTypes) { - cumulative += value - if (fate < cumulative) { - return key - } - } - return mutatonTypes.keys.random() - } - - protected interface GeneticApi { - @Description("Mutate the given prompt; rephrase, make random edits, etc.") - fun mutate( - systemPrompt: Prompt, - directive: String = "Rephrase" - ): Prompt - - @Description("Recombine the given prompts to produce a third with about the same length; swap phrases, reword, etc.") - fun recombine( - systemPromptA: Prompt, - systemPromptB: Prompt - ): Prompt - - data class Prompt( - val prompt: String - ) - } - - protected open fun geneticApi(temperature: Double = 0.3) = ChatProxy( - clazz = GeneticApi::class.java, - api = api, - model = model, - temperature = temperature - ).create() - - companion object { - private val log = LoggerFactory.getLogger(ActorOptimization::class.java) - fun String.toChatMessage(role: ApiModel.Role = ApiModel.Role.user) = ApiModel.ChatMessage( - role = role, content = this.toContentList() - ) - } - -} diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/Expectation.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/Expectation.kt deleted file mode 100644 index 816e2684..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/opt/Expectation.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.opt - -import com.simiacryptus.jopenai.OpenAIClient -import com.simiacryptus.jopenai.models.EmbeddingModels -import com.simiacryptus.jopenai.opt.DistanceType -import org.slf4j.LoggerFactory - -abstract class Expectation { - companion object { - private val log = LoggerFactory.getLogger(Expectation::class.java) - } - - open class VectorMatch(private val example: String, private val metric: DistanceType = DistanceType.Cosine) : - Expectation() { - override fun matches(api: OpenAIClient, response: String): Boolean { - return true - } - - override fun score(api: OpenAIClient, response: String): Double { - val contentEmbedding = createEmbedding(api, example) - val promptEmbedding = createEmbedding(api, response) - val distance = metric.distance(contentEmbedding, promptEmbedding) - log.info( - """Distance = $distance - | from "${example.replace("\n", "\\n")}" - | to "${response.replace("\n", "\\n")}" - """.trimMargin().trim() - ) - return -distance - } - - private fun createEmbedding(api: OpenAIClient, str: String) = api.createEmbedding( - com.simiacryptus.jopenai.ApiModel.EmbeddingRequest( - model = EmbeddingModels.AdaEmbedding.modelName, input = str - ) - ).data.first().embedding!! - } - - open class ContainsMatch( - private val pattern: Regex, - val critical: Boolean = true - ) : Expectation() { - override fun matches(api: OpenAIClient, response: String): Boolean { - if (!critical) return true - return _matches(response) - } - - override fun score(api: OpenAIClient, response: String): Double { - return if (_matches(response)) 1.0 else 0.0 - } - - private fun _matches(response: String?): Boolean { - if (pattern.containsMatchIn(response ?: "")) return true - log.info( - """Failed to match ${ - pattern.pattern.replace("\n", "\\n") - } in ${ - response?.replace("\n", "\\n") ?: "" - }""" - ) - return false - } - - } - - abstract fun matches(api: OpenAIClient, response: String): Boolean - - abstract fun score(api: OpenAIClient, response: String): Double - - -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/CodingActorInterceptor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/CodingActorInterceptor.kt deleted file mode 100644 index 9e958d5e..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/CodingActorInterceptor.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.record - -import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel.ChatMessage -import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.skyenet.core.actors.CodingActor -import com.simiacryptus.skyenet.core.util.FunctionWrapper - -class CodingActorInterceptor( - val inner: CodingActor, - private val functionInterceptor: FunctionWrapper, -) : CodingActor( - interpreterClass = inner.interpreterClass, - symbols = inner.symbols, - describer = inner.describer, - name = inner.name, - details = inner.details, - model = inner.model, - fallbackModel = inner.fallbackModel, - temperature = inner.temperature, -) { - override fun response( - vararg input: ChatMessage, - model: OpenAIModel, - api: API - ) = functionInterceptor.wrap( - input.toList().toTypedArray(), - model - ) { messages: Array, - model: OpenAIModel -> - inner.response(*messages, model = model, api = api) - } - - - override fun respond(input: CodeRequest, api: API, vararg messages: ChatMessage) = inner.CodeResultImpl( - messages = messages, - input = input, - api = api as com.simiacryptus.jopenai.OpenAIClient, - givenCode = super.respond(input = input, api = api, *messages).code, - ) - - override fun execute(prefix: String, code: String) = - functionInterceptor.wrap(prefix, code) - { prefix, code -> inner.execute(prefix, code) } -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ImageActorInterceptor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ImageActorInterceptor.kt deleted file mode 100644 index 1a80a4ec..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ImageActorInterceptor.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.record - -import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.skyenet.core.actors.ImageActor -import com.simiacryptus.skyenet.core.util.FunctionWrapper -import java.awt.image.BufferedImage - -class ImageActorInterceptor( - val inner: ImageActor, - private val functionInterceptor: FunctionWrapper, -) : ImageActor( - prompt = inner.prompt, - name = inner.name, - textModel = inner.model, - imageModel = inner.imageModel, - temperature = inner.temperature, - width = inner.width, - height = inner.height, -) { - - override fun response( - vararg input: com.simiacryptus.jopenai.ApiModel.ChatMessage, - model: OpenAIModel, - api: API - ) = functionInterceptor.wrap( - input.toList().toTypedArray(), - model - ) { messages: Array, - model: OpenAIModel -> - inner.response(*messages, model = model, api = api) - } - - override fun render(text: String, api: API): BufferedImage = functionInterceptor.wrap(text) { - inner.render(it, api = api) - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ParsedActorInterceptor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ParsedActorInterceptor.kt deleted file mode 100644 index 385d4f09..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/ParsedActorInterceptor.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.record - -import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.skyenet.core.actors.ParsedActor -import com.simiacryptus.skyenet.core.actors.ParsedResponse -import com.simiacryptus.skyenet.core.util.FunctionWrapper -import java.util.function.Function - -class ParsedActorInterceptor( - val inner: ParsedActor<*>, - private val functionInterceptor: FunctionWrapper, -) : ParsedActor( - resultClass = inner.resultClass as Class, - exampleInstance = inner.exampleInstance, - prompt = inner.prompt, - name = inner.name, - model = inner.model, - temperature = inner.temperature, - parsingModel = inner.parsingModel, -) { - - override fun respond( - input: List, - api: API, - vararg messages: com.simiacryptus.jopenai.ApiModel.ChatMessage, - ) = - object : ParsedResponse(resultClass!!) { - private val parser: Function = getParser(api) - - private val _obj: Any by lazy { parse() } - - private fun parse(): Any = functionInterceptor.inner.intercept(text, resultClass!!) { parser.apply(text) } - override val text get() = super@ParsedActorInterceptor.respond(input = input, api = api, *messages).text - override val obj get() = _obj - } - - override fun response( - vararg input: com.simiacryptus.jopenai.ApiModel.ChatMessage, - model: OpenAIModel, - api: API - ) = functionInterceptor.wrap(input.toList().toTypedArray(), model) { messages, model -> - inner.response(*messages, model = model, api = api) - } - -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/SimpleActorInterceptor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/SimpleActorInterceptor.kt deleted file mode 100644 index d0f3dd95..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/SimpleActorInterceptor.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.record - -import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.skyenet.core.actors.SimpleActor -import com.simiacryptus.skyenet.core.util.FunctionWrapper - -class SimpleActorInterceptor( - val inner: SimpleActor, - private val functionInterceptor: FunctionWrapper, -) : SimpleActor( - prompt = inner.prompt, - name = inner.name, - model = inner.model, - temperature = inner.temperature, -) { - - override fun response( - vararg input: com.simiacryptus.jopenai.ApiModel.ChatMessage, - model: OpenAIModel, - api: API - ) = functionInterceptor.wrap( - input.toList().toTypedArray(), - model - ) { messages: Array, - model: OpenAIModel -> - inner.response(*messages, model = model, api = api) - } - -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/TextToSpeechActorInterceptor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/TextToSpeechActorInterceptor.kt deleted file mode 100644 index 3a783312..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/record/TextToSpeechActorInterceptor.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.record - -import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.skyenet.core.actors.TextToSpeechActor -import com.simiacryptus.skyenet.core.util.FunctionWrapper - -class TextToSpeechActorInterceptor( - val inner: TextToSpeechActor, - private val functionInterceptor: FunctionWrapper, -) : TextToSpeechActor( - name = inner.name, - audioModel = inner.audioModel, - "alloy", - 1.0, - ChatModels.GPT35Turbo, -) { - override fun response( - vararg input: com.simiacryptus.jopenai.ApiModel.ChatMessage, - model: OpenAIModel, - api: API - ) = functionInterceptor.wrap( - input.toList().toTypedArray(), - model - ) { messages: Array, - model: OpenAIModel -> - inner.response(*messages, model = model, api = api) - } - - override fun render(text: String, api: API): ByteArray = functionInterceptor.wrap(text) { - inner.render(it, api = api) - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ActorTestBase.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ActorTestBase.kt deleted file mode 100644 index cc204c44..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ActorTestBase.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.test - -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.OpenAIClient -import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.skyenet.core.actors.BaseActor -import com.simiacryptus.skyenet.core.actors.opt.ActorOptimization -import org.slf4j.LoggerFactory -import org.slf4j.event.Level - -abstract class ActorTestBase { - - open val api = OpenAIClient(logLevel = Level.DEBUG) - - abstract val testCases: List - abstract val actor: BaseActor - abstract fun actorFactory(prompt: String): BaseActor - abstract fun getPrompt(actor: BaseActor): String - abstract fun resultMapper(result: R): String - - open fun opt( - actor: BaseActor = this.actor, - testCases: List = this.testCases, - actorFactory: (String) -> BaseActor = this::actorFactory, - resultMapper: (R) -> String = this::resultMapper - ) { - ActorOptimization( - api, ChatModels.GPT35Turbo - ).runGeneticGenerations( - populationSize = 1, - generations = 1, - selectionSize = 1, - actorFactory = actorFactory as (String) -> BaseActor, R>, - resultMapper = resultMapper, - prompts = listOf( - getPrompt(actor), - ), - testCases = testCases, - ) - } - - open fun testOptimize() { - opt() - } - - open fun testRun() { - testCases.forEach { testCase -> - val messages = arrayOf( - ApiModel.ChatMessage( - role = com.simiacryptus.jopenai.ApiModel.Role.system, - content = actor.prompt.toContentList() - ), - ) + testCase.userMessages.toTypedArray() - val answer = answer(messages) - log.info("Answer: ${resultMapper(answer)}") - } - } - - open fun answer(messages: Array): R = actor.respond( - input = (messages.map { it.content?.first()?.text }) as I, - api = api, - *messages - ) - - companion object { - private val log = LoggerFactory.getLogger(ActorTestBase::class.java) - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/CodingActorTestBase.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/CodingActorTestBase.kt deleted file mode 100644 index 6e311e7f..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/CodingActorTestBase.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.test - -import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.skyenet.core.actors.BaseActor -import com.simiacryptus.skyenet.core.actors.CodingActor -import com.simiacryptus.skyenet.core.actors.CodingActor.CodeResult -import com.simiacryptus.skyenet.interpreter.Interpreter -import kotlin.reflect.KClass - -abstract class CodingActorTestBase : ActorTestBase() { - abstract val interpreterClass: KClass - override fun actorFactory(prompt: String): CodingActor = CodingActor( - interpreterClass = interpreterClass, - details = prompt, - model = ChatModels.GPT35Turbo - ) - - override fun getPrompt(actor: BaseActor): String = - (actor as CodingActor).details!! - - override fun resultMapper(result: CodeResult): String = result.code -} - diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ImageActorTestBase.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ImageActorTestBase.kt deleted file mode 100644 index b310cf64..00000000 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/test/ImageActorTestBase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.simiacryptus.skyenet.core.actors.test - -import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.skyenet.core.actors.ImageActor -import com.simiacryptus.skyenet.core.actors.ImageResponse - -abstract class ImageActorTestBase : ActorTestBase, ImageResponse>() { - override fun actorFactory(prompt: String) = ImageActor( - prompt = prompt, - textModel = ChatModels.GPT35Turbo - ) - -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt index f01facae..606aaa01 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt @@ -3,35 +3,28 @@ package com.simiacryptus.skyenet.core.platform import com.google.common.util.concurrent.ListeningScheduledExecutorService import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.ThreadFactoryBuilder -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.HttpClientManager -import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.APIProvider -import com.simiacryptus.jopenai.models.OpenAIModel import com.simiacryptus.jopenai.util.ClientUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices.dataStorageFactory import com.simiacryptus.skyenet.core.platform.ApplicationServices.userSettingsManager import com.simiacryptus.skyenet.core.platform.ApplicationServicesConfig.dataStorageRoot import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient -import org.apache.hc.core5.http.HttpRequest import org.slf4j.LoggerFactory -import org.slf4j.event.Level -import java.io.File import java.util.concurrent.* open class ClientManager { private data class SessionKey(val session: Session, val user: User?) - private val clientCache = mutableMapOf() + private val clientCache = mutableMapOf() private val poolCache = mutableMapOf() private val scheduledPoolCache = mutableMapOf() fun getClient( session: Session, user: User?, - ): OpenAIClient { + ): ChatClient { log.debug("Fetching client for session: {}, user: {}", session, user) val key = SessionKey(session, user) return clientCache.getOrPut(key) { createClient(session, user)!! } @@ -90,13 +83,14 @@ open class ClientManager { protected open fun createClient( session: Session, user: User?, - ): OpenAIClient? { + ): ChatClient? { log.debug("Creating client for session: {}, user: {}", session, user) val sessionDir = dataStorageFactory(dataStorageRoot).getDataDir(user, session).apply { mkdirs() } if (user != null) { val userSettings = userSettingsManager.getUserSettings(user) val userApi = - if (userSettings.apiKeys.isNotEmpty()) + if (userSettings.apiKeys.isNotEmpty()) { + /* MonitoredClient( key = userSettings.apiKeys, apiBase = userSettings.apiBase, @@ -104,7 +98,17 @@ open class ClientManager { session = session, user = user, workPool = getPool(session, user), - ) else null + )*/ + ChatClient( + key = userSettings.apiKeys, + apiBase = userSettings.apiBase, + workPool = getPool(session, user), + ).apply { + this.session = session + this.user = user + logStreams += sessionDir.resolve("openai.log").outputStream().buffered() + } + } else null if (userApi != null) return userApi } val canUseGlobalKey = ApplicationServices.authorizationManager.isAuthorized( @@ -112,59 +116,26 @@ open class ClientManager { ) if (!canUseGlobalKey) throw RuntimeException("No API key") return (if (ClientUtil.keyMap.isNotEmpty()) { - MonitoredClient( + /*MonitoredClient( key = ClientUtil.keyMap.mapKeys { APIProvider.valueOf(it.key) }, - logfile = sessionDir?.resolve("openai.log"), + logfile = sessionDir.resolve("openai.log"), session = session, user = user, workPool = getPool(session, user), - ) + )*/ + ChatClient( + key = ClientUtil.keyMap.mapKeys { APIProvider.valueOf(it.key) }, + workPool = getPool(session, user), + ).apply { + this.session = session + this.user = user + logStreams += sessionDir.resolve("openai.log").outputStream().buffered() + } } else { null })!! } - inner class MonitoredClient( - key: Map, - logfile: File?, - private val session: Session, - private val user: User?, - apiBase: Map = APIProvider.values().associate { it to (it.base ?: "") }, - scheduledPool: ListeningScheduledExecutorService = HttpClientManager.scheduledPool, - workPool: ThreadPoolExecutor = HttpClientManager.workPool, - client: CloseableHttpClient = HttpClientManager.createHttpClient() - ) : OpenAIClient( - key = key, - logLevel = Level.DEBUG, - logStreams = listOfNotNull( - logfile?.outputStream()?.buffered() - ).toMutableList(), - scheduledPool = scheduledPool, - workPool = workPool, - client = client, - apiBase = apiBase, - ) { - var budget = 2.00 - override fun authorize(request: HttpRequest, apiProvider: APIProvider) { - log.debug("Authorizing request for session: {}, user: {}, apiProvider: {}", session, user, apiProvider) - require(budget > 0.0) { "Budget Exceeded" } - super.authorize(request, ClientUtil.defaultApiProvider) - } - - override fun onUsage(model: OpenAIModel?, tokens: ApiModel.Usage) { - log.debug( - "Usage recorded for session: {}, user: {}, model: {}, tokens: {}", - session, - user, - model, - tokens - ) - ApplicationServices.usageManager.incrementUsage(session, user, model!!, tokens) - budget -= tokens.cost ?: 0.0 - super.onUsage(model, tokens) - } - } - companion object { private val log = LoggerFactory.getLogger(ClientManager::class.java) } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt index f1aab0e8..043bbaed 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt @@ -170,6 +170,6 @@ class HSQLUsageManager(private val dbFile: File) : UsageInterface { } companion object { - private val log = org.slf4j.LoggerFactory.getLogger(HSQLUsageManager::class.java) + private val log = LoggerFactory.getLogger(HSQLUsageManager::class.java) } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt index a795e5fa..8f292836 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt @@ -220,7 +220,7 @@ open class DataStorage( val globalSessions = listSessions(dataDir.resolve("global"), path) val userSessions = if (user == null) listOf() else listSessions( dataDir.resolve("user-sessions").resolve( - if (user?.email != null) { + if (user.email != null) { user.email } else { throw IllegalArgumentException("User required for private session") diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/Ears.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/Ears.kt index 6b94ac26..b156b00b 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/Ears.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/Ears.kt @@ -1,5 +1,6 @@ package com.simiacryptus.skyenet.core.util +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.audio.AudioRecorder import com.simiacryptus.jopenai.audio.LookbackLoudnessWindowBuffer @@ -17,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger */ @Suppress("unused") open class Ears( - val api: OpenAIClient, + val api: ChatClient, private val secondsPerAudioPacket: Double = 0.25, ) { diff --git a/core/src/test/java/com/simiacryptus/skyenet/core/actors/ActorOptTest.kt b/core/src/test/java/com/simiacryptus/skyenet/core/actors/ActorOptTest.kt deleted file mode 100644 index 79f40f81..00000000 --- a/core/src/test/java/com/simiacryptus/skyenet/core/actors/ActorOptTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.simiacryptus.skyenet.core.actors - -import com.simiacryptus.jopenai.OpenAIClient -import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.skyenet.core.actors.opt.ActorOptimization -import com.simiacryptus.skyenet.core.actors.opt.ActorOptimization.Companion.toChatMessage -import com.simiacryptus.skyenet.core.actors.opt.Expectation -import org.slf4j.LoggerFactory -import org.slf4j.event.Level -import kotlin.system.exitProcess - -object ActorOptTest { - - private val log = LoggerFactory.getLogger(ActorOptTest::class.java) - - @JvmStatic - fun main(args: Array) { - try { - ActorOptimization( - api = OpenAIClient( - logLevel = Level.DEBUG - ), - model = ChatModels.GPT35Turbo - ).runGeneticGenerations( - populationSize = 7, - generations = 5, - actorFactory = { - SimpleActor( - prompt = it, - model = ChatModels.GPT35Turbo - ) - }, - resultMapper = { it }, - prompts = listOf( - """ - |As the intermediary between the user and the search engine, your main task is to generate search queries based on user requests. - |Please respond to each user request by providing one or more calls to the "`search('query text')`" function. - |""".trimMargin(), - """ - |You act as a bridge between the user and the search engine by creating search queries. - |Output one or more calls to "`search('query text')`" in response to each user request. - |""".trimMargin().trim(), - """ - |You play the role of a search assistant. - |Provide one or more "`search('query text')`" calls as a response to each user request. - |Make sure to use single quotes around the query text. - |Surround the search function call with backticks. - |""".trimMargin().trim(), - ), - testCases = listOf( - ActorOptimization.TestCase( - userMessages = listOf( - "I want to buy a book.", - "A history book about Napoleon.", - ).map { it.toChatMessage() }, - expectations = listOf( - Expectation.ContainsMatch("""`search\('.*?'\)`""".toRegex(), critical = false), - Expectation.ContainsMatch("""search\(.*?\)""".toRegex(), critical = false), - Expectation.VectorMatch("Great, what kind of book are you looking for?") - ) - ) - ), - ) - } catch (e: Throwable) { - log.error("Error", e) - } finally { - exitProcess(0) - } - } - -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 77798c55..37bb4de2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.0.91 +libraryVersion = 1.2.0 gradleVersion = 7.6.1 kotlin.daemon.jvmargs=-Xmx2g diff --git a/webui/build.gradle.kts b/webui/build.gradle.kts index fe451fd9..e19909c8 100644 --- a/webui/build.gradle.kts +++ b/webui/build.gradle.kts @@ -35,7 +35,7 @@ val jetty_version = "11.0.18" val jackson_version = "2.17.0" dependencies { - implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.0.68") { + implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.1.0") { exclude(group = "org.slf4j", module = "slf4j-api") } diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt index c8802b7f..18b6a7dd 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt @@ -228,16 +228,16 @@ private fun SocketManagerBase.renderDiffBlock( val fixTask = ui.newTask(root = false) val verifyFwdTabs = if (!newCode.isValid) displayMapInTabs( mapOf( - "Code" to (prevCodeTask?.placeholder ?: ""), - "Preview" to (newCodeTask?.placeholder ?: ""), - "Echo" to (patchTask?.placeholder ?: ""), - "Fix" to (fixTask?.placeholder ?: ""), + "Code" to prevCodeTask.placeholder, + "Preview" to newCodeTask.placeholder, + "Echo" to patchTask.placeholder, + "Fix" to fixTask.placeholder, ) ) else displayMapInTabs( mapOf( - "Code" to (prevCodeTask?.placeholder ?: ""), - "Preview" to (newCodeTask?.placeholder ?: ""), - "Echo" to (patchTask?.placeholder ?: ""), + "Code" to prevCodeTask.placeholder, + "Preview" to newCodeTask.placeholder, + "Echo" to patchTask.placeholder, ) ) @@ -250,9 +250,9 @@ private fun SocketManagerBase.renderDiffBlock( val patch2TaskSB = patch2Task.add("") val verifyRevTabs = displayMapInTabs( mapOf( - "Code" to (prevCode2Task?.placeholder ?: ""), - "Preview" to (newCode2Task?.placeholder ?: ""), - "Echo" to (patch2Task?.placeholder ?: ""), + "Code" to prevCode2Task.placeholder, + "Preview" to newCode2Task.placeholder, + "Echo" to patch2Task.placeholder, ) ) @@ -264,8 +264,8 @@ private fun SocketManagerBase.renderDiffBlock( try { originalCode = load(filepath) newCode = patch(originalCode, diffVal) - filepath?.toFile()?.writeText(newCode.newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") - handle(mapOf(relativize!! to newCode.newCode)) + filepath.toFile()?.writeText(newCode.newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") + handle(mapOf(relativize to newCode.newCode)) hrefLink.set("""
Diff Applied
""" + revert) applydiffTask.complete() } catch (e: Throwable) { @@ -371,8 +371,8 @@ private fun SocketManagerBase.renderDiffBlock( val diffLines = diffVal.reverseLines() val patch1 = patch(originalLines, diffLines) val newCode2 = patch1.newCode.reverseLines() - filepath?.toFile()?.writeText(newCode2, Charsets.UTF_8) ?: log.warn("File not found: $filepath") - handle(mapOf(relativize!! to newCode2)) + filepath.toFile()?.writeText(newCode2, Charsets.UTF_8) ?: log.warn("File not found: $filepath") + handle(mapOf(relativize to newCode2)) hrefLink.set("""
Diff Applied (Bottom to Top)
""" + revert) applydiffTask.complete() } catch (e: Throwable) { @@ -383,8 +383,8 @@ private fun SocketManagerBase.renderDiffBlock( // Create "Revert" button revert = hrefLink("Revert", classname = "href-link cmd-button") { try { - filepath?.toFile()?.writeText(originalCode, Charsets.UTF_8) - handle(mapOf(relativize!! to originalCode)) + filepath.toFile()?.writeText(originalCode, Charsets.UTF_8) + handle(mapOf(relativize to originalCode)) hrefLink.set("""
Reverted
""" + apply1 + apply2) applydiffTask.complete() } catch (e: Throwable) { @@ -453,7 +453,7 @@ private fun SocketManagerBase.renderDiffBlock( // Create main tabs for displaying diff and verification information val mainTabs = displayMapInTabs( mapOf( - "Diff" to (diffTask?.placeholder ?: ""), + "Diff" to diffTask.placeholder, "Verify" to displayMapInTabs( mapOf( "Forward" to verifyFwdTabs, diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt index 1d0b4487..0ce984be 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt @@ -31,7 +31,7 @@ import java.io.InputStreamReader } val exitCode = process.waitFor() output.append("Exit code: $exitCode") - executionTask.complete(MarkdownUtil.renderMarkdown("```\n${output.toString()}\n```", ui = ui)) + executionTask.complete(MarkdownUtil.renderMarkdown("```\n$output\n```", ui = ui)) } catch (e: Throwable) { executionTask.error(null, e) } diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt b/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt index f31d0b86..e1adcdc2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt @@ -139,8 +139,7 @@ class FileValidationUtils { val pattern = line.trim().trimStart('/').trimEnd('/') .replace(".", "\\.").replace("*", ".*") - if (!path.fileName.toString().trimEnd('/').matches(Regex(pattern))) return@any false - return@any true + return@any path.fileName.toString().trimEnd('/').matches(Regex(pattern)) } catch (e: Throwable) { return@any false } diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt b/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt index c7408ac0..9535b218 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt @@ -224,7 +224,7 @@ object IterativePatchUtil { // search for prior, unlinked source lines var priorSourceLine = sourceLine.previousLine val lineBuffer = mutableListOf() - while (priorSourceLine != null && (priorSourceLine.matchingLine == null || priorSourceLine?.type == DELETE)) { + while (priorSourceLine != null && (priorSourceLine.matchingLine == null || priorSourceLine.type == DELETE)) { // Note the deletion of the prior source line lineBuffer.add(LineRecord(-1, priorSourceLine.line, type = DELETE)) priorSourceLine = priorSourceLine.previousLine diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt index 143fc8cf..66982b14 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt @@ -2,6 +2,7 @@ package com.simiacryptus.skyenet.apps.coding import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAITextModel @@ -85,7 +86,7 @@ open class CodingAgent( Retryable(ui, newTask) { val newTask = ui.newTask(root = false) ui.socketManager?.scheduledThreadPoolExecutor!!.schedule({ - ui.socketManager?.pool?.submit { + ui.socketManager.pool?.submit { val statusSB = newTask.add("Running...") displayCode(newTask, codeRequest) statusSB?.clear() @@ -109,7 +110,7 @@ open class CodingAgent( actor.CodeResultImpl( messages = actor.chatMessages(codeRequest), input = codeRequest, - api = api as OpenAIClient, + api = api as ChatClient, givenCode = lastUserMessage.removePrefix("```").removeSuffix("```") ) } else { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt index 259c9d79..53b4e71b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt @@ -2,6 +2,7 @@ package com.simiacryptus.skyenet.apps.coding import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.describe.TypeDescriber @@ -374,7 +375,7 @@ abstract class ShellToolAgent( actor.CodeResultImpl( messages = actor.chatMessages(codeRequest), input = codeRequest, - api = super.api as OpenAIClient, + api = super.api as ChatClient, givenCode = lastUserMessage.removePrefix("```").removeSuffix("```") ) } else { @@ -425,7 +426,7 @@ abstract class ShellToolAgent( actor.CodeResultImpl( messages = actor.chatMessages(codeRequest), input = codeRequest, - api = super.api as OpenAIClient, + api = super.api as ChatClient, givenCode = lastUserMessage.removePrefix("```").removeSuffix("```") ) } else { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt index f892f7ae..432af83a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt @@ -2,7 +2,7 @@ package com.simiacryptus.skyenet.apps.general import com.simiacryptus.diff.FileValidationUtils -import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.set @@ -16,7 +16,7 @@ class CmdPatchApp( root: Path, session: Session, settings: Settings, - api: OpenAIClient, + api: ChatClient, val files: Array?, model: OpenAITextModel ) : PatchApp(root.toFile(), session, settings, api, model) { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt index 7920a4ed..85485c20 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.apps.general import com.simiacryptus.diff.FileValidationUtils -import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.webui.session.SessionTask @@ -12,7 +12,7 @@ class CommandPatchApp( root: File, session: Session, settings: Settings, - api: OpenAIClient, + api: ChatClient, model: OpenAITextModel, private val files: Array?, val command: String, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt index 0a32231e..41b7ebdf 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt @@ -2,7 +2,7 @@ package com.simiacryptus.skyenet.apps.general import com.simiacryptus.diff.FileValidationUtils import com.simiacryptus.diff.addApplyFileDiffLinks -import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.jopenai.util.JsonUtil @@ -26,8 +26,9 @@ abstract class PatchApp( override val root: File, val session: Session, val settings: Settings, - val api: OpenAIClient, - val model: OpenAITextModel + val api: ChatClient, + val model: OpenAITextModel, + val promptPrefix: String = """The following command was run and produced an error:""" ) : ApplicationServer( applicationName = "Magic Code Fixer", path = "/fixCmd", @@ -152,7 +153,7 @@ abstract class PatchApp( output: OutputResult, task: SessionTask, ui: ApplicationInterface, - api: OpenAIClient, + api: ChatClient, ) { Retryable(ui, task) { content -> fixAllInternal( @@ -174,7 +175,7 @@ abstract class PatchApp( task: SessionTask, ui: ApplicationInterface, changed: MutableSet, - api: OpenAIClient, + api: ChatClient, ) { val plan = ParsedActor( resultClass = ParsedErrors::class.java, @@ -199,7 +200,7 @@ abstract class PatchApp( ).answer( listOf( """ - |The following command was run and produced an error: + |$promptPrefix | |${tripleTilde} |${output.output} @@ -259,7 +260,7 @@ abstract class PatchApp( content: StringBuilder, autoFix: Boolean, changed: MutableSet, - api: OpenAIClient, + api: ChatClient, ) { val paths = ( @@ -324,7 +325,7 @@ abstract class PatchApp( ).answer( listOf( """ - |The following command was run and produced an error: + |$promptPrefix | |${tripleTilde} |${output.output} diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt index 28aa8ea6..a77dce4a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt @@ -1,40 +1,72 @@ package com.simiacryptus.skyenet.apps.general import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.OpenAITextModel +import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.apps.plan.PlanCoordinator -import com.simiacryptus.skyenet.apps.plan.Settings -import com.simiacryptus.skyenet.core.platform.ClientManager +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.PlanUtil import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer +import com.simiacryptus.skyenet.webui.application.ApplicationSocketManager +import com.simiacryptus.skyenet.webui.session.SocketManager import org.slf4j.LoggerFactory import java.io.File class PlanAheadApp( applicationName: String = "Task Planning v1.1", path: String = "/taskDev", - val rootFile: File?, - val settings: Settings, + val rootFile: File, + val planSettings: PlanSettings, val model: OpenAITextModel, val parsingModel: OpenAITextModel, val domainName : String = "localhost", showMenubar: Boolean = true, + val initialPlan: PlanUtil.TaskBreakdownWithPrompt? = null, + val api: API? = null, ) : ApplicationServer( applicationName = applicationName, path = path, showMenubar = showMenubar, ) { - override val root: File get() = rootFile ?: super.root - override val settingsClass: Class<*> get() = Settings::class.java + override val singleInput: Boolean get() = true + override val root: File get() = rootFile @Suppress("UNCHECKED_CAST") - override fun initSettings(session: Session): T = settings.let { + override fun initSettings(session: Session): T = planSettings.let { if (null == rootFile) it.copy(workingDir = root.absolutePath) else it } as T + override fun newSession(user: User?, session: Session): SocketManager { + val socketManager = super.newSession(user, session) + val ui = (socketManager as ApplicationSocketManager).applicationInterface + if (initialPlan != null) { + socketManager.pool.submit { + try { + val planSettings = getSettings(session, user, PlanSettings::class.java) + if (api is ChatClient) api.budget = planSettings?.budget + val coordinator = PlanCoordinator( + user = user, + session = session, + dataStorage = dataStorage, + ui = ui, + root = rootFile.toPath(), + planSettings = planSettings!! + ) + coordinator.executeTaskBreakdownWithPrompt(JsonUtil.toJson(initialPlan), api!!) + } catch (e: Throwable) { + ui.newTask().error(ui, e) + log.warn("Error", e) + } + } + } + return socketManager + } + override fun userMessage( session: Session, user: User?, @@ -43,17 +75,17 @@ class PlanAheadApp( api: API ) { try { - val settings = getSettings(session, user) - if (api is ClientManager.MonitoredClient) api.budget = settings?.budget ?: 2.0 - PlanCoordinator( + val planSettings = getSettings(session, user, PlanSettings::class.java) + if (api is ChatClient) api.budget = planSettings?.budget ?: 2.0 + val coordinator = PlanCoordinator( user = user, session = session, dataStorage = dataStorage, - api = api, ui = ui, - root = (rootFile ?: dataStorage.getDataDir(user, session)).toPath(), - settings = settings!! - ).startProcess(userMessage = userMessage) + root = rootFile.toPath(), + planSettings = planSettings!! + ) + coordinator.startProcess(userMessage = userMessage, api = api) } catch (e: Throwable) { ui.newTask().error(ui, e) log.warn("Error", e) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt index 0f4d1dd1..68a521b3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt @@ -4,6 +4,7 @@ import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.ApiModel.Role +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.ImageModels @@ -47,7 +48,7 @@ open class WebDevApp( api: API ) { val settings = getSettings(session, user) ?: Settings() - (api as ClientManager.MonitoredClient).budget = settings.budget ?: 2.00 + (api as ChatClient).budget = settings.budget ?: 2.00 WebDevAgent( api = api, dataStorage = dataStorage, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt index 2ab393b4..9aa2f3b2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt @@ -1,20 +1,22 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.apps.general.PatchApp -import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.apps.general.CommandPatchApp +import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanTask +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import org.slf4j.LoggerFactory import java.io.File abstract class AbstractAnalysisTask( - settings: Settings, + planSettings: PlanSettings, planTask: PlanTask -) : AbstractTask(settings, planTask) { +) : AbstractTask(planSettings, planTask) { abstract val actorName: String abstract val actorPrompt: String @@ -23,8 +25,8 @@ abstract class AbstractAnalysisTask( SimpleActor( name = actorName, prompt = actorPrompt, - model = settings.model, - temperature = settings.temperature, + model = planSettings.model, + temperature = planSettings.temperature, ) } @@ -32,27 +34,28 @@ abstract class AbstractAnalysisTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { val analysisResult = analysisActor.answer( listOf( userMessage, JsonUtil.toJson(plan), - getPriorCode(genState), + getPriorCode(planProcessingState), getInputFileCode(), "${getAnalysisInstruction()}:\n${getInputFileCode()}", - ).filter { it.isNotBlank() }, api = agent.api + ).filter { it.isNotBlank() }, api = api ) - genState.taskResult[taskId] = analysisResult - applyChanges(agent, task, analysisResult) + planProcessingState.taskResult[taskId] = analysisResult + applyChanges(agent, task, analysisResult, api) } abstract fun getAnalysisInstruction(): String - private fun applyChanges(agent: PlanCoordinator, task: SessionTask, analysisResult: String) { + private fun applyChanges(agent: PlanCoordinator, task: SessionTask, analysisResult: String, api: API) { val outputResult = CommandPatchApp( root = agent.root.toFile(), session = agent.session, @@ -61,10 +64,10 @@ abstract class AbstractAnalysisTask( workingDirectory = agent.root.toFile(), exitCodeOption = "nonzero", additionalInstructions = "", - autoFix = agent.settings.autoFix + autoFix = agent.planSettings.autoFix ), - api = agent.api as OpenAIClient, - model = agent.settings.model, + api = api as ChatClient, + model = agent.planSettings.model, files = agent.files, command = analysisResult ).run( diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt index 37f7db5f..5429e874 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt @@ -1,27 +1,28 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.FileValidationUtils +import com.simiacryptus.jopenai.API import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanTask +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.set import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask import java.io.File import java.nio.file.Path +import java.nio.file.Files +import java.nio.file.FileSystems +import kotlin.streams.asSequence abstract class AbstractTask( - val settings: Settings, + val planSettings: PlanSettings, val planTask: PlanTask ) { - val outputFiles: List? = planTask.output_files - val inputFiles: List? = planTask.input_files - val taskDependencies: List? = planTask.task_dependencies - val description: String? = planTask.description var state: TaskState? = TaskState.Pending val codeFiles = mutableMapOf() open val root: Path - get() = File(settings.workingDir).toPath() + get() = File(planSettings.workingDir).toPath() enum class TaskState { Pending, @@ -29,27 +30,42 @@ abstract class AbstractTask( Completed, } - fun getPriorCode(genState: PlanCoordinator.GenState) = taskDependencies?.joinToString("\n\n\n") { dependency -> - """ + fun getPriorCode(planProcessingState: PlanProcessingState) = + planTask.task_dependencies?.joinToString("\n\n\n") { dependency -> + """ |# $dependency | - |${genState.taskResult[dependency] ?: ""} + |${planProcessingState.taskResult[dependency] ?: ""} """.trimMargin() - } ?: "" + } ?: "" - - fun getInputFileCode(): String = ((inputFiles ?: listOf()) + (outputFiles ?: listOf())) - .filter { FileValidationUtils.isLLMIncludable(root.toFile().resolve(it)) }.joinToString("\n\n") { + fun getInputFileCode(): String = ((planTask.input_files ?: listOf()) + (planTask.output_files ?: listOf())) + .flatMap { pattern: String -> + val matcher = FileSystems.getDefault().getPathMatcher("glob:$pattern") + Files.walk(root).asSequence() + .filter { path -> + matcher.matches(root.relativize(path)) && + FileValidationUtils.isLLMIncludable(path.toFile()) + } + .map { path -> + root.relativize(path).toString() + } + .toList() + } + .distinct() + .sortedBy { it } + .joinToString("\n\n") { relativePath -> + val file = root.resolve(relativePath).toFile() try { """ - |# $it + |# $relativePath | |$TRIPLE_TILDE - |${codeFiles[File(it).toPath()] ?: root.resolve(it).toFile().readText()} + |${codeFiles[file.toPath()] ?: file.readText()} |$TRIPLE_TILDE """.trimMargin() } catch (e: Throwable) { - PlanCoordinator.log.warn("Error: root=$root ", e) + log.warn("Error reading file: $relativePath", e) "" } } @@ -62,23 +78,27 @@ abstract class AbstractTask( textHandle.set("""
Accepted
""") footerTask.complete() } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) + log.warn("Error", e) } fn() })!! return footerTask.placeholder } - abstract fun promptSegment(): String abstract fun run( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) + + companion object { + private val log = org.slf4j.LoggerFactory.getLogger(AbstractTask::class.java) + } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt index b2c70f4d..6255688d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt @@ -3,33 +3,33 @@ package com.simiacryptus.skyenet.apps.plan import org.slf4j.LoggerFactory class CodeOptimizationTask( - settings: Settings, - planTask: PlanTask -) : AbstractAnalysisTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractAnalysisTask(planSettings, planTask) { override val actorName = "CodeOptimization" override val actorPrompt = """ - Analyze the provided code and suggest optimizations to improve code quality. Focus exclusively on: - 1. Code structure and organization - 2. Readability improvements - 3. Maintainability enhancements - 4. Proper use of language-specific features and best practices - 5. Design pattern applications - | - |Provide detailed explanations for each suggested optimization, including: - |- The reason for the optimization - |- The expected benefits - |- Any potential trade-offs or considerations - | - Format the response as a markdown document with appropriate headings and code snippets. - Use diff format to show the proposed changes clearly. - """.trimIndent() + |Analyze the provided code and suggest optimizations to improve code quality. Focus exclusively on: + |1. Code structure and organization + |2. Readability improvements + |3. Maintainability enhancements + |4. Proper use of language-specific features and best practices + |5. Design pattern applications + | + |Provide detailed explanations for each suggested optimization, including: + |- The reason for the optimization + |- The expected benefits + |- Any potential trade-offs or considerations + | + |Format the response as a markdown document with appropriate headings and code snippets. + |Use diff format to show the proposed changes clearly. + """.trimMargin() override fun promptSegment(): String { return """ -CodeOptimization - Analyze and optimize existing code for better readability, maintainability, and adherence to best practices - ** Specify the files to be optimized - ** Optionally provide specific areas of focus for the optimization (e.g., code structure, readability, design patterns) - """.trimMargin() + |CodeOptimization - Analyze and optimize existing code for better readability, maintainability, and adherence to best practices + | * Specify the files to be optimized + | * Optionally provide specific areas of focus for the optimization (e.g., code structure, readability, design patterns) + """.trimMargin() } override fun getAnalysisInstruction(): String { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt index 41b85706..9941d5a5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt @@ -3,9 +3,9 @@ package com.simiacryptus.skyenet.apps.plan import org.slf4j.LoggerFactory class CodeReviewTask( - settings: Settings, - planTask: PlanTask -) : AbstractAnalysisTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractAnalysisTask(planSettings, planTask) { override val actorName: String = "CodeReview" override val actorPrompt: String = """ Perform a comprehensive code review for the provided code files. Analyze the code for: diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt index 3789c56c..ff794a1b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt @@ -1,11 +1,12 @@ package com.simiacryptus.skyenet.apps.plan -import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.TabbedDisplay import com.simiacryptus.skyenet.apps.general.CmdPatchApp import com.simiacryptus.skyenet.apps.general.PatchApp -import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil import org.slf4j.LoggerFactory @@ -13,9 +14,9 @@ import java.io.File import java.util.concurrent.Semaphore class CommandAutoFixTask( - settings: Settings, - planTask: PlanTask -) : AbstractTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractTask(planSettings, planTask) { override fun promptSegment(): String { return """ |CommandAutoFix - Run a command and automatically fix any issues that arise @@ -24,7 +25,7 @@ class CommandAutoFixTask( | ** Provide the command arguments in the 'commandArguments' field | ** List input files/tasks to be examined when fixing issues | ** Available commands: - | ${settings.commandAutoFixCommands.joinToString("\n ") { "* ${File(it).name}" }} + | ${planSettings.commandAutoFixCommands?.joinToString("\n ") { "* ${File(it).name}" }} """.trimMargin() } @@ -32,27 +33,29 @@ class CommandAutoFixTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { val semaphore = Semaphore(0) val onComplete = { semaphore.release() } - if (!agent.settings.enableCommandAutoFix) { + if (!agent.planSettings.enableCommandAutoFix) { task.add("Command Auto Fix is disabled") onComplete() } else { Retryable(agent.ui, task = task) { val task = agent.ui.newTask(false).apply { it.append(placeholder) } val alias = this.planTask.command?.first() - val commandAutoFixCommands = agent.settings.commandAutoFixCommands - val cmds = commandAutoFixCommands.filter { - File(it).name.startsWith(alias ?: "") - } - val executable = cmds.firstOrNull() + val commandAutoFixCommands = agent.planSettings.commandAutoFixCommands + val cmds = commandAutoFixCommands + ?.map { File(it) }?.associateBy { it.name } + ?.filterKeys { it.startsWith(alias ?: "") } + ?: emptyMap() + val executable = cmds.entries.firstOrNull()?.value if (executable == null) { throw IllegalArgumentException("Command not found: $alias") } @@ -63,23 +66,23 @@ class CommandAutoFixTask( root = agent.root, session = agent.session, settings = PatchApp.Settings( - executable = File(executable), + executable = executable, arguments = this.planTask.command?.drop(1)?.joinToString(" ") ?: "", workingDirectory = workingDirectory, exitCodeOption = "nonzero", additionalInstructions = "", - autoFix = agent.settings.autoFix + autoFix = agent.planSettings.autoFix ), - api = agent.api as OpenAIClient, + api = api as ChatClient, files = agent.files, - model = agent.settings.model, + model = agent.planSettings.model, ).run( ui = agent.ui, task = task ) - genState.taskResult[taskId] = "Command Auto Fix completed" + planProcessingState.taskResult[taskId] = "Command Auto Fix completed" task.add(if (outputResult.exitCode == 0) { - if (agent.settings.autoFix) { + if (agent.planSettings.autoFix) { taskTabs.selectedTab += 1 taskTabs.update() onComplete() @@ -114,9 +117,9 @@ class CommandAutoFixTask( try { semaphore.acquire() } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) + log.warn("Error", e) } - PlanCoordinator.log.debug("Completed command auto fix: $taskId") + log.debug("Completed command auto fix: $taskId") } companion object { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt new file mode 100644 index 00000000..03417778 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt @@ -0,0 +1,3 @@ +package com.simiacryptus.skyenet.apps.plan + +class DisabledTaskException(taskType: TaskType) : Exception("Task type $taskType is disabled") \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt index 56e24656..e995314d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt @@ -1,9 +1,10 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil @@ -11,9 +12,9 @@ import org.slf4j.LoggerFactory import java.util.concurrent.Semaphore class DocumentationTask( - settings: Settings, - planTask: PlanTask -) : AbstractTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractTask(planSettings, planTask) { override fun promptSegment(): String { return """ |Documentation - Generate documentation @@ -30,8 +31,8 @@ class DocumentationTask( |Include code examples where applicable, and explain the rationale behind key design decisions and algorithm choices. |Document any known issues or areas for improvement, providing guidance for future developers on how to extend or maintain the code. """.trimMargin(), - model = settings.model, - temperature = settings.temperature, + model = planSettings.model, + temperature = planSettings.temperature, ) } @@ -39,10 +40,11 @@ class DocumentationTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { val semaphore = Semaphore(0) val onComplete = { @@ -53,12 +55,12 @@ class DocumentationTask( listOf( userMessage, JsonUtil.toJson(plan), - getPriorCode(genState), + getPriorCode(planProcessingState), getInputFileCode(), - ).filter { it.isNotBlank() }, agent.api + ).filter { it.isNotBlank() }, api ) - genState.taskResult[taskId] = docResult - if (agent.settings.autoFix) { + planProcessingState.taskResult[taskId] = docResult + if (agent.planSettings.autoFix) { taskTabs.selectedTab += 1 taskTabs.update() task.complete() @@ -80,7 +82,7 @@ class DocumentationTask( try { semaphore.acquire() } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) + log.warn("Error", e) } } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt index d18367e5..083218de 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt @@ -1,10 +1,11 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.addApplyFileDiffLinks +import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil @@ -12,9 +13,9 @@ import org.slf4j.LoggerFactory import java.util.concurrent.Semaphore class FileModificationTask( - settings: Settings, - planTask: PlanTask -) : AbstractTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractTask(planSettings, planTask) { val fileModificationActor by lazy { SimpleActor( name = "FileModification", @@ -62,8 +63,8 @@ class FileModificationTask( |} |${TRIPLE_TILDE} """.trimMargin(), - model = settings.model, - temperature = settings.temperature, + model = planSettings.model, + temperature = planSettings.temperature, ) } @@ -79,12 +80,13 @@ class FileModificationTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { - if(((inputFiles ?: listOf()) + (outputFiles ?: listOf())).isEmpty()) { + if(((planTask.input_files ?: listOf()) + (planTask.output_files ?: listOf())).isEmpty()) { task.complete("No input files specified") return } @@ -95,13 +97,13 @@ class FileModificationTask( listOf( userMessage, JsonUtil.toJson(plan), - getPriorCode(genState), + getPriorCode(planProcessingState), getInputFileCode(), - this.description ?: "", - ).filter { it.isNotBlank() }, agent.api + this.planTask.description ?: "", + ).filter { it.isNotBlank() }, api ) - genState.taskResult[taskId] = codeResult - if (agent.settings.autoFix) { + planProcessingState.taskResult[taskId] = codeResult + if (agent.planSettings.autoFix) { val diffLinks = agent.ui.socketManager!!.addApplyFileDiffLinks( root = agent.root, response = codeResult, @@ -111,7 +113,7 @@ class FileModificationTask( } }, ui = agent.ui, - api = agent.api, + api = api, shouldAutoApply = { true } ) taskTabs.selectedTab += 1 @@ -130,7 +132,7 @@ class FileModificationTask( } }, ui = agent.ui, - api = agent.api + api = api ) + acceptButtonFooter(agent.ui) { taskTabs.selectedTab += 1 taskTabs.update() @@ -144,7 +146,7 @@ class FileModificationTask( try { semaphore.acquire() } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) + log.warn("Error", e) } } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt index 3cb3337e..56d49b91 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt @@ -1,14 +1,17 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.API import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.webui.session.SessionTask +import com.simiacryptus.skyenet.apps.plan.PlanUtil.diagram +import com.simiacryptus.skyenet.apps.plan.PlanUtil.executionOrder import org.slf4j.LoggerFactory class ForeachTask( - settings: Settings, - planTask: PlanTask -) : AbstractTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractTask(planSettings, planTask) { override fun promptSegment(): String { return """ @@ -22,27 +25,39 @@ ForeachTask - Execute a task for each item in a list agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { val items = planTask.foreachItems ?: throw RuntimeException("No items specified for ForeachTask") + val subTasks = planTask.subTasksByID ?: throw RuntimeException("No subTasks specified for ForeachTask") + val subPlanTask = agent.ui.newTask(false) + task.add(subPlanTask.placeholder) + items.forEachIndexed { index, item -> - val subTask = agent.ui.newTask(false) - task.add(subTask.placeholder) - - // Create a new PlanTask for each item, copying the original task's properties - val itemTask = planTask.copy( - description = "${planTask.description} - Item $index: $item", - foreachItems = null // Remove the foreach items to prevent infinite recursion + val itemSubTasks = subTasks.mapValues { (_, subTaskPlan) -> + subTaskPlan.copy(description = "${subTaskPlan.description} - Item $index: $item") + } + val itemPlanProcessingState = PlanProcessingState(itemSubTasks.toMutableMap()) + agent.executePlan( + task = subPlanTask, + diagramBuffer = subPlanTask.add(diagram(agent.ui, itemPlanProcessingState.subTasks)), + subTasks = itemSubTasks, + diagramTask = subPlanTask, + planProcessingState = itemPlanProcessingState, + taskIdProcessingQueue = executionOrder(itemSubTasks).toMutableList(), + pool = agent.pool, + userMessage = "$userMessage\nProcessing item $index: $item", + plan = object : TaskBreakdownInterface { + override val tasksByID = itemSubTasks + override val finalTaskID = null + }, + api = api ) - - // Execute the task for this item - val subTaskImpl = settings.getImpl(itemTask) - subTaskImpl.run(agent, "$taskId-$index", userMessage, plan, genState, subTask, taskTabs) } - task.complete("Completed ForeachTask for ${items.size} items") + subPlanTask.complete("Completed ForeachTask for ${items.size} items") } companion object { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt index 5c966059..8d3935b3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt @@ -1,11 +1,12 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.Discussable import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil @@ -14,9 +15,9 @@ import java.util.concurrent.Semaphore import java.util.concurrent.atomic.AtomicReference class InquiryTask( - settings: Settings, - planTask: PlanTask -) : AbstractTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractTask(planSettings, planTask) { val inquiryActor by lazy { SimpleActor( name = "Inquiry", @@ -29,14 +30,14 @@ class InquiryTask( |When generating insights, consider the existing project context and focus on information that is directly relevant and applicable. |Focus on generating insights and information that support the task types available in the system (Requirements, NewFile, EditFile, ${ - if (!settings.taskPlanningEnabled) "" else "TaskPlanning, " + if (!planSettings.taskPlanningEnabled) "" else "TaskPlanning, " }${ - if (!settings.shellCommandTaskEnabled) "" else "RunShellCommand, " + if (!planSettings.shellCommandTaskEnabled) "" else "RunShellCommand, " }Documentation). |This will ensure that the inquiries are tailored to assist in the planning and execution of tasks within the system's framework. """.trimMargin(), - model = settings.model, - temperature = settings.temperature, + model = planSettings.model, + temperature = planSettings.temperature, ) } @@ -52,25 +53,26 @@ class InquiryTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { val toInput = { it: String -> listOf( userMessage, JsonUtil.toJson(plan), - getPriorCode(genState), + getPriorCode(planProcessingState), getInputFileCode(), it, ).filter { it.isNotBlank() } } val inquiryResult = Discussable( task = task, - userMessage = { "Expand ${this.description ?: ""}\n${JsonUtil.toJson(data = this)}" }, + userMessage = { "Expand ${this.planTask.description ?: ""}\n${JsonUtil.toJson(data = this)}" }, heading = "", - initialResponse = { it: String -> inquiryActor.answer(toInput(it), api = agent.api) }, + initialResponse = { it: String -> inquiryActor.answer(toInput(it), api = api) }, outputFn = { design: String -> MarkdownUtil.renderMarkdown(design, ui = agent.ui) }, @@ -79,14 +81,14 @@ class InquiryTask( inquiryActor.respond( messages = (userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } .toTypedArray()), - input = toInput("Expand ${this.description ?: ""}\n${JsonUtil.toJson(data = this)}"), - api = agent.api + input = toInput("Expand ${this.planTask.description ?: ""}\n${JsonUtil.toJson(data = this)}"), + api = api ) }, atomicRef = AtomicReference(), semaphore = Semaphore(0), ).call() - genState.taskResult[taskId] = inquiryResult + planProcessingState.taskResult[taskId] = inquiryResult } companion object { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt index ab640679..ee599a45 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt @@ -4,9 +4,9 @@ import org.slf4j.LoggerFactory class PerformanceAnalysisTask( - settings: Settings, - planTask: PlanTask -) : AbstractAnalysisTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractAnalysisTask(planSettings, planTask) { override val actorName = "PerformanceAnalysis" override val actorPrompt = """ Analyze the provided code for performance issues and bottlenecks. Focus exclusively on: diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt index 53fddb16..c613021f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt @@ -4,11 +4,17 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.FileValidationUtils import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.jopenai.util.JsonUtil -import com.simiacryptus.skyenet.AgentPatterns import com.simiacryptus.skyenet.Discussable import com.simiacryptus.skyenet.TabbedDisplay +import com.simiacryptus.skyenet.apps.plan.PlanUtil.buildMermaidGraph +import com.simiacryptus.skyenet.apps.plan.PlanUtil.filterPlan +import com.simiacryptus.skyenet.apps.plan.PlanUtil.getAllDependencies +import com.simiacryptus.skyenet.apps.plan.PlanUtil.render +import com.simiacryptus.skyenet.apps.plan.PlanningTask.* +import com.simiacryptus.skyenet.apps.plan.PlanningTask.Companion.planningActor +import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.getImpl import com.simiacryptus.skyenet.core.actors.ParsedResponse import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.Session @@ -18,11 +24,11 @@ import com.simiacryptus.skyenet.set import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.jopenai.util.JsonUtil import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path -import java.util.* -import java.util.concurrent.Future +import java.util.UUID import java.util.concurrent.ThreadPoolExecutor class PlanCoordinator( @@ -30,16 +36,9 @@ class PlanCoordinator( val session: Session, val dataStorage: StorageInterface, val ui: ApplicationInterface, - val api: API, - val settings: Settings, + val planSettings: PlanSettings, val root: Path ) { - private val taskBreakdownActor by lazy { settings.planningActor() } - - data class TaskBreakdownResult( - val tasksByID: Map? = null, - val finalTaskID: String? = null, - ) val pool: ThreadPoolExecutor by lazy { ApplicationServices.clientManager.getPool(session, user) } @@ -47,108 +46,87 @@ class PlanCoordinator( FileValidationUtils.expandFileList(root.toFile()) } - private val codeFiles: Map + val codeFiles: Map get() = files .filter { it.exists() && it.isFile } .filter { !it.name.startsWith(".") } - .associate { file -> getKey(file) to getValue(file) } - + .associate { file -> + root.relativize(file.toPath()) to try { + file.inputStream().bufferedReader().use { it.readText() } + } catch (e: Exception) { + log.warn("Error reading file", e) + "" + } + } - private fun getValue(file: File) = try { - file.inputStream().bufferedReader().use { it.readText() } - } catch (e: Exception) { - log.warn("Error reading file", e) - "" + fun startProcess(userMessage: String, api: API) { + val task = ui.newTask() + executePlan( + ( + initialPlan( + codeFiles = codeFiles, + files = files, + root = root, + task = task, + userMessage = userMessage, + ui = ui, + planSettings = planSettings, + api = api + ) + ).plan, task, userMessage, api + ) } - private fun getKey(file: File) = root.relativize(file.toPath()) - - fun startProcess(userMessage: String) { - val codeFiles = codeFiles - val eventStatus = if (!codeFiles.all { it.key.toFile().isFile } || codeFiles.size > 2) """ - | Files: - | ${codeFiles.keys.joinToString("\n") { "* $it" }} - """.trimMargin() else files.joinToString("\n\n") { - val path = root.relativize(it.toPath()) - """ - |## $path - | - |${(codeFiles[path] ?: "").let { "$TRIPLE_TILDE\n${it/*.indent(" ")*/}\n$TRIPLE_TILDE" }} - """.trimMargin() - } + fun executeTaskBreakdownWithPrompt(jsonInput: String, api: API) { val task = ui.newTask() - val toInput = { it: String -> - listOf( - eventStatus, - it - ) + try { + lateinit var taskBreakdownWithPrompt: PlanUtil.TaskBreakdownWithPrompt + val plan = filterPlan { + taskBreakdownWithPrompt = JsonUtil.fromJson(jsonInput, PlanUtil.TaskBreakdownWithPrompt::class.java) + taskBreakdownWithPrompt.plan + } + task.add(MarkdownUtil.renderMarkdown( + """ + |## Executing TaskBreakdownWithPrompt + |Prompt: ${taskBreakdownWithPrompt.prompt} + |Plan Text: + |``` + |${taskBreakdownWithPrompt.planText} + |``` + """.trimMargin(), ui = ui)) + executePlan(plan, task, taskBreakdownWithPrompt.prompt, api) + } catch (e: Exception) { + task.error(ui, e) } - val highLevelPlan = Discussable( - task = task, - heading = MarkdownUtil.renderMarkdown(userMessage, ui = ui), - userMessage = { userMessage }, - initialResponse = { it: String -> taskBreakdownActor.answer(toInput(it), api = api) }, - outputFn = { design: ParsedResponse -> - AgentPatterns.displayMapInTabs( - mapOf( - "Text" to MarkdownUtil.renderMarkdown(design.text, ui = ui), - "JSON" to MarkdownUtil.renderMarkdown( - "${TRIPLE_TILDE}json\n${JsonUtil.toJson(filterPlan(design.obj))}\n$TRIPLE_TILDE", - ui = ui - ), - "Diagram" to MarkdownUtil.renderMarkdown( - "```mermaid\n" + buildMermaidGraph( - (filterPlan( - design.obj - ).tasksByID ?: emptyMap()).toMutableMap() - ) + "\n```\n" - ) - ) - ) - }, - ui = ui, - reviseResponse = { userMessages: List> -> - taskBreakdownActor.respond( - messages = (userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } - .toTypedArray()), - input = toInput(userMessage), - api = api - ) - }, - ).call() - - initPlan(filterPlan(highLevelPlan.obj), userMessage, task) } - fun initPlan( - plan: TaskBreakdownResult, + fun executePlan( + plan: TaskBreakdownInterface, + task: SessionTask, userMessage: String, - task: SessionTask + api: API ) { try { - val tasksByID = - filterPlan(plan).tasksByID?.entries?.toTypedArray()?.associate { it.key to it.value } ?: mapOf() - val genState = GenState(tasksByID.toMutableMap()) + val planProcessingState = + PlanProcessingState((filterPlan{plan}.tasksByID?.entries?.toTypedArray>() + ?.associate { it.key to it.value } ?: mapOf()).toMutableMap()) val diagramTask = ui.newTask(false).apply { task.add(placeholder) } - val diagramBuffer = - diagramTask.add( + executePlan( + task = task, + diagramBuffer = diagramTask.add( MarkdownUtil.renderMarkdown( - "## Task Dependency Graph\n${TRIPLE_TILDE}mermaid\n${buildMermaidGraph(genState.subTasks)}\n$TRIPLE_TILDE", + "## Task Dependency Graph\n${TRIPLE_TILDE}mermaid\n${buildMermaidGraph(planProcessingState.subTasks)}\n$TRIPLE_TILDE", ui = ui ) - ) - val taskIdProcessingQueue = genState.taskIdProcessingQueue - val subTasks = genState.subTasks - executePlan( - task, - diagramBuffer, - subTasks, - diagramTask, - genState, - taskIdProcessingQueue, - pool, - userMessage, - plan + ), + subTasks = planProcessingState.subTasks, + diagramTask = diagramTask, + planProcessingState = planProcessingState, + taskIdProcessingQueue = planProcessingState.taskIdProcessingQueue, + pool = pool, + userMessage = userMessage, + plan = plan, + api = api ) } catch (e: Throwable) { log.warn("Error during incremental code generation process", e) @@ -161,13 +139,22 @@ class PlanCoordinator( diagramBuffer: StringBuilder?, subTasks: Map, diagramTask: SessionTask, - genState: GenState, + planProcessingState: PlanProcessingState, taskIdProcessingQueue: MutableList, pool: ThreadPoolExecutor, userMessage: String, - plan: TaskBreakdownResult + plan: TaskBreakdownInterface, + api: API ) { - val taskTabs = object : TabbedDisplay(ui.newTask(false).apply { task.add(placeholder) }) { + val sessionTask = ui.newTask(false).apply { task.add(placeholder) } + val api = (api as ChatClient).getChildClient().apply { + val createFile = sessionTask.createFile("api-${UUID.randomUUID()}.log") + createFile.second?.apply { + logStreams += this.outputStream().buffered() + sessionTask.add("API log: $this") + } + } + val taskTabs = object : TabbedDisplay(sessionTask) { override fun renderTabButtons(): String { diagramBuffer?.set( MarkdownUtil.renderMarkdown( @@ -184,7 +171,7 @@ class PlanCoordinator( append("
\n") super.tabs.withIndex().forEach { (idx, t) -> val (taskId, taskV) = t - val subTask = genState.tasksByDescription[taskId] + val subTask = planProcessingState.tasksByDescription[taskId] if (null == subTask) { log.warn("Task tab not found: $taskId") } @@ -201,11 +188,10 @@ class PlanCoordinator( } } } - // Initialize task tabs taskIdProcessingQueue.forEach { taskId -> val newTask = ui.newTask(false) - genState.uitaskMap[taskId] = newTask - val subtask = genState.subTasks[taskId] + planProcessingState.uitaskMap[taskId] = newTask + val subtask = planProcessingState.subTasks[taskId] val description = subtask?.description log.debug("Creating task tab: $taskId ${System.identityHashCode(subtask)} $description") taskTabs[description ?: taskId] = newTask.placeholder @@ -213,13 +199,13 @@ class PlanCoordinator( Thread.sleep(100) while (taskIdProcessingQueue.isNotEmpty()) { val taskId = taskIdProcessingQueue.removeAt(0) - val subTask = genState.subTasks[taskId] ?: throw RuntimeException("Task not found: $taskId") - genState.taskFutures[taskId] = pool.submit { + val subTask = planProcessingState.subTasks[taskId] ?: throw RuntimeException("Task not found: $taskId") + planProcessingState.taskFutures[taskId] = pool.submit { subTask.state = AbstractTask.TaskState.Pending taskTabs.update() log.debug("Awaiting dependencies: ${subTask.task_dependencies?.joinToString(", ") ?: ""}") subTask.task_dependencies - ?.associate { it to genState.taskFutures[it] } + ?.associate { it to planProcessingState.taskFutures[it] } ?.forEach { (id, future) -> try { future?.get() ?: log.warn("Dependency not found: $id") @@ -230,14 +216,14 @@ class PlanCoordinator( subTask.state = AbstractTask.TaskState.InProgress taskTabs.update() log.debug("Running task: ${System.identityHashCode(subTask)} ${subTask.description}") - val task1 = genState.uitaskMap.get(taskId) ?: ui.newTask(false).apply { + val task1 = planProcessingState.uitaskMap.get(taskId) ?: ui.newTask(false).apply { taskTabs[taskId] = placeholder } try { val dependencies = subTask.task_dependencies?.toMutableSet() ?: mutableSetOf() dependencies += getAllDependencies( subPlanTask = subTask, - subTasks = genState.subTasks, + subTasks = planProcessingState.subTasks, visited = mutableSetOf() ) @@ -257,27 +243,28 @@ class PlanCoordinator( """.trimMargin(), ui = ui ) ) - settings.getImpl(subTask).run( + getImpl(planSettings, subTask).run( agent = this, taskId = taskId, userMessage = userMessage, plan = plan, - genState = genState, + planProcessingState = planProcessingState, task = task1, - taskTabs = taskTabs + taskTabs = taskTabs, + api = api ) } catch (e: Throwable) { log.warn("Error during task execution", e) task1.error(ui, e) } finally { - genState.completedTasks.add(element = taskId) + planProcessingState.completedTasks.add(element = taskId) subTask.state = AbstractTask.TaskState.Completed log.debug("Completed task: $taskId ${System.identityHashCode(subTask)}") taskTabs.update() } } } - genState.taskFutures.forEach { (id, future) -> + planProcessingState.taskFutures.forEach { (id, future) -> try { future.get() ?: log.warn("Dependency not found: $id") } catch (e: Throwable) { @@ -286,111 +273,87 @@ class PlanCoordinator( } } - private fun getAllDependencies( - subPlanTask: PlanTask, - subTasks: Map, - visited: MutableSet - ): List { - val dependencies = subPlanTask.task_dependencies?.toMutableList() ?: mutableListOf() - subPlanTask.task_dependencies?.forEach { dep -> - if (dep in visited) return@forEach - val subTask = subTasks[dep] - if (subTask != null) { - visited.add(dep) - dependencies.addAll(getAllDependencies(subTask, subTasks, visited)) - } - } - return dependencies - } - companion object { - val log = LoggerFactory.getLogger(PlanCoordinator::class.java) + private val log = LoggerFactory.getLogger(PlanCoordinator::class.java) - fun executionOrder(tasks: Map): List { - val taskIds: MutableList = mutableListOf() - val taskMap = tasks.toMutableMap() - while (taskMap.isNotEmpty()) { - val nextTasks = - taskMap.filter { (_, task) -> task.task_dependencies?.all { taskIds.contains(it) } ?: true } - if (nextTasks.isEmpty()) { - throw RuntimeException("Circular dependency detected in task breakdown") + fun initialPlan( + codeFiles: Map, + files: Array, + root: Path, + task: SessionTask, + userMessage: String, + ui: ApplicationInterface, + planSettings: PlanSettings, + api: API + ): PlanUtil.TaskBreakdownWithPrompt { + val toInput = inputFn(codeFiles, files, root) + return ( + Discussable( + task = task, + heading = MarkdownUtil.renderMarkdown(userMessage, ui = ui), + userMessage = { userMessage }, + initialResponse = { + planningActor(planSettings).answer( + toInput(it), + api = api + ) as ParsedResponse + }, + outputFn = { render( + withPrompt = PlanUtil.TaskBreakdownWithPrompt( + prompt = userMessage, + plan = it.obj as TaskBreakdownResult, + planText = it.text + ), + ui = ui + ) }, + ui = ui, + reviseResponse = { userMessages: List> -> + val messages = userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } + .toTypedArray() + planningActor(planSettings).respond( + messages = messages, + input = toInput(userMessage), + api = api + ) as ParsedResponse + }, + ).call().let { + PlanUtil.TaskBreakdownWithPrompt( + prompt = userMessage, + plan = filterPlan{it.obj} as TaskBreakdownResult, + planText = it.text + ) } - taskIds.addAll(nextTasks.keys) - nextTasks.keys.forEach { taskMap.remove(it) } - } - return taskIds + ) } - val isWindows = System.getProperty("os.name").lowercase(Locale.getDefault()).contains("windows") - private fun sanitizeForMermaid(input: String) = input - .replace(" ", "_") - .replace("\"", "\\\"") - .replace("[", "\\[") - .replace("]", "\\]") - .replace("(", "\\(") - .replace(")", "\\)") - .let { "`$it`" } - - private fun escapeMermaidCharacters(input: String) = input - .replace("\"", "\\\"") - .let { '"' + it + '"' } - fun buildMermaidGraph(subTasks: Map): String { - val graphBuilder = StringBuilder("graph TD;\n") - subTasks.forEach { (taskId, task) -> - val sanitizedTaskId = sanitizeForMermaid(taskId) - val taskType = task.taskType?.name ?: "Unknown" - val escapedDescription = escapeMermaidCharacters(task.description ?: "") - val style = when (task.state) { - AbstractTask.TaskState.Completed -> ":::completed" - AbstractTask.TaskState.InProgress -> ":::inProgress" - else -> ":::$taskType" - } - graphBuilder.append(" ${sanitizedTaskId}[$escapedDescription]$style;\n") - task.task_dependencies?.forEach { dependency -> - val sanitizedDependency = sanitizeForMermaid(dependency) - graphBuilder.append(" $sanitizedDependency --> ${sanitizedTaskId};\n") - } + fun inputFn( + codeFiles: Map, + files: Array, + root: Path + ): (String) -> List { + val toInput = { it: String -> + listOf( + if (!codeFiles.all { it.key.toFile().isFile } || codeFiles.size > 2) """ + | Files: + | ${codeFiles.keys.joinToString("\n") { "* $it" }} + """.trimMargin() else { + files.joinToString("\n\n") { + val path = root.relativize(it.toPath()) + """ + |## $path + | + |${(codeFiles[path] ?: "").let { "$TRIPLE_TILDE\n${it/*.indent(" ")*/}\n$TRIPLE_TILDE" }} + """.trimMargin() + } + }, + it + ) } - graphBuilder.append(" classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef NewFile fill:lightblue,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef EditFile fill:lightgreen,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef Documentation fill:lightyellow,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef Inquiry fill:orange,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef TaskPlanning fill:lightgrey,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef completed fill:#90EE90,stroke:#333,stroke-width:2px;\n") - graphBuilder.append(" classDef inProgress fill:#FFA500,stroke:#333,stroke-width:2px;\n") - return graphBuilder.toString() - } - - fun filterPlan(obj: TaskBreakdownResult): TaskBreakdownResult { - val tasksByID = obj.tasksByID?.filter { (k, v) -> - when { - v.taskType == TaskType.TaskPlanning && v.task_dependencies.isNullOrEmpty() -> false - else -> true - } - } ?: mapOf() - if (tasksByID.size == obj.tasksByID?.size) return obj - return filterPlan(obj.copy( - tasksByID = tasksByID.mapValues { (_, v) -> - v.copy( - task_dependencies = v.task_dependencies?.filter { it in tasksByID.keys } - ) - } - )) + return toInput } } - data class GenState( - val subTasks: Map, - val tasksByDescription: MutableMap = subTasks.entries.toTypedArray() - .associate { it.value.description to it.value }.toMutableMap(), - val taskIdProcessingQueue: MutableList = executionOrder(subTasks).toMutableList(), - val taskResult: MutableMap = mutableMapOf(), - val completedTasks: MutableList = mutableListOf(), - val taskFutures: MutableMap> = mutableMapOf(), - val uitaskMap: MutableMap = mutableMapOf() - ) } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt new file mode 100644 index 00000000..b8e0b10a --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt @@ -0,0 +1,15 @@ +package com.simiacryptus.skyenet.apps.plan + +import com.simiacryptus.skyenet.webui.session.SessionTask +import java.util.concurrent.Future + +data class PlanProcessingState( + val subTasks: Map, + val tasksByDescription: MutableMap = subTasks.entries.toTypedArray() + .associate { it.value.description to it.value }.toMutableMap(), + val taskIdProcessingQueue: MutableList = PlanUtil.executionOrder(subTasks).toMutableList(), + val taskResult: MutableMap = mutableMapOf(), + val completedTasks: MutableList = mutableListOf(), + val taskFutures: MutableMap> = mutableMapOf(), + val uitaskMap: MutableMap = mutableMapOf() +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt new file mode 100644 index 00000000..00fbce84 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt @@ -0,0 +1,30 @@ +package com.simiacryptus.skyenet.apps.plan + +import com.simiacryptus.jopenai.models.OpenAITextModel +import com.simiacryptus.skyenet.apps.plan.PlanUtil.isWindows + +data class PlanSettings( + var model: OpenAITextModel, + val parsingModel: OpenAITextModel, + val command: List = listOf(if (isWindows) "powershell" else "bash"), + var temperature: Double = 0.2, + val budget: Double = 2.0, + var taskPlanningEnabled: Boolean = false, + var shellCommandTaskEnabled: Boolean = false, + var documentationEnabled: Boolean = false, + var fileModificationEnabled: Boolean = true, + var inquiryEnabled: Boolean = true, + var codeReviewEnabled: Boolean = false, + var testGenerationEnabled: Boolean = false, + var optimizationEnabled: Boolean = false, + var securityAuditEnabled: Boolean = false, + var performanceAnalysisEnabled: Boolean = false, + var refactorTaskEnabled: Boolean = false, + var foreachTaskEnabled: Boolean = false, + var autoFix: Boolean = false, + var enableCommandAutoFix: Boolean = false, + var commandAutoFixCommands: List? = listOf(), + val env: Map? = mapOf(), + val workingDir: String? = ".", + val language: String? = if (isWindows) "powershell" else "bash", +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt deleted file mode 100644 index 45cea499..00000000 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.simiacryptus.skyenet.apps.plan - -import com.simiacryptus.jopenai.describe.Description - -data class PlanTask( - val description: String? = null, - val taskType: TaskType? = null, - var task_dependencies: List? = null, - val input_files: List? = null, - val output_files: List? = null, - var state: AbstractTask.TaskState? = null, - @Description("Command and arguments (in list form) for the task") - val command: List? = null, - @Description("Working directory for the command execution") - val workingDir: String? = null, - @Description("List of items to iterate over") - val foreachItems: List? = null -) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt new file mode 100644 index 00000000..687c554a --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt @@ -0,0 +1,165 @@ +package com.simiacryptus.skyenet.apps.plan + +import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.skyenet.AgentPatterns +import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanTask +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface +import com.simiacryptus.skyenet.webui.application.ApplicationInterface +import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import java.util.* + +object PlanUtil { + + fun diagram( + ui: ApplicationInterface, + taskMap: Map + ) = MarkdownUtil.renderMarkdown( + """ + |## Sub-Plan Task Dependency Graph + |${TRIPLE_TILDE}mermaid + |${buildMermaidGraph(taskMap)} + |$TRIPLE_TILDE + """.trimMargin(), + ui = ui + ) + + data class TaskBreakdownWithPrompt( + val prompt: String, + val plan: PlanningTask.TaskBreakdownResult, + val planText: String + ) + + fun render( + withPrompt: TaskBreakdownWithPrompt, + ui: ApplicationInterface + ) = AgentPatterns.displayMapInTabs( + mapOf( + "Text" to MarkdownUtil.renderMarkdown(withPrompt.planText, ui = ui), + "JSON" to MarkdownUtil.renderMarkdown( + "${TRIPLE_TILDE}json\n${JsonUtil.toJson(withPrompt)}\n$TRIPLE_TILDE", + ui = ui + ), + "Diagram" to MarkdownUtil.renderMarkdown( + "```mermaid\n" + buildMermaidGraph( + (filterPlan { + withPrompt.plan + }.tasksByID ?: emptyMap()).toMutableMap() + ) + "\n```\n", ui = ui + ) + ) + ) + + fun executionOrder(tasks: Map): List { + val taskIds: MutableList = mutableListOf() + val taskMap = tasks.mapValues { it.value.copy(task_dependencies = it.value.task_dependencies?.filter { entry -> + entry in tasks.keys + }) }.toMutableMap() + while (taskMap.isNotEmpty()) { + val nextTasks = + taskMap.filter { (_, task) -> task.task_dependencies?.all { taskIds.contains(it) } ?: true } + if (nextTasks.isEmpty()) { + throw RuntimeException("Circular dependency detected in task breakdown") + } + taskIds.addAll(nextTasks.keys) + nextTasks.keys.forEach { taskMap.remove(it) } + } + return taskIds + } + + val isWindows = System.getProperty("os.name").lowercase(Locale.getDefault()).contains("windows") + private fun sanitizeForMermaid(input: String) = input + .replace(" ", "_") + .replace("\"", "\\\"") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("(", "\\(") + .replace(")", "\\)") + .let { "`$it`" } + + private fun escapeMermaidCharacters(input: String) = input + .replace("\"", "\\\"") + .let { '"' + it + '"' } + + fun buildMermaidGraph(subTasks: Map): String { + val graphBuilder = StringBuilder("graph TD;\n") + subTasks.forEach { (taskId, task) -> + val sanitizedTaskId = sanitizeForMermaid(taskId) + val taskType = task.taskType?.name ?: "Unknown" + val escapedDescription = escapeMermaidCharacters(task.description ?: "") + val style = when (task.state) { + AbstractTask.TaskState.Completed -> ":::completed" + AbstractTask.TaskState.InProgress -> ":::inProgress" + else -> ":::$taskType" + } + graphBuilder.append(" ${sanitizedTaskId}[$escapedDescription]$style;\n") + task.task_dependencies?.forEach { dependency -> + val sanitizedDependency = sanitizeForMermaid(dependency) + graphBuilder.append(" $sanitizedDependency --> ${sanitizedTaskId};\n") + } + } + graphBuilder.append(" classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef NewFile fill:lightblue,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef EditFile fill:lightgreen,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef Documentation fill:lightyellow,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef Inquiry fill:orange,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef TaskPlanning fill:lightgrey,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef completed fill:#90EE90,stroke:#333,stroke-width:2px;\n") + graphBuilder.append(" classDef inProgress fill:#FFA500,stroke:#333,stroke-width:2px;\n") + return graphBuilder.toString() + } + + fun filterPlan(retries: Int = 3, fn: () -> TaskBreakdownInterface): TaskBreakdownInterface { + val obj = fn() + var tasksByID = obj.tasksByID?.filter { (k, v) -> + when { + v.taskType == TaskType.TaskPlanning && v.task_dependencies.isNullOrEmpty() -> false + else -> true + } + }?.map { + it.key to it.value.copy( + task_dependencies = it.value.task_dependencies?.filter { it in (obj.tasksByID?.keys ?: setOf()) } + ) + }?.toMap() ?: emptyMap() + try { + executionOrder(tasksByID) + } catch (e: RuntimeException) { + if (retries <= 0) { + log.warn("Error filtering plan: " + JsonUtil.toJson(obj), e) + throw e + } else { + log.info("Circular dependency detected in task breakdown") + return filterPlan(retries - 1, fn) + } + } + return if (tasksByID.size == obj.tasksByID?.size) { + obj + } else filterPlan { + tasksByID = tasksByID.mapValues { (_, v) -> + v.copy( + task_dependencies = v.task_dependencies?.filter { it in tasksByID.keys } + ) + } + PlanningTask.TaskBreakdownResult(tasksByID, obj.finalTaskID) + } + } + + fun getAllDependencies( + subPlanTask: PlanTask, + subTasks: Map, + visited: MutableSet + ): List { + val dependencies = subPlanTask.task_dependencies?.toMutableList() ?: mutableListOf() + subPlanTask.task_dependencies?.forEach { dep -> + if (dep in visited) return@forEach + val subTask = subTasks[dep] + if (subTask != null) { + visited.add(dep) + dependencies.addAll(getAllDependencies(subTask, subTasks, visited)) + } + } + return dependencies + } + + val log = org.slf4j.LoggerFactory.getLogger(PlanUtil::class.java) + +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt index c9e44202..6455a632 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt @@ -1,25 +1,57 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.util.ClientUtil.toContentList import com.simiacryptus.jopenai.util.JsonUtil -import com.simiacryptus.skyenet.AgentPatterns -import com.simiacryptus.skyenet.Discussable -import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.Companion.buildMermaidGraph -import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.Companion.filterPlan +import com.simiacryptus.skyenet.* +import com.simiacryptus.skyenet.apps.plan.PlanUtil.diagram +import com.simiacryptus.skyenet.apps.plan.PlanUtil.executionOrder +import com.simiacryptus.skyenet.apps.plan.PlanUtil.filterPlan +import com.simiacryptus.skyenet.apps.plan.PlanUtil.render +import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.getAvailableTaskTypes +import com.simiacryptus.skyenet.core.actors.ParsedActor import com.simiacryptus.skyenet.core.actors.ParsedResponse -import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil import org.slf4j.LoggerFactory +import kotlin.text.set class PlanningTask( - settings: Settings, + planSettings: PlanSettings, planTask: PlanTask -) : AbstractTask(settings, planTask) { - val taskBreakdownActor by lazy { settings.planningActor() } +) : AbstractTask(planSettings, planTask) { + + + interface TaskBreakdownInterface { + val tasksByID: Map? + val finalTaskID: String? + } + + data class TaskBreakdownResult( + override val tasksByID: Map? = null, + override val finalTaskID: String? = null, + ) : TaskBreakdownInterface + + data class PlanTask( + val description: String? = null, + val taskType: TaskType? = null, + var task_dependencies: List? = null, + val input_files: List? = null, + val output_files: List? = null, + var state: TaskState? = null, + @Description("Command and arguments (in list form) for the task") + val command: List? = null, + @Description("Working directory for the command execution") + val workingDir: String? = null, + @Description("List of items to iterate over") + val foreachItems: List? = null, + @Description("When applicable, sub-tasks to execute") + val subTasksByID: Map? = null, + ) + + private val taskBreakdownActor by lazy { planningActor(planSettings) } override fun promptSegment(): String { return """ @@ -34,41 +66,35 @@ class PlanningTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { - if (!agent.settings.taskPlanningEnabled) throw RuntimeException("Task planning is disabled") + if (!agent.planSettings.taskPlanningEnabled) throw RuntimeException("Task planning is disabled") @Suppress("NAME_SHADOWING") val task = agent.ui.newTask(false).apply { task.add(placeholder) } fun toInput(s: String) = listOf( userMessage, JsonUtil.toJson(plan), - getPriorCode(genState), + getPriorCode(planProcessingState), getInputFileCode(), s ).filter { it.isNotBlank() } - - if (!settings.autoFix) { + if (!planSettings.autoFix) { val subPlan = Discussable( task = task, - userMessage = { "Expand ${description ?: ""}" }, + userMessage = { "Expand ${planTask.description ?: ""}" }, heading = "", - initialResponse = { it: String -> taskBreakdownActor.answer(toInput(it), api = agent.api) }, - outputFn = { design: ParsedResponse -> - AgentPatterns.displayMapInTabs( - mapOf( - "Text" to MarkdownUtil.renderMarkdown(design.text, ui = agent.ui), - "JSON" to MarkdownUtil.renderMarkdown( - "${TRIPLE_TILDE}json\n${JsonUtil.toJson(filterPlan(design.obj))/*.indent(" ")*/}\n$TRIPLE_TILDE", - ui = agent.ui - ), - "Diagram" to diagram( - PlanCoordinator.GenState( - (filterPlan(design.obj).tasksByID ?: emptyMap()).toMutableMap() - ), agent.ui - ) - ) + initialResponse = { it: String -> taskBreakdownActor.answer(toInput(it), api = api) }, + outputFn = { design: ParsedResponse -> + render( + withPrompt = PlanUtil.TaskBreakdownWithPrompt( + plan = filterPlan { design.obj } as TaskBreakdownResult, + planText = design.text, + prompt = userMessage + ), + ui = agent.ui ) }, ui = agent.ui, @@ -76,61 +102,76 @@ class PlanningTask( taskBreakdownActor.respond( messages = (userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } .toTypedArray()), - input = toInput("Expand ${description ?: ""}\n${JsonUtil.toJson(this)}"), - api = agent.api + input = toInput("Expand ${planTask.description ?: ""}\n${JsonUtil.toJson(this)}"), + api = api ) }, ).call() // Execute sub-tasks - executeSubTasks(agent, userMessage, filterPlan(subPlan.obj), task) + executeSubTasks(agent, userMessage, filterPlan{subPlan.obj}, task, api) } else { - val subPlan = taskBreakdownActor.answer(toInput("Expand ${description ?: ""}"), api = agent.api) // Execute sub-tasks - Retryable(agent.ui,task) { +/* + Retryable(agent.ui,task) { sb -> val task = agent.ui.newTask(false) - executeSubTasks(agent, userMessage, filterPlan(subPlan.obj), task) + sb.set(task.placeholder) task.placeholder } +*/ + executeSubTasks(agent, userMessage, filterPlan{ + taskBreakdownActor.answer( + toInput("Expand ${planTask.description ?: ""}"), + api = api + ).obj + }, task, api) } } private fun executeSubTasks( agent: PlanCoordinator, userMessage: String, - subPlan: PlanCoordinator.TaskBreakdownResult, - parentTask: SessionTask + subPlan: TaskBreakdownInterface, + parentTask: SessionTask, + api: API ) { val subPlanTask = agent.ui.newTask(false) parentTask.add(subPlanTask.placeholder) val subTasks = subPlan.tasksByID ?: emptyMap() - val genState = PlanCoordinator.GenState(subTasks.toMutableMap()) + val planProcessingState = PlanProcessingState(subTasks.toMutableMap()) agent.executePlan( task = subPlanTask, - diagramBuffer = subPlanTask.add(diagram(genState, agent.ui)), + diagramBuffer = subPlanTask.add(diagram(agent.ui, planProcessingState.subTasks)), subTasks = subTasks, diagramTask = subPlanTask, - genState = genState, - taskIdProcessingQueue = PlanCoordinator.executionOrder(subTasks).toMutableList(), + planProcessingState = planProcessingState, + taskIdProcessingQueue = executionOrder(subTasks).toMutableList(), pool = agent.pool, userMessage = userMessage, plan = subPlan, + api = api, ) subPlanTask.complete() } - private fun diagram( - genState: PlanCoordinator.GenState, - ui: ApplicationInterface - ) = MarkdownUtil.renderMarkdown( - """ - |## Sub-Plan Task Dependency Graph - |${TRIPLE_TILDE}mermaid - |${buildMermaidGraph(genState.subTasks)} - |$TRIPLE_TILDE - """.trimMargin(), - ui = ui - ) - companion object { private val log = LoggerFactory.getLogger(PlanningTask::class.java) + fun planningActor(planSettings: PlanSettings) = ParsedActor( + name = "TaskBreakdown", + resultClass = TaskBreakdownResult::class.java, + prompt = """ + |Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. + |Detail files input and output as well as task execution dependencies. + |Creating directories and initializing source control are out of scope. + | + |Tasks can be of the following types: + | + |${getAvailableTaskTypes(planSettings).joinToString("\n") { "* ${it.promptSegment()}" }} + | + |${if (planSettings.taskPlanningEnabled) "Do not start your plan with a plan to plan!\n" else ""} + """.trimMargin(), + model = planSettings.model, + parsingModel = planSettings.parsingModel, + temperature = planSettings.temperature, + ) + } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt index 86939b5c..f437cd6d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt @@ -3,9 +3,9 @@ package com.simiacryptus.skyenet.apps.plan import org.slf4j.LoggerFactory class RefactorTask( - settings: Settings, - planTask: PlanTask -) : AbstractAnalysisTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractAnalysisTask(planSettings, planTask) { override val actorName: String = "Refactor" override val actorPrompt: String = """ Analyze the provided code and suggest refactoring to improve code structure, readability, and maintainability. Focus on: diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt index d335a8e0..438523e3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt @@ -1,11 +1,12 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.TabbedDisplay import com.simiacryptus.skyenet.apps.coding.CodingAgent +import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.core.actors.CodingActor -import com.simiacryptus.skyenet.core.actors.ParsedResponse import com.simiacryptus.skyenet.interpreter.ProcessInterpreter import com.simiacryptus.skyenet.webui.session.SessionTask import org.slf4j.LoggerFactory @@ -14,9 +15,9 @@ import java.util.concurrent.Semaphore import kotlin.reflect.KClass class RunShellCommandTask( - settings: Settings, - planTask: PlanTask -) : AbstractTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractTask(planSettings, planTask) { val shellCommandActor by lazy { CodingActor( name = "RunShellCommand", @@ -26,14 +27,14 @@ class RunShellCommandTask( | Note: This task is for running simple and safe commands. Avoid executing commands that can cause harm to the system or compromise security. """.trimMargin(), - symbols = mapOf( - "env" to settings.env, - "workingDir" to (planTask.workingDir?.let { File(it).absolutePath } ?: File(settings.workingDir).absolutePath), - "language" to settings.language, - "command" to settings.command, + symbols = mapOf( + "env" to (planSettings.env ?: emptyMap()), + "workingDir" to (planTask.workingDir?.let { File(it).absolutePath } ?: File(planSettings.workingDir).absolutePath), + "language" to (planSettings.language ?: "bash"), + "command" to planSettings.command, ), - model = settings.model, - temperature = settings.temperature, + model = planSettings.model, + temperature = planSettings.temperature, ) } @@ -42,7 +43,7 @@ class RunShellCommandTask( RunShellCommand - Execute shell commands and provide the output ** Specify the command to be executed, or describe the task to be performed ** List input files/tasks to be examined when writing the command - ** Optionally specify a working directory for the command execution + ** Optionally specify a working directory for the command execution """.trimMargin() } @@ -50,15 +51,16 @@ class RunShellCommandTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: PlanCoordinator.TaskBreakdownResult, - genState: PlanCoordinator.GenState, + plan: TaskBreakdownInterface, + planProcessingState: PlanProcessingState, task: SessionTask, - taskTabs: TabbedDisplay + taskTabs: TabbedDisplay, + api: API ) { - if (!agent.settings.shellCommandTaskEnabled) throw RuntimeException("Shell command task is disabled") + if (!agent.planSettings.shellCommandTaskEnabled) throw RuntimeException("Shell command task is disabled") val semaphore = Semaphore(0) object : CodingAgent( - api = agent.api, + api = api, dataStorage = agent.dataStorage, session = agent.session, user = agent.user, @@ -95,7 +97,7 @@ class RunShellCommandTask( response: CodingActor.CodeResult ): String { return ui.hrefLink("Accept", "href-link play-button") { - genState.taskResult[taskId] = response.let { + planProcessingState.taskResult[taskId] = response.let { """ |## Shell Command Output | @@ -114,10 +116,10 @@ class RunShellCommandTask( }.apply> { start( codeRequest( - listOf>( + listOf( userMessage to ApiModel.Role.user, JsonUtil.toJson(plan) to ApiModel.Role.assistant, - getPriorCode(genState) to ApiModel.Role.assistant, + getPriorCode(planProcessingState) to ApiModel.Role.assistant, getInputFileCode() to ApiModel.Role.assistant, ) ) @@ -126,9 +128,9 @@ class RunShellCommandTask( try { semaphore.acquire() } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) + log.warn("Error", e) } - PlanCoordinator.log.debug("Completed shell command: $taskId") + log.debug("Completed shell command: $taskId") } companion object { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt index 449b7614..8e84b592 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt @@ -3,9 +3,9 @@ package com.simiacryptus.skyenet.apps.plan import org.slf4j.LoggerFactory class SecurityAuditTask( - settings: Settings, - planTask: PlanTask -) : AbstractAnalysisTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractAnalysisTask(planSettings, planTask) { override val actorName: String = "SecurityAudit" override val actorPrompt: String = """ Perform a comprehensive security audit for the provided code files. Analyze the code for: diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt deleted file mode 100644 index a6efb9f2..00000000 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.simiacryptus.skyenet.apps.plan - -import com.simiacryptus.jopenai.models.OpenAITextModel -import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.TaskBreakdownResult -import com.simiacryptus.skyenet.core.actors.ParsedActor - -data class Settings( - val model: OpenAITextModel, - val parsingModel: OpenAITextModel, - val command: List, - val temperature: Double = 0.2, - val budget: Double = 2.0, - val taskPlanningEnabled: Boolean = false, - val shellCommandTaskEnabled: Boolean = true, - val documentationEnabled: Boolean = true, - val fileModificationEnabled: Boolean = true, - val inquiryEnabled: Boolean = true, - val codeReviewEnabled: Boolean = true, - val testGenerationEnabled: Boolean = true, - val optimizationEnabled: Boolean = true, - val securityAuditEnabled: Boolean = true, - val performanceAnalysisEnabled: Boolean = true, - val refactorTaskEnabled: Boolean = true, - val foreachTaskEnabled: Boolean = true, - val autoFix: Boolean = false, - val enableCommandAutoFix: Boolean = false, - var commandAutoFixCommands: List = listOf(), - val env: Map = mapOf(), - val workingDir: String = ".", - val language: String = if (PlanCoordinator.isWindows) "powershell" else "bash", -) { - fun getImpl(planTask: PlanTask): AbstractTask { - return when (planTask.taskType) { - TaskType.TaskPlanning -> if (taskPlanningEnabled) PlanningTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.Documentation -> if (documentationEnabled) DocumentationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.FileModification -> if (fileModificationEnabled) FileModificationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.RunShellCommand -> if (shellCommandTaskEnabled) RunShellCommandTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.CommandAutoFix -> if (enableCommandAutoFix) CommandAutoFixTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.Inquiry -> if (inquiryEnabled) InquiryTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.CodeReview -> if (codeReviewEnabled) CodeReviewTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.TestGeneration -> if (testGenerationEnabled) TestGenerationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.Optimization -> if (optimizationEnabled) CodeOptimizationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) - TaskType.SecurityAudit -> if (securityAuditEnabled) SecurityAuditTask( - this, - planTask - ) else throw DisabledTaskException(planTask.taskType) - - TaskType.PerformanceAnalysis -> if (performanceAnalysisEnabled) PerformanceAnalysisTask( - this, - planTask - ) else throw DisabledTaskException(planTask.taskType) - - TaskType.RefactorTask -> if (refactorTaskEnabled) RefactorTask( - this, - planTask - ) else throw DisabledTaskException(planTask.taskType) - TaskType.ForeachTask -> if (foreachTaskEnabled) ForeachTask( - this, - planTask - ) else throw DisabledTaskException(planTask.taskType) - else -> throw RuntimeException("Unknown task type: ${planTask.taskType}") - } - } - - fun planningActor() = ParsedActor( - name = "TaskBreakdown", - resultClass = TaskBreakdownResult::class.java, - prompt = """ - |Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. - |Detail files input and output as well as task execution dependencies. - |Creating directories and initializing source control are out of scope. - | - |Tasks can be of the following types: - | - |${getAvailableTaskTypes().joinToString("\n") { "* ${it.promptSegment()}" }} - | - |${if (taskPlanningEnabled) "Do not start your plan with a plan to plan!\n" else ""} - """.trimMargin(), - model = this.model, - parsingModel = this.parsingModel, - temperature = this.temperature, - ) - - private fun getAvailableTaskTypes(): List = TaskType.values().filter { - when (it) { - TaskType.TaskPlanning -> this.taskPlanningEnabled - TaskType.RunShellCommand -> this.shellCommandTaskEnabled - TaskType.Documentation -> this.documentationEnabled - TaskType.FileModification -> this.fileModificationEnabled - TaskType.Inquiry -> this.inquiryEnabled - TaskType.CodeReview -> this.codeReviewEnabled - TaskType.TestGeneration -> this.testGenerationEnabled - TaskType.Optimization -> this.optimizationEnabled - TaskType.CommandAutoFix -> this.enableCommandAutoFix - TaskType.SecurityAudit -> this.securityAuditEnabled - TaskType.PerformanceAnalysis -> this.performanceAnalysisEnabled - TaskType.RefactorTask -> this.refactorTaskEnabled - TaskType.ForeachTask -> this.foreachTaskEnabled - } - }.map { this.getImpl(PlanTask(taskType = it)) } -} -class DisabledTaskException(taskType: TaskType) : Exception("Task type $taskType is disabled") \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt index 88acf6c1..4f7b8bd0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt @@ -13,5 +13,97 @@ enum class TaskType { SecurityAudit, PerformanceAnalysis, RefactorTask, - ForeachTask, + ForeachTask; + + companion object { + + fun getImpl(planSettings: PlanSettings, planTask: PlanningTask.PlanTask): AbstractTask { + return when (planTask.taskType) { + TaskPlanning -> if (planSettings.taskPlanningEnabled) PlanningTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + Documentation -> if (planSettings.documentationEnabled) DocumentationTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + FileModification -> if (planSettings.fileModificationEnabled) FileModificationTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + RunShellCommand -> if (planSettings.shellCommandTaskEnabled) RunShellCommandTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + CommandAutoFix -> if (planSettings.enableCommandAutoFix) CommandAutoFixTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + Inquiry -> if (planSettings.inquiryEnabled) InquiryTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + CodeReview -> if (planSettings.codeReviewEnabled) CodeReviewTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + TestGeneration -> if (planSettings.testGenerationEnabled) TestGenerationTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + Optimization -> if (planSettings.optimizationEnabled) CodeOptimizationTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + SecurityAudit -> if (planSettings.securityAuditEnabled) SecurityAuditTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + PerformanceAnalysis -> if (planSettings.performanceAnalysisEnabled) PerformanceAnalysisTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + RefactorTask -> if (planSettings.refactorTaskEnabled) RefactorTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + ForeachTask -> if (planSettings.foreachTaskEnabled) ForeachTask( + planSettings, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + else -> throw RuntimeException("Unknown task type: ${planTask.taskType}") + } + } + + fun getAvailableTaskTypes(planSettings: PlanSettings): List = TaskType.values().filter { + when (it) { + TaskPlanning -> planSettings.taskPlanningEnabled + RunShellCommand -> planSettings.shellCommandTaskEnabled + Documentation -> planSettings.documentationEnabled + FileModification -> planSettings.fileModificationEnabled + Inquiry -> planSettings.inquiryEnabled + CodeReview -> planSettings.codeReviewEnabled + TestGeneration -> planSettings.testGenerationEnabled + Optimization -> planSettings.optimizationEnabled + CommandAutoFix -> planSettings.enableCommandAutoFix + SecurityAudit -> planSettings.securityAuditEnabled + PerformanceAnalysis -> planSettings.performanceAnalysisEnabled + RefactorTask -> planSettings.refactorTaskEnabled + ForeachTask -> planSettings.foreachTaskEnabled + } + }.map { getImpl(planSettings, PlanningTask.PlanTask(taskType = it)) } + } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt index 379596ae..957e18c6 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt @@ -3,9 +3,9 @@ package com.simiacryptus.skyenet.apps.plan import org.slf4j.LoggerFactory class TestGenerationTask( - settings: Settings, - planTask: PlanTask -) : AbstractAnalysisTask(settings, planTask) { + planSettings: PlanSettings, + planTask: PlanningTask.PlanTask +) : AbstractAnalysisTask(planSettings, planTask) { override val actorName: String = "TestGeneration" override val actorPrompt: String = """ |Generate comprehensive unit tests for the provided code files. The tests should: diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt index e60b8dad..8c666d35 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt @@ -220,7 +220,7 @@ abstract class ApplicationDirectory( context.resourceBase = "application" context.welcomeFiles = arrayOf("index.html") val servletHolder = ServletHolder(servlet) - servletHolder.getRegistration().setMultipartConfig(MultipartConfigElement("./tmp")); + servletHolder.getRegistration().setMultipartConfig(MultipartConfigElement("./tmp")) context.addServlet(servletHolder, "/") return context } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt index cb694b32..5fc5e474 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt @@ -1,6 +1,7 @@ package com.simiacryptus.skyenet.webui.chat import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.util.ClientUtil.toContentList @@ -19,7 +20,7 @@ open class ChatSocketManager( val userInterfacePrompt: String, open val initialAssistantPrompt: String = "", open val systemPrompt: String, - val api: OpenAIClient, + val api: ChatClient, val temperature: Double = 0.3, applicationClass: Class, val storage: StorageInterface?, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/FileServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/FileServlet.kt index 061a4826..47d6324f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/FileServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/FileServlet.kt @@ -16,7 +16,7 @@ import java.nio.MappedByteBuffer import java.nio.channels.FileChannel import java.nio.file.StandardOpenOption -abstract class FileServlet() : HttpServlet() { +abstract class FileServlet : HttpServlet() { abstract fun getDir( req: HttpServletRequest, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt index e63d9909..34530260 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt @@ -59,7 +59,7 @@ open class ProxyHttpServlet( val proxyKey = ApiKeyServlet.apiKeyRecords.find { it.apiKey == requestKey } val path = (req.servletPath ?: "").removePrefix("/") val proxyRequest = getProxyRequest(req) - if (null != proxyKey) proxyRequest?.addHeader("Authorization", "Bearer " + proxyKey.mappedKey) + if (null != proxyKey) proxyRequest.addHeader("Authorization", "Bearer " + proxyKey.mappedKey) val totalUsage = ApplicationServices.usageManager.getUserUsageSummary(requestKey).values.map { it.cost ?: 0.0 }.sum() if (totalUsage > (proxyKey?.budget ?: 0.0)) { @@ -85,7 +85,7 @@ open class ProxyHttpServlet( proxyResponse, proxyResponseBody, proxyKey, - proxyRequest?.body?.bodyBytes + proxyRequest.body?.bodyBytes ) ) asyncContext.complete() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt index 7800b20e..130d79f0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt @@ -8,6 +8,7 @@ import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.awt.image.BufferedImage +import java.io.File abstract class SessionTask( val operationID: String, @@ -202,6 +203,8 @@ abstract class SessionTask( } } + + abstract fun createFile(relativePath: String): Pair } val Throwable.stackTraceTxt: String diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt index 996a070e..49ad53c4 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt @@ -6,6 +6,7 @@ import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationTy import com.simiacryptus.skyenet.webui.chat.ChatSocket import com.simiacryptus.skyenet.webui.util.MarkdownUtil import org.slf4j.LoggerFactory +import java.io.File import java.net.URLDecoder import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -27,7 +28,7 @@ abstract class SocketManagerBase( private val sendQueues: MutableMap> = ConcurrentHashMap() private val messageVersions = HashMap() val pool get() = clientManager.getPool(session, owner) - val scheduledThreadPoolExecutor get() = clientManager.getScheduledPool(session, owner, dataStorage)!! + val scheduledThreadPoolExecutor get() = clientManager.getScheduledPool(session, owner, dataStorage) override fun removeSocket(socket: ChatSocket) { log.debug("Removing socket: {}", socket) @@ -78,6 +79,15 @@ abstract class SocketManagerBase( } return "fileIndex/$session/$relativePath" } + override fun createFile(relativePath: String): Pair { + log.debug("Saving file at path: {}", relativePath) + return Pair("fileIndex/$session/$relativePath", dataStorage?.getSessionDir(owner, session)?.let { dir -> + dir.mkdirs() + val resolve = dir.resolve(relativePath) + resolve.parentFile.mkdirs() + resolve + }) + } } fun send(out: String) { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt index f455775d..13559b6b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt @@ -2,6 +2,7 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent import com.simiacryptus.skyenet.core.platform.ApplicationServices @@ -30,7 +31,7 @@ open class CodingActorTestApp( ui: ApplicationInterface, api: API ) { - (api as ClientManager.MonitoredClient).budget = 2.00 + (api as ChatClient).budget = 2.00 val message = ui.newTask() try { message.echo(renderMarkdown(userMessage, ui = ui)) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt index ec471316..ff39b1ae 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt @@ -1,6 +1,7 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.core.actors.ImageActor import com.simiacryptus.skyenet.core.platform.ClientManager import com.simiacryptus.skyenet.core.platform.Session @@ -34,7 +35,7 @@ open class ImageActorTestApp( ui: ApplicationInterface, api: API ) { - (api as ClientManager.MonitoredClient).budget = 2.00 + (api as ChatClient).budget = 2.00 val message = ui.newTask() try { val actor = getSettings(session, user)?.actor ?: actor diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt index b6dd8ba7..7ac00185 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt @@ -1,6 +1,7 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.core.actors.ParsedActor import com.simiacryptus.skyenet.core.platform.ClientManager @@ -26,7 +27,7 @@ open class ParsedActorTestApp( ui: ApplicationInterface, api: API ) { - (api as ClientManager.MonitoredClient).budget = 2.00 + (api as ChatClient).budget = 2.00 val message = ui.newTask() try { message.echo(renderMarkdown(userMessage, ui = ui)) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt index 7e4bbcd0..1a072111 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt @@ -1,6 +1,7 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.core.platform.ClientManager import com.simiacryptus.skyenet.core.platform.Session @@ -34,7 +35,7 @@ open class SimpleActorTestApp( ui: ApplicationInterface, api: API ) { - (api as ClientManager.MonitoredClient).budget = 2.00 + (api as ChatClient).budget = 2.00 val message = ui.newTask() try { val actor = getSettings(session, user)?.actor ?: actor diff --git a/webui/src/main/resources/application/main.js b/webui/src/main/resources/application/main.js index 4c2b7996..d432c51c 100644 --- a/webui/src/main/resources/application/main.js +++ b/webui/src/main/resources/application/main.js @@ -52,11 +52,11 @@ const updateDocumentComponents = debounce(function () { } catch (e) { console.error("Error running mermaid:", e); } - try { - applyToAllSvg(); - } catch (e) { - console.error("Error applying SVG pan zoom:", e); - } + // try { + // applyToAllSvg(); + // } catch (e) { + // console.error("Error applying SVG pan zoom:", e); + // } }, 250); document.addEventListener('DOMContentLoaded', () => { diff --git a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt index 3fc77b06..16a19108 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt @@ -5,7 +5,8 @@ import com.simiacryptus.jopenai.util.ClientUtil.keyTxt import com.simiacryptus.skyenet.apps.general.PlanAheadApp import com.simiacryptus.skyenet.apps.general.StressTestApp import com.simiacryptus.skyenet.apps.plan.PlanCoordinator -import com.simiacryptus.skyenet.apps.plan.Settings +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.PlanUtil.isWindows import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.core.actors.ImageActor import com.simiacryptus.skyenet.core.actors.ParsedActor @@ -18,6 +19,7 @@ import com.simiacryptus.skyenet.groovy.GroovyInterpreter import com.simiacryptus.skyenet.kotlin.KotlinInterpreter import com.simiacryptus.skyenet.scala.ScalaLocalInterpreter import com.simiacryptus.skyenet.webui.test.* +import java.io.File object ActorTestAppServer : com.simiacryptus.skyenet.webui.application.ApplicationDirectory(port = 8082) { @@ -82,8 +84,8 @@ object ActorTestAppServer : com.simiacryptus.skyenet.webui.application.Applicati ChildWebApp( "/taskDev", PlanAheadApp( - rootFile = null, - settings = Settings( + rootFile = File("."), + planSettings = PlanSettings( model = ChatModels.GPT4o, parsingModel = ChatModels.GPT4oMini, command = listOf("task"), @@ -98,7 +100,7 @@ object ActorTestAppServer : com.simiacryptus.skyenet.webui.application.Applicati ), env = mapOf(), workingDir = ".", - language = if (PlanCoordinator.isWindows) "powershell" else "bash", + language = if (isWindows) "powershell" else "bash", ), model = ChatModels.GPT4o, parsingModel = ChatModels.GPT4oMini,