Skip to content

Commit 8129e58

Browse files
author
Robert Winkler
committed
Initial Kotlin-SDK
1 parent f5a4107 commit 8129e58

File tree

34 files changed

+319
-167
lines changed

34 files changed

+319
-167
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ subprojects {
1010
apply(plugin = "maven-publish")
1111

1212
group = "ai.ancf.lmos"
13-
version = "0.1.0-SNAPSHOT"
13+
version = "0.1.3-SNAPSHOT"
1414

1515
dependencies {
1616
testImplementation(kotlin("test"))

kotlin-wot-binding-http/src/main/kotlin/http/HttpProtocolClient.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,15 @@ fun createHttpClient(): HttpClient {
124124
engine {
125125
proxy = ProxyBuilder.http(proxyUrl)
126126
}
127+
install(HttpTimeout) {
128+
requestTimeoutMillis = 50000
129+
}
127130
}
128131
} else {
129-
HttpClient(CIO)
132+
HttpClient(CIO) {
133+
install(HttpTimeout) {
134+
requestTimeoutMillis = 50000
135+
}
136+
}
130137
}
131138
}

kotlin-wot-integration-tests/build.gradle.kts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ import org.springframework.boot.gradle.tasks.run.BootRun
33
import java.net.URI
44

55
plugins {
6-
kotlin("plugin.spring") version "1.9.10"
7-
id("org.springframework.boot") version "3.1.5" // Use the latest compatible version
8-
id("io.spring.dependency-management") version "1.1.3"
6+
kotlin("plugin.spring") version "1.9.25"
7+
id("org.springframework.boot") version "3.4.2"
8+
id("io.spring.dependency-management") version "1.1.7"
99
}
1010

11-
tasks.named<Test>("test") {
12-
enabled = false
13-
}
11+
//tasks.named<Test>("test") {
12+
// enabled = false
13+
//}
1414

1515
dependencies {
1616
// Replace the following with the starter dependencies of specific modules you wish to use
1717
api(project(":kotlin-wot-binding-http"))
1818
api(project(":kotlin-wot-binding-websocket"))
1919
api(project(":kotlin-wot-binding-mqtt"))
2020
api(project(":kotlin-wot-spring-boot-starter"))
21-
api(project(":kotlin-wot-lmos-protocol"))
21+
api(project(":lmos-kotlin-sdk-client"))
22+
api(project(":lmos-kotlin-sdk-server"))
2223
implementation("org.eclipse.lmos:arc-azure-client:0.1.0-SNAPSHOT")
2324

2425
api("org.eclipse.lmos:arc-spring-boot-starter:0.1.0-SNAPSHOT")
@@ -40,6 +41,8 @@ dependencies {
4041
//implementation("io.opentelemetry:opentelemetry-exporter-otlp")
4142

4243
implementation("dev.langchain4j:langchain4j-azure-open-ai:1.0.0-beta1")
44+
implementation("org.jsoup:jsoup:1.7.2")
45+
4346
//implementation("dev.langchain4j:langchain4j:0.35.0")
4447
testImplementation("org.springframework.boot:spring-boot-starter-test")
4548
testImplementation("com.hivemq:hivemq-mqtt-client:1.3.3")

kotlin-wot-integration-tests/src/main/kotlin/integration/AgentConfiguration.kt

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ai.ancf.lmos.wot.security.SecurityScheme
77
import ai.ancf.lmos.wot.thing.schema.WoTConsumedThing
88
import com.fasterxml.jackson.module.kotlin.readValue
99
import kotlinx.coroutines.runBlocking
10+
import org.eclipse.lmos.arc.agents.dsl.AllTools
1011
import org.eclipse.lmos.arc.agents.functions.LLMFunction
1112
import org.eclipse.lmos.arc.spring.Agents
1213
import org.eclipse.lmos.arc.spring.Functions
@@ -22,14 +23,15 @@ import org.springframework.context.annotation.Configuration
2223
@EnableConfigurationProperties(ToolProperties::class)
2324
class AgentConfiguration {
2425

25-
private lateinit var thingDescriptionsMap : Map<String, WoTConsumedThing>
26+
private lateinit var thingDescriptionsMap: Map<String, WoTConsumedThing>
2627

27-
private val log : Logger = LoggerFactory.getLogger(AgentConfiguration::class.java)
28+
private val log: Logger = LoggerFactory.getLogger(AgentConfiguration::class.java)
2829

2930
@Bean
3031
fun chatArcAgent(agent: Agents) = agent {
3132
name = "ChatAgent"
32-
prompt { """
33+
prompt {
34+
"""
3335
You are a professional smart home assistant.
3436
## Instructions
3537
@@ -63,7 +65,8 @@ class AgentConfiguration {
6365
- "Set the thermostat to 72°F."
6466
- "Lock the front door."
6567
66-
""".trimIndent() }
68+
""".trimIndent()
69+
}
6770
model = { "GPT-4o" }
6871
filterInput { -"Hello world" }
6972
tools = listOf("devices")
@@ -81,11 +84,13 @@ class AgentConfiguration {
8184
name = "ScraperAgent"
8285
prompt { "You can scrape a page by using the scraper tool." }
8386
model = { "GPT-4o" }
84-
tools = listOf("fetchContent")
87+
tools = AllTools
8588
}
8689

8790
@Bean
88-
fun agentEventListener(applicationEventPublisher: ApplicationEventPublisher) = ArcEventListener(applicationEventPublisher)
91+
fun agentEventListener(applicationEventPublisher: ApplicationEventPublisher) =
92+
ArcEventListener(applicationEventPublisher)
93+
8994

9095
@Bean
9196
fun discoverTools(toolProperties: ToolProperties, functions: Functions, wot: Wot) : List<LLMFunction> = runBlocking {
@@ -108,12 +113,19 @@ class AgentConfiguration {
108113
}
109114
}
110115

111-
}
112-
113-
data class Resources(
114-
val milk: Int,
115-
val water: Int ,
116-
val chocolate : Int,
117-
val coffeeBeans: Int
118-
)
116+
/*
117+
@Bean
118+
fun discoverLocalTools(functions: Functions, wot: Wot, scraperTool: ScraperTool) : List<LLMFunction>{
119+
log.info("Map Scraper to LLM Functions")
120+
val exposedThing = ExposedThingBuilder.createExposedThing(wot, scraperTool, ScraperTool::class)
121+
return if(exposedThing != null) {
122+
ThingToFunctionsMapper
123+
.mapThingDescriptionToFunctions2(functions, "scraper",
124+
exposedThing.getThingDescription()).toList()
125+
}else{
126+
emptyList()
127+
}
128+
}
129+
*/
119130

131+
}
Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
11
package ai.ancf.lmos.wot.integration
22

3-
import ai.ancf.lmos.wot.protocol.ConversationalAgent
4-
import io.opentelemetry.api.trace.Span
5-
import io.opentelemetry.instrumentation.annotations.SpanAttribute
3+
4+
import ai.ancf.lmos.sdk.agents.ConversationalAgent
5+
import ai.ancf.lmos.sdk.model.AgentRequest
6+
import ai.ancf.lmos.sdk.model.AgentResult
7+
import integration.executeAgent
68
import io.opentelemetry.instrumentation.annotations.WithSpan
79
import org.eclipse.lmos.arc.agents.AgentProvider
8-
import org.eclipse.lmos.arc.agents.User
9-
import org.eclipse.lmos.arc.agents.conversation.AssistantMessage
10-
import org.eclipse.lmos.arc.agents.conversation.latest
11-
import org.eclipse.lmos.arc.agents.conversation.toConversation
1210
import org.eclipse.lmos.arc.agents.getAgentByName
13-
import org.eclipse.lmos.arc.core.getOrThrow
1411
import org.slf4j.Logger
1512
import org.slf4j.LoggerFactory
1613
import org.springframework.stereotype.Component
1714

1815
@Component
19-
class ArcConversationalAgent(agentProvider: AgentProvider) : ConversationalAgent<String, String> {
16+
class ArcConversationalAgent(agentProvider: AgentProvider) : ConversationalAgent {
2017

2118
private val agent = agentProvider.getAgentByName("ChatAgent") as org.eclipse.lmos.arc.agents.ChatAgent
2219

2320
private val log : Logger = LoggerFactory.getLogger(ArcConversationalAgent::class.java)
2421

2522
@WithSpan
26-
override suspend fun chat(@SpanAttribute message: String): String {
27-
log.info("Chat input: $message")
28-
val assistantMessage = agent.execute(message.toConversation(User("myId"))).getOrThrow().latest<AssistantMessage>()
29-
?: throw RuntimeException("No Assistant response")
30-
31-
val currentSpan = Span.current()
32-
currentSpan.setAttribute("assistantMessage", assistantMessage.content)
33-
return assistantMessage.content
23+
override suspend fun chat(message: AgentRequest): AgentResult {
24+
return executeAgent(message, agent::execute)
3425
}
3526
}
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package ai.ancf.lmos.wot.integration
22

33

4+
5+
import ai.ancf.lmos.sdk.model.AgentEvent
46
import ai.ancf.lmos.wot.JsonMapper
57
import org.eclipse.lmos.arc.agents.events.Event
68
import org.eclipse.lmos.arc.agents.events.EventHandler
@@ -10,8 +12,14 @@ import org.springframework.context.ApplicationEventPublisher
1012
class ArcEventListener(private val applicationEventPublisher: ApplicationEventPublisher) : EventHandler<Event> {
1113

1214
override fun onEvent(event: Event) {
13-
applicationEventPublisher.publishEvent(AgentEvent(JsonMapper.instance.writeValueAsString(event)))
15+
applicationEventPublisher.publishEvent(SpringApplicationAgentEvent(
16+
AgentEvent(
17+
event::class.simpleName.toString(),
18+
JsonMapper.instance.writeValueAsString(event),
19+
event.context["conversationId"],
20+
event.context["turnId"])
21+
))
1422
}
1523
}
1624

17-
data class AgentEvent(val message: String) : ApplicationEvent(message)
25+
data class SpringApplicationAgentEvent(val event: AgentEvent) : ApplicationEvent(event)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package integration
2+
3+
import ai.ancf.lmos.sdk.model.AgentRequest
4+
import ai.ancf.lmos.sdk.model.AgentResult
5+
import ai.ancf.lmos.sdk.model.Message
6+
import org.eclipse.lmos.arc.agents.AgentFailedException
7+
import org.eclipse.lmos.arc.agents.User
8+
import org.eclipse.lmos.arc.agents.conversation.AssistantMessage
9+
import org.eclipse.lmos.arc.agents.conversation.Conversation
10+
import org.eclipse.lmos.arc.agents.conversation.latest
11+
import org.eclipse.lmos.arc.agents.conversation.toConversation
12+
import org.eclipse.lmos.arc.core.Result
13+
import org.eclipse.lmos.arc.core.getOrThrow
14+
15+
suspend fun executeAgent(message: AgentRequest, function: suspend (Conversation) -> Result<Conversation, AgentFailedException>): AgentResult {
16+
val lastMessage : String = message.messages.last().content
17+
val assistantMessage = function(lastMessage.toConversation(User("myId"))).getOrThrow().latest<AssistantMessage>()
18+
?: throw RuntimeException("No Assistant response")
19+
return AgentResult(
20+
messages = listOf(
21+
Message(
22+
role = "assistant",
23+
content = assistantMessage.content,
24+
turnId = assistantMessage.turnId
25+
)
26+
)
27+
)
28+
}
29+

kotlin-wot-integration-tests/src/main/kotlin/integration/ChatAgent.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package ai.ancf.lmos.wot.integration
22

3-
import ai.ancf.lmos.wot.JsonMapper
4-
import ai.ancf.lmos.wot.protocol.ConversationalAgent
5-
import ai.ancf.lmos.wot.protocol.LMOSContext
3+
import ai.ancf.lmos.sdk.LMOSContext
4+
import ai.ancf.lmos.sdk.agents.ConversationalAgent
5+
import ai.ancf.lmos.sdk.model.AgentEvent
6+
import ai.ancf.lmos.sdk.model.AgentRequest
7+
import ai.ancf.lmos.sdk.model.AgentResult
68
import ai.ancf.lmos.wot.protocol.LMOSThingType
79
import ai.ancf.lmos.wot.reflection.annotations.*
810
import kotlinx.coroutines.CoroutineScope
@@ -20,25 +22,25 @@ import org.springframework.stereotype.Component
2022
@Link(href = "lmos/capabilities", rel = "service-meta", type = "application/json")
2123
@VersionInfo(instance = "1.0.0")
2224
@Component
23-
class ChatAgent(private val arcAgent: ConversationalAgent<String, String>): ApplicationListener<AgentEvent> {
25+
class ChatAgent(private val arcAgent: ConversationalAgent): ApplicationListener<SpringApplicationAgentEvent> {
2426

25-
private val agentEventFlow = MutableSharedFlow<String>(replay = 1) // Replay last emitted value
27+
private val agentEventFlow = MutableSharedFlow<AgentEvent>(replay = 1) // Replay last emitted value
2628

2729
@Action(title = "Chat", description = "Ask the agent a question.")
2830
@ActionInput(title = "The question", description = "A question")
29-
@ActionOutput(title = "The question", description = "A question")
30-
suspend fun chat(message: String) : String {
31+
@ActionOutput(title = "The answer", description = "The Answer")
32+
suspend fun chat(message: AgentRequest) : AgentResult {
3133
return arcAgent.chat(message)
3234
}
3335

3436
@Event(title = "Agent Event", description = "An event from the agent.")
35-
fun agentEvent() : Flow<String> {
37+
fun agentEvent() : Flow<AgentEvent> {
3638
return agentEventFlow
3739
}
3840

39-
override fun onApplicationEvent(event: AgentEvent) {
41+
override fun onApplicationEvent(event: SpringApplicationAgentEvent) {
4042
CoroutineScope(Dispatchers.IO).launch {
41-
agentEventFlow.emit(JsonMapper.instance.writeValueAsString(event))
43+
agentEventFlow.emit(event.event)
4244
}
4345
}
4446
}
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
package ai.ancf.lmos.wot.integration
22

33

4+
import ai.ancf.lmos.sdk.model.AgentRequest
5+
import ai.ancf.lmos.sdk.model.AgentResult
46
import ai.ancf.lmos.wot.protocol.LMOSContext
57
import ai.ancf.lmos.wot.protocol.LMOSThingType
68
import ai.ancf.lmos.wot.reflection.annotations.Action
79
import ai.ancf.lmos.wot.reflection.annotations.Context
810
import ai.ancf.lmos.wot.reflection.annotations.Thing
911
import ai.ancf.lmos.wot.reflection.annotations.VersionInfo
12+
import integration.executeAgent
1013
import kotlinx.coroutines.flow.MutableSharedFlow
1114
import org.eclipse.lmos.arc.agents.AgentProvider
12-
import org.eclipse.lmos.arc.agents.User
13-
import org.eclipse.lmos.arc.agents.conversation.AssistantMessage
14-
import org.eclipse.lmos.arc.agents.conversation.latest
15-
import org.eclipse.lmos.arc.agents.conversation.toConversation
1615
import org.eclipse.lmos.arc.agents.getAgentByName
17-
import org.eclipse.lmos.arc.core.getOrThrow
1816
import org.springframework.stereotype.Component
1917

2018

@@ -30,10 +28,8 @@ class ResearcherAgent(agentProvider: AgentProvider) {
3028
val agent = agentProvider.getAgentByName("ResearcherAgent") as org.eclipse.lmos.arc.agents.ChatAgent
3129

3230
@Action(title = "Chat", description = "Ask the agent a question.")
33-
suspend fun chat(message : String) : String {
34-
val assistantMessage = agent.execute(message.toConversation(User("myId"))).getOrThrow().latest<AssistantMessage>() ?:
35-
throw RuntimeException("No Assistant response")
36-
return assistantMessage.content
31+
suspend fun chat(message : AgentRequest) : AgentResult {
32+
return executeAgent(message, agent::execute)
3733
}
3834
}
3935

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package ai.ancf.lmos.wot.integration
22

33

4+
import ai.ancf.lmos.sdk.model.AgentEvent
5+
import ai.ancf.lmos.sdk.model.AgentRequest
6+
import ai.ancf.lmos.sdk.model.AgentResult
47
import ai.ancf.lmos.wot.protocol.LMOSContext
58
import ai.ancf.lmos.wot.protocol.LMOSThingType
69
import ai.ancf.lmos.wot.reflection.annotations.*
10+
import integration.executeAgent
11+
import kotlinx.coroutines.CoroutineScope
12+
import kotlinx.coroutines.Dispatchers
713
import kotlinx.coroutines.flow.Flow
814
import kotlinx.coroutines.flow.MutableSharedFlow
15+
import kotlinx.coroutines.launch
916
import org.eclipse.lmos.arc.agents.AgentProvider
10-
import org.eclipse.lmos.arc.agents.User
11-
import org.eclipse.lmos.arc.agents.conversation.AssistantMessage
12-
import org.eclipse.lmos.arc.agents.conversation.latest
13-
import org.eclipse.lmos.arc.agents.conversation.toConversation
1417
import org.eclipse.lmos.arc.agents.getAgentByName
15-
import org.eclipse.lmos.arc.core.getOrThrow
18+
import org.springframework.context.ApplicationListener
1619
import org.springframework.stereotype.Component
1720

1821

@@ -21,23 +24,26 @@ import org.springframework.stereotype.Component
2124
@Context(prefix = LMOSContext.prefix, url = LMOSContext.url)
2225
@VersionInfo(instance = "1.0.0")
2326
@Component
24-
class ScraperAgent(agentProvider: AgentProvider) {
27+
class ScraperAgent(agentProvider: AgentProvider) : ApplicationListener<SpringApplicationAgentEvent> {
2528

26-
private val messageFlow = MutableSharedFlow<String>(replay = 1) // Replay last emitted value
29+
private val agentEventFlow = MutableSharedFlow<AgentEvent>()
2730

2831
val agent = agentProvider.getAgentByName("ScraperAgent") as org.eclipse.lmos.arc.agents.ChatAgent
2932

30-
@Event(description = "HTML Content of the scraped web site")
31-
fun contentRetrieved() : Flow<String> {
32-
return messageFlow
33+
@Action(title = "chat", description = "Ask the agent a question.")
34+
suspend fun chat(message: AgentRequest) : AgentResult {
35+
return executeAgent(message, agent::execute)
3336
}
3437

35-
@Action(title = "chat", description = "Ask the agent a question.")
36-
suspend fun chat(message: String) : String {
37-
val assistantMessage = agent.execute(message.toConversation(User("myId"))).getOrThrow().latest<AssistantMessage>() ?:
38-
throw RuntimeException("No Assistant response")
39-
messageFlow.emit(assistantMessage.content)
40-
return assistantMessage.content
38+
@Event(title = "Agent Event", description = "An event from the agent.")
39+
fun agentEvent() : Flow<AgentEvent> {
40+
return agentEventFlow
41+
}
42+
43+
override fun onApplicationEvent(event: SpringApplicationAgentEvent) {
44+
CoroutineScope(Dispatchers.IO).launch {
45+
agentEventFlow.emit(event.event)
46+
}
4147
}
4248
}
4349

0 commit comments

Comments
 (0)