Skip to content

Commit

Permalink
Merge branch 'main' of github.com:sincerity-being/spring-ai-alibaba
Browse files Browse the repository at this point in the history
  • Loading branch information
sincerity-being committed Dec 8, 2024
2 parents 2beae9f + d6de43a commit eeb0a6f
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ target/

### IntelliJ IDEA ###
.idea/*
**/.idea/*
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
import com.alibaba.cloud.ai.dashscope.rerank.DashScopeRerankModel;
import com.alibaba.dashscope.audio.asr.transcription.Transcription;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import io.micrometer.observation.ObservationRegistry;
import org.jetbrains.annotations.NotNull;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Expand Down Expand Up @@ -96,7 +98,7 @@ public DashScopeChatModel dashscopeChatModel(DashScopeConnectionProperties commo
DashScopeChatProperties chatProperties, RestClient.Builder restClientBuilder,
WebClient.Builder webClientBuilder, List<FunctionCallback> toolFunctionCallbacks,
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler) {
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry) {

if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
Expand All @@ -105,8 +107,8 @@ public DashScopeChatModel dashscopeChatModel(DashScopeConnectionProperties commo
var dashscopeApi = dashscopeChatApi(commonProperties, chatProperties, restClientBuilder, webClientBuilder,
responseErrorHandler);

return new DashScopeChatModel(dashscopeApi, chatProperties.getOptions(), functionCallbackContext,
retryTemplate);
return new DashScopeChatModel(dashscopeApi, chatProperties.getOptions(), functionCallbackContext, retryTemplate,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
}

@Bean
Expand Down
5 changes: 5 additions & 0 deletions spring-ai-alibaba-examples/observability-example/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
zipkin:
image: 'openzipkin/zipkin:latest'
ports:
- '9411:9411'
120 changes: 120 additions & 0 deletions spring-ai-alibaba-examples/observability-example/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>observability-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>observability-example</name>
<description>Demo project for Spring AI Alibaba</description>

<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M3</spring-ai.version>
<spring-ai-alibaba.version>1.0.0-M3.2</spring-ai-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.13.6</version>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
<version>1.3.4</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.opentelemetry</groupId>-->
<!-- <artifactId>opentelemetry-exporter-zipkin</artifactId>-->
<!-- <version>1.37.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-common</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp-common</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>

<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-docker-compose</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.observability;

import com.alibaba.cloud.ai.example.observability.exporter.oltp.OtlpFileSpanExporter;
import com.alibaba.cloud.ai.example.observability.exporter.oltp.OtlpFileSpanExporterProvider;

import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass({OtlpFileSpanExporter.class})
public class FileSpanExporterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledTracing
OtlpFileSpanExporter outputSpanExporter() {
OtlpFileSpanExporterProvider provider = new OtlpFileSpanExporterProvider();
return (OtlpFileSpanExporter)provider.createExporter(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.alibaba.cloud.ai.example.observability;

import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatProperties;
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import io.micrometer.observation.ObservationRegistry;
import io.opentelemetry.api.trace.Span;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;

@SpringBootApplication
public class ObservabilityApplication {

public static void main(String[] args) {
SpringApplication.run(ObservabilityApplication.class, args);
}

@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.build();
}

@Bean
@ConditionalOnProperty(prefix = DashScopeChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public DashScopeChatModel dashscopeChatModel(DashScopeChatProperties chatProperties, List<FunctionCallback> toolFunctionCallbacks,
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate,
ObjectProvider<ObservationRegistry> observationRegistry, DashScopeApi dashScopeApi) {

if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
}

return new DashScopeChatModel(dashScopeApi, chatProperties.getOptions(), functionCallbackContext, retryTemplate,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
}
}

@Controller
@ResponseBody
class JokeController {

private final ChatClient chatClient;

JokeController(ChatClient chatClient) {
this.chatClient = chatClient;
}

@GetMapping("/joke")
Map<String, String> joke() {
var reply = chatClient
.prompt()
.user("""
tell me a joke. be concise. don't send anything except the joke.
""")
.call()
.content();
Span currentSpan = Span.current();
return Map.of("joke", reply, "traceId", currentSpan.getSpanContext().getTraceId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package com.alibaba.cloud.ai.example.observability.exporter.oltp;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import java.io.IOException;

final class JsonUtil {

static final JsonFactory JSON_FACTORY = new JsonFactory();

static JsonGenerator create(SegmentedStringWriter stringWriter) {
try {
return JSON_FACTORY.createGenerator(stringWriter);
} catch (IOException e) {
throw new IllegalStateException("Unable to create in-memory JsonGenerator, can't happen.", e);
}
}

private JsonUtil() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package com.alibaba.cloud.ai.example.observability.exporter.oltp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* A {@link SpanExporter} which writes {@linkplain SpanData spans} to a {@link Logger} in OTLP JSON
* format. Each log line will include a single {@code ResourceSpans}.
*/
public final class OtlpFileSpanExporter implements SpanExporter {

private static final Logger logger =
Logger.getLogger(OtlpFileSpanExporter.class.getName());

private final AtomicBoolean isShutdown = new AtomicBoolean();

/** Returns a new {@link OtlpFileSpanExporter}. */
public static SpanExporter create() {
return new OtlpFileSpanExporter();
}

private OtlpFileSpanExporter() {}

@Override
public CompletableResultCode export(Collection<SpanData> spans) {
if (isShutdown.get()) {
return CompletableResultCode.ofFailure();
}

ResourceSpansMarshaler[] allResourceSpans = ResourceSpansMarshaler.create(spans);
for (ResourceSpansMarshaler resourceSpans : allResourceSpans) {
SegmentedStringWriter sw =
new SegmentedStringWriter(JsonUtil.JSON_FACTORY._getBufferRecycler());
try (JsonGenerator gen = JsonUtil.create(sw)) {
resourceSpans.writeJsonTo(gen);
} catch (IOException e) {
// Shouldn't happen in practice, just skip it.
continue;
}
try {
logger.log(Level.INFO, sw.getAndClear());
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to read OTLP JSON spans", e);
}
}
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
if (!isShutdown.compareAndSet(false, true)) {
logger.log(Level.INFO, "Calling shutdown() multiple times.");
}
return CompletableResultCode.ofSuccess();
}
}
Loading

0 comments on commit eeb0a6f

Please sign in to comment.