diff --git a/gradle.properties b/gradle.properties index a05675d7..cbff40b4 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.2.4 +libraryVersion = 1.2.5 gradleVersion = 7.6.1 kotlin.daemon.jvmargs=-Xmx2g 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 188b33fa..bd2d22d1 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 @@ -8,6 +8,7 @@ import com.simiacryptus.skyenet.apps.plan.PlanCoordinator import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.Companion.initialPlan import com.simiacryptus.skyenet.apps.plan.PlanSettings import com.simiacryptus.skyenet.apps.plan.PlanUtil +import com.simiacryptus.skyenet.core.platform.ApplicationServicesConfig.dataStorageRoot import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface @@ -20,7 +21,6 @@ import java.io.File open class PlanAheadApp( applicationName: String = "Task Planning v1.1", path: String = "/taskDev", - val rootFile: File? = null, val planSettings: PlanSettings, val model: OpenAITextModel, val parsingModel: OpenAITextModel, @@ -32,13 +32,13 @@ open class PlanAheadApp( applicationName = applicationName, path = path, showMenubar = showMenubar, + root = planSettings.workingDir?.let { File(it) } ?: dataStorageRoot, ) { override val singleInput: Boolean get() = true - override val root: File get() = rootFile ?: super.root @Suppress("UNCHECKED_CAST") override fun initSettings(session: Session): T = planSettings.let { - if (null == rootFile) it.copy(workingDir = root.absolutePath) else + if (null == root) it.copy(workingDir = root.absolutePath) else it } as T @@ -55,7 +55,7 @@ open class PlanAheadApp( session = session, dataStorage = dataStorage, ui = ui, - root = dataStorage.getDataDir(user, session).toPath(), + root = planSettings?.workingDir?.let { File(it).toPath() } ?: dataStorage.getDataDir(user, session).toPath(), planSettings = planSettings!! ) coordinator.executeTaskBreakdownWithPrompt(JsonUtil.toJson(initialPlan), api!!) @@ -83,7 +83,7 @@ open class PlanAheadApp( session = session, dataStorage = dataStorage, ui = ui, - root = dataStorage.getDataDir(user, session).toPath(), + root = planSettings?.workingDir?.let { File(it).toPath() } ?: dataStorage.getDataDir(user, session).toPath(), planSettings = planSettings!! ) val task = coordinator.ui.newTask() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt index 5cfa1363..ef1af580 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt @@ -4,7 +4,7 @@ import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.skyenet.apps.plan.* -import com.simiacryptus.skyenet.apps.plan.InquiryTask.InquiryTaskData +import com.simiacryptus.skyenet.apps.plan.file.InquiryTask.InquiryTaskData import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.util.MarkdownUtil @@ -16,7 +16,6 @@ import java.util.* open class PlanChatApp( applicationName: String = "Task Planning Chat v1.0", path: String = "/taskChat", - rootFile: File? = null, planSettings: PlanSettings, model: OpenAITextModel, parsingModel: OpenAITextModel, @@ -27,7 +26,6 @@ open class PlanChatApp( ) : PlanAheadApp( applicationName = applicationName, path = path, - rootFile = rootFile, planSettings = planSettings, model = model, parsingModel = parsingModel, @@ -89,7 +87,7 @@ open class PlanChatApp( session = session, dataStorage = dataStorage, ui = ui, - root = dataStorage.getDataDir(user, session).toPath(), + root = planSettings?.workingDir?.let { File(it).toPath() } ?: dataStorage.getDataDir(user, session).toPath(), planSettings = planSettings ) val mainTask = coordinator.ui.newTask() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ActorDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ActorDesigner.kt new file mode 100644 index 00000000..82012145 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ActorDesigner.kt @@ -0,0 +1,51 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.jopenai.models.OpenAIModels +import com.simiacryptus.skyenet.core.actors.ParsedActor + +class ActorDesigner( + model: ChatModels, + temperature: Double +) : ParsedActor( + resultClass = AgentActorDesign::class.java, + exampleInstance = AgentActorDesign( + actors = listOf( + ActorDesign( + name = "Actor 1", + description = "Actor 1 description", + type = "Simple", + resultClass = "String", + ) + ) + ), + model = model, + temperature = temperature, + parsingModel = OpenAIModels.GPT4oMini, + prompt = """ + You are an AI actor designer. + + Your task is to expand on a high-level design with requirements for each actor. + + For each actor in the given design, detail: + + 1. The purpose of the actor + 2. Actor Type, which can be one of: + 1. "Simple" actors work like a chatbot, and simply return the chat model's response to the system and user prompts + 2. "Parsed" actors produce complex data structures as output, which can be used in the application logic + * **IMPORTANT**: If the output is a string, use a "simple" actor instead + 3. "Coding" actors are used to invoke tools via dynamically compiled scripts + 4. "Image" actors produce images from a user (and system) prompt. + 3. Required details for each actor type: + 1. Simple and Image actors + 1. System prompt + 2. Parsed actors + 1. system prompt + 2. output data structure + 1. java class name + 2. definition + 3. Coding actors + 1. defined symbols and functions + 2. libraries used + """.trimIndent() +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/CodingActorDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/CodingActorDesigner.kt new file mode 100644 index 00000000..e3844314 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/CodingActorDesigner.kt @@ -0,0 +1,78 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.skyenet.apps.meta.FlowStepDesigner.Companion.fixups +import com.simiacryptus.skyenet.core.actors.CodingActor +import com.simiacryptus.skyenet.interpreter.Interpreter +import kotlin.reflect.KClass + +class CodingActorDesigner( + interpreterClass: KClass, + symbols: Map, + model: ChatModels, + temperature: Double +) : CodingActor( + interpreterClass = interpreterClass, + symbols = symbols, + details = """ + | + |Your task is to design a system that uses gpt "actors" to form a "community" of actors interacting to solve problems. + |Your task is to implement a "script" or "coding" actor that takes part in a larger system. + |"Script" actors use a multi-stage process that combines an environment definition of predefined symbols/functions and a pluggable script compilation system using Scala, Kotlin, or Groovy. The actor will return a valid script with a convenient "execute" method. This can provide both simple function calling responses and complex code generation. + | + |For context, here is the constructor signature for CodingActor class: + |```kotlin + |package com.simiacryptus.skyenet.core.actors + | + |import com.simiacryptus.jopenai.models.OpenAIModels + |import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber + |import com.simiacryptus.jopenai.describe.TypeDescriber + |import com.simiacryptus.skyenet.interpreter.Interpreter + |import kotlin.reflect.KClass + | + |class CodingActor( + | val interpreterClass: KClass, + | val symbols: Map = mapOf(), + | val describer: TypeDescriber = AbbrevWhitelistYamlDescriber( + | "com.simiacryptus", + | "com.github.simiacryptus" + | ), + | name: String? = interpreterClass.simpleName, + | val details: String? = null, + | model: OpenAITextModel = OpenAIModels.GPT4o, + | val fallbackModel: ChatModels = OpenAIModels.GPT4o, + | temperature: Double = 0.1, + |) + |``` + | + |In this code example an example actor is defined with a prompt, name, and a standard configuration: + |```kotlin + |import com.simiacryptus.skyenet.core.actors.CodingActor + |import com.simiacryptus.skyenet.kotlin.KotlinInterpreter + | + |fun exampleCodingActor() = CodingActor( + | interpreterClass = KotlinInterpreter::class, + | details = ""${'"'} + | |You are a software implementation assistant. + | | + | |Defined functions: + | |* ... + | | + | |Expected code structure: + | |* ... + | ""${'"'}.trimMargin().trim(), + |) + |``` + | + |Respond to the request with an instantiation function of the requested actor, similar to the provided example. + |DO NOT subclass the CodingActor class. Use the constructor directly within the function. + | + """.trimMargin().trim(), + model = model, + temperature = temperature, +) { + init { + evalFormat = false + codeInterceptor = { fixups(it) } + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/DetailDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/DetailDesigner.kt new file mode 100644 index 00000000..07b9ecf7 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/DetailDesigner.kt @@ -0,0 +1,86 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.jopenai.models.OpenAIModels +import com.simiacryptus.skyenet.core.actors.ParsedActor + +class DetailDesigner( + model: ChatModels, + temperature: Double +) : ParsedActor( + resultClass = AgentFlowDesign::class.java, + exampleInstance = AgentFlowDesign( + name = "TextAnalyzer", + description = "Analyze input text for sentiment and key topics", + mainInput = DataInfo( + type = "String", + description = "raw text" + ), + logicFlow = LogicFlow( + items = listOf( + LogicFlowItem( + name = "Preprocess text", + description = "Preprocess text (remove noise, normalize)", + actors = listOf( + "TextPreprocessor" + ), + inputs = listOf( + DataInfo( + type = "String", + description = "raw text" + ) + ), + output = DataInfo( + type = "String", + description = "preprocessed text" + ) + ), + ) + ) + ), + model = model, + temperature = temperature, + parsingModel = OpenAIModels.GPT4o, + prompt = """ + You are an expert detailed software designer specializing in AI agent systems. + + Your task is to expand on the high-level architecture and design a detailed "agent" system that uses GPT "actors" to model a creative process. + The system should have a procedural overall structure, with creative steps implemented by GPT actors. + + Consider the following system interactions: + 1. File storage and retrieval: Design a shared session folder accessible by both the user and the application. + 2. Concurrent operations: Plan for individual actors and actions to run in parallel using Java threading. + + Design user interactions including: + 1. Rich message display: HTML and image rendering in the web interface. + 2. User input mechanisms: Text input fields and clickable links with callback handling. + + Incorporate these important design patterns: + 1. Iterative Thinking: Implement user feedback loops and step-by-step processing using sequences of specialized actors. + 2. Parse-and-Expand: Use an initial actor to generate a base data structure, then expand it using various (potentially recursive) actors. + 3. File Builder: Design the main web interface for monitoring and control, with primary outputs written to files and displayed as links. + + Your detailed design output should include: + 1. Actor Specifications: For each actor, provide: + * Purpose and description + * Input and output formats + * Key responsibilities and operations + 2. Logic Flow: Detailed pseudocode for the overall system flow + 3. Data Structures: Specifications for data structures used to pass and handle information between actors + 4. User Interface: Mockup or description of key UI components and their functionality + 5. File Management: Strategy for file storage, retrieval, and organization + 6. Concurrency Plan: Outline of how parallel operations will be managed + Example Actor Specification: + Actor: TextAnalyzer + Purpose: Analyze input text for sentiment and key topics + Inputs: String (raw text) + Outputs: + * Float (sentiment score from -1 to 1) + * List (key topics) + Operations: + 1. Preprocess text (remove noise, normalize) + 2. Perform sentiment analysis + 3. Extract key topics using NLP techniques + Ensure your design is comprehensive, clear, and ready for implementation. + """.trimIndent() +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/FlowStepDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/FlowStepDesigner.kt new file mode 100644 index 00000000..cf478a8d --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/FlowStepDesigner.kt @@ -0,0 +1,102 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.skyenet.core.actors.CodingActor +import com.simiacryptus.skyenet.interpreter.Interpreter +import org.slf4j.LoggerFactory +import kotlin.reflect.KClass + +class FlowStepDesigner( + interpreterClass: KClass, + symbols: Map, + model: ChatModels, + temperature: Double +) : CodingActor( + interpreterClass = interpreterClass, + symbols = symbols, + details = """ + |You are a software implementor. + | + |Your task is to implement logic for an "agent" system that uses gpt "actors" to construct a model of a creative process. + |This "creative process" can be thought of as a cognitive process, an individual's work process, or an organizational process. + |The idea is that the overall structure is procedural and can be modeled in code, but individual steps are creative and can be modeled with gpt. + | + |Actors process inputs in the form of ChatGPT messages (often a single string) but vary in their output. + |Usage examples of each actor type follows: + | + |Simple actors contain a system directive, and simply return the chat model's response to the user query. + |Simple actors answer queries consisting of a list of strings representing a conversation thread, and respond with a string. + |```kotlin + |val actor : com.simiacryptus.skyenet.core.actors.SimpleActor + |val answer : String = actor.answer(listOf("This is an example question"), api = api) + |log.info("Answer: " + answer) + |``` + | + |Parsed actors use a 2-stage system; first, queries are responded in the same manner as simple actors using a system prompt. + |This natural-language response is then parsed into a typed object, which can be used in the application logic. + |Parsed actors answer queries consisting of a list of strings representing a conversation thread, and responds with an object containing text and a parsed object. + |```kotlin + |val actor : com.simiacryptus.skyenet.core.actors.ParsedActor + |val answer : com.simiacryptus.skyenet.core.actors.ParsedResponse = actor.answer(listOf("This is some example data"), api = api) + |log.info("Natural Language Answer: " + answer.text) + |log.info("Parsed Answer: " + com.simiacryptus.jopenai.util.JsonUtil.toJson(answer.obj)) + |``` + | + |Coding actors combine ChatGPT-powered code generation with compilation and validation to produce quality code without having to run it. + |Coding actors answer queries expressed using CodeRequest, and responds with an object that defines a code block and an execution method. + |```kotlin + |val actor : com.simiacryptus.skyenet.core.actors.CodingActor + |val answer : com.simiacryptus.skyenet.core.actors.CodingActor.CodeResult = actor.answer(listOf("Do an example task"), api = api) + |log.info("Implemented Code: " + answer.code) + |val executionResult : com.simiacryptus.skyenet.core.actors.CodingActor.ExecutionResult = answer.result + |log.info("Execution Log: " + executionResult.resultOutput) + |log.info("Execution Result: " + executionResult.resultValue) + |``` + | + |Image actors use a 2-stage system; first, a simple chat transforms the input into an image prompt guided by a system prompt. + |Image actors answer queries consisting of a list of strings representing a conversation thread, and respond with an image. + |```kotlin + |val actor : com.simiacryptus.skyenet.core.actors.ImageActor + |val answer : com.simiacryptus.skyenet.core.actors.ImageResponse = actor.answer(listOf("Draw an example image"), api = api) + |log.info("Image description: " + answer.text) + |val image : BufferedImage = answer.image + |``` + | + |While implementing logic, the progress should be displayed to the user using the `ui` object. + |The UI display generally follows a pattern similar to: + |```kotlin + |val task = ui.newTask() + |try { + | task.header("Main Function") + | task.add("Normal message") + | task.verbose("Verbose output - not shown by default") + | task.add(ui.textInput { log.info("Message Received: " + it) }) + | task.add(ui.hrefLink("Click Me!") { log.info("Link clicked") }) + | task.complete() + |} catch (e: Throwable) { + | task.error(e) + | throw e + |} + |``` + | + |**IMPORTANT**: Do not redefine any symbol defined in the preceding code messages. + | + |""".trimMargin().trim(), + model = model, + temperature = temperature, + runtimeSymbols = mapOf( + "log" to log + ), +) { + init { + evalFormat = false + codeInterceptor = { fixups(it) } + } + + companion object { + private val log = LoggerFactory.getLogger(FlowStepDesigner::class.java) + fun fixups(it: String) = it + .replace("ChatModels.GPT_3_5_TURBO", "OpenAIModels.GPT35Turbo") + .replace("OpenAIModels.DallE3", "ImageModels.DallE3") + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/HighLevelDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/HighLevelDesigner.kt new file mode 100644 index 00000000..72e4cf6d --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/HighLevelDesigner.kt @@ -0,0 +1,34 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.skyenet.core.actors.SimpleActor + +class HighLevelDesigner( + model: ChatModels, + temperature: Double +) : SimpleActor( + model = model, + temperature = temperature, + prompt = """ + You are an expert high-level software architect specializing in AI-based automated assistants. + Your task is to gather requirements and create a detailed design based on the user's idea. + Follow these steps: + 1. Analyze the user's query and identify key requirements. + 2. Propose detailed specifications for inputs, outputs, and core logic. + 3. Design a high-level architecture for an AI-based automated assistant with a web interface. + 4. Consider potential use cases and edge cases in your design. + Your design should include: + - A clear description of the assistant's purpose and main features + - Detailed input and output specifications + - An overview of the core logic and processing steps + - Considerations for user interaction and experience + Example structure: + 1. Assistant Purpose: [Brief description] + 2. Main Features: [List of key functionalities] + 3. Inputs: [Detailed list of input types and formats] + 4. Outputs: [Detailed list of output types and formats] + 5. Core Logic: [Overview of main processing steps] + 6. User Interaction: [Description of how users will interact with the assistant] + Remember, this design can be iteratively refined based on user feedback. + """.trimIndent() +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ImageActorDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ImageActorDesigner.kt new file mode 100644 index 00000000..b23f4ae4 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ImageActorDesigner.kt @@ -0,0 +1,62 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.skyenet.apps.meta.FlowStepDesigner.Companion.fixups +import com.simiacryptus.skyenet.core.actors.CodingActor +import com.simiacryptus.skyenet.interpreter.Interpreter +import kotlin.reflect.KClass + +class ImageActorDesigner( + interpreterClass: KClass, + symbols: Map, + model: ChatModels, + temperature: Double +) : CodingActor( + interpreterClass = interpreterClass, + symbols = symbols, + details = """ + | + |You are a software implementation assistant. + |Your task is to implement a "image" actor that takes part in a larger system. + |"Image" actors contain a system directive and can process a list of user messages into a response. + | + |For context, here is the constructor signature for ImageActor class: + |```kotlin + |import com.simiacryptus.jopenai.models.ChatModels + |import com.simiacryptus.jopenai.models.ImageModels + |import com.simiacryptus.skyenet.core.actors.ImageActor + | + |class ImageActor( + | prompt: String = "Transform the user request into an image generation prompt that the user will like", + | name: String? = null, + | textModel: ChatModels = OpenAIModels.GPT4oMini, + | val imageModel: ImageModels = ImageModels.DallE3, + | temperature: Double = 0.3, + | val width: Int = 1024, + | val height: Int = 1024, + |) + |``` + | + |In this code example an example actor is defined with a prompt and a name: + |```kotlin + |import com.simiacryptus.skyenet.core.actors.ImageActor + | + |fun exampleSimpleActor() = ImageActor( + | prompt = ""${'"'} + | |You are a writing assistant. + | ""${'"'}.trimMargin().trim(), + |) + |``` + | + |Respond to the request with an instantiation function of the requested actor, similar to the provided example. + |DO NOT subclass the ImageActor class. Use the constructor directly within the function. + | + """.trimMargin().trim(), + model = model, + temperature = temperature, +) { + init { + evalFormat = false + codeInterceptor = { fixups(it) } + } +} diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/MetaAgentApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/MetaAgentApp.kt new file mode 100644 index 00000000..0f6fa627 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/MetaAgentApp.kt @@ -0,0 +1,749 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.ChatClient +import com.simiacryptus.jopenai.models.ApiModel +import com.simiacryptus.jopenai.models.ApiModel.Role +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.jopenai.models.OpenAIModels +import com.simiacryptus.jopenai.proxy.ValidatedObject +import com.simiacryptus.jopenai.util.ClientUtil.toContentList +import com.simiacryptus.util.JsonUtil +import com.simiacryptus.skyenet.Discussable +import com.simiacryptus.skyenet.Retryable +import com.simiacryptus.skyenet.apps.meta.MetaAgentActors.Companion.notIn +import com.simiacryptus.skyenet.core.actors.CodingActor +import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.camelCase +import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.imports +import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent +import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.pascalCase +import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.sortCode +import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.stripImports +import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.core.actors.PoolSystem +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.interpreter.Interpreter +import com.simiacryptus.skyenet.kotlin.KotlinInterpreter +import com.simiacryptus.skyenet.webui.application.ApplicationInterface +import com.simiacryptus.skyenet.webui.application.ApplicationServer +import com.simiacryptus.skyenet.webui.session.SessionTask +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown +import org.eclipse.jetty.webapp.WebAppClassLoader +import org.intellij.lang.annotations.Language +import org.slf4j.LoggerFactory +import java.io.IOException +import java.net.SocketTimeoutException +import java.util.* +import kotlin.collections.HashMap +import kotlin.reflect.KClass + +open class MetaAgentApp( + applicationName: String = "Meta-Agent-Agent v1.1", +) : ApplicationServer( + applicationName = applicationName, + path = "/meta_agent", +) { + override val description: String + @Language("Markdown") + get() = "
${ + + renderMarkdown( + """ + **It's agents all the way down!** + Welcome to the MetaAgentAgent, an innovative tool designed to streamline the process of creating custom AI agents. + This powerful system leverages the capabilities of OpenAI's language models to assist you in designing and implementing your very own AI agent tailored to your specific needs and preferences. + + Here's how it works: + 1. **Provide a Prompt**: Describe the purpose of your agent. + 2. **High Level Design**: A multi-step high-level design process will guide you through the creation of your agent. During each phase, you can provide feedback and iterate. When you're satisfied with the design, you can move on to the next step. + 3. **Implementation**: The MetaAgentAgent will generate the code for your agent, which you can then download and tailor to your needs. + + Get started with MetaAgentAgent today and bring your custom AI agent to life with ease! + Whether you're looking to automate customer service, streamline data analysis, or create an interactive chatbot, MetaAgentAgent is here to help you make it happen. + """.trimIndent() + ) + }
" + + data class Settings( + val model: ChatModels = OpenAIModels.GPT4o, + val validateCode: Boolean = true, + val temperature: Double = 0.2, + val budget: Double = 2.0, + ) + + override val settingsClass: Class<*> get() = Settings::class.java + @Suppress("UNCHECKED_CAST") + override fun initSettings(session: Session): T? = Settings() as T + + override fun userMessage( + session: Session, + user: User?, + userMessage: String, + ui: ApplicationInterface, + api: API + ) { + val task = ui.newTask() + task.add("User Message Processing") + try { + val settings = getSettings(session, user) + val agent = MetaAgentAgent( + user = user, + session = session, + dataStorage = dataStorage, + api = api, + ui = ui, + model = settings?.model ?: OpenAIModels.GPT4oMini, + autoEvaluate = settings?.validateCode ?: true, + temperature = settings?.temperature ?: 0.3, + ) + try { + agent.buildAgent(userMessage = userMessage) + } catch (e: SocketTimeoutException) { + log.error("Network timeout during agent building", e) + task.add("The operation timed out. Please check your network connection and try again.") + return + } catch (e: IOException) { + log.error("I/O error during agent building", e) + task.add("An I/O error occurred. Please try again later.") + return + } catch (e: Exception) { + log.error("Unexpected error during agent building", e) + task.add("An unexpected error occurred. Please try again later.") + return + } + task.complete() + } catch (e: Throwable) { + log.error("Error in userMessage", e) + task.error(ui, e) + when (e) { + is IllegalArgumentException -> task.add("Invalid input: ${e.message}") + is IllegalStateException -> task.add("Operation failed: ${e.message}") + else -> task.add("An unexpected error occurred: ${e.message}. Please try again later.") + } + } + } + + companion object { + private val log = LoggerFactory.getLogger(MetaAgentApp::class.java) + } + +} + +open class MetaAgentAgent( + user: User?, + session: Session, + dataStorage: StorageInterface, + val ui: ApplicationInterface, + val api: API, + model: ChatModels = OpenAIModels.GPT4oMini, + var autoEvaluate: Boolean = true, + temperature: Double = 0.3, +) : PoolSystem( + dataStorage, user, session +) { + + private val highLevelDesigner by lazy { HighLevelDesigner(model, temperature) } + private val detailDesigner by lazy { DetailDesigner(model, temperature) } + private val interpreterClass: KClass = KotlinInterpreter::class + + val symbols = mapOf( + "ui" to ui, + "api" to api, + "pool" to ApplicationServices.clientManager.getPool(session, user), + ) + private val actorDesigner by lazy { ActorDesigner(model, temperature) } + private val simpleActorDesigner by lazy { SimpleActorDesigner(interpreterClass, symbols, model, temperature) } + private val imageActorDesigner by lazy { ImageActorDesigner(interpreterClass, symbols, model, temperature) } + private val parsedActorDesigner by lazy { ParsedActorDesigner(interpreterClass, symbols, model, temperature) } + private val codingActorDesigner by lazy { CodingActorDesigner(interpreterClass, symbols, model, temperature) } + private val flowStepDesigner by lazy { FlowStepDesigner(interpreterClass, symbols, model, temperature) } + + @Language("kotlin") + val standardImports = """ + |import com.simiacryptus.jopenai.API + |import com.simiacryptus.jopenai.models.ChatModels + |import com.simiacryptus.skyenet.core.actors.BaseActor + |import com.simiacryptus.skyenet.core.actors.ActorSystem + |import com.simiacryptus.skyenet.core.actors.CodingActor + |import com.simiacryptus.skyenet.core.actors.ParsedActor + |import com.simiacryptus.skyenet.core.actors.ImageActor + |import com.simiacryptus.skyenet.core.platform.file.DataStorage + |import com.simiacryptus.skyenet.core.platform.Session + |import com.simiacryptus.skyenet.core.platform.StorageInterface + |import com.simiacryptus.skyenet.core.actors.PoolSystem + |import com.simiacryptus.skyenet.core.platform.User + |import com.simiacryptus.skyenet.webui.application.ApplicationServer + |import com.simiacryptus.skyenet.webui.session.* + |import com.simiacryptus.skyenet.webui.application.ApplicationInterface + |import java.awt.image.BufferedImage + |import org.slf4j.LoggerFactory + |import java.io.File + |import javax.imageio.ImageIO + """.trimMargin() + + fun buildAgent(userMessage: String) { + val design = initialDesign(userMessage) + val actImpls = implementActors(userMessage, design) + val flowImpl = getFlowStepCode(userMessage, design, actImpls) + val mainImpl = getMainFunction(userMessage, design, actImpls, flowImpl) + buildFinalCode(actImpls, flowImpl, mainImpl, design) + } + + private fun buildFinalCode( + actImpls: Map, + flowImpl: Map, + mainImpl: String, design: ParsedResponse + ) { + val task = ui.newTask() + task.add("Building Final Code") + try { + task.header("Final Code") + + val imports = (actImpls.values + flowImpl.values + listOf(mainImpl)).flatMap { it.imports() }.toSortedSet() + .joinToString("\n") + + val classBaseName = (design.obj.name?.pascalCase() ?: "MyAgent").replace("[^A-Za-z0-9]".toRegex(), "") + + val actorInits = design.obj.actors?.joinToString("\n") { actImpls[it.name] ?: "" } ?: "" + + @Language("kotlin") val appCode = """ + |$standardImports + | + |$imports + | + |open class ${classBaseName}App( + | applicationName: String = "${design.obj.name}", + | path: String = "/${design.obj.path ?: ""}", + |) : ApplicationServer( + | applicationName = applicationName, + | path = path, + |) { + | + | data class Settings( + | val model: ChatModels = OpenAIModels.GPT4oMini, + | val temperature: Double = 0.1, + | ) + | override val settingsClass: Class<*> get() = Settings::class.java + | @Suppress("UNCHECKED_CAST") override fun initSettings(session: Session): T? = Settings() as T + | + | override fun userMessage( + | session: Session, + | user: User?, + | userMessage: String, + | ui: ApplicationInterface, + | api: API + | ) { + | try { + | val settings = getSettings(session, user) + | ${classBaseName}Agent( + | user = user, + | session = session, + | dataStorage = dataStorage, + | api = api, + | ui = ui, + | model = settings?.model ?: OpenAIModels.GPT4oMini, + | temperature = settings?.temperature ?: 0.3, + | ).${design.obj.name?.camelCase()}(userMessage) + | } catch (e: Throwable) { + | log.warn("Error", e) + | } + | } + | + | companion object { + | private val log = LoggerFactory.getLogger(${classBaseName}App::class.java) + | } + | + |} + """.trimMargin() + + @Language("kotlin") val agentCode = """ + |$standardImports + | + |open class ${classBaseName}Agent( + | user: User?, + | session: Session, + | dataStorage: StorageInterface, + | val ui: ApplicationInterface, + | val api: API, + | model: ChatModels = OpenAIModels.GPT4oMini, + | temperature: Double = 0.3, + |) : PoolSystem(dataStorage, user, session) { + | + | ${actorInits.indent(" ")} + | + | ${mainImpl.trimIndent().stripImports().indent(" ")} + | + | ${flowImpl.values.joinToString("\n\n") { flowStep -> flowStep.trimIndent() }.stripImports().indent(" ")} + | + | companion object { + | private val log = org.slf4j.LoggerFactory.getLogger(${classBaseName}Agent::class.java) + | + | } + |} + """.trimMargin() + + //language=MARKDOWN + val code = """ + |```kotlin + |${ + """ + |$appCode + | + |$agentCode + """.trimMargin().sortCode() + } + |``` + """.trimMargin() + + //language=HTML + task.complete(renderMarkdown(code, ui = ui)) + } catch (e: IOException) { + task.complete("An I/O error occurred. Please try again later.") + } catch (e: Throwable) { + task.error(ui, e) + throw e + } + } + + private fun initialDesign(input: String): ParsedResponse { + val toInput = { it: String -> listOf(it) } + val highLevelDesign = Discussable( + task = ui.newTask(), + userMessage = { input }, + heading = renderMarkdown(input, ui = ui), + initialResponse = { it: String -> highLevelDesigner.answer(toInput(it), api = api) }, + outputFn = { design -> renderMarkdown(design.toString(), ui = ui) }, + ui = ui, + reviseResponse = { userMessages: List> -> + highLevelDesigner.respond( + messages = (userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } + .toTypedArray()), + input = toInput(input), + api = api + ) + }, + ).call() + val toInput1 = { it: String -> listOf(it) } + val flowDesign = Discussable( + task = ui.newTask(), + userMessage = { highLevelDesign }, + heading = "Flow Design", + initialResponse = { it: String -> detailDesigner.answer(toInput1(it), api = api) }, + outputFn = { design: ParsedResponse -> + try { + renderMarkdown( + """ + |$design + |```json + |${JsonUtil.toJson(design.obj)} + |``` + """.trimMargin(), ui = ui + ) + } catch (e: Throwable) { + renderMarkdown(e.message ?: e.toString(), ui = ui) + } + }, + ui = ui, + reviseResponse = { userMessages: List> -> + detailDesigner.respond( + messages = (userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } + .toTypedArray()), + input = toInput1(highLevelDesign), + api = api + ) + }, + ).call() + val actorDesignParsedResponse: ParsedResponse = Discussable( + task = ui.newTask(), + userMessage = { flowDesign.text }, + heading = "Actor Design", + initialResponse = { it: String -> actorDesigner.answer(listOf(it), api = api) }, + outputFn = { design: ParsedResponse -> + try { + renderMarkdown( + """ + |$design + |```json + |${JsonUtil.toJson(design.obj)} + |``` + """.trimMargin(), ui = ui + ) + } catch (e: Throwable) { + renderMarkdown(e.message ?: e.toString(), ui = ui) + } + }, + ui = ui, + reviseResponse = { userMessages: List> -> + actorDesigner.respond( + messages = (userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } + .toTypedArray()), input = listOf(flowDesign.text), + api = api + ) + }, + ).call() + return object : ParsedResponse(AgentDesign::class.java) { + override val text get() = flowDesign.text + "\n" + actorDesignParsedResponse.text + override val obj + get() = AgentDesign( + name = flowDesign.obj.name, + description = flowDesign.obj.description, + mainInput = flowDesign.obj.mainInput, + logicFlow = flowDesign.obj.logicFlow, + actors = actorDesignParsedResponse.obj.actors, + ) + } + } + + private fun getMainFunction( + userMessage: String, design: ParsedResponse, + actorImpls: Map, + flowStepCode: Map + ): String { + val task = ui.newTask() + try { + task.header("Main Function") + val codeRequest = CodingActor.CodeRequest( + messages = listOf( + userMessage to Role.user, + design.text to Role.assistant, + "Implement `fun ${design.obj.name?.camelCase()}(${ + listOf(design.obj.mainInput!!) + .joinToString(", ") { (it.name ?: "") + " : " + (it.type ?: "") } + })`" to Role.user + ), codePrefix = ((standardImports.lines() + actorImpls.values + flowStepCode.values) + .joinToString("\n\n") { it.trimIndent() }).sortCode(), + autoEvaluate = autoEvaluate + ) + val mainFunction = execWrap { flowStepDesigner.answer(codeRequest, api = api).code } + task.verbose( + renderMarkdown( + """ + |```kotlin + |$mainFunction + |``` + """.trimMargin(), ui = ui + ), tag = "div" + ) + task.complete() + return mainFunction + } catch (e: CodingActor.FailedToImplementException) { + task.verbose(e.code ?: throw e) + task.error(ui, e) + return e.code ?: throw e + } catch (e: Throwable) { + task.error(ui, e) + throw e + } + } + + private fun implementActors( + userMessage: String, + design: ParsedResponse, + ) = design.obj.actors?.map { actorDesign -> + pool.submit> { + val task = ui.newTask() + try { + implementActor(task, actorDesign, userMessage, design) + } catch (e: Throwable) { + task.error(ui, e) + throw e + } + } + }?.toTypedArray()?.associate { it.get() } ?: mapOf() + + private fun implementActor( + task: SessionTask, actorDesign: ActorDesign, + userMessage: String, design: ParsedResponse + ): Pair { + val api = (api as ChatClient).getChildClient().apply { + val createFile = task.createFile(".logs/api-${UUID.randomUUID()}.log") + createFile.second?.apply { + logStreams += this.outputStream().buffered() + task.verbose("API log: $this") + } + } + //language=HTML + task.header("Actor: ${actorDesign.name}") + val type = actorDesign.type + val codeRequest = CodingActor.CodeRequest( + messages = listOf( + userMessage to Role.user, + design.text to Role.assistant, + "Implement `val ${(actorDesign.name).camelCase()} : ${ + when (type.lowercase()) { + "simple" -> "SimpleActor" + "parsed" -> "ParsedActor<${actorDesign.simpleClassName}>" + "coding" -> "CodingActor" + "image" -> "ImageActor" + "tts" -> "TextToSpeechActor" + else -> throw IllegalArgumentException("Unknown actor type: $type") + } + }`" to Role.user + ), autoEvaluate = autoEvaluate, codePrefix = standardImports.sortCode() + ) + var code = "" + val onComplete = java.util.concurrent.Semaphore(0) + Retryable(ui, task) { + try { + val response = execWrap { + when (type.lowercase()) { + "simple" -> simpleActorDesigner.answer(codeRequest, api = api) + "parsed" -> parsedActorDesigner.answer(codeRequest, api = api) + "coding" -> codingActorDesigner.answer(codeRequest, api = api) + "image" -> imageActorDesigner.answer(codeRequest, api = api) + else -> throw IllegalArgumentException("Unknown actor type: $type") + } + } + code = response.code + onComplete.release() + renderMarkdown( + """ + |```kotlin + |$code + |``` + """.trimMargin(), ui = ui + ) + } catch (e: CodingActor.FailedToImplementException) { + task.error(ui, e) + code = e.code ?: "" + renderMarkdown( + """ + |```kotlin + |$code + |``` + |${ + ui.hrefLink("Accept", classname = "href-link cmd-button") { + autoEvaluate = false + onComplete.release() + } + } + """.trimMargin(), ui = ui + ) + } + } + onComplete.acquire() + //language=HTML + task.complete() + return actorDesign.name to code + } + + private fun execWrap(fn: () -> T): T { + val classLoader = Thread.currentThread().contextClassLoader + val prevCL = KotlinInterpreter.classLoader + KotlinInterpreter.classLoader = classLoader //req.javaClass.classLoader + return try { + WebAppClassLoader.runWithServerClassAccess { + require(null != classLoader.loadClass("org.eclipse.jetty.server.Response")) + require(null != classLoader.loadClass("org.eclipse.jetty.server.Request")) + fn() + } + } finally { + KotlinInterpreter.classLoader = prevCL + } + } + + private fun getFlowStepCode( + userMessage: String, + design: ParsedResponse, + actorImpls: Map, + ): Map { + val flowImpls = HashMap() + design.obj.logicFlow?.items?.forEach { logicFlowItem -> + val message = ui.newTask() + try { + val api = (api as ChatClient).getChildClient().apply { + val createFile = message.createFile(".logs/api-${UUID.randomUUID()}.log") + createFile.second?.apply { + logStreams += this.outputStream().buffered() + message.verbose("API log: $this") + } + } + message.header("Logic Flow: ${logicFlowItem.name}") + var code: String? = null + val onComplete = java.util.concurrent.Semaphore(0) + Retryable(ui, message) { + try { + code = execWrap { + flowStepDesigner.answer(CodingActor.CodeRequest(messages = listOf(userMessage to Role.user, + design.text to Role.assistant, + "Implement `fun ${(logicFlowItem.name!!).camelCase()}(${ + logicFlowItem.inputs?.joinToString(", ") { (it.name ?: "") + " : " + (it.type ?: "") } ?: "" + })`" to Role.user), + autoEvaluate = autoEvaluate, + codePrefix = (actorImpls.values + flowImpls.values).joinToString("\n\n") { it.trimIndent() } + .sortCode() + ), api = api + ).code + } + onComplete.release() + renderMarkdown( + """ + |```kotlin + |$code + |``` + """.trimMargin(), ui = ui + ) + } catch (e: CodingActor.FailedToImplementException) { + message.error(ui, e) + code = e.code ?: "" + renderMarkdown( + """ + |```kotlin + |$code + |``` + |${ + ui.hrefLink("Accept", classname = "href-link cmd-button") { + autoEvaluate = false + onComplete.release() + } + } + """.trimMargin(), ui = ui + ) + } + } + onComplete.acquire() + message.complete() + flowImpls[logicFlowItem.name!!] = code!! + } catch (e: Throwable) { + message.error(ui, e) + throw e + } + } + return flowImpls + } + + companion object +} + +class MetaAgentActors( + val symbols: Map = mapOf(), + val model: ChatModels = OpenAIModels.GPT4o, + val temperature: Double = 0.3, +) { + + companion object { + val log = LoggerFactory.getLogger(MetaAgentActors::class.java) + fun T.notIn(vararg examples: T) = !examples.contains(this) + } +} + +data class AgentFlowDesign( + val name: String? = null, + val description: String? = null, + val mainInput: DataInfo? = null, + val logicFlow: LogicFlow? = null, +) : ValidatedObject { + override fun validate(): String? = when { + null == logicFlow -> "logicFlow is required" + null != logicFlow.validate() -> logicFlow.validate() + else -> null + } +} + +data class AgentDesign( + val name: String? = null, + val path: String? = null, + val description: String? = null, + val mainInput: DataInfo? = null, + val logicFlow: LogicFlow? = null, + val actors: List? = null, +) : ValidatedObject { + override fun validate(): String? = when { + null == logicFlow -> "logicFlow is required" + null == actors -> "actors is required" + actors.isEmpty() -> "actors is required" + null != logicFlow.validate() -> logicFlow.validate() + !actors.all { null == it.validate() } -> actors.map { it.validate() }.filter { null != it }.joinToString("\n") + + else -> null + } +} + +data class AgentActorDesign( + val actors: List? = null, +) : ValidatedObject { + override fun validate(): String? = when { + null == actors -> "actors is required" + actors.isEmpty() -> "actors is required" + !actors.all { null == it.validate() } -> actors.map { it.validate() }.filter { null != it }.joinToString("\n") + + else -> null + } +} + +data class ActorDesign( + @Description("Java class name of the actor") val name: String = "", + val description: String? = null, + @Description("simple, parsed, image, tts, or coding") val type: String = "", + @Description("Simple actors: string; Image actors: image; Coding actors: code; Text-to-speech actors: mp3; Parsed actors: a simple java class name for the data structure") val resultClass: String = "", +) : ValidatedObject { + val simpleClassName: String get() = resultClass.split(".").last() + override fun validate(): String? = when { + name.isEmpty() -> "name is required" + name.chars().anyMatch { !Character.isJavaIdentifierPart(it) } -> "name must be a valid java identifier" + type.isEmpty() -> "type is required" + type.lowercase().notIn( + "simple", "parsed", "coding", "image", "tts" + ) -> "type must be simple, parsed, coding, tts, or image" + + resultClass.isEmpty() -> "resultType is required" + resultClass.lowercase().notIn( + "string", "code", "image", "mp3" + ) && !validClassName(resultClass) -> "resultType must be string, code, image, mp3, or a valid class name" + + else -> null + } + + private fun validClassName(resultType: String): Boolean { + return when { + resultType.isEmpty() -> false + validClassNamePattern.matches(resultType) -> true + else -> false + } + } + + companion object { + val validClassNamePattern = "[A-Za-z][a-zA-Z0-9_<>.]{3,}".toRegex() + } + +} + +data class LogicFlow( + val items: List? = null, +) : ValidatedObject { + override fun validate(): String? = items?.map { it.validate() }?.firstOrNull { !it.isNullOrBlank() } +} + +data class LogicFlowItem( + val name: String? = null, + val description: String? = null, + val actors: List? = null, + @Description("symbol names of variables/values used as input to this step") val inputs: List? = null, + @Description("description of the output of this step") val output: DataInfo? = null, +) : ValidatedObject { + override fun validate(): String? = when { + null == name -> "name is required" + name.isEmpty() -> "name is required" + //inputs?.isEmpty() != false && inputs?.isEmpty() != false -> "inputs is required" + else -> null + } +} + +data class DataInfo( + val name: String? = null, + val description: String? = null, + val type: String? = null, +) : ValidatedObject { + override fun validate(): String? = when { + null == name -> "name is required" + name.isEmpty() -> "name is required" + null == type -> "type is required" + type.isEmpty() -> "type is required" + else -> null + } +} + diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ParsedActorDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ParsedActorDesigner.kt new file mode 100644 index 00000000..74978eac --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/ParsedActorDesigner.kt @@ -0,0 +1,79 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.jopenai.models.OpenAIModels +import com.simiacryptus.skyenet.apps.meta.FlowStepDesigner.Companion.fixups +import com.simiacryptus.skyenet.core.actors.CodingActor +import com.simiacryptus.skyenet.interpreter.Interpreter +import com.simiacryptus.skyenet.kotlin.KotlinInterpreter +import kotlin.reflect.KClass + +class ParsedActorDesigner( + interpreterClass: KClass = KotlinInterpreter::class, + symbols: Map = mapOf(), + model: ChatModels = OpenAIModels.GPT4o, + temperature: Double = 0.3, +) : CodingActor( + interpreterClass = interpreterClass, + symbols = symbols, + details = """ + | + |Your task is to design a system that uses gpt "actors" to form a "community" of actors interacting to solve problems. + |Your task is to implement a "parsed" actor that takes part in a larger system. + |"Parsed" actors use a 2-stage system; first, queries are responded in the same manner as simple actors. A second pass uses GPT3.5_Turbo to parse the text response into a predefined kotlin data class + | + |For context, here is the constructor signature for ParsedActor class: + |```kotlin + |import com.simiacryptus.jopenai.models.ChatModels + |import com.simiacryptus.jopenai.models.OpenAIModels + |import java.util.function.Function + | + |open class ParsedActor( + | val resultClass: Class, + | val exampleInstance: T? = resultClass.getConstructor().newInstance(), + | prompt: String, + | val name: String? = null, + | model: ChatModels = OpenAIModels.GPT4oMini, + | temperature: Double = 0.3, + |) + |``` + | + |In this code example an example actor is defined with a prompt, name, and parsing class: + |```kotlin + |import com.simiacryptus.jopenai.describe.Description + |import com.simiacryptus.jopenai.models.ChatModels + |import com.simiacryptus.jopenai.proxy.ValidatedObject + |import com.simiacryptus.skyenet.core.actors.ParsedActor + |import java.util.function.Function + | + |data class ExampleResult( + | @Description("The name of the example") + | val name: String? = null, + |) : ValidatedObject { + | override fun validate() = when { + | name.isNullOrBlank() -> "name is required" + | else -> null + | } + |} + | + |fun exampleParsedActor() = ParsedActor( + | resultClass = ExampleResult::class.java, + | model = OpenAIModels.GPT4o, + | prompt = ""${'"'} + | |You are a question answering assistant. + | |""${'"'}.trimMargin().trim(), + |) + |``` + | + |Respond to the request with an instantiation function of the requested actor, similar to the provided example. + |DO NOT subclass the ParsedActor class. Use the constructor directly within the function. + | + """.trimMargin().trim(), + model = model, + temperature = temperature, +) { + init { + evalFormat = false + codeInterceptor = { fixups(it) } + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/SimpleActorDesigner.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/SimpleActorDesigner.kt new file mode 100644 index 00000000..f0479c64 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/meta/SimpleActorDesigner.kt @@ -0,0 +1,60 @@ +package com.simiacryptus.skyenet.apps.meta + +import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.skyenet.apps.meta.FlowStepDesigner.Companion.fixups +import com.simiacryptus.skyenet.core.actors.CodingActor +import com.simiacryptus.skyenet.interpreter.Interpreter +import kotlin.reflect.KClass + +class SimpleActorDesigner( + interpreterClass: KClass, + symbols: Map, + model: ChatModels, + temperature: Double +) : CodingActor( + interpreterClass = interpreterClass, + symbols = symbols, + model = model, + temperature = temperature, + details = """ + You are a software implementation assistant. + Your task is to implement a "simple" actor that takes part in a larger system. + "Simple" actors contain a system directive and can process a list of user messages into a response. + + For context, here is the constructor signature for SimpleActor class: + ```kotlin + import com.simiacryptus.jopenai.models.ChatModels + import com.simiacryptus.skyenet.core.actors.SimpleActor + import org.intellij.lang.annotations.Language + import com.simiacryptus.jopenai.models.ChatModels + + class SimpleActor( + prompt: String, + name: String? = null, + model: ChatModels = OpenAIModels.GPT4oMini, + temperature: Double = 0.3, + ) + ``` + + In this code example an example actor is defined with a prompt and a name: + ```kotlin + import com.simiacryptus.skyenet.core.actors.SimpleActor + import com.simiacryptus.skyenet.heart.KotlinInterpreter + import org.intellij.lang.annotations.Language + + @Language("Markdown")fun exampleSimpleActor() = SimpleActor( + prompt = "${'"'}" + |You are a writing assistant. + "${'"'}".trimMargin().trim(), + ) + ``` + + Respond to the request with an instantiation function of the requested actor, similar to the provided example. + DO NOT subclass the SimpleActor class. Use the constructor directly within the function. + """.trimIndent() +) { + init { + evalFormat = false + codeInterceptor = { fixups(it) } + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DefaultParsingModel.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DefaultParsingModel.kt similarity index 99% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DefaultParsingModel.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DefaultParsingModel.kt index 94f82560..589bb93a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DefaultParsingModel.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DefaultParsingModel.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.apps.general.parsers +package com.simiacryptus.skyenet.apps.parsers import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.describe.Description diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DocumentParserApp.kt similarity index 97% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DocumentParserApp.kt index 24ca2d96..477a4b36 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DocumentParserApp.kt @@ -1,15 +1,10 @@ -package com.simiacryptus.skyenet.apps.general +package com.simiacryptus.skyenet.apps.parsers import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.AnthropicModels -import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.apps.general.parsers.DefaultParsingModel -import com.simiacryptus.skyenet.apps.general.parsers.PDFReader -import com.simiacryptus.skyenet.apps.general.parsers.ParsingModel -import com.simiacryptus.skyenet.apps.general.parsers.TextReader import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DocumentRecord.kt similarity index 99% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DocumentRecord.kt index 2a3b874d..996b76d0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/DocumentRecord.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.apps.general.parsers +package com.simiacryptus.skyenet.apps.parsers import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.OpenAIClient diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/PDFReader.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/PDFReader.kt similarity index 88% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/PDFReader.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/PDFReader.kt index f30634d9..7bb6c90d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/PDFReader.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/PDFReader.kt @@ -1,6 +1,5 @@ -package com.simiacryptus.skyenet.apps.general.parsers +package com.simiacryptus.skyenet.apps.parsers -import com.simiacryptus.skyenet.apps.general.DocumentParserApp import org.apache.pdfbox.pdmodel.PDDocument import org.apache.pdfbox.rendering.PDFRenderer import org.apache.pdfbox.text.PDFTextStripper diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/ParsingModel.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/ParsingModel.kt similarity index 87% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/ParsingModel.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/ParsingModel.kt index f73f3d90..7ffd7c62 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/ParsingModel.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/ParsingModel.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.apps.general.parsers +package com.simiacryptus.skyenet.apps.parsers import com.simiacryptus.jopenai.API diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/TextReader.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/TextReader.kt similarity index 83% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/TextReader.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/TextReader.kt index 8292ff1c..d91bb0f9 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/TextReader.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/parsers/TextReader.kt @@ -1,6 +1,5 @@ -package com.simiacryptus.skyenet.apps.general.parsers +package com.simiacryptus.skyenet.apps.parsers -import com.simiacryptus.skyenet.apps.general.DocumentParserApp import java.awt.image.BufferedImage import java.io.File 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 b4d05829..7afb49e3 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,15 +1,12 @@ package com.simiacryptus.skyenet.apps.plan -import com.simiacryptus.diff.FileValidationUtils import com.simiacryptus.jopenai.API 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.FileSystems -import java.nio.file.Files import java.nio.file.Path -import kotlin.streams.asSequence + abstract class AbstractTask( val planSettings: PlanSettings, @@ -37,37 +34,6 @@ abstract class AbstractTask( """.trimMargin() } ?: "" - protected 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 { - """ - |# $relativePath - | - |$TRIPLE_TILDE - |${codeFiles[file.toPath()] ?: file.readText()} - |$TRIPLE_TILDE - """.trimMargin() - } catch (e: Throwable) { - log.warn("Error reading file: $relativePath", e) - "" - } - } protected fun acceptButtonFooter(ui: ApplicationInterface, fn: () -> Unit): String { val footerTask = ui.newTask(false) 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 5f88c199..1be71058 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 @@ -24,15 +24,11 @@ class CommandAutoFixTask( val workingDir: String? = null, task_description: String? = null, task_dependencies: List? = null, - input_files: List? = null, - output_files: List? = null, state: TaskState? = null ) : PlanTaskBase( task_type = TaskType.CommandAutoFix.name, task_description = task_description, task_dependencies = task_dependencies, - input_files = input_files, - output_files = output_files, state = state ) 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 33b644c1..a0a1eb69 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 @@ -20,15 +20,11 @@ class ForeachTask( val foreach_subplan: Map? = null, task_description: String? = null, task_dependencies: List? = null, - input_files: List? = null, - output_files: List? = null, state: TaskState? = null, ) : PlanTaskBase( task_type = TaskType.ForeachTask.name, task_description = task_description, task_dependencies = task_dependencies, - input_files = input_files, - output_files = output_files, state = state ) 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 index 54447a77..f854ac1d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt @@ -3,7 +3,7 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.skyenet.apps.plan.CommandAutoFixTask.CommandAutoFixTaskData -import com.simiacryptus.skyenet.apps.plan.FileModificationTask.FileModificationTaskData +import com.simiacryptus.skyenet.apps.plan.file.FileModificationTask.FileModificationTaskData import com.simiacryptus.skyenet.apps.plan.PlanUtil.isWindows import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanningTaskData import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownResult @@ -149,7 +149,6 @@ ${taskType.name}: "3" to PlanningTaskData( task_description = "Task 3", task_dependencies = listOf("2"), - input_files = listOf("input3.txt"), ) ), ) 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 index 7b4be204..1fa4dd40 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt @@ -126,7 +126,7 @@ object PlanUtil { fun filterPlan(retries: Int = 3, fn: () -> Map?): Map? { val obj = fn() ?: emptyMap() - var tasksByID = obj?.filter { (k, v) -> + val tasksByID = obj.filter { (k, v) -> when { v.task_type == TaskType.TaskPlanning.name && v.task_dependencies.isNullOrEmpty() -> if (retries <= 0) { @@ -136,9 +136,10 @@ object PlanUtil { log.info("TaskPlanning task $k has no dependencies") return filterPlan(retries - 1, fn) } + else -> true } - } ?: emptyMap() + } tasksByID.forEach { it.value.task_dependencies = it.value.task_dependencies?.filter { it in tasksByID.keys } it.value.state = TaskState.Pending @@ -154,7 +155,7 @@ object PlanUtil { return filterPlan(retries - 1, fn) } } - return if (tasksByID.size == obj?.size) { + return if (tasksByID.size == obj.size) { obj } else filterPlan { tasksByID 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 f17d50a2..d0fa7824 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 @@ -10,6 +10,7 @@ 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.PlanningTask.PlanningTaskData +import com.simiacryptus.skyenet.apps.plan.file.AbstractFileTask import com.simiacryptus.skyenet.core.actors.ParsedResponse import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask @@ -20,18 +21,15 @@ class PlanningTask( planSettings: PlanSettings, planTask: PlanningTaskData? ) : AbstractTask(planSettings, planTask) { + class PlanningTaskData( task_description: String? = null, task_dependencies: List? = null, - input_files: List? = null, - output_files: List? = null, state: TaskState? = TaskState.Pending, ) : PlanTaskBase( task_type = TaskType.TaskPlanning.name, task_description = task_description, task_dependencies = task_dependencies, - input_files = input_files, - output_files = output_files, state = state ) @@ -40,7 +38,6 @@ class PlanningTask( val tasksByID: Map? = null, ) - override fun promptSegment(): String { return """ |Task Planning: @@ -69,7 +66,6 @@ class PlanningTask( userMessage, JsonUtil.toJson(plan.entries.associate { it.key to it.value }), getPriorCode(planProcessingState), - getInputFileCode(), s ).filter { it.isNotBlank() } val subPlan = if (planSettings.allowBlocking && !planSettings.autoFix) { 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 f629653d..35f6cdc9 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 @@ -5,6 +5,7 @@ import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.skyenet.apps.coding.CodingAgent import com.simiacryptus.skyenet.apps.plan.RunShellCommandTask.RunShellCommandTaskData +import com.simiacryptus.skyenet.apps.plan.file.AbstractFileTask import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.interpreter.ProcessInterpreter import com.simiacryptus.skyenet.webui.session.SessionTask @@ -26,15 +27,11 @@ class RunShellCommandTask( val workingDir: String? = null, task_description: String? = null, task_dependencies: List? = null, - input_files: List? = null, - output_files: List? = null, state: TaskState? = null ) : PlanTaskBase( task_type = TaskType.RunShellCommand.name, task_description = task_description, task_dependencies = task_dependencies, - input_files = input_files, - output_files = output_files, state = state ) @@ -141,7 +138,6 @@ Note: This task is for running simple and safe commands. Avoid executing command userMessage to ApiModel.Role.user, JsonUtil.toJson(plan) to ApiModel.Role.assistant, getPriorCode(planProcessingState) to ApiModel.Role.assistant, - getInputFileCode() to ApiModel.Role.assistant, ) ) ) 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 2561fd28..fa2fde7f 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 @@ -1,7 +1,6 @@ package com.simiacryptus.skyenet.apps.plan import com.fasterxml.jackson.annotation.JsonTypeInfo -import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize @@ -9,20 +8,20 @@ import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.skyenet.apps.plan.AbstractTask.TaskState -import com.simiacryptus.skyenet.apps.plan.CodeOptimizationTask.CodeOptimizationTaskData -import com.simiacryptus.skyenet.apps.plan.CodeReviewTask.CodeReviewTaskData +import com.simiacryptus.skyenet.apps.plan.file.CodeOptimizationTask.CodeOptimizationTaskData +import com.simiacryptus.skyenet.apps.plan.file.CodeReviewTask.CodeReviewTaskData import com.simiacryptus.skyenet.apps.plan.CommandAutoFixTask.CommandAutoFixTaskData -import com.simiacryptus.skyenet.apps.plan.DocumentationTask.DocumentationTaskData -import com.simiacryptus.skyenet.apps.plan.FileModificationTask.FileModificationTaskData +import com.simiacryptus.skyenet.apps.plan.file.DocumentationTask.DocumentationTaskData +import com.simiacryptus.skyenet.apps.plan.file.FileModificationTask.FileModificationTaskData import com.simiacryptus.skyenet.apps.plan.ForeachTask.ForeachTaskData -import com.simiacryptus.skyenet.apps.plan.InquiryTask.InquiryTaskData -import com.simiacryptus.skyenet.apps.plan.PerformanceAnalysisTask.PerformanceAnalysisTaskData +import com.simiacryptus.skyenet.apps.plan.file.InquiryTask.InquiryTaskData +import com.simiacryptus.skyenet.apps.plan.file.PerformanceAnalysisTask.PerformanceAnalysisTaskData import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanningTaskData -import com.simiacryptus.skyenet.apps.plan.RefactorTask.RefactorTaskData +import com.simiacryptus.skyenet.apps.plan.file.RefactorTask.RefactorTaskData import com.simiacryptus.skyenet.apps.plan.RunShellCommandTask.RunShellCommandTaskData -import com.simiacryptus.skyenet.apps.plan.SecurityAuditTask.SecurityAuditTaskData -import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.valueOf -import com.simiacryptus.skyenet.apps.plan.TestGenerationTask.TestGenerationTaskData +import com.simiacryptus.skyenet.apps.plan.file.* +import com.simiacryptus.skyenet.apps.plan.file.SecurityAuditTask.SecurityAuditTaskData +import com.simiacryptus.skyenet.apps.plan.file.TestGenerationTask.TestGenerationTaskData import com.simiacryptus.util.DynamicEnum import com.simiacryptus.util.DynamicEnumDeserializer import com.simiacryptus.util.DynamicEnumSerializer @@ -112,7 +111,6 @@ class TaskType( @JsonTypeIdResolver(PlanTaskTypeIdResolver::class) @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "task_type") -//@JsonDeserialize(using = PlanTaskBaseDeserializer::class) abstract class PlanTaskBase( @Description("An enumeration indicating the type of task to be executed. Must be a single value from the TaskType enum.") val task_type: String? = null, @@ -120,10 +118,6 @@ abstract class PlanTaskBase( var task_description: String? = null, @Description("A list of IDs of tasks that must be completed before this task can be executed. This defines upstream dependencies ensuring proper task order and information flow.") var task_dependencies: List? = null, - @Description("A list of file paths specifying the input files required by this task. These may be outputs from dependent tasks, facilitating data transfer between tasks.") - val input_files: List? = null, - @Description("A list of file paths specifying the output files generated by this task. These may serve as inputs for subsequent tasks, enabling information sharing.") - val output_files: List? = null, @Description("The current execution state of the task. Important for coordinating task execution and managing dependencies.") var state: TaskState? = null ) @@ -155,15 +149,4 @@ class PlanTaskTypeIdResolver : TypeIdResolverBase() { } class TaskTypeSerializer : DynamicEnumSerializer>(TaskType::class.java) -class TaskTypeDeserializer : DynamicEnumDeserializer>(TaskType::class.java) -class PlanTaskBaseDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PlanTaskBase { - val node = ctxt.readTree(p) - val taskType = node.get("task_type")?.asText() - if (taskType == null) { - throw JsonMappingException(p, "Missing task_type") - } - val baseTaskType = valueOf(taskType) - return ctxt.readValue(node.traverse(p.codec), baseTaskType.taskDataClass) - } -} +class TaskTypeDeserializer : DynamicEnumDeserializer>(TaskType::class.java) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/AbstractAnalysisTask.kt similarity index 93% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/AbstractAnalysisTask.kt index 8fa9653f..8df2b44e 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/AbstractAnalysisTask.kt @@ -1,19 +1,20 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.apps.general.CommandPatchApp import com.simiacryptus.skyenet.apps.general.PatchApp +import com.simiacryptus.skyenet.apps.plan.* import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.io.File -abstract class AbstractAnalysisTask( +abstract class AbstractAnalysisTask( planSettings: PlanSettings, planTask: T? -) : AbstractTask(planSettings, planTask) { +) : AbstractFileTask(planSettings, planTask) { abstract val actorName: String abstract val actorPrompt: String diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/AbstractFileTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/AbstractFileTask.kt new file mode 100644 index 00000000..247d2027 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/AbstractFileTask.kt @@ -0,0 +1,68 @@ +package com.simiacryptus.skyenet.apps.plan.file + +import com.simiacryptus.diff.FileValidationUtils +import com.simiacryptus.skyenet.apps.plan.AbstractTask +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.PlanTaskBase +import com.simiacryptus.skyenet.apps.plan.file.AbstractFileTask.FileTaskBase +import java.nio.file.FileSystems +import java.nio.file.Files +import kotlin.streams.asSequence + +abstract class AbstractFileTask( + planSettings: PlanSettings, + planTask: T? +) : AbstractTask(planSettings, planTask) { + + open class FileTaskBase( + task_type: String, + task_description: String? = null, + task_dependencies: List? = null, + val input_files: List? = null, + val output_files: List? = null, + state: TaskState? = TaskState.Pending, + ) : PlanTaskBase( + task_type = task_type, + task_description = task_description, + task_dependencies = task_dependencies, + state = state + ) + + protected 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 { + """ + |# $relativePath + | + |$TRIPLE_TILDE + |${codeFiles[file.toPath()] ?: file.readText()} + |$TRIPLE_TILDE + """.trimMargin() + } catch (e: Throwable) { + log.warn("Error reading file: $relativePath", e) + "" + } + } + + companion object { + private val log = org.slf4j.LoggerFactory.getLogger(AbstractFileTask::class.java) + private const val TRIPLE_TILDE = "```" + + } +} \ 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/file/CodeOptimizationTask.kt similarity index 90% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/CodeOptimizationTask.kt index c14e8f0e..b3547cc0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/CodeOptimizationTask.kt @@ -1,8 +1,9 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.skyenet.apps.plan.CodeOptimizationTask.CodeOptimizationTaskData - +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskType +import com.simiacryptus.skyenet.apps.plan.file.CodeOptimizationTask.CodeOptimizationTaskData import org.slf4j.LoggerFactory class CodeOptimizationTask( @@ -20,7 +21,7 @@ class CodeOptimizationTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.Optimization.name, task_description = task_description, task_dependencies = task_dependencies, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/CodeReviewTask.kt similarity index 90% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/CodeReviewTask.kt index 65f5c4f9..2a293e9f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/CodeReviewTask.kt @@ -1,7 +1,9 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.skyenet.apps.plan.CodeReviewTask.CodeReviewTaskData +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskType +import com.simiacryptus.skyenet.apps.plan.file.CodeReviewTask.CodeReviewTaskData import org.slf4j.LoggerFactory class CodeReviewTask( @@ -18,7 +20,7 @@ class CodeReviewTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.CodeReview.name, task_description = task_description, task_dependencies = task_dependencies, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/DocumentationTask.kt similarity index 93% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/DocumentationTask.kt index cf02797f..5bb9b6e1 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/DocumentationTask.kt @@ -1,9 +1,10 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.apps.plan.DocumentationTask.DocumentationTaskData +import com.simiacryptus.skyenet.apps.plan.* +import com.simiacryptus.skyenet.apps.plan.file.DocumentationTask.DocumentationTaskData import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask @@ -14,7 +15,7 @@ import java.util.concurrent.Semaphore class DocumentationTask( planSettings: PlanSettings, planTask: DocumentationTaskData? -) : AbstractTask(planSettings, planTask) { +) : AbstractFileTask(planSettings, planTask) { class DocumentationTaskData( @Description("List of files or tasks to be documented") val items_to_document: List? = null, @@ -23,7 +24,7 @@ class DocumentationTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.Documentation.name, task_description = task_description, task_dependencies = task_dependencies, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/FileModificationTask.kt similarity index 94% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/FileModificationTask.kt index 3cf4f665..4959c22b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/FileModificationTask.kt @@ -1,10 +1,11 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.apps.plan.FileModificationTask.FileModificationTaskData +import com.simiacryptus.skyenet.apps.plan.* +import com.simiacryptus.skyenet.apps.plan.file.FileModificationTask.FileModificationTaskData import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask @@ -15,7 +16,7 @@ import java.util.concurrent.Semaphore class FileModificationTask( planSettings: PlanSettings, planTask: FileModificationTaskData? -) : AbstractTask(planSettings, planTask) { +) : AbstractFileTask(planSettings, planTask) { class FileModificationTaskData( @Description("List of input files to be examined when designing the modifications") input_files: List? = null, @@ -26,7 +27,7 @@ class FileModificationTask( task_description: String? = null, task_dependencies: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.FileModification.name, task_description = task_description, task_dependencies = task_dependencies, @@ -55,7 +56,7 @@ class FileModificationTask( Response format: For existing files: Use ${TRIPLE_TILDE}diff code blocks with a header specifying the file path. - For new files: Use ${TRIPLE_TILDE} code blocks with a header specifying the new file path. + For new files: Use $TRIPLE_TILDE code blocks with a header specifying the new file path. The diff format should use + for line additions, - for line deletions. Include 2 lines of context before and after every change in diffs. Separate code blocks with a single blank line. @@ -72,7 +73,7 @@ class FileModificationTask( return 'old result'; return 'new result'; | } - ${TRIPLE_TILDE} + $TRIPLE_TILDE ### src/utils/newFile.js ${TRIPLE_TILDE}js @@ -80,7 +81,7 @@ class FileModificationTask( function newFunction() { return 'new functionality'; |} - ${TRIPLE_TILDE} + $TRIPLE_TILDE """.trimMargin(), model = planSettings.getTaskSettings(TaskType.FileModification).model ?: planSettings.defaultModel, temperature = planSettings.temperature, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/InquiryTask.kt similarity index 95% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/InquiryTask.kt index a81a7c4b..1c8e4dad 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/InquiryTask.kt @@ -1,10 +1,11 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList import com.simiacryptus.skyenet.Discussable +import com.simiacryptus.skyenet.apps.plan.* import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask @@ -16,7 +17,7 @@ import java.util.concurrent.atomic.AtomicReference class InquiryTask( planSettings: PlanSettings, planTask: InquiryTaskData? -) : AbstractTask(planSettings, planTask) { +) : AbstractFileTask(planSettings, planTask) { class InquiryTaskData( @Description("The specific questions or topics to be addressed in the inquiry") val inquiry_questions: List? = null, @@ -27,7 +28,7 @@ class InquiryTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.Inquiry.name, task_description = task_description, task_dependencies = task_dependencies, @@ -48,8 +49,8 @@ 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 (${ - planSettings.taskSettings.filter { it.value.enabled }.keys.joinToString(", ") - }). + planSettings.taskSettings.filter { it.value.enabled }.keys.joinToString(", ") + }). This will ensure that the inquiries are tailored to assist in the planning and execution of tasks within the system's framework. """.trimMargin(), model = planSettings.getTaskSettings(TaskType.valueOf(planTask?.task_type!!)).model diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/PerformanceAnalysisTask.kt similarity index 90% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/PerformanceAnalysisTask.kt index 47be71b8..4b5924a2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/PerformanceAnalysisTask.kt @@ -1,10 +1,11 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.skyenet.apps.plan.PerformanceAnalysisTask.PerformanceAnalysisTaskData +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskType +import com.simiacryptus.skyenet.apps.plan.file.PerformanceAnalysisTask.PerformanceAnalysisTaskData import org.slf4j.LoggerFactory - class PerformanceAnalysisTask( planSettings: PlanSettings, planTask: PerformanceAnalysisTaskData? @@ -19,7 +20,7 @@ class PerformanceAnalysisTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null, - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.PerformanceAnalysis.name, task_description = task_description, task_dependencies = task_dependencies, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/RefactorTask.kt similarity index 90% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/RefactorTask.kt index eb7cd1e0..32ffcb6a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/RefactorTask.kt @@ -1,7 +1,9 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.skyenet.apps.plan.RefactorTask.RefactorTaskData +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskType +import com.simiacryptus.skyenet.apps.plan.file.RefactorTask.RefactorTaskData import org.slf4j.LoggerFactory class RefactorTask( @@ -18,7 +20,7 @@ class RefactorTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.RefactorTask.name, task_description = task_description, task_dependencies = task_dependencies, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/SecurityAuditTask.kt similarity index 89% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/SecurityAuditTask.kt index a5a4b34f..5b6e22f7 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/SecurityAuditTask.kt @@ -1,7 +1,9 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.skyenet.apps.plan.SecurityAuditTask.SecurityAuditTaskData +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskType +import com.simiacryptus.skyenet.apps.plan.file.SecurityAuditTask.SecurityAuditTaskData import org.slf4j.LoggerFactory class SecurityAuditTask( @@ -20,7 +22,7 @@ class SecurityAuditTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.SecurityAudit.name, task_description = task_description, task_dependencies = task_dependencies, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/TestGenerationTask.kt similarity index 88% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/TestGenerationTask.kt index 88b69e11..5d1c8c87 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/file/TestGenerationTask.kt @@ -1,7 +1,9 @@ -package com.simiacryptus.skyenet.apps.plan +package com.simiacryptus.skyenet.apps.plan.file import com.simiacryptus.jopenai.describe.Description -import com.simiacryptus.skyenet.apps.plan.TestGenerationTask.TestGenerationTaskData +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskType +import com.simiacryptus.skyenet.apps.plan.file.TestGenerationTask.TestGenerationTaskData import org.slf4j.LoggerFactory class TestGenerationTask( @@ -19,7 +21,7 @@ class TestGenerationTask( input_files: List? = null, output_files: List? = null, state: TaskState? = null - ) : PlanTaskBase( + ) : FileTaskBase( task_type = TaskType.TestGeneration.name, task_description = task_description, task_dependencies = task_dependencies, @@ -43,7 +45,7 @@ class TestGenerationTask( |Add comments explaining the purpose of each test case. | |Response format: - |- Use ${TRIPLE_TILDE} code blocks with a header specifying the new test file path. + |- Use ${com.simiacryptus.skyenet.apps.plan.TRIPLE_TILDE} code blocks with a header specifying the new test file path. |- Specify the language for syntax highlighting after the opening triple backticks. |- Separate code blocks with a single blank line. | @@ -52,7 +54,7 @@ class TestGenerationTask( |Here are the generated unit tests: | |### src/test/java/com/example/UtilsTest.java - |${TRIPLE_TILDE}java + |${com.simiacryptus.skyenet.apps.plan.TRIPLE_TILDE}java |import org.junit.jupiter.api.Test; |import static org.junit.jupiter.api.Assertions.*; | @@ -72,7 +74,7 @@ class TestGenerationTask( | }); | } |} - ${TRIPLE_TILDE} + ${com.simiacryptus.skyenet.apps.plan.TRIPLE_TILDE} """.trimMargin() override fun getAnalysisInstruction(): String = "Generate tests for the following code" diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/AppInfoData.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/AppInfoData.kt new file mode 100644 index 00000000..2e776159 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/AppInfoData.kt @@ -0,0 +1,14 @@ +package com.simiacryptus.skyenet.webui.application + +data class AppInfoData( + val applicationName: String, + val singleInput: Boolean, + val stickyInput: Boolean, + val loadImages: Boolean, + val showMenubar: Boolean +) { + + fun toMap(): Map { + return this::class.java.declaredFields.associate { it.name to it.get(this) } + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt index ad3adff5..a9fe8020 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt @@ -1,16 +1,19 @@ package com.simiacryptus.skyenet.webui.application import com.simiacryptus.jopenai.API -import com.simiacryptus.util.JsonUtil -import com.simiacryptus.skyenet.core.platform.* import com.simiacryptus.skyenet.core.platform.ApplicationServices.authenticationManager import com.simiacryptus.skyenet.core.platform.ApplicationServices.authorizationManager import com.simiacryptus.skyenet.core.platform.ApplicationServices.dataStorageFactory import com.simiacryptus.skyenet.core.platform.ApplicationServicesConfig.dataStorageRoot +import com.simiacryptus.skyenet.core.platform.AuthenticationInterface import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType +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.webui.chat.ChatServer import com.simiacryptus.skyenet.webui.servlet.* import com.simiacryptus.skyenet.webui.session.SocketManager +import com.simiacryptus.util.JsonUtil import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.eclipse.jetty.servlet.FilterHolder @@ -29,20 +32,20 @@ abstract class ApplicationServer( open val description: String = "" open val singleInput = true open val stickyInput = false - open val appInfo: Map by lazy { - mapOf( - "applicationName" to applicationName, - "singleInput" to singleInput, - "stickyInput" to stickyInput, - "loadImages" to false, - "showMenubar" to showMenubar, + open fun appInfo(session: Session) = appInfoMap.getOrPut(session) { + AppInfoData( + applicationName = applicationName, + singleInput = singleInput, + stickyInput = stickyInput, + loadImages = false, + showMenubar = showMenubar ) - } + }.toMap() final override val dataStorage: StorageInterface by lazy { dataStorageFactory(dataStorageRoot) } - protected open val appInfoServlet by lazy { ServletHolder("appInfo", AppInfoServlet { - session -> sessionAppInfoMap[session] ?: appInfo + protected open val appInfoServlet by lazy { ServletHolder("appInfo", AppInfoServlet { session -> + appInfo(Session(session!!)) }) } protected open val userInfo by lazy { ServletHolder("userInfo", UserInfoServlet()) } protected open val usageServlet by lazy { ServletHolder("usage", UsageServlet()) } @@ -180,8 +183,8 @@ abstract class ApplicationServer( fun HttpServletRequest.getCookie(name: String = AuthenticationInterface.AUTH_COOKIE) = cookies?.find { it.name == name }?.value - val sessionAppInfoMap = mutableMapOf>() + val appInfoMap = mutableMapOf() } } \ No newline at end of file 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 2f5fbfe1..c197734d 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt @@ -2,7 +2,7 @@ package com.simiacryptus.skyenet.webui import com.simiacryptus.jopenai.models.OpenAIModels import com.simiacryptus.jopenai.util.ClientUtil.keyTxt -import com.simiacryptus.skyenet.apps.general.DocumentParserApp +import com.simiacryptus.skyenet.apps.parsers.DocumentParserApp import com.simiacryptus.skyenet.apps.general.PlanAheadApp import com.simiacryptus.skyenet.apps.general.StressTestApp import com.simiacryptus.skyenet.apps.plan.PlanSettings