Skip to content

Commit c3cdf9c

Browse files
committed
Move common types to agent-api
1 parent 3c07ca9 commit c3cdf9c

File tree

33 files changed

+1085
-862
lines changed

33 files changed

+1085
-862
lines changed

README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ fun onMessage(event: MessageEvent) {
7575
Messages have explicit sender and recipient:
7676

7777
```
78-
(message:StoredMessage)-[:AUTHORED_BY]->(from:SessionUser)
79-
(message:StoredMessage)-[:SENT_TO]->(to:SessionUser)
78+
(message:StoredMessage)-[:AUTHORED_BY]->(from:User)
79+
(message:StoredMessage)-[:SENT_TO]->(to:User)
8080
```
8181

8282
### Role-Based Routing (1-1 Chats)
@@ -148,29 +148,42 @@ val factory = StoredConversationFactory(
148148
)
149149
```
150150

151-
## Session User
151+
## User Implementation
152152

153-
Implement `SessionUser` for your user type:
153+
Implement `StoredUser` for your user type. The `StoredUser` interface extends `User` from
154+
`embabel-agent-api` with Drivine annotations for Neo4j persistence:
154155

155156
```kotlin
156-
@NodeFragment(labels = ["SessionUser", "MyUser"])
157+
@NodeFragment(labels = ["User", "MyUser"])
157158
data class MyUser(
158159
@NodeId override val id: String,
159160
override val displayName: String,
160-
val email: String
161-
) : SessionUser
161+
override val username: String,
162+
override val email: String?
163+
) : StoredUser
162164
```
163165

164-
Register with Drivine:
166+
Register with Drivine for polymorphic loading:
165167

166168
```kotlin
167169
persistenceManager.registerSubtype(
168-
SessionUser::class.java,
169-
"MyUser|SessionUser",
170+
StoredUser::class.java,
171+
"MyUser|User", // Labels sorted alphabetically
170172
MyUser::class.java
171173
)
172174
```
173175

176+
For simple cases without extra fields, use the built-in `SimpleStoredUser`:
177+
178+
```kotlin
179+
val user = SimpleStoredUser(
180+
id = "user-123",
181+
displayName = "Alice",
182+
username = "alice",
183+
email = "alice@example.com"
184+
)
185+
```
186+
174187
## Spring Boot Auto-Configuration
175188

176189
Add the dependency and configure:
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.embabel.agent</groupId>
8+
<artifactId>embabel-chat-store-parent</artifactId>
9+
<version>0.2.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>embabel-agent-starter-chat-store</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Embabel Agent Chat Store Starter</name>
15+
<description>Starter for Embabel Chat Store - brings in library and autoconfiguration</description>
16+
17+
<dependencies>
18+
<!-- Autoconfigure (transitively brings in embabel-chat-store) -->
19+
<dependency>
20+
<groupId>com.embabel.agent</groupId>
21+
<artifactId>embabel-chat-store-autoconfigure</artifactId>
22+
</dependency>
23+
</dependencies>
24+
25+
</project>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.embabel.agent</groupId>
8+
<artifactId>embabel-chat-store-parent</artifactId>
9+
<version>0.2.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>embabel-chat-store-autoconfigure</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Embabel Chat Store Autoconfigure</name>
15+
<description>Spring Boot Auto-Configuration for Embabel Chat Store</description>
16+
17+
<dependencies>
18+
<!-- Core library -->
19+
<dependency>
20+
<groupId>com.embabel.agent</groupId>
21+
<artifactId>embabel-chat-store</artifactId>
22+
</dependency>
23+
24+
<!-- Embabel Agent API (for ConversationFactory, etc.) -->
25+
<dependency>
26+
<groupId>com.embabel.agent</groupId>
27+
<artifactId>embabel-agent-api</artifactId>
28+
</dependency>
29+
30+
<!-- Spring Boot Autoconfigure -->
31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-autoconfigure</artifactId>
34+
</dependency>
35+
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-configuration-processor</artifactId>
39+
<optional>true</optional>
40+
</dependency>
41+
42+
<!-- Kotlin Support -->
43+
<dependency>
44+
<groupId>org.jetbrains.kotlin</groupId>
45+
<artifactId>kotlin-stdlib</artifactId>
46+
</dependency>
47+
48+
<!-- Logging -->
49+
<dependency>
50+
<groupId>org.slf4j</groupId>
51+
<artifactId>slf4j-api</artifactId>
52+
</dependency>
53+
</dependencies>
54+
55+
<build>
56+
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.jetbrains.kotlin</groupId>
60+
<artifactId>kotlin-maven-plugin</artifactId>
61+
</plugin>
62+
</plugins>
63+
</build>
64+
65+
</project>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2024-2026 Embabel Pty Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.embabel.chat.store.autoconfigure
17+
18+
import com.embabel.chat.ConversationFactory
19+
import com.embabel.chat.ConversationFactoryProvider
20+
import com.embabel.chat.MapConversationFactoryProvider
21+
import com.embabel.chat.store.adapter.StoredConversationFactory
22+
import com.embabel.chat.store.adapter.TitleGenerator
23+
import com.embabel.chat.store.repository.ChatSessionRepository
24+
import com.embabel.chat.support.InMemoryConversationFactory
25+
import org.slf4j.LoggerFactory
26+
import org.springframework.beans.factory.annotation.Autowired
27+
import org.springframework.boot.autoconfigure.AutoConfiguration
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
31+
import org.springframework.boot.context.properties.EnableConfigurationProperties
32+
import org.springframework.context.ApplicationEventPublisher
33+
import org.springframework.context.annotation.Bean
34+
35+
/**
36+
* Auto-configuration for Embabel Chat Store.
37+
*
38+
* Automatically activates when:
39+
* - Chat store classes are on the classpath
40+
* - `embabel.chat.store.enabled=true` (default)
41+
*
42+
* This configuration provides:
43+
* - [StoredConversationFactory] - for persistent conversations
44+
* - [InMemoryConversationFactory] - for ephemeral conversations
45+
* - [ConversationFactoryProvider] - to access factories by type
46+
*
47+
* Apps can override by defining their own [ConversationFactory] beans.
48+
*
49+
* To disable entirely:
50+
* ```
51+
* embabel.chat.store.enabled=false
52+
* ```
53+
*/
54+
@AutoConfiguration
55+
@ConditionalOnClass(ChatSessionRepository::class)
56+
@ConditionalOnProperty(
57+
prefix = "embabel.chat.store",
58+
name = ["enabled"],
59+
havingValue = "true",
60+
matchIfMissing = true
61+
)
62+
@EnableConfigurationProperties(ChatStoreProperties::class)
63+
open class ChatStoreAutoConfiguration {
64+
65+
private val logger = LoggerFactory.getLogger(ChatStoreAutoConfiguration::class.java)
66+
67+
/**
68+
* Creates a [StoredConversationFactory] for persistent conversations.
69+
*
70+
* Only created if:
71+
* - No existing [ConversationFactory] bean with qualifier "stored"
72+
* - [ChatSessionRepository] is available
73+
*
74+
* Optionally wires in:
75+
* - [TitleGenerator] - for auto-generating session titles
76+
* - [ApplicationEventPublisher] - for message lifecycle events
77+
*/
78+
@Bean("storedConversationFactory")
79+
@ConditionalOnMissingBean(name = ["storedConversationFactory"])
80+
open fun storedConversationFactory(
81+
repository: ChatSessionRepository,
82+
@Autowired(required = false) titleGenerator: TitleGenerator?,
83+
@Autowired(required = false) eventPublisher: ApplicationEventPublisher?
84+
): ConversationFactory {
85+
logger.info(
86+
"Creating StoredConversationFactory (titleGenerator={}, eventPublisher={})",
87+
titleGenerator?.javaClass?.simpleName ?: "none",
88+
if (eventPublisher != null) "present" else "none"
89+
)
90+
91+
return StoredConversationFactory(
92+
repository = repository,
93+
eventPublisher = eventPublisher,
94+
titleGenerator = titleGenerator
95+
)
96+
}
97+
98+
/**
99+
* Creates an [InMemoryConversationFactory] for ephemeral conversations.
100+
*
101+
* Useful for:
102+
* - Testing
103+
* - Short-lived sessions that don't need persistence
104+
* - Fallback when storage is unavailable
105+
*/
106+
@Bean("inMemoryConversationFactory")
107+
@ConditionalOnMissingBean(name = ["inMemoryConversationFactory"])
108+
open fun inMemoryConversationFactory(
109+
@Autowired(required = false) eventPublisher: ApplicationEventPublisher?
110+
): ConversationFactory {
111+
logger.info("Creating InMemoryConversationFactory")
112+
return InMemoryConversationFactory(eventPublisher)
113+
}
114+
115+
/**
116+
* Creates a [ConversationFactoryProvider] that provides access to all registered factories.
117+
*
118+
* Factories are looked up by [com.embabel.chat.ConversationStoreType]:
119+
* - `STORED` -> storedConversationFactory
120+
* - `IN_MEMORY` -> inMemoryConversationFactory
121+
*/
122+
@Bean
123+
@ConditionalOnMissingBean(ConversationFactoryProvider::class)
124+
open fun conversationFactoryProvider(
125+
factories: List<ConversationFactory>
126+
): ConversationFactoryProvider {
127+
logger.info("Creating ConversationFactoryProvider with factories: {}",
128+
factories.map { it.storeType })
129+
return MapConversationFactoryProvider(factories)
130+
}
131+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2024-2026 Embabel Pty Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.embabel.chat.store.autoconfigure
17+
18+
import org.springframework.boot.context.properties.ConfigurationProperties
19+
20+
/**
21+
* Configuration properties for Embabel Chat Store.
22+
*/
23+
@ConfigurationProperties(prefix = "embabel.chat.store")
24+
class ChatStoreProperties {
25+
/**
26+
* Whether chat store auto-configuration is enabled.
27+
* Default: true
28+
*/
29+
var enabled: Boolean = true
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.embabel.chat.store.autoconfigure.ChatStoreAutoConfiguration

0 commit comments

Comments
 (0)