diff --git a/CHANGELOG.md b/CHANGELOG.md index 558ddc8f..aa02e559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ ### Added - +## [1.0.19] + +### Improved +- Quality for generated projects + ## [1.0.18] ### Improved diff --git a/gradle.properties b/gradle.properties index 0cbaaee5..347f1af4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.github.simiacryptus pluginName = intellij-aicoder pluginRepositoryUrl = https://github.com/SimiaCryptus/intellij-aicoder # SemVer format -> https://semver.org -pluginVersion = 1.0.18 +pluginVersion = 1.0.19 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 203 diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/SoftwareProjectAI.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/SoftwareProjectAI.kt index a93a5cac..ab188038 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/SoftwareProjectAI.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/SoftwareProjectAI.kt @@ -19,7 +19,7 @@ interface SoftwareProjectAI { data class Project( val name: String? = "", val description: String? = "", - val language: String? = "", + val languages: List? = listOf(), val features: List? = listOf(), val libraries: List? = listOf(), val buildTools: List? = listOf(), @@ -42,16 +42,17 @@ interface SoftwareProjectAI { @Description("Documentation files e.g. README.md, LICENSE, etc.") val documents: List? = listOf(), @Description("Individual test cases") - val tests: List? = listOf(), + val testCases: List? = listOf(), ) : ValidatedObject data class ComponentDetails( val name: String? = "", val description: String? = "", - val features: List? = listOf(), - ) : ValidatedObject + val requirements: List? = listOf(), + val dependencies: List? = listOf(), + ) - data class TestDetails( + data class TestCase( val name: String? = "", val steps: List? = listOf(), val expectations: List? = listOf(), @@ -63,33 +64,36 @@ interface SoftwareProjectAI { val sections: List? = listOf(), ) : ValidatedObject - fun buildProjectFileSpecifications( - project: Project, - requirements: ProjectStatements, - design: ProjectDesign, - recursive: Boolean = true - ): List + data class CodeSpecifications( + val specifications: List? = listOf(), + ) : ValidatedObject - fun buildComponentFileSpecifications( + fun getComponentFiles( project: Project, requirements: ProjectStatements, design: ComponentDetails, - recursive: Boolean = true - ): List + ): CodeSpecifications - fun buildTestFileSpecifications( + fun getTestFiles( project: Project, requirements: ProjectStatements, - design: TestDetails, - recursive: Boolean = true - ): List + test: TestCase, + ): TestSpecifications + + data class TestSpecifications( + val specifications: List? = listOf(), + ) : ValidatedObject fun buildDocumentationFileSpecifications( project: Project, requirements: ProjectStatements, design: DocumentationDetails, - recursive: Boolean = true - ): List + ): DocumentSpecifications + + data class DocumentSpecifications( + @Description("Specifications for each human-language document. Does not include code files.") + val documents: List? = listOf(), + ) : ValidatedObject data class CodeSpecification( val description: String? = "", @@ -98,7 +102,12 @@ interface SoftwareProjectAI { val publicMethodSignatures: List? = listOf(), val language: String? = "", val location: FilePath? = FilePath(), - ) : ValidatedObject + ) : ValidatedObject { + override fun validate(): Boolean { + if(description?.isBlank() != false) return false + return super.validate() + } + } data class DocumentSpecification( val description: String? = "", @@ -106,7 +115,12 @@ interface SoftwareProjectAI { val sections: List? = listOf(), val language: String? = "", val location: FilePath? = FilePath(), - ) : ValidatedObject + ) : ValidatedObject { + override fun validate(): Boolean { + if(description?.isBlank() != false) return false + return super.validate() + } + } data class TestSpecification( val description: String? = "", @@ -115,7 +129,12 @@ interface SoftwareProjectAI { val expectations: List? = listOf(), val language: String? = "", val location: FilePath? = FilePath(), - ) : ValidatedObject + ) : ValidatedObject { + override fun validate(): Boolean { + if(description?.isBlank() != false) return false + return super.validate() + } + } data class FilePath( @Description("File name relative to project root, e.g. src/main/java/Foo.java") @@ -131,7 +150,7 @@ interface SoftwareProjectAI { } } - fun implementComponentSpecification( + fun implementCode( project: Project, component: ComponentDetails, imports: List, @@ -139,27 +158,32 @@ interface SoftwareProjectAI { ): SourceCode - fun implementTestSpecification( + fun implementTest( project: Project, - specification: TestSpecification, - test: TestDetails, + test: TestCase, imports: List, - specificationAgain: TestSpecification, + specification: TestSpecification, ): SourceCode - fun implementDocumentationSpecification( + fun writeDocument( project: Project, - specification: DocumentSpecification, documentation: DocumentationDetails, imports: List, - specificationAgain: DocumentSpecification, - ): SourceCode + specification: DocumentSpecification, + ): Document + + data class Document( + @Description("e.g. \"markdown\" or \"text\"") + val language: String? = "", + @Description("Complete Document Text") + val text: String? = "", + ) : ValidatedObject data class SourceCode( - @Description("language of the code, e.g. \"java\" or \"kotlin\"") + @Description("e.g. \"java\" or \"kotlin\"") val language: String? = "", - @Description("Fully implemented source code") + @Description("Raw File Contents") val code: String? = "", ) : ValidatedObject @@ -168,9 +192,9 @@ interface SoftwareProjectAI { fun parallelImplement( api: SoftwareProjectAI, project: Project, - components: Map>?, - documents: Map>?, - tests: Map>?, + components: Map?, + documents: Map?, + tests: Map?, drafts: Int, threads: Int ): Map = parallelImplementWithAlternates( @@ -186,16 +210,20 @@ interface SoftwareProjectAI { fun parallelImplementWithAlternates( api: SoftwareProjectAI, project: Project, - components: Map>, - documents: Map>, - tests: Map>, + components: Map, + documents: Map, + tests: Map, drafts: Int, threads: Int, progress: (Double) -> Unit = {} ): Map> { val threadPool = Executors.newFixedThreadPool(threads) try { - val totalDrafts = (components + tests + documents).values.sumOf { it.size } * drafts + val totalDrafts = ( + components.values.sumOf { it.specifications?.size ?: 0 } + + tests.values.sumOf { it.specifications?.size ?: 0 } + + documents.values.sumOf { it.documents?.size ?: 0 } + ) * drafts val currentDraft = AtomicInteger(0) val fileImplCache = ConcurrentHashMap>>>() val normalizeFileName: (String?) -> String = { @@ -214,7 +242,7 @@ interface SoftwareProjectAI { return fileImplCache.getOrPut(normalizeFileName(file.location.file)) { (0 until drafts).map { _ -> threadPool.submit(Callable { - val implement = api.implementComponentSpecification( + val implement = api.implementCode( project, component, files.filter { file.requires?.contains(it.location) ?: false }.toList(), @@ -239,7 +267,7 @@ interface SoftwareProjectAI { } val componentFutures = components.flatMap { (component, files) -> - buildComponentDetails(component, files) + buildComponentDetails(component, files.specifications ?: listOf()) }.toTypedArray() // Build Documents @@ -254,9 +282,8 @@ interface SoftwareProjectAI { return fileImplCache.getOrPut(normalizeFileName(file.location.file)) { (0 until drafts).map { _ -> threadPool.submit(Callable { - val implement = api.implementDocumentationSpecification( + val implement = api.writeDocument( project, - file.copy(requires = listOf()), documentation, files.filter { file.requires?.contains(it.location) ?: false }.toList(), file.copy(requires = listOf()) @@ -264,7 +291,10 @@ interface SoftwareProjectAI { (currentDraft.incrementAndGet().toDouble() / totalDrafts) .also { progress(it) } .also { log.info("Progress: $it") } - file.location to implement + file.location to SourceCode( + language = implement.language, + code = implement.text + ) }) } } @@ -280,12 +310,12 @@ interface SoftwareProjectAI { } val documentFutures = documents.flatMap { (documentation, files) -> - buildDocumentDetails(documentation, files) + buildDocumentDetails(documentation, files.documents ?: listOf()) }.toTypedArray() // Build Tests fun buildTestSpec( - test: TestDetails, + test: TestCase, files: List, file: TestSpecification ): List>> { @@ -295,9 +325,8 @@ interface SoftwareProjectAI { return fileImplCache.getOrPut(normalizeFileName(file.location.file)) { (0 until drafts).map { _ -> threadPool.submit(Callable { - val implement = api.implementTestSpecification( + val implement = api.implementTest( project, - file.copy(requires = listOf()), test, files.filter { file.requires?.contains(it.location) ?: false }.toList(), file.copy(requires = listOf()) @@ -312,7 +341,7 @@ interface SoftwareProjectAI { } fun buildTestDetails( - test: TestDetails, + test: TestCase, files: List ): List>> { return files.flatMap(fun(file: TestSpecification): List>> { @@ -321,7 +350,7 @@ interface SoftwareProjectAI { } val testFutures = tests.flatMap { (test, files) -> - buildTestDetails(test, files) + buildTestDetails(test, files.specifications ?: listOf()) }.toTypedArray() return (getAll(componentFutures) + getAll(documentFutures) + getAll(testFutures)).mapValues { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt index 83a445e9..264752d0 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt @@ -76,20 +76,12 @@ class GenerateProjectAction : AnAction() { if (it.isCanceled) throw InterruptedException() buildProjectDesign } - val files = UITools.run( - e.project, "Specifying Files", true - ) { - val buildProjectFileSpecifications = - api.buildProjectFileSpecifications(project, requirements, projectDesign) - if (it.isCanceled) throw InterruptedException() - buildProjectFileSpecifications - } val components = UITools.run( e.project, "Specifying Components", true ) { - projectDesign.components?.map { it to api.buildComponentFileSpecifications(project, requirements, it) } + projectDesign.components?.map { it to api.getComponentFiles(project, requirements, it) } ?.toMap() } @@ -108,19 +100,19 @@ class GenerateProjectAction : AnAction() { val tests = UITools.run( e.project, "Specifying Tests", true - ) { projectDesign.tests?.map { it to api.buildTestFileSpecifications(project, requirements, it) }?.toMap() } + ) { projectDesign.testCases?.map { it to api.getTestFiles(project, requirements, it) }?.toMap() } val sourceCodeMap = UITools.run( e.project, "Implementing Files", true ) { SoftwareProjectAI.parallelImplementWithAlternates( - api, - project, - components ?: emptyMap(), - documents ?: emptyMap(), - tests ?: emptyMap(), - config.drafts, - AppSettingsState.instance.apiThreads + api = api, + project = project, + components = components ?: emptyMap(), + documents = documents ?: emptyMap(), + tests = tests ?: emptyMap(), + drafts = config.drafts, + threads = AppSettingsState.instance.apiThreads ) { progress -> if (it.isCanceled) throw InterruptedException() it.fraction = progress diff --git a/src/main/kotlin/com/github/simiacryptus/openai/proxy/ChatProxy.kt b/src/main/kotlin/com/github/simiacryptus/openai/proxy/ChatProxy.kt index 161fae41..7dbd9c07 100644 --- a/src/main/kotlin/com/github/simiacryptus/openai/proxy/ChatProxy.kt +++ b/src/main/kotlin/com/github/simiacryptus/openai/proxy/ChatProxy.kt @@ -36,9 +36,11 @@ class ChatProxy( ChatMessage( ChatMessage.Role.system, """ |You are a JSON-RPC Service - |Responses are expected to be a single JSON object without explaining text. + |Responses are in JSON format + |Do not include explaining text outside the JSON |All input arguments are optional - |You will respond to the following method: + |Outputs are based on inputs, with any missing information filled randomly + |You will respond to the following method | |${prompt.apiYaml} |""".trimMargin().trim() diff --git a/src/main/kotlin/com/github/simiacryptus/openai/proxy/GPTProxyBase.kt b/src/main/kotlin/com/github/simiacryptus/openai/proxy/GPTProxyBase.kt index b05b0445..798587f4 100644 --- a/src/main/kotlin/com/github/simiacryptus/openai/proxy/GPTProxyBase.kt +++ b/src/main/kotlin/com/github/simiacryptus/openai/proxy/GPTProxyBase.kt @@ -46,7 +46,7 @@ abstract class GPTProxyBase( } writeToJsonLog(ProxyRecord(method.name, prompt.argList, result)) try { - val obj = fromJson(type, result) + val obj = fromJson(result, type) if (obj is ValidatedObject && !obj.validate()) { log.warn("Invalid response: $result") continue @@ -68,7 +68,7 @@ abstract class GPTProxyBase( private fun loadExamples(file: File = File("api.examples.json")): List { if (!file.exists()) return listOf() val json = file.readText() - return fromJson(object : ArrayList() {}.javaClass, json) + return fromJson(json, object : ArrayList() {}.javaClass) } fun addExamples(file: File) { @@ -96,9 +96,11 @@ abstract class GPTProxyBase( return objectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(data) } - open fun fromJson(type: Type, data: String): T { - if (type is Class<*> && type.isAssignableFrom(String::class.java)) return data as T - return objectMapper().readValue(data, objectMapper().typeFactory.constructType(type)) as T + open fun fromJson(data: String, type: Type): T { + if (type is Class<*> && type.isAssignableFrom(String::class.java)) return data as T + val value = objectMapper().readValue(data, objectMapper().typeFactory.constructType(type)) as T + //log.debug("Deserialized $data to $value") + return value } open fun objectMapper(): ObjectMapper { @@ -127,13 +129,13 @@ abstract class GPTProxyBase( val yaml = if (description != null) { """ |- name: ${this.name} - | description: $description - | ${this.parameterizedType.toYaml().replace("\n", "\n ")} + | description: $description + | ${this.parameterizedType.toYaml().replace("\n", "\n ")} |""".trimMargin().trim() } else { """ |- name: ${this.name} - | ${this.parameterizedType.toYaml().replace("\n", "\n ")} + | ${this.parameterizedType.toYaml().replace("\n", "\n ")} |""".trimMargin().trim() } return yaml @@ -148,51 +150,48 @@ abstract class GPTProxyBase( """ |type: array |items: - | ${this.actualTypeArguments[0].toYaml().replace("\n", "\n ")} + | ${this.actualTypeArguments[0].toYaml().replace("\n", "\n ")} |""".trimMargin() } else if (this.isArray) { """ |type: array |items: - | ${this.componentType?.toYaml()?.replace("\n", "\n ")} + | ${this.componentType?.toYaml()?.replace("\n", "\n ")} |""".trimMargin() } else { val rawType = TypeToken.of(this).rawType - val declaredFieldYaml = rawType.declaredFields.map { - """ - |${it.name}: - | ${it.genericType.toYaml().replace("\n", "\n ")} - """.trimMargin().trim() - }.toTypedArray() val propertiesYaml = if (rawType.isKotlinClass() && rawType.kotlin.isData) { rawType.kotlin.memberProperties.map { - val allAnnotations = - getAllAnnotations(rawType, it) - val description = allAnnotations.find { x -> x is Description } as? Description + val description = getAllAnnotations(rawType, it).find { x -> x is Description } as? Description // Find annotation on the kotlin data class constructor parameter val yaml = if (description != null) { """ |${it.name}: - | description: ${description.value} - | ${it.returnType.javaType.toYaml().replace("\n", "\n ")} + | description: ${description.value} + | ${it.returnType.javaType.toYaml().replace("\n", "\n ")} """.trimMargin().trim() } else { """ |${it.name}: - | ${it.returnType.javaType.toYaml().replace("\n", "\n ")} + | ${it.returnType.javaType.toYaml().replace("\n", "\n ")} """.trimMargin().trim() } yaml }.toTypedArray() } else { - arrayOf() + rawType.declaredFields.map { + """ + |${it.name}: + | ${it.genericType.toYaml().replace("\n", "\n ")} + """.trimMargin().trim() + }.toTypedArray() } - val fieldsYaml = (declaredFieldYaml.toList() + propertiesYaml.toList()).distinct().joinToString("\n") + val fieldsYaml = propertiesYaml.toList().joinToString("\n") """ - |type: object - |properties: - | ${fieldsYaml.replace("\n", "\n ")} - """.trimMargin() + |type: object + |properties: + | ${fieldsYaml.replace("\n", "\n ")} + """.trimMargin() } return yaml } @@ -200,24 +199,25 @@ abstract class GPTProxyBase( private fun getAllAnnotations( rawType: Class, property: KProperty1 - ) = property.annotations + (rawType.kotlin.constructors.first().parameters.find { x -> x.name == property.name }?.annotations + ) = + property.annotations + (rawType.kotlin.constructors.first().parameters.find { x -> x.name == property.name }?.annotations ?: listOf()) fun Method.toYaml(): String { - val parameterYaml = parameters.map { it.toYaml() }.toTypedArray().joinToString("").trim() + val parameterYaml = parameters.map { it.toYaml() }.toTypedArray().joinToString("\n").trim() val returnTypeYaml = genericReturnType.toYaml().trim() val responseYaml = """ - |responses: - | application/json: - | schema: - | ${returnTypeYaml.replace("\n", "\n ")} - """.trimMargin().trim() + |responses: + | application/json: + | schema: + | ${returnTypeYaml.replace("\n", "\n ")} + """.trimMargin().trim() val yaml = """ - |operationId: ${"${declaringClass.simpleName}.$name"} - |parameters: - | ${parameterYaml.replace("\n", "\n ")} - |$responseYaml - """.trimMargin() + |operationId: $name + |parameters: + | ${parameterYaml.replace("\n", "\n ")} + |$responseYaml + """.trimMargin() return yaml } diff --git a/src/main/kotlin/com/github/simiacryptus/openai/proxy/ValidatedObject.kt b/src/main/kotlin/com/github/simiacryptus/openai/proxy/ValidatedObject.kt index b12b5818..0b916eab 100644 --- a/src/main/kotlin/com/github/simiacryptus/openai/proxy/ValidatedObject.kt +++ b/src/main/kotlin/com/github/simiacryptus/openai/proxy/ValidatedObject.kt @@ -13,12 +13,28 @@ interface ValidatedObject { if (value is ValidatedObject && !value.validate()) { return false } + // If the property is a list, validate each element + if (value is List<*>) { + value.forEach { + if (it is ValidatedObject && !it.validate()) { + return false + } + } + } } obj.javaClass.kotlin.memberProperties.forEach { property -> val value = property.getter.call(obj) if (value is ValidatedObject && !value.validate()) { return false } + // If the property is a list, validate each element + if (value is List<*>) { + value.forEach { + if (it is ValidatedObject && !it.validate()) { + return false + } + } + } } return true } diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt index ec22cb1f..144ecd4d 100644 --- a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt @@ -21,19 +21,19 @@ class AutoDevelop : GenerationReportBase() { @Test fun testMethodImplementation() { runReport("SoftwareProject_Impl", SoftwareProjectAI::class) { api, logJson, out -> - val sourceCode = api.implementComponentSpecification( + val sourceCode = api.implementCode( proxy.fromJson( - SoftwareProjectAI.Project::class.java, - "{\r\n \"name\" : \"SupportAliasBot\",\r\n \"description\" : \"Slack bot to monitor a support alias\",\r\n \"language\" : \"Kotlin\",\r\n \"features\" : [ \"Record all requests tagging an alias in a database\", \"Send a message to a Slack channel when requests are tagged with a specific label\" ],\r\n \"libraries\" : [ \"Gradle\", \"Spring\" ],\r\n \"buildTools\" : [ \"Gradle\" ]\r\n}" + "{\r\n \"name\" : \"SupportAliasBot\",\r\n \"description\" : \"Slack bot to monitor a support alias\",\r\n \"language\" : \"Kotlin\",\r\n \"features\" : [ \"Record all requests tagging an alias in a database\", \"Send a message to a Slack channel when requests are tagged with a specific label\" ],\r\n \"libraries\" : [ \"Gradle\", \"Spring\" ],\r\n \"buildTools\" : [ \"Gradle\" ]\r\n}", + SoftwareProjectAI.Project::class.java ), proxy.fromJson( - SoftwareProjectAI.ComponentDetails::class.java, - "{\r\n \"description\" : \"Main class for the SupportAliasBot\",\r\n \"requires\" : [ ],\r\n \"publicProperties\" : [ ],\r\n \"publicMethodSignatures\" : [ \"fun main(args: Array): Unit\" ],\r\n \"language\" : \"Kotlin\",\r\n \"location\" : {\r\n \"file\" : \"src/main/kotlin/com/example/supportaliasbot/SupportAliasBot.kt\"\r\n }\r\n}" + "{\r\n \"description\" : \"Main class for the SupportAliasBot\",\r\n \"requires\" : [ ],\r\n \"publicProperties\" : [ ],\r\n \"publicMethodSignatures\" : [ \"fun main(args: Array): Unit\" ],\r\n \"language\" : \"Kotlin\",\r\n \"location\" : {\r\n \"file\" : \"src/main/kotlin/com/example/supportaliasbot/SupportAliasBot.kt\"\r\n }\r\n}", + SoftwareProjectAI.ComponentDetails::class.java ), listOf(), proxy.fromJson( - SoftwareProjectAI.CodeSpecification::class.java, - "{\r\n \"name\" : \"SupportAliasBot\",\r\n \"description\" : \"Slack bot to monitor a support alias\",\r\n \"language\" : \"Kotlin\",\r\n \"features\" : [ \"Record all requests tagging an alias in a database\", \"Send a message to a Slack channel when requests are tagged with a specific label\" ],\r\n \"libraries\" : [ \"Gradle\", \"Spring\" ],\r\n \"buildTools\" : [ \"Gradle\" ]\r\n}" + "{\r\n \"name\" : \"SupportAliasBot\",\r\n \"description\" : \"Slack bot to monitor a support alias\",\r\n \"language\" : \"Kotlin\",\r\n \"features\" : [ \"Record all requests tagging an alias in a database\", \"Send a message to a Slack channel when requests are tagged with a specific label\" ],\r\n \"libraries\" : [ \"Gradle\", \"Spring\" ],\r\n \"buildTools\" : [ \"Gradle\" ]\r\n}", + SoftwareProjectAI.CodeSpecification::class.java ), ) out(""" @@ -55,27 +55,20 @@ class AutoDevelop : GenerationReportBase() { @Suppress("JoinDeclarationAndAssignment") val description: String - description = """ + """ | |Slack bot to monitor a support alias |All requests tagging an alias are recorded in a database |When requests are tagged with a specific label, the bot will send a message to a slack channel - |Fully implement all functions - |Do not comment code - |Include documentation and build scripts | |Language: Kotlin |Frameworks: Gradle, Spring | """.trimMargin() - """ + description = """ | |Create a website where users can upload stories, share them, and rate them | - |Fully implement all functions - |Do not comment code - |Include documentation and build scripts - | |Language: Kotlin |Frameworks: Gradle, Spring | @@ -98,10 +91,9 @@ class AutoDevelop : GenerationReportBase() { var project: SoftwareProjectAI.Project? = null var requirements: SoftwareProjectAI.ProjectStatements? = null var projectDesign: SoftwareProjectAI.ProjectDesign? = null - var components: Map>? = null - var documents: Map>? = - null - var tests: Map>? = null + var components: Map? = null + var documents: Map? = null + var tests: Map? = null var implementations: Map? = null try { @@ -113,7 +105,7 @@ class AutoDevelop : GenerationReportBase() { | |Description: ${project.description} | - |Language: ${project.language} + |Language: ${project.languages?.joinToString(", ")} | |Libraries: ${project.libraries?.joinToString(", ")} | @@ -141,23 +133,15 @@ class AutoDevelop : GenerationReportBase() { |""".trimMargin() ) logJson(projectDesign) + components = projectDesign.components?.map { - it to (api.buildComponentFileSpecifications( + it to (api.getComponentFiles( project, requirements, it )) }?.toMap() - out( - """ - | - |## Components - | - |""".trimMargin() - ) - logJson(components) - documents = projectDesign.documents?.map { it to (api.buildDocumentationFileSpecifications( @@ -167,6 +151,19 @@ class AutoDevelop : GenerationReportBase() { ) ) }?.toMap() + tests = projectDesign.testCases?.map { + it to (api.getTestFiles(project, requirements, it)) + }?.toMap() + + out( + """ + | + |## Components + | + |""".trimMargin() + ) + logJson(components) + out( """ | @@ -175,9 +172,6 @@ class AutoDevelop : GenerationReportBase() { |""".trimMargin() ) logJson(documents) - tests = projectDesign.tests?.map { - it to (api.buildTestFileSpecifications(project, requirements, it)) - }?.toMap() out( """ | diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateSimulator.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateSimulator.kt index fd79c638..9aecebb9 100644 --- a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateSimulator.kt +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateSimulator.kt @@ -67,14 +67,6 @@ class DebateSimulator : GenerationReportBase() { val writingStyle: String = "", ) - /** - * - * A data class representing the judgement of a debate. - * - * @property winner the team judged to have won the debate - * @property reasoning the judge's reasoning for the judgement - * @property pointsAwarded the number of points awarded to the winner - */ data class DebateJudgement( val winner: String = "", val reasoning: String = "", val pointsAwarded: Int = 0 ) diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/FractalBookTest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/FractalBookTest.kt new file mode 100644 index 00000000..816749ae --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/FractalBookTest.kt @@ -0,0 +1,135 @@ +package com.github.simiacryptus.aicoder.proxy + +import com.github.simiacryptus.openai.proxy.Description +import org.junit.Test + + +class FractalBookTest : GenerationReportBase() { + interface FractalBook { + data class Idea(var title: String? = "", var description: String? = "") + + fun generateIdeas(prompt: String): Ideas + + data class Ideas(var ideas: List? = listOf()) + + fun generatePlot(idea: Idea): StoryTemplate + data class Character(var name: String? = "", var age: Int? = 0, var traits: List? = listOf()) + data class Setting(var location: String? = "", var timePeriod: String? = "") + data class StoryTemplate( + var protagonist: Character? = Character(), + var antagonist: Character? = Character(), + var setting: Setting? = Setting(), + var conflict: String? = "", + var supportingCharacters: List? = listOf(), + var resolution: String? = "", + ) + + data class PlotPoints(var plotPoints: List? = listOf()) + + data class PlotPoint( + @Description("Single sentence description of the plot point") + var title: String? = "", + @Description("Text describing the plot point; about 3 paragraphs") + var description: String? = "" + ) + + fun generatePlotPoints(story: StoryTemplate): PlotPoints + + fun expandEvents( + //story: StoryTemplate, + @Description("Previous events to use for interpolation") + previous: PlotPoints?, + @Description("Next events to use for interpolation") + next: PlotPoints?, + @Description("Current event titla to expand") + title: String?, + @Description("Current event text") + description: String?, + @Description("The number of events to generate between the previous and next event") + count: Int = 5, + ): PlotPoints + + } + + @Test + fun writeBook() { + runReport("WriteBook", FractalBook::class) { + // api to use + api: FractalBook, + // logJson is a function to write JSON for debugging + logJson: (Any?) -> Unit, + // out is a function to write markdown to the report + out: (Any?) -> Unit -> + + val ideas = api.generateIdeas( + """ + I want to write a children's book for a 10 year old boy. He likes Roblox and Subnautica. + """.trimIndent() + ) + logJson(ideas) + val selectedIdea = ideas.ideas!!.random() + out( + """ + |Selected Idea: ${selectedIdea.title} + |""".trimMargin() + ) + + + val story = api.generatePlot(selectedIdea) + + out( + """ + |Story: ${story.protagonist!!.name} is a ${story.protagonist!!.age} year old ${story.protagonist!!.traits!!.random()} who lives in ${story.setting!!.location} during the ${story.setting!!.timePeriod}. ${story.protagonist!!.name} is ${story.conflict} and ${story.resolution}. + |""".trimMargin() + ) + + val plotPoints = api.generatePlotPoints(story) + + + // Expand plot points by keeping track of the previous and next points + var points = plotPoints.plotPoints!! + for (i in 0..2) { + out( + """ + |# Level $i: + |""".trimMargin() + ) + logJson(points) + points = getExpandedPlotPoints(api, story, points) + } + + } + } + + private fun getExpandedPlotPoints(api: FractalBook, story: FractalBook.StoryTemplate, points: List): List + { + val previousBuffer = mutableListOf() + for (point in points.withIndex()) { + val expandedPoints = api.expandEvents( + previous = FractalBook.PlotPoints(previousBuffer.takeLast(4).toList()), + next = FractalBook.PlotPoints(points.drop(point.index + 1).take(1)), + title = point.value.title, + description = point.value.description + ) + previousBuffer.addAll(expandedPoints.plotPoints!!) + } + return previousBuffer.toList() + } + + private fun getExpandedPlotPoints1(api: FractalBook, story: FractalBook.StoryTemplate, points: List): List + { + return points.mapIndexed { index, plotPoint -> + // Call the expandEvents function on the api + api.expandEvents( + // Pass in the plot points from 0 to the current index - 1 + FractalBook.PlotPoints(points.take(Math.max(0,index - 1)).takeLast(2)), + // Pass in the plot points from the current index + 1 to the end + FractalBook.PlotPoints(points.drop(index+1).take(2)), + // Pass in the current plot point + plotPoint.title, + plotPoint.description + ).plotPoints + // Flat map the result + }.flatMap { it!! } + } +} diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/KidsBookTest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/KidsBookTest.kt new file mode 100644 index 00000000..b195785b --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/KidsBookTest.kt @@ -0,0 +1,146 @@ +package com.github.simiacryptus.aicoder.proxy + +import com.github.simiacryptus.openai.proxy.Description +import org.junit.Test +import kotlin.reflect.javaType +import kotlin.reflect.typeOf + + +class KidsBookTest : GenerationReportBase() { + /** + * I want to write a children's book for a 10 year old. However, I don't know how to write an entertaining book! Please provide some instructions to gather ideas, develop the story, and then write the book. Describe this like a programmer would - describe objects in terms of Kotlin data classes, and operations as method or api calls. + */ + + interface KidsBook { + data class Idea(var title: String? = "", var description: String? = "") + + fun generateIdeas(prompt: String): Ideas + + data class Ideas(var ideas: List? = listOf()) + data class Character(var name: String? = "", var age: Int? = 0, var traits: List? = listOf()) + data class Setting(var location: String? = "", var timePeriod: String? = "") + data class Plot( + var protagonist: Character? = Character(), + var antagonist: Character? = Character(), + var setting: Setting? = Setting(), + var conflict: String? = "", + var supportingCharacters: List? = listOf(), + var events: List? = listOf(), + var resolution: String? = "", + ) + + fun generatePlot(idea: Idea): Plot + + data class ChapterSummary(var title: String? = "", var subPlot: List? = listOf()) + + fun planChapters(idea: Idea, plot: Plot, subPlotPointsPerChapter: Int = 5): ChapterSummaries + + data class ChapterSummaries(var chapters: List? = listOf()) + + data class Chapter(var title: String? = "", var pages: List? = listOf()) + + data class Page(@Description("One page (~3 paragraphs) of narrative text in the format of a story.") var text: String? = "", + var image: Image? = Image()) + + data class Image(var filename: String? = "", + @Description("Caption used for Dall-E 2 image generation") var caption: String? = "") + + fun writeChapter( + idea: Idea, plot: Plot, + previousChapter: ChapterSummary?, nextChapter: ChapterSummary?, + thisChapter: ChapterSummary, pageCount: Int = 10 + ): Chapter + + } + + @Test + fun writeKidsBook() { + runReport("KidsBook", KidsBook::class) { api, logJson, out -> + val ideas = api.generateIdeas(""" + I want to write a children's book for a 10 year old boy. He likes Roblox and Subnautica. + """.trimIndent()) + logJson(ideas) + val selectedIdea = ideas.ideas!!.random() + out(""" + |Selected Idea: ${selectedIdea.title} + |""".trimMargin()) + + val plot = api.generatePlot(selectedIdea) + val characters = listOf(plot.antagonist, plot.protagonist) + val setting = plot?.setting + logJson(characters) + out("""Characters: ${characters?.joinToString { it?.name ?: "" }}""".trimMargin()) + + logJson(setting) + out("""Setting: ${setting?.location}, ${setting?.timePeriod}""".trimMargin()) + + logJson(plot) + out(""" + |Plot: + | + |${plot.events?.map {"1. " + it}?.joinToString("\n") ?: ""} + | + |""".trimMargin()) + + val chapterSummaries = api.planChapters(selectedIdea, plot) + logJson(chapterSummaries) + val summaries = chapterSummaries.chapters!! + out(""" + |Chapter Summaries: + | + |${summaries?.joinToString("\n") { "1. " + (it?.title ?: "") }} + | + |""".trimMargin()) + + val chapters = mutableListOf() + for (i in chapterSummaries.chapters?.indices ?: listOf()) { + val prevChapter = if (i > 0) summaries[i - 1] else null + val nextChapter = if (i < summaries.lastIndex) summaries[i + 1] else null + val thisChapter = summaries[i] + + val chapter = + api.writeChapter(selectedIdea, plot, prevChapter, nextChapter, thisChapter) + chapters.add(chapter) + logJson(chapter) + } + + out(""" + | + |# ${selectedIdea.title} + | + |""".trimMargin()) + for (chapter in chapters) { + out( + """ + | + |## Chapter: ${chapter.title} + | + """.trimMargin() + ) + for (page in chapter?.pages ?: listOf()) { + val caption = page.image?.caption ?: "" + if(caption.isNotBlank()) { + val bufferedImage = proxy.api.render( + caption, + resolution = 512 + )[0] + val writeImage = writeImage(bufferedImage) + out(""" + | + |![${caption}](${writeImage}) + | + |${page.text} + | + |""".trimMargin()) + } else { + out(""" + | + |${page.text} + | + |""".trimMargin()) + } + } + } + } + } +} diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/SchoolCurriculumCreatorTest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/SchoolCurriculumCreatorTest.kt new file mode 100644 index 00000000..501ebbf0 --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/SchoolCurriculumCreatorTest.kt @@ -0,0 +1,170 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +/** + * Define the objectives: Clearly outline the educational objectives, learning goals, and values you want students to achieve by the end of their K-12 education. Consider local, regional, or national education standards, and align your goals with these benchmarks. + * + * Identify the subjects and scope: Determine the subjects that will be covered at each grade level. Consider core subjects (math, language arts, science, and social studies), as well as electives and enrichment courses (arts, physical education, technology, etc.). Ensure the curriculum covers the necessary knowledge and skills for each subject. + * + * Sequence and progression: Organize the content and skills within each subject in a logical and sequential manner, allowing for scaffolding and building on previous knowledge. Create a scope and sequence document to outline the learning objectives and content for each grade level. + * + * Integration and interdisciplinary connections: Identify opportunities for integrating subjects and promoting interdisciplinary learning. This can help students make connections between different subjects and apply their knowledge in a more meaningful way. + * + * Teaching methodologies: Select diverse and evidence-based teaching strategies that cater to various learning styles and promote active, student-centered learning. Consider including project-based learning, inquiry-based learning, cooperative learning, and other approaches. + * + * Assessment and evaluation: Develop a variety of assessment methods to measure student progress and evaluate the effectiveness of the curriculum. Use both formative (ongoing) and summative (end of unit/term) assessments, including performance-based tasks, quizzes, tests, and projects. + * + * Inclusivity and accessibility: Ensure the curriculum is inclusive and accessible to all students, regardless of their abilities, backgrounds, and learning needs. Consider incorporating differentiated instruction, universal design for learning (UDL), and culturally responsive teaching practices. + * + * Social and emotional learning (SEL): Embed opportunities for developing social and emotional skills throughout the curriculum. This can include teaching empathy, self-awareness, self-regulation, problem-solving, and collaboration. + * + * Teacher support and professional development: Provide teachers with the necessary resources, training, and support to effectively implement the curriculum. This can include lesson plans, instructional materials, and ongoing professional development opportunities. + * + * Review and revision: Regularly evaluate and update the curriculum based on feedback from teachers, students, parents, and administrators, as well as current research and best practices in education. + * + * Outputs to produce for the curriculum: + * + * Curriculum guide: A comprehensive document outlining the learning objectives, content, and skills for each subject and grade level. + * + * Scope and sequence documents: Detailed roadmaps for each subject, outlining the progression of topics, skills, and learning objectives across grade levels. + * + * Lesson plans and instructional materials: Ready-to-use lesson plans, activities, and resources for teachers to implement the curriculum. + * + * Assessment tools: A variety of formative and summative assessments to measure student learning and evaluate the curriculum's effectiveness. + * + * Teacher support materials: Professional development resources, training materials, and ongoing support for teachers implementing the curriculum. + * + * Parent and community resources: Information and resources to help parents and community members understand and support the curriculum. + */ +class SchoolCurriculumCreatorTest : GenerationReportBase() { + + interface SchoolCurriculumCreator { + + fun defineObjectives(): List + + fun identifySubjectsAndScope(): Map> + + fun sequenceAndProgression(): Map>> + + fun integrationAndInterdisciplinaryConnections(): List + + fun teachingMethodologies(): List + + fun assessmentAndEvaluation(): List + + fun inclusivityAndAccessibility(): List + + fun socialAndEmotionalLearning(): List + + fun teacherSupportAndProfessionalDevelopment(): List + + fun reviewAndRevision(): List + + data class CurriculumOutputs( + val curriculumGuide: String, + val scopeAndSequenceDocuments: Map, + val lessonPlansAndInstructionalMaterials: Map>, + val assessmentTools: List, + val teacherSupportMaterials: List, + val parentAndCommunityResources: List + ) + + fun generateCurriculumOutputs(): CurriculumOutputs + } + + @Test + fun testSchoolCurriculumCreator() { + runReport("SchoolCurriculumCreator", SchoolCurriculumCreator::class) { api, logJson, out -> + val objectives = api.defineObjectives() + logJson(objectives) + out("Objectives:\n${objectives.joinToString("\n")}\n") + + val subjectsAndScope = api.identifySubjectsAndScope() + logJson(subjectsAndScope) + out( + "Subjects and Scope:\n${ + subjectsAndScope.map { "${it.key}: ${it.value.joinToString()}" }.joinToString("\n") + }\n") + + val sequenceAndProgression = api.sequenceAndProgression() + logJson(sequenceAndProgression) + out( + "Sequence and Progression:\n${ + sequenceAndProgression.map { + "${it.key}: ${ + it.value.map { "${it.key}: ${it.value.joinToString()}" }.joinToString() + }" + }.joinToString("\n") + }\n") + + val integrationAndInterdisciplinaryConnections = api.integrationAndInterdisciplinaryConnections() + logJson(integrationAndInterdisciplinaryConnections) + out( + "Integration and Interdisciplinary Connections:\n${ + integrationAndInterdisciplinaryConnections.joinToString( + "\n" + ) + }\n" + ) + + val teachingMethodologies = api.teachingMethodologies() + logJson(teachingMethodologies) + out("Teaching Methodologies:\n${teachingMethodologies.joinToString("\n")}\n") + + val assessmentAndEvaluation = api.assessmentAndEvaluation() + logJson(assessmentAndEvaluation) + out("Assessment and Evaluation:\n${assessmentAndEvaluation.joinToString("\n")}\n") + + val inclusivityAndAccessibility = api.inclusivityAndAccessibility() + logJson(inclusivityAndAccessibility) + out("Inclusivity and Accessibility:\n${inclusivityAndAccessibility.joinToString("\n")}\n") + + val socialAndEmotionalLearning = api.socialAndEmotionalLearning() + logJson(socialAndEmotionalLearning) + out("Social and Emotional Learning:\n${socialAndEmotionalLearning.joinToString("\n")}\n") + + val teacherSupportAndProfessionalDevelopment = api.teacherSupportAndProfessionalDevelopment() + logJson(teacherSupportAndProfessionalDevelopment) + out( + "Teacher Support and Professional Development:\n${ + teacherSupportAndProfessionalDevelopment.joinToString( + "\n" + ) + }\n" + ) + + val reviewAndRevision = api.reviewAndRevision() + logJson(reviewAndRevision) + out("Review and Revision:\n${reviewAndRevision.joinToString("\n")}\n") + + val curriculumOutputs = api.generateCurriculumOutputs() + logJson(curriculumOutputs) + out( + """ + |Curriculum Outputs: + |Curriculum Guide: + |${curriculumOutputs.curriculumGuide} + | + |Scope and Sequence Documents: + |${curriculumOutputs.scopeAndSequenceDocuments.map { "${it.key}:\n${it.value}" }.joinToString("\n\n")} + | + |Lesson Plans and Instructional Materials: + |${ + curriculumOutputs.lessonPlansAndInstructionalMaterials.map { "${it.key}: ${it.value.joinToString()}" } + .joinToString("\n") + } + | + |Assessment Tools: + |${curriculumOutputs.assessmentTools.joinToString("\n")} + | + |Teacher Support Materials: + |${curriculumOutputs.teacherSupportMaterials.joinToString("\n")} + | + |Parent and Community Resources: + |${curriculumOutputs.parentAndCommunityResources.joinToString("\n")} + """.trimMargin() + ) + } + } +} \ No newline at end of file