Skip to content

Commit 5abe465

Browse files
committed
Add Kotlin documentation
1 parent 9c1e897 commit 5abe465

File tree

2 files changed

+134
-4
lines changed

2 files changed

+134
-4
lines changed

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ NOTE: Adhere to the OpenAI link:https://platform.openai.com/docs/guides/structur
270270

271271
You can leverage existing xref::api/structured-output-converter.adoc#_bean_output_converter[BeanOutputConverter] utilities to automatically generate the JSON Schema from your domain objects and later convert the structured response into domain-specific instances:
272272

273+
--
274+
[tabs]
275+
======
276+
Java::
277+
+
273278
[source,java]
274279
----
275280
record MathReasoning(
@@ -301,8 +306,41 @@ String content = this.response.getResult().getOutput().getContent();
301306
302307
MathReasoning mathReasoning = this.outputConverter.convert(this.content);
303308
----
309+
Kotlin::
310+
+
311+
[source,kotlin]
312+
----
313+
data class MathReasoning(
314+
@get:JsonProperty(required = true, value = "steps") val steps: Steps,
315+
@get:JsonProperty(required = true, value = "final_answer") val finalAnswer: String) {
316+
317+
data class Steps(@get:JsonProperty(required = true, value = "items") val items: Array<Items>) {
318+
319+
data class Items(
320+
@get:JsonProperty(required = true, value = "explanation") val explanation: String,
321+
@get:JsonProperty(required = true, value = "output") val output: String)
322+
}
323+
}
324+
325+
val outputConverter = BeanOutputConverter(MathReasoning::class.java)
326+
327+
val jsonSchema = outputConverter.jsonSchema;
328+
329+
val prompt = Prompt("how can I solve 8x + 7 = -23",
330+
OpenAiChatOptions.builder()
331+
.withModel(ChatModel.GPT_4_O_MINI)
332+
.withResponseFormat(ResponseFormat(ResponseFormat.Type.JSON_SCHEMA, jsonSchema))
333+
.build())
334+
335+
val response = openAiChatModel.call(prompt)
336+
val content = response.getResult().getOutput().getContent()
337+
338+
val mathReasoning = outputConverter.convert(content)
339+
----
340+
======
341+
--
304342

305-
NOTE: Ensure you use the `@JsonProperty(required = true,...)` annotation.
343+
NOTE: Ensure you use the `@JsonProperty(required = true,...)` annotation (`@get:JsonProperty(required = true,...)` with Kotlin in order to generate the annotation on the related getters, see link:https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets[related documentation]).
306344
This is crucial for generating a schema that accurately marks fields as `required`.
307345
Although this is optional for JSON Schema, OpenAI link:https://platform.openai.com/docs/guides/structured-outputs/all-fields-must-be-required[mandates] it for the structured response to function correctly.
308346

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,13 @@ When the model needs to answer a question such as `"What’s the weather like in
5656

5757
Our function calls some SaaS-based weather service API and returns the weather response back to the model to complete the conversation. In this example, we will use a simple implementation named `MockWeatherService` that hard-codes the temperature for various locations.
5858

59-
The following `MockWeatherService.java` represents the weather service API:
59+
The following `MockWeatherService` class represents the weather service API:
6060

61+
--
62+
[tabs]
63+
======
64+
Java::
65+
+
6166
[source,java]
6267
----
6368
public class MockWeatherService implements Function<Request, Response> {
@@ -71,20 +76,39 @@ public class MockWeatherService implements Function<Request, Response> {
7176
}
7277
}
7378
----
79+
Kotlin::
80+
+
81+
[source,kotlin]
82+
----
83+
class MockWeatherService : Function1<Request, Response> {
84+
override fun invoke(request: Request) = Response(30.0, Unit.C)
85+
}
86+
87+
enum class Unit { C, F }
88+
data class Request(val location: String, val unit: Unit) {}
89+
data class Response(val temp: Double, val unit: Unit) {}
90+
----
91+
======
92+
--
7493

7594
=== Registering Functions as Beans
7695

7796
Spring AI provides multiple ways to register custom functions as beans in the Spring context.
7897

7998
We start by describing the most POJO-friendly options.
8099

81-
==== Plain Java Functions
100+
==== Plain Functions
82101

83102
In this approach, you define a `@Bean` in your application context as you would any other Spring managed object.
84103

85104
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` that adds the logic for it being invoked via the AI model.
86105
The name of the `@Bean` is used function name.
87106

107+
--
108+
[tabs]
109+
======
110+
Java::
111+
+
88112
[source,java]
89113
----
90114
@Configuration
@@ -98,31 +122,75 @@ static class Config {
98122
99123
}
100124
----
125+
Kotlin::
126+
+
127+
[source,kotlin]
128+
----
129+
@Configuration
130+
class Config {
131+
132+
@Bean
133+
@Description("Get the weather in location") // function description
134+
fun currentWeather(): (Request) -> Response = MockWeatherService()
135+
136+
}
137+
----
138+
======
139+
--
101140

102141
The `@Description` annotation is optional and provides a function description that helps the model understand when to call the function. It is an important property to set to help the AI model determine what client side function to invoke.
103142

104143
Another option for providing the description of the function is to use the `@JsonClassDescription` annotation on the `MockWeatherService.Request`:
105144

145+
--
146+
[tabs]
147+
======
148+
Java::
149+
+
106150
[source,java]
107151
----
108152
@Configuration
109153
static class Config {
154+
110155
@Bean
111156
public Function<Request, Response> currentWeather() { // bean name as function name
112157
return new MockWeatherService();
113158
}
114159
}
115160
116-
@JsonClassDescription("Get the weather in location") // // function description
161+
@JsonClassDescription("Get the weather in location") // function description
117162
public record Request(String location, Unit unit) {}
118163
----
164+
Kotlin::
165+
+
166+
[source,kotlin]
167+
----
168+
@Configuration
169+
class Config {
170+
171+
@Bean
172+
fun currentWeather(): (Request) -> Response { // bean name as function name
173+
return MockWeatherService()
174+
}
175+
}
176+
177+
@JsonClassDescription("Get the weather in location") // function description
178+
data class Request(val location: String, val unit: Unit)
179+
----
180+
======
181+
--
119182

120183
It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke.
121184

122185
==== FunctionCallback Wrapper
123186

124187
Another way to register a function is to create a `FunctionCallbackWrapper` like this:
125188

189+
--
190+
[tabs]
191+
======
192+
Java::
193+
+
126194
[source,java]
127195
----
128196
@Configuration
@@ -138,6 +206,30 @@ static class Config {
138206
}
139207
}
140208
----
209+
Kotlin::
210+
+
211+
[source,kotlin]
212+
----
213+
import org.springframework.ai.model.function.withInputType
214+
215+
@Configuration
216+
class Config {
217+
218+
@Bean
219+
fun weatherFunctionInfo(): FunctionCallback {
220+
221+
return FunctionCallbackWrapper.builder(MockWeatherService())
222+
.withName("CurrentWeather") // (1) function name
223+
.withDescription("Get the weather in location") // (2) function description
224+
// (3) Required due to Kotlin SAM conversion beeing an opaque lambda
225+
.withInputType<MockWeatherService.Request>()
226+
.build();
227+
}
228+
}
229+
230+
----
231+
======
232+
--
141233

142234
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `ChatClient`.
143235
It also provides a description (2) and an optional response converter to convert the response into a text as expected by the model.

0 commit comments

Comments
 (0)