Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration with LLM SenseNova. #1035

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ See example [here](https://github.com/langchain4j/langchain4j-examples/blob/main
| [Nomic](https://docs.langchain4j.dev/integrations/language-models/nomic) | | | | ✅ | | | |
| [Anthropic](https://docs.langchain4j.dev/integrations/language-models/anthropic) | ✅ | ✅ | ✅ | | | | ✅ |
| [Zhipu AI](https://docs.langchain4j.dev/integrations/language-models/zhipu-ai) | | ✅ | ✅ | ✅ | | | ✅ |
| [SenseNova](https://docs.langchain4j.dev/integrations/language-models/sensenova) | | ✅ | ✅ | ✅ | | | ✅ |

## Disclaimer

Expand Down
1 change: 1 addition & 0 deletions docs/docs/integrations/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ of course some LLM providers offer large multimodal model (accepting text or ima
| Nomic | | | |✅ | | | |
| [Anthropic](/integrations/language-models/anthropic) | |✅ | | | | | ✅ |
| [Zhipu AI](/integrations/language-models/zhipu-ai) | |✅| ✅| ✅| | |✅ |
| [SenseNova](/integrations/language-models/sensenova) | |✅| ✅| ✅| | |✅ |


9 changes: 9 additions & 0 deletions docs/docs/integrations/language-models/sensenova.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
sidebar_position: 14
---

# SenseNova

[SenseNova](https://platform.sensenova.cn)

- Chats (sync + streaming + functions + embedding)
121 changes: 121 additions & 0 deletions langchain4j-sensenova/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-parent</artifactId>
<version>0.30.0</version>
<relativePath>../langchain4j-parent/pom.xml</relativePath>
</parent>
<properties>
<java-jwt.version>4.4.0</java-jwt.version>
<guava.version>32.0.0-jre</guava.version>
</properties>
<modelVersion>4.0.0</modelVersion>

<artifactId>langchain4j-sensenova</artifactId>
<name>LangChain4j :: Integration :: SenseNova</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
</dependency>

<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
</dependency>

<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-gson</artifactId>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>${okhttp.version}</version>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.tinylog</groupId>
<artifactId>tinylog-impl</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.tinylog</groupId>
<artifactId>slf4j-tinylog</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>

</dependencies>

<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package dev.langchain4j.model.sensenova;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import dev.langchain4j.model.sensenova.chat.AssistantMessage;
import dev.langchain4j.model.sensenova.chat.ToolCall;


import java.io.IOException;
import java.util.List;

class AssistantMessageTypeAdapter extends TypeAdapter<AssistantMessage> {

static final TypeAdapterFactory ASSISTANT_MESSAGE_TYPE_ADAPTER_FACTORY = new TypeAdapterFactory() {

@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != AssistantMessage.class) {
return null;
}
TypeAdapter<AssistantMessage> delegate = (TypeAdapter<AssistantMessage>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new AssistantMessageTypeAdapter(delegate);
}
};

private final TypeAdapter<AssistantMessage> delegate;

private AssistantMessageTypeAdapter(TypeAdapter<AssistantMessage> delegate) {
this.delegate = delegate;
}

@Override
public void write(JsonWriter out, AssistantMessage assistantMessage) throws IOException {
out.beginObject();

out.name("role");
out.value(assistantMessage.getRole().toString().toLowerCase());

out.name("content");
if (assistantMessage.getContent() == null) {
boolean serializeNulls = out.getSerializeNulls();
out.setSerializeNulls(true);
out.nullValue(); // serialize "content": null
out.setSerializeNulls(serializeNulls);
} else {
out.value(assistantMessage.getContent());
}

if (assistantMessage.getName() != null) {
out.name("name");
out.value(assistantMessage.getName());
}

List<ToolCall> toolCalls = assistantMessage.getToolCalls();
if (toolCalls != null && !toolCalls.isEmpty()) {
out.name("tool_calls");
out.beginArray();
TypeAdapter<ToolCall> toolCallTypeAdapter = Json.GSON.getAdapter(ToolCall.class);
for (ToolCall toolCall : toolCalls) {
toolCallTypeAdapter.write(out, toolCall);
}
out.endArray();
}

out.endObject();
}

@Override
public AssistantMessage read(JsonReader in) throws IOException {
return delegate.read(in);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package dev.langchain4j.model.sensenova;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;


import java.io.IOException;

import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static dev.langchain4j.internal.Utils.getOrDefault;


class AuthorizationInterceptor implements Interceptor {

private static final long expireMillis = 1000 * 60 * 30;
private static final long notBeforeMillis = 1000 * 5;

private final String apiKeyId;
private final String apiKeySecret;
private final Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofMillis(expireMillis))
.build();


public AuthorizationInterceptor(String apiKeyId, String apiKeySecret) {
this.apiKeyId = apiKeyId;
this.apiKeySecret = apiKeySecret;
}

@Override
public Response intercept(Chain chain) throws IOException {
String token = getOrDefault(cache.getIfPresent(this.apiKeyId), generateToken());
Request request = chain.request()
.newBuilder()
.addHeader("Authorization", "Bearer " + token)
.removeHeader("Accept")
.build();
return chain.proceed(request);
}

private String generateToken() {
try {
Date expiredAt = new Date(System.currentTimeMillis() + expireMillis);
Date notBefore = new Date(System.currentTimeMillis() - notBeforeMillis);
Algorithm algo = Algorithm.HMAC256(apiKeySecret);
Map<String, Object> header = new HashMap<>(2);
header.put("alg", "HS256");
final String token = JWT.create()
.withIssuer(apiKeyId)
.withHeader(header)
.withExpiresAt(expiredAt)
.withNotBefore(notBefore)
.sign(algo);
cache.put(this.apiKeyId, token);
return token;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

}