Skip to content

Commit

Permalink
Ported all modules from prototype (#2)
Browse files Browse the repository at this point in the history
* Ported all modules from prototype

* Added agent example

* Fix build
  • Loading branch information
nsmnds authored May 1, 2024
1 parent 2ceb28b commit c7b5ff6
Show file tree
Hide file tree
Showing 62 changed files with 313,515 additions and 25 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ plugins {
id("root.publication")
//trick: for the same plugin versions in all sub-modules
alias(libs.plugins.kotlinMultiplatform).apply(false)
alias(libs.plugins.kotlinx.serialization) apply false
alias(libs.plugins.spotless) apply false
alias(libs.plugins.kotest.multiplatform) apply false
alias(libs.plugins.dokka)
}

Expand Down
41 changes: 40 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
[versions]
kotlin = "1.9.23"
coroutines = "1.7.2"
serialization = "1.6.3"
ktor = "2.3.10"
nexus-publish = "2.0.0"
kotlinOpenapiBindings = "0.0.24"
kotest = "5.8.1"
mockk = "1.13.10"

[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
# Coroutines
coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
# Serialization
serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "serialization" }
serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
nexus-publish = { module = "io.github.gradle-nexus.publish-plugin:io.github.gradle-nexus.publish-plugin.gradle.plugin", version.ref = "nexus-publish" }
# Ktor
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
ktor-client-curl = { group = "io.ktor", name = "ktor-client-curl", version.ref = "ktor" }
ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" }
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
# Ktor engines
ktor-client-apache = { group = "io.ktor", name = "ktor-client-apache", version.ref = "ktor" }
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
ktor-client-java = { group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" }
ktor-client-jetty = { group = "io.ktor", name = "ktor-client-jetty", version.ref = "ktor" }
ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" }
# OpenAPI
kotlinx-openapi-bindings = { group = "community.flock.kotlinx.openapi.bindings", name = "kotlin-openapi-bindings", version.ref = "kotlinOpenapiBindings" }
#Test
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotest-framework-engine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" }
kotest-framework-datatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest" }
kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" }
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }

[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
spotless = { id = "com.diffplug.gradle.spotless", version = "6.20.0" }
dokka = { id = "org.jetbrains.dokka", version = "1.8.20" }
dokka = { id = "org.jetbrains.dokka", version = "1.8.20" }
kotest-multiplatform = { id = "io.kotest.multiplatform", version.ref = "kotest" }
42 changes: 42 additions & 0 deletions kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
# yarn lockfile v1


[email protected]:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"

[email protected]:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
Expand Down Expand Up @@ -160,6 +167,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==

event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==

fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
Expand Down Expand Up @@ -359,6 +371,13 @@ [email protected]:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==

[email protected]:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"

normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
Expand Down Expand Up @@ -486,11 +505,29 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"

tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==

[email protected]:
version "5.0.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==

webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==

whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"

[email protected]:
version "6.2.1"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
Expand All @@ -510,6 +547,11 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==

[email protected]:
version "8.5.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==

y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
Expand Down
7 changes: 5 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ dependencyResolutionManagement {

rootProject.name = "aigentic"
include(
"src:http-tools",
"src:providers:openai"
"src:core",
"src:tools:http",
"src:tools:openapi",
"src:providers:openai",
"src:example"
)
7 changes: 4 additions & 3 deletions src/http-tools/build.gradle.kts → src/core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
id("module.publication")
id("org.jetbrains.dokka")
alias(libs.plugins.dokka)
kotlin("plugin.serialization")
}

kotlin {

jvm()
linuxX64()
js(IR) {
Expand All @@ -16,7 +16,8 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
//put your multiplatform dependencies here
api(libs.coroutines.core)
implementation(libs.serialization.json)
}
}
val commonTest by getting {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package community.flock.aigentic.core.agent

import community.flock.aigentic.core.agent.prompt.SystemPromptBuilder
import community.flock.aigentic.core.message.Message
import community.flock.aigentic.core.tool.ToolName
import community.flock.aigentic.core.model.Model
import community.flock.aigentic.core.tool.InternalTool
import community.flock.aigentic.core.tool.Tool
import kotlinx.coroutines.flow.MutableSharedFlow

data class Task(
val description: String,
val instructions: List<Instruction>
)

data class Instruction(val text: String)

sealed interface Context {
data class Text(val text: String) : Context
data class Image(val base64: String) : Context
}

data class Agent(
val id: String,
val systemPromptBuilder: SystemPromptBuilder,
val model: Model,
val task: Task,
val contexts: List<Context>,
val tools: Map<ToolName, Tool>,
) {

internal val messages = MutableSharedFlow<Message>(replay = 100)
internal val internalTools = mutableMapOf<ToolName, InternalTool<*>>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package community.flock.aigentic.core.agent

import community.flock.aigentic.core.agent.tool.FinishedOrStuck
import community.flock.aigentic.core.agent.tool.finishOrStuckTool
import community.flock.aigentic.core.message.*
import community.flock.aigentic.core.tool.DefaultToolPermissionHandler
import community.flock.aigentic.core.tool.ToolName
import community.flock.aigentic.core.tool.ToolPermissionHandler
import community.flock.aigentic.core.model.ModelResponse
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow

class AgentExecutor(
val schedules: List<Schedule>,
val permissionHandler: ToolPermissionHandler = DefaultToolPermissionHandler()
) {
private val listeners: MutableList<suspend (event: Pair<String, Message>) -> Unit> = mutableListOf()

suspend fun start() =
schedules
.flatMap { it.agents }
.forEach { runAgent(it) }

suspend fun runAgent(agent: Agent): FinishedOrStuck {
applyListeners(agent)
agent.initialize() // Maybe move to Agent builder?
val modelResponse = agent.sendModelRequest()

val result = CompletableDeferred<FinishedOrStuck>()
processResponse(agent, modelResponse) { result.complete(it) }
return result.await()
}

private suspend fun Agent.initialize() {
internalTools[finishOrStuckTool.name] = finishOrStuckTool
messages.emit(systemPromptBuilder.buildSystemPrompt(this))
contexts.map {
when (it) {
is Context.Image -> Message.Image(Sender.Aigentic, it.base64)
is Context.Text -> Message.Text(Sender.Aigentic, it.text)
}
}.forEach { messages.emit(it) }
}

private suspend fun processResponse(agent: Agent, response: ModelResponse, onFinished: (FinishedOrStuck) -> Unit) {
val message = response.message
agent.messages.emit(message)

when (message) {
is Message.ToolCalls -> {
val shouldSendNextRequest = message.toolCalls
.map { toolCall ->
when (toolCall.name) {
finishOrStuckTool.name.value -> {
val finishedOrStuck = finishOrStuckTool.handler(toolCall.argumentsAsJson())
onFinished(finishedOrStuck)
false
}

else -> {
val toolResult = agent.execute(toolCall)
agent.messages.emit(toolResult)
true
}
}
}
.contains(true)

if (shouldSendNextRequest) {
sendToolResponse(agent, onFinished)
}
}

else -> error("Expected ToolCalls message, got $message")
}
}

private suspend fun Agent.execute(toolCall: ToolCall): Message.ToolResult {
val functionArgs = toolCall.argumentsAsJson()
val tool = tools[ToolName(toolCall.name)] ?: error("Tool not registered: $toolCall")
while (!permissionHandler.hasPermission(tool.toolConfiguration, toolCall)) {
println("Waiting for permission for ${toolCall.name}")
delay(300)
}
val result = tool.handler(functionArgs)
return Message.ToolResult(toolCall.id, toolCall.name, ToolResultContent(result))
}

private suspend fun sendToolResponse(agent: Agent, onFinished: (FinishedOrStuck) -> Unit) {
val response = agent.sendModelRequest()
processResponse(agent, response, onFinished)
}

private suspend fun Agent.sendModelRequest(): ModelResponse =
model.sendRequest(messages.replayCache, tools.values.toList() + internalTools.values.toList())

fun getMessages(): Map<String, MutableSharedFlow<Message>> =
schedules.flatMap { it.agents }.associate { it.id to it.messages }

fun addListener(function: suspend (event: Pair<String, Message>) -> Unit) {
listeners.add(function)
}

@OptIn(DelicateCoroutinesApi::class)
private fun applyListeners(agent: Agent) {
listeners.forEach { function ->
GlobalScope.launch {
agent.messages.collect { function.invoke(Pair(agent.id, it)) }
}
}
}
}

class Schedule(
val agents: List<Agent>,
val type: ScheduleType
)

sealed interface ScheduleType {
/**
* Just run a single time
*/
data object Single : ScheduleType
}


//suspend fun Agent.execute() = runAgent(this)
Loading

0 comments on commit c7b5ff6

Please sign in to comment.