package io.github.pigmesh.ai.deepseek.core.embedding;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Getter;
import lombok.Setter;

import java.util.List;
import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingModel.BGE_M3;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = EmbeddingRequest.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class EmbeddingRequest {
	@JsonProperty
	@Getter
	@Setter
	private String model;
	@JsonProperty
	private final List<String> input;
	@JsonProperty
	private final Integer dimensions;
	@JsonProperty
	private final String user;
	private EmbeddingRequest(Builder builder) {
		this.model = builder.model;
		this.input = builder.input;
		this.dimensions = builder.dimensions;
		this.user = builder.user;
	}
	public String model() {
		return model;
	}
	public List<String> input() {
		return input;
	}
	public Integer dimensions() {
		return dimensions;
	}
	public String user() {
		return user;
	}
	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof EmbeddingRequest && equalTo((EmbeddingRequest) another);
	}
	private boolean equalTo(EmbeddingRequest another) {
		return Objects.equals(model, another.model) && Objects.equals(input, another.input)
				&& Objects.equals(dimensions, another.dimensions) && Objects.equals(user, another.user);
	}
	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(input);
		h += (h << 5) + Objects.hashCode(dimensions);
		h += (h << 5) + Objects.hashCode(user);
		return h;
	}
	@Override
	public String toString() {
		return "EmbeddingRequest{" + "model=" + model + ", input=" + input + ", dimensions=" + dimensions + ", user="
				+ user + "}";
	}
	public static Builder builder() {
		return new Builder();
	}
	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {
		private String model = BGE_M3.toString();
		private List<String> input;
		private Integer dimensions;
		private String user;
		private Builder() {
		}
		public Builder model(EmbeddingModel model) {
			return model(model.toString());
		}
		public Builder model(String model) {
			this.model = model;
			return this;
		}
		public Builder input(String... input) {
			return input(asList(input));
		}
		public Builder input(List<String> input) {
			if (input != null) {
				this.input = unmodifiableList(input);
			}
			return this;
		}
		public Builder dimensions(Integer dimensions) {
			this.dimensions = dimensions;
			return this;
		}
		public Builder user(String user) {
			this.user = user;
			return this;
		}
		public EmbeddingRequest build() {
			return new EmbeddingRequest(this);
		}
	}
}

package io.github.pigmesh.ai.deepseek.core.moderation;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = ModerationRequest.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ModerationRequest {

	@JsonProperty
	private final String model;

	@JsonProperty
	private final List<String> input;

	private ModerationRequest(Builder builder) {
		this.model = builder.model;
		this.input = builder.input;
	}

	public String model() {
		return model;
	}

	public List<String> input() {
		return input;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ModerationRequest && equalTo((ModerationRequest) another);
	}

	private boolean equalTo(ModerationRequest another) {
		return Objects.equals(model, another.model) && Objects.equals(input, another.input);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(input);
		return h;
	}

	@Override
	public String toString() {
		return "ModerationRequest{" + "model=" + model + ", input=" + input + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String model;

		private List<String> input;

		private Builder() {
		}

		public Builder model(ModerationModel model) {
			return model(model.toString());
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		public Builder input(List<String> input) {
			if (input != null) {
				this.input = unmodifiableList(input);
			}
			return this;
		}

		public Builder input(String input) {
			return input(singletonList(input));
		}

		public ModerationRequest build() {
			return new ModerationRequest(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.embedding;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import io.github.pigmesh.ai.deepseek.core.shared.Usage;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = EmbeddingResponse.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class EmbeddingResponse {

	@JsonProperty
	private final String model;

	@JsonProperty
	private final List<Embedding> data;

	@JsonProperty
	private final Usage usage;

	private EmbeddingResponse(Builder builder) {
		this.model = builder.model;
		this.data = builder.data;
		this.usage = builder.usage;
	}

	public String model() {
		return model;
	}

	public List<Embedding> data() {
		return data;
	}

	public Usage usage() {
		return usage;
	}

	/**
	 * Convenience method to get the embedding from the first data.
	 */
	public List<Float> embedding() {
		return data.get(0).embedding();
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof EmbeddingResponse && equalTo((EmbeddingResponse) another);
	}

	private boolean equalTo(EmbeddingResponse another) {
		return Objects.equals(model, another.model) && Objects.equals(data, another.data)
				&& Objects.equals(usage, another.usage);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(data);
		h += (h << 5) + Objects.hashCode(usage);
		return h;
	}

	@Override
	public String toString() {
		return "EmbeddingResponse{" + "model=" + model + ", data=" + data + ", usage=" + usage + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String model;

		private List<Embedding> data;

		private Usage usage;

		private Builder() {
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		public Builder data(List<Embedding> data) {
			if (data != null) {
				this.data = unmodifiableList(data);
			}
			return this;
		}

		public Builder usage(Usage usage) {
			this.usage = usage;
			return this;
		}

		public EmbeddingResponse build() {
			return new EmbeddingResponse(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.completion;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import io.github.pigmesh.ai.deepseek.core.shared.StreamOptions;

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

import static io.github.pigmesh.ai.deepseek.core.completion.CompletionModel.GPT_3_5_TURBO_INSTRUCT;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;

@JsonDeserialize(builder = CompletionRequest.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class CompletionRequest {

	@JsonProperty
	private final String model;

	@JsonProperty
	private final String prompt;

	@JsonProperty
	private final String suffix;

	@JsonProperty
	private final Integer maxTokens;

	@JsonProperty
	private final Double temperature;

	@JsonProperty
	private final Double topP;

	@JsonProperty
	private final Integer n;

	@JsonProperty
	private final Boolean stream;

	@JsonProperty
	private final StreamOptions streamOptions;

	@JsonProperty
	private final Integer logprobs;

	@JsonProperty
	private final Boolean echo;

	@JsonProperty
	private final List<String> stop;

	@JsonProperty
	private final Double presencePenalty;

	@JsonProperty
	private final Double frequencyPenalty;

	@JsonProperty
	private final Integer bestOf;

	@JsonProperty
	private final Map<String, Integer> logitBias;

	@JsonProperty
	private final String user;

	private CompletionRequest(Builder builder) {
		this.model = builder.model;
		this.prompt = builder.prompt;
		this.suffix = builder.suffix;
		this.maxTokens = builder.maxTokens;
		this.temperature = builder.temperature;
		this.topP = builder.topP;
		this.n = builder.n;
		this.stream = builder.stream;
		this.streamOptions = builder.streamOptions;
		this.logprobs = builder.logprobs;
		this.echo = builder.echo;
		this.stop = builder.stop;
		this.presencePenalty = builder.presencePenalty;
		this.frequencyPenalty = builder.frequencyPenalty;
		this.bestOf = builder.bestOf;
		this.logitBias = builder.logitBias;
		this.user = builder.user;
	}

	public String model() {
		return model;
	}

	public String prompt() {
		return prompt;
	}

	public String suffix() {
		return suffix;
	}

	public Integer maxTokens() {
		return maxTokens;
	}

	public Double temperature() {
		return temperature;
	}

	public Double topP() {
		return topP;
	}

	public Integer n() {
		return n;
	}

	public Boolean stream() {
		return stream;
	}

	public StreamOptions streamOptions() {
		return streamOptions;
	}

	public Integer logprobs() {
		return logprobs;
	}

	public Boolean echo() {
		return echo;
	}

	public List<String> stop() {
		return stop;
	}

	public Double presencePenalty() {
		return presencePenalty;
	}

	public Double frequencyPenalty() {
		return frequencyPenalty;
	}

	public Integer bestOf() {
		return bestOf;
	}

	public Map<String, Integer> logitBias() {
		return logitBias;
	}

	public String user() {
		return user;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof CompletionRequest && equalTo((CompletionRequest) another);
	}

	private boolean equalTo(CompletionRequest another) {
		return Objects.equals(model, another.model) && Objects.equals(prompt, another.prompt)
				&& Objects.equals(suffix, another.suffix) && Objects.equals(maxTokens, another.maxTokens)
				&& Objects.equals(temperature, another.temperature) && Objects.equals(topP, another.topP)
				&& Objects.equals(n, another.n) && Objects.equals(stream, another.stream)
				&& Objects.equals(streamOptions, another.streamOptions) && Objects.equals(logprobs, another.logprobs)
				&& Objects.equals(echo, another.echo) && Objects.equals(stop, another.stop)
				&& Objects.equals(presencePenalty, another.presencePenalty)
				&& Objects.equals(frequencyPenalty, another.frequencyPenalty) && Objects.equals(bestOf, another.bestOf)
				&& Objects.equals(logitBias, another.logitBias) && Objects.equals(user, another.user);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(prompt);
		h += (h << 5) + Objects.hashCode(suffix);
		h += (h << 5) + Objects.hashCode(maxTokens);
		h += (h << 5) + Objects.hashCode(temperature);
		h += (h << 5) + Objects.hashCode(topP);
		h += (h << 5) + Objects.hashCode(n);
		h += (h << 5) + Objects.hashCode(stream);
		h += (h << 5) + Objects.hashCode(streamOptions);
		h += (h << 5) + Objects.hashCode(logprobs);
		h += (h << 5) + Objects.hashCode(echo);
		h += (h << 5) + Objects.hashCode(stop);
		h += (h << 5) + Objects.hashCode(presencePenalty);
		h += (h << 5) + Objects.hashCode(frequencyPenalty);
		h += (h << 5) + Objects.hashCode(bestOf);
		h += (h << 5) + Objects.hashCode(logitBias);
		h += (h << 5) + Objects.hashCode(user);
		return h;
	}

	@Override
	public String toString() {
		return "CompletionRequest{" + "model=" + model + ", prompt=" + prompt + ", suffix=" + suffix + ", maxTokens="
				+ maxTokens + ", temperature=" + temperature + ", topP=" + topP + ", n=" + n + ", stream=" + stream
				+ ", streamOptions=" + streamOptions + ", logprobs=" + logprobs + ", echo=" + echo + ", stop=" + stop
				+ ", presencePenalty=" + presencePenalty + ", frequencyPenalty=" + frequencyPenalty + ", bestOf="
				+ bestOf + ", logitBias=" + logitBias + ", user=" + user + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String model = GPT_3_5_TURBO_INSTRUCT.toString();

		private String prompt;

		private String suffix;

		private Integer maxTokens;

		private Double temperature;

		private Double topP;

		private Integer n;

		private Boolean stream;

		private StreamOptions streamOptions;

		private Integer logprobs;

		private Boolean echo;

		private List<String> stop;

		private Double presencePenalty;

		private Double frequencyPenalty;

		private Integer bestOf;

		private Map<String, Integer> logitBias;

		private String user;

		private Builder() {
		}

		public Builder from(CompletionRequest request) {
			model(request.model);
			prompt(request.prompt);
			suffix(request.suffix);
			maxTokens(request.maxTokens);
			temperature(request.temperature);
			topP(request.topP);
			n(request.n);
			stream(request.stream);
			streamOptions(request.streamOptions);
			logprobs(request.logprobs);
			echo(request.echo);
			stop(request.stop);
			presencePenalty(request.presencePenalty);
			frequencyPenalty(request.frequencyPenalty);
			bestOf(request.bestOf);
			logitBias(request.logitBias);
			user(request.user);
			return this;
		}

		public Builder model(CompletionModel model) {
			return model(model.toString());
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		public Builder prompt(String prompt) {
			this.prompt = prompt;
			return this;
		}

		public Builder suffix(String suffix) {
			this.suffix = suffix;
			return this;
		}

		public Builder maxTokens(Integer maxTokens) {
			this.maxTokens = maxTokens;
			return this;
		}

		public Builder temperature(Double temperature) {
			this.temperature = temperature;
			return this;
		}

		public Builder topP(Double topP) {
			this.topP = topP;
			return this;
		}

		public Builder n(Integer n) {
			this.n = n;
			return this;
		}

		public Builder stream(Boolean stream) {
			this.stream = stream;
			return this;
		}

		public Builder streamOptions(StreamOptions streamOptions) {
			this.streamOptions = streamOptions;
			return this;
		}

		public Builder logprobs(Integer logprobs) {
			this.logprobs = logprobs;
			return this;
		}

		public Builder echo(Boolean echo) {
			this.echo = echo;
			return this;
		}

		public Builder stop(List<String> stop) {
			if (stop != null) {
				this.stop = unmodifiableList(stop);
			}
			return this;
		}

		public Builder presencePenalty(Double presencePenalty) {
			this.presencePenalty = presencePenalty;
			return this;
		}

		public Builder frequencyPenalty(Double frequencyPenalty) {
			this.frequencyPenalty = frequencyPenalty;
			return this;
		}

		public Builder bestOf(Integer bestOf) {
			this.bestOf = bestOf;
			return this;
		}

		public Builder logitBias(Map<String, Integer> logitBias) {
			if (logitBias != null) {
				this.logitBias = unmodifiableMap(logitBias);
			}
			return this;
		}

		public Builder user(String user) {
			this.user = user;
			return this;
		}

		public CompletionRequest build() {
			return new CompletionRequest(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.embedding;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = Embedding.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class Embedding {

	@JsonProperty
	private final List<Float> embedding;

	@JsonProperty
	private final Integer index;

	private Embedding(Builder builder) {
		this.embedding = builder.embedding;
		this.index = builder.index;
	}

	public List<Float> embedding() {
		return embedding;
	}

	public Integer index() {
		return index;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Embedding && equalTo((Embedding) another);
	}

	private boolean equalTo(Embedding another) {
		return Objects.equals(embedding, another.embedding) && Objects.equals(index, another.index);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(embedding);
		h += (h << 5) + Objects.hashCode(index);
		return h;
	}

	@Override
	public String toString() {
		return "Embedding{" + "embedding=" + embedding + ", index=" + index + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private List<Float> embedding;

		private Integer index;

		private Builder() {
		}

		public Builder embedding(List<Float> embedding) {
			if (embedding != null) {
				this.embedding = unmodifiableList(embedding);
			}
			return this;
		}

		public Builder index(Integer index) {
			this.index = index;
			return this;
		}

		public Embedding build() {
			return new Embedding(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import io.github.pigmesh.ai.deepseek.core.shared.StreamOptions;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;

@JsonDeserialize(builder = ChatCompletionRequest.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ChatCompletionRequest {

	@Getter
	@Setter
	@JsonProperty
	private String model;

	@JsonProperty
	private final List<Message> messages;

	@JsonProperty
	private final Double temperature;

	@JsonProperty
	private final Double topP;

	@JsonProperty
	private final Integer n;

	@JsonProperty
	private final Boolean stream;

	@JsonProperty
	private final StreamOptions streamOptions;

	@JsonProperty
	private final List<String> stop;

	@JsonProperty
	private final Integer maxTokens;

	@JsonProperty
	private final Integer maxCompletionTokens;

	@JsonProperty
	private final Double presencePenalty;

	@JsonProperty
	private final Double frequencyPenalty;

	@JsonProperty
	private final Map<String, Integer> logitBias;

	@JsonProperty
	private String user;

	@JsonProperty
	private final ResponseFormat responseFormat;

	@JsonProperty
	private final Integer seed;

	@JsonProperty
	private final List<Tool> tools;

	@JsonProperty
	private final Object toolChoice;

	@JsonProperty
	private final Boolean parallelToolCalls;

	@JsonProperty
	private final Boolean store;

	@JsonProperty
	private final Map<String, String> metadata;

	@JsonProperty
	private final String reasoningEffort;

	@JsonProperty
	private final String serviceTier;

	@JsonProperty
	@Deprecated
	private final List<Function> functions;

	@JsonProperty
	@Deprecated
	private final FunctionCall functionCall;

	@JsonProperty
	private final boolean includeReasoning;

	public ChatCompletionRequest(Builder builder) {
		this.model = builder.model;
		this.messages = builder.messages;
		this.temperature = builder.temperature;
		this.topP = builder.topP;
		this.n = builder.n;
		this.stream = builder.stream;
		this.streamOptions = builder.streamOptions;
		this.stop = builder.stop;
		this.maxTokens = builder.maxTokens;
		this.maxCompletionTokens = builder.maxCompletionTokens;
		this.presencePenalty = builder.presencePenalty;
		this.frequencyPenalty = builder.frequencyPenalty;
		this.logitBias = builder.logitBias;
		this.user = builder.user;
		this.responseFormat = builder.responseFormat;
		this.seed = builder.seed;
		this.tools = builder.tools;
		this.toolChoice = builder.toolChoice;
		this.parallelToolCalls = builder.parallelToolCalls;
		this.store = builder.store;
		this.metadata = builder.metadata;
		this.reasoningEffort = builder.reasoningEffort;
		this.serviceTier = builder.serviceTier;
		this.functions = builder.functions;
		this.functionCall = builder.functionCall;
		this.includeReasoning = builder.includeReasoning;
	}

	public String model() {
		return model;
	}

	public List<Message> messages() {
		return messages;
	}

	public Double temperature() {
		return temperature;
	}

	public Double topP() {
		return topP;
	}

	public Integer n() {
		return n;
	}

	public Boolean stream() {
		return stream;
	}

	public StreamOptions streamOptions() {
		return streamOptions;
	}

	public List<String> stop() {
		return stop;
	}

	public Integer maxTokens() {
		return maxTokens;
	}

	public Integer maxCompletionTokens() {
		return maxCompletionTokens;
	}

	public Double presencePenalty() {
		return presencePenalty;
	}

	public Double frequencyPenalty() {
		return frequencyPenalty;
	}

	public Map<String, Integer> logitBias() {
		return logitBias;
	}

	public String user() {
		return user;
	}

	public ResponseFormat responseFormat() {
		return responseFormat;
	}

	public Integer seed() {
		return seed;
	}

	public List<Tool> tools() {
		return tools;
	}

	public Object toolChoice() {
		return toolChoice;
	}

	public Boolean parallelToolCalls() {
		return parallelToolCalls;
	}

	public Boolean store() {
		return store;
	}

	public Map<String, String> metadata() {
		return metadata;
	}

	public String reasoningEffort() {
		return reasoningEffort;
	}

	public String serviceTier() {
		return serviceTier;
	}

	public boolean includeReasoning() {
		return includeReasoning;
	}

	@Deprecated
	public List<Function> functions() {
		return functions;
	}

	@Deprecated
	public FunctionCall functionCall() {
		return functionCall;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ChatCompletionRequest && equalTo((ChatCompletionRequest) another);
	}

	private boolean equalTo(ChatCompletionRequest another) {
		return Objects.equals(model, another.model) && Objects.equals(messages, another.messages)
				&& Objects.equals(temperature, another.temperature) && Objects.equals(topP, another.topP)
				&& Objects.equals(n, another.n) && Objects.equals(stream, another.stream)
				&& Objects.equals(streamOptions, another.streamOptions) && Objects.equals(stop, another.stop)
				&& Objects.equals(maxTokens, another.maxTokens)
				&& Objects.equals(maxCompletionTokens, another.maxCompletionTokens)
				&& Objects.equals(presencePenalty, another.presencePenalty)
				&& Objects.equals(frequencyPenalty, another.frequencyPenalty)
				&& Objects.equals(logitBias, another.logitBias) && Objects.equals(user, another.user)
				&& Objects.equals(responseFormat, another.responseFormat) && Objects.equals(seed, another.seed)
				&& Objects.equals(tools, another.tools) && Objects.equals(toolChoice, another.toolChoice)
				&& Objects.equals(parallelToolCalls, another.parallelToolCalls) && Objects.equals(store, another.store)
				&& Objects.equals(metadata, another.metadata)
				&& Objects.equals(reasoningEffort, another.reasoningEffort)
				&& Objects.equals(serviceTier, another.serviceTier) && Objects.equals(functions, another.functions)
				&& Objects.equals(functionCall, another.functionCall);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(messages);
		h += (h << 5) + Objects.hashCode(temperature);
		h += (h << 5) + Objects.hashCode(topP);
		h += (h << 5) + Objects.hashCode(n);
		h += (h << 5) + Objects.hashCode(stream);
		h += (h << 5) + Objects.hashCode(streamOptions);
		h += (h << 5) + Objects.hashCode(stop);
		h += (h << 5) + Objects.hashCode(maxTokens);
		h += (h << 5) + Objects.hashCode(maxCompletionTokens);
		h += (h << 5) + Objects.hashCode(presencePenalty);
		h += (h << 5) + Objects.hashCode(frequencyPenalty);
		h += (h << 5) + Objects.hashCode(logitBias);
		h += (h << 5) + Objects.hashCode(user);
		h += (h << 5) + Objects.hashCode(responseFormat);
		h += (h << 5) + Objects.hashCode(seed);
		h += (h << 5) + Objects.hashCode(tools);
		h += (h << 5) + Objects.hashCode(toolChoice);
		h += (h << 5) + Objects.hashCode(parallelToolCalls);
		h += (h << 5) + Objects.hashCode(store);
		h += (h << 5) + Objects.hashCode(metadata);
		h += (h << 5) + Objects.hashCode(reasoningEffort);
		h += (h << 5) + Objects.hashCode(serviceTier);
		h += (h << 5) + Objects.hashCode(functions);
		h += (h << 5) + Objects.hashCode(functionCall);
		return h;
	}

	@Override
	public String toString() {
		return "ChatCompletionRequest{" + "model=" + model + ", messages=" + messages + ", temperature=" + temperature
				+ ", topP=" + topP + ", n=" + n + ", stream=" + stream + ", streamOptions=" + streamOptions + ", stop="
				+ stop + ", maxTokens=" + maxTokens + ", maxCompletionTokens=" + maxCompletionTokens
				+ ", presencePenalty=" + presencePenalty + ", frequencyPenalty=" + frequencyPenalty + ", logitBias="
				+ logitBias + ", user=" + user + ", responseFormat=" + responseFormat + ", seed=" + seed + ", tools="
				+ tools + ", toolChoice=" + toolChoice + ", parallelToolCalls=" + parallelToolCalls + ", store=" + store
				+ ", metadata=" + metadata + ", reasoningEffort=" + reasoningEffort + ", serviceTier=" + serviceTier
				+ ", functions=" + functions + ", functionCall=" + functionCall + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String model;

		private List<Message> messages;

		private Double temperature;

		private Double topP;

		private Integer n;

		private Boolean stream;

		private StreamOptions streamOptions;

		private List<String> stop;

		private Integer maxTokens;

		private Integer maxCompletionTokens;

		private Double presencePenalty;

		private Double frequencyPenalty;

		private Map<String, Integer> logitBias;

		private String user;

		private ResponseFormat responseFormat;

		private Integer seed;

		private List<Tool> tools;

		private Object toolChoice;

		private Boolean parallelToolCalls;

		private Boolean store;

		private Map<String, String> metadata;

		private String reasoningEffort;

		private String serviceTier;

		private boolean includeReasoning = true;

		@Deprecated
		private List<Function> functions;

		@Deprecated
		private FunctionCall functionCall;

		private Builder() {
		}

		public Builder from(ChatCompletionRequest instance) {
			model(instance.model);
			messages(instance.messages);
			temperature(instance.temperature);
			topP(instance.topP);
			n(instance.n);
			stream(instance.stream);
			streamOptions(instance.streamOptions);
			stop(instance.stop);
			maxTokens(instance.maxTokens);
			maxCompletionTokens(instance.maxCompletionTokens);
			presencePenalty(instance.presencePenalty);
			frequencyPenalty(instance.frequencyPenalty);
			logitBias(instance.logitBias);
			user(instance.user);
			responseFormat(instance.responseFormat);
			seed(instance.seed);
			tools(instance.tools);
			toolChoice(instance.toolChoice);
			parallelToolCalls(instance.parallelToolCalls);
			store(instance.store);
			metadata(instance.metadata);
			reasoningEffort(instance.reasoningEffort);
			serviceTier(instance.serviceTier);
			functions(instance.functions);
			functionCall(instance.functionCall);
			includeReasoning(instance.includeReasoning);
			return this;
		}

		public Builder model(ChatCompletionModel model) {
			return model(model.toString());
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		@JsonSetter
		public Builder messages(List<Message> messages) {
			if (messages != null) {
				this.messages = unmodifiableList(messages);
			}
			return this;
		}

		public Builder messages(Message... messages) {
			return messages(asList(messages));
		}

		public Builder addSystemMessage(String systemMessage) {
			if (this.messages == null) {
				this.messages = new ArrayList<>();
			}
			this.messages.add(SystemMessage.from(systemMessage));
			return this;
		}

		public Builder addUserMessage(String userMessage) {
			if (this.messages == null) {
				this.messages = new ArrayList<>();
			}
			this.messages.add(UserMessage.from(userMessage));
			return this;
		}

		public Builder addAssistantMessage(String assistantMessage) {
			if (this.messages == null) {
				this.messages = new ArrayList<>();
			}
			this.messages.add(AssistantMessage.from(assistantMessage));
			return this;
		}

		public Builder addToolMessage(String toolCallId, String content) {
			if (this.messages == null) {
				this.messages = new ArrayList<>();
			}
			this.messages.add(ToolMessage.from(toolCallId, content));
			return this;
		}

		public Builder temperature(Double temperature) {
			this.temperature = temperature;
			return this;
		}

		public Builder topP(Double topP) {
			this.topP = topP;
			return this;
		}

		public Builder n(Integer n) {
			this.n = n;
			return this;
		}

		public Builder stream(Boolean stream) {
			this.stream = stream;
			return this;
		}

		public Builder streamOptions(StreamOptions streamOptions) {
			this.streamOptions = streamOptions;
			return this;
		}

		@JsonSetter
		public Builder stop(List<String> stop) {
			if (stop != null) {
				this.stop = unmodifiableList(stop);
			}
			return this;
		}

		public Builder stop(String... stop) {
			return stop(asList(stop));
		}

		/**
		 * @deprecated use {@link #maxCompletionTokens(Integer)} instead
		 */
		@Deprecated
		public Builder maxTokens(Integer maxTokens) {
			this.maxTokens = maxTokens;
			return this;
		}

		public Builder maxCompletionTokens(Integer maxCompletionTokens) {
			this.maxCompletionTokens = maxCompletionTokens;
			return this;
		}

		public Builder presencePenalty(Double presencePenalty) {
			this.presencePenalty = presencePenalty;
			return this;
		}

		public Builder frequencyPenalty(Double frequencyPenalty) {
			this.frequencyPenalty = frequencyPenalty;
			return this;
		}

		public Builder logitBias(Map<String, Integer> logitBias) {
			if (logitBias != null) {
				this.logitBias = unmodifiableMap(logitBias);
			}
			return this;
		}

		public Builder user(String user) {
			this.user = user;
			return this;
		}

		public Builder responseFormat(ResponseFormatType responseFormatType) {
			if (responseFormatType != null) {
				responseFormat = ResponseFormat.builder().type(responseFormatType).build();
			}
			return this;
		}

		@JsonSetter
		public Builder responseFormat(ResponseFormat responseFormat) {
			this.responseFormat = responseFormat;
			return this;
		}

		public Builder seed(Integer seed) {
			this.seed = seed;
			return this;
		}

		@JsonSetter
		public Builder tools(List<Tool> tools) {
			if (tools != null) {
				this.tools = unmodifiableList(tools);
			}
			return this;
		}

		public Builder includeReasoning(boolean includeReasoning) {
			this.includeReasoning = includeReasoning;
			return this;
		}

		public Builder tools(Tool... tools) {
			return tools(asList(tools));
		}

		public Builder toolChoice(ToolChoiceMode toolChoiceMode) {
			this.toolChoice = toolChoiceMode;
			return this;
		}

		public Builder toolChoice(String functionName) {
			return toolChoice(ToolChoice.from(functionName));
		}

		public Builder toolChoice(Object toolChoice) {
			this.toolChoice = toolChoice;
			return this;
		}

		public Builder parallelToolCalls(Boolean parallelToolCalls) {
			this.parallelToolCalls = parallelToolCalls;
			return this;
		}

		public Builder store(Boolean store) {
			this.store = store;
			return this;
		}

		public Builder metadata(Map<String, String> metadata) {
			if (metadata != null) {
				this.metadata = unmodifiableMap(metadata);
			}
			return this;
		}

		public Builder reasoningEffort(String reasoningEffort) {
			this.reasoningEffort = reasoningEffort;
			return this;
		}

		public Builder serviceTier(String serviceTier) {
			this.serviceTier = serviceTier;
			return this;
		}

		@Deprecated
		public Builder functions(Function... functions) {
			return functions(asList(functions));
		}

		@JsonSetter
		@Deprecated
		public Builder functions(List<Function> functions) {
			if (functions != null) {
				this.functions = unmodifiableList(functions);
			}
			return this;
		}

		@Deprecated
		public Builder functionCall(String functionName) {
			if (functionName != null) {
				this.functionCall = FunctionCall.builder().name(functionName).build();
			}
			return this;
		}

		@Deprecated
		public Builder functionCall(FunctionCall functionCall) {
			this.functionCall = functionCall;
			return this;
		}

		public ChatCompletionRequest build() {
			return new ChatCompletionRequest(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.completion;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import io.github.pigmesh.ai.deepseek.core.shared.Usage;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = CompletionResponse.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class CompletionResponse {

	@JsonProperty
	private final String id;

	@JsonProperty
	private final Integer created;

	@JsonProperty
	private final String model;

	@JsonProperty
	private final List<CompletionChoice> choices;

	@JsonProperty
	private final Usage usage;

	private CompletionResponse(Builder builder) {
		this.id = builder.id;
		this.created = builder.created;
		this.model = builder.model;
		this.choices = builder.choices;
		this.usage = builder.usage;
	}

	public String id() {
		return id;
	}

	public Integer created() {
		return created;
	}

	public String model() {
		return model;
	}

	public List<CompletionChoice> choices() {
		return choices;
	}

	public Usage usage() {
		return usage;
	}

	/**
	 * Convenience method to get the text from the first choice.
	 */
	public String text() {
		return choices().get(0).text();
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof CompletionResponse && equalTo((CompletionResponse) another);
	}

	private boolean equalTo(CompletionResponse another) {
		return Objects.equals(id, another.id) && Objects.equals(created, another.created)
				&& Objects.equals(model, another.model) && Objects.equals(choices, another.choices)
				&& Objects.equals(usage, another.usage);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(id);
		h += (h << 5) + Objects.hashCode(created);
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(choices);
		h += (h << 5) + Objects.hashCode(usage);
		return h;
	}

	@Override
	public String toString() {
		return "CompletionResponse{" + "id=" + id + ", created=" + created + ", model=" + model + ", choices=" + choices
				+ ", usage=" + usage + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String id;

		private Integer created;

		private String model;

		private List<CompletionChoice> choices;

		private Usage usage;

		private Builder() {
		}

		public Builder id(String id) {
			this.id = id;
			return this;
		}

		public Builder created(Integer created) {
			this.created = created;
			return this;
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		public Builder choices(List<CompletionChoice> choices) {
			if (choices != null) {
				this.choices = unmodifiableList(choices);
			}
			return this;
		}

		public Builder usage(Usage usage) {
			this.usage = usage;
			return this;
		}

		public CompletionResponse build() {
			return new CompletionResponse(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.moderation;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = ModerationResponse.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class ModerationResponse {

	@JsonProperty
	private final String id;

	@JsonProperty
	private final String model;

	@JsonProperty
	private final List<ModerationResult> results;

	private ModerationResponse(Builder builder) {
		this.id = builder.id;
		this.model = builder.model;
		this.results = builder.results;
	}

	public String id() {
		return id;
	}

	public String model() {
		return model;
	}

	public List<ModerationResult> results() {
		return results;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ModerationResponse && equalTo((ModerationResponse) another);
	}

	private boolean equalTo(ModerationResponse another) {
		return Objects.equals(id, another.id) && Objects.equals(model, another.model)
				&& Objects.equals(results, another.results);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(id);
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(results);
		return h;
	}

	@Override
	public String toString() {
		return "ModerationResponse{" + "id=" + id + ", model=" + model + ", results=" + results + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		public String id;

		public String model;

		public List<ModerationResult> results;

		private Builder() {
		}

		public Builder id(String id) {
			this.id = id;
			return this;
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		public Builder results(List<ModerationResult> results) {
			if (results != null) {
				this.results = unmodifiableList(results);
			}
			return this;
		}

		public ModerationResponse build() {
			return new ModerationResponse(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = Content.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class Content {

	@JsonProperty
	private final ContentType type;

	@JsonProperty
	private final String text;

	@JsonProperty
	private final ImageUrl imageUrl;

	@JsonProperty
	private final InputAudio inputAudio;

	public Content(Builder builder) {
		this.type = builder.type;
		this.text = builder.text;
		this.imageUrl = builder.imageUrl;
		this.inputAudio = builder.inputAudio;
	}

	public ContentType type() {
		return type;
	}

	public String text() {
		return text;
	}

	public ImageUrl imageUrl() {
		return imageUrl;
	}

	public InputAudio inputAudio() {
		return inputAudio;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Content && equalTo((Content) another);
	}

	private boolean equalTo(Content another) {
		return Objects.equals(type, another.type) && Objects.equals(text, another.text)
				&& Objects.equals(imageUrl, another.imageUrl) && Objects.equals(inputAudio, another.inputAudio);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(type);
		h += (h << 5) + Objects.hashCode(text);
		h += (h << 5) + Objects.hashCode(imageUrl);
		h += (h << 5) + Objects.hashCode(inputAudio);
		return h;
	}

	@Override
	public String toString() {
		return "Content{" + "type=" + type + ", text=" + text + ", imageUrl=" + imageUrl + ", inputAudio=" + inputAudio
				+ "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private ContentType type;

		private String text;

		private ImageUrl imageUrl;

		private InputAudio inputAudio;

		public Builder type(ContentType type) {
			this.type = type;
			return this;
		}

		public Builder text(String text) {
			this.text = text;
			return this;
		}

		public Builder imageUrl(ImageUrl imageUrl) {
			this.imageUrl = imageUrl;
			return this;
		}

		public Builder inputAudio(InputAudio inputAudio) {
			this.inputAudio = inputAudio;
			return this;
		}

		public Content build() {
			return new Content(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import io.github.pigmesh.ai.deepseek.core.shared.Usage;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = ChatCompletionResponse.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class ChatCompletionResponse {

	@JsonProperty
	private final String id;

	@JsonProperty
	private final Integer created;

	@JsonProperty
	private final String model;

	@JsonProperty
	private final List<ChatCompletionChoice> choices;

	@JsonProperty
	private final Usage usage;

	@JsonProperty
	private final String systemFingerprint;

	@JsonProperty
	private final String serviceTier;

	private ChatCompletionResponse(Builder builder) {
		this.id = builder.id;
		this.created = builder.created;
		this.model = builder.model;
		this.choices = builder.choices;
		this.usage = builder.usage;
		this.systemFingerprint = builder.systemFingerprint;
		this.serviceTier = builder.serviceTier;
	}

	public String id() {
		return id;
	}

	public Integer created() {
		return created;
	}

	public String model() {
		return model;
	}

	public List<ChatCompletionChoice> choices() {
		return choices;
	}

	public Usage usage() {
		return usage;
	}

	public String systemFingerprint() {
		return systemFingerprint;
	}

	public String serviceTier() {
		return serviceTier;
	}

	/**
	 * Convenience method to get the content of the message from the first choice.
	 */
	public String content() {
		return choices().get(0).message().content();
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ChatCompletionResponse && equalTo((ChatCompletionResponse) another);
	}

	private boolean equalTo(ChatCompletionResponse another) {
		return Objects.equals(id, another.id) && Objects.equals(created, another.created)
				&& Objects.equals(model, another.model) && Objects.equals(choices, another.choices)
				&& Objects.equals(usage, another.usage) && Objects.equals(systemFingerprint, another.systemFingerprint)
				&& Objects.equals(serviceTier, another.serviceTier);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(id);
		h += (h << 5) + Objects.hashCode(created);
		h += (h << 5) + Objects.hashCode(model);
		h += (h << 5) + Objects.hashCode(choices);
		h += (h << 5) + Objects.hashCode(usage);
		h += (h << 5) + Objects.hashCode(systemFingerprint);
		h += (h << 5) + Objects.hashCode(serviceTier);
		return h;
	}

	@Override
	public String toString() {
		return "ChatCompletionResponse{" + "id=" + id + ", created=" + created + ", model=" + model + ", choices="
				+ choices + ", usage=" + usage + ", systemFingerprint=" + systemFingerprint + ", serviceTier="
				+ serviceTier + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String id;

		private Integer created;

		private String model;

		private List<ChatCompletionChoice> choices;

		private Usage usage;

		private String systemFingerprint;

		private String serviceTier;

		private Builder() {
		}

		public Builder id(String id) {
			this.id = id;
			return this;
		}

		public Builder created(Integer created) {
			this.created = created;
			return this;
		}

		public Builder model(String model) {
			this.model = model;
			return this;
		}

		public Builder choices(List<ChatCompletionChoice> choices) {
			if (choices != null) {
				this.choices = unmodifiableList(choices);
			}
			return this;
		}

		public Builder usage(Usage usage) {
			this.usage = usage;
			return this;
		}

		public Builder systemFingerprint(String systemFingerprint) {
			this.systemFingerprint = systemFingerprint;
			return this;
		}

		public Builder serviceTier(String serviceTier) {
			this.serviceTier = serviceTier;
			return this;
		}

		public ChatCompletionResponse build() {
			return new ChatCompletionResponse(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.completion;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;

@JsonDeserialize(builder = Logprobs.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class Logprobs {

	@JsonProperty
	private final List<String> tokens;

	@JsonProperty
	private final List<Double> tokenLogprobs;

	@JsonProperty
	private final List<Map<String, Double>> topLogprobs;

	@JsonProperty
	private final List<Integer> textOffset;

	private Logprobs(Builder builder) {
		this.tokens = builder.tokens;
		this.tokenLogprobs = builder.tokenLogprobs;
		this.topLogprobs = builder.topLogprobs;
		this.textOffset = builder.textOffset;
	}

	public List<String> tokens() {
		return tokens;
	}

	public List<Double> tokenLogprobs() {
		return tokenLogprobs;
	}

	public List<Map<String, Double>> topLogprobs() {
		return topLogprobs;
	}

	public List<Integer> textOffset() {
		return textOffset;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Logprobs && equalTo((Logprobs) another);
	}

	private boolean equalTo(Logprobs another) {
		return Objects.equals(tokens, another.tokens) && Objects.equals(tokenLogprobs, another.tokenLogprobs)
				&& Objects.equals(topLogprobs, another.topLogprobs) && Objects.equals(textOffset, another.textOffset);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(tokens);
		h += (h << 5) + Objects.hashCode(tokenLogprobs);
		h += (h << 5) + Objects.hashCode(topLogprobs);
		h += (h << 5) + Objects.hashCode(textOffset);
		return h;
	}

	@Override
	public String toString() {
		return "Logprobs{" + "tokens=" + tokens + ", tokenLogprobs=" + tokenLogprobs + ", topLogprobs=" + topLogprobs
				+ ", textOffset=" + textOffset + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private List<String> tokens;

		private List<Double> tokenLogprobs;

		private List<Map<String, Double>> topLogprobs;

		private List<Integer> textOffset;

		private Builder() {
		}

		public Builder tokens(List<String> tokens) {
			if (tokens != null) {
				this.tokens = unmodifiableList(tokens);
			}
			return this;
		}

		public Builder tokenLogprobs(List<Double> tokenLogprobs) {
			if (tokenLogprobs != null) {
				this.tokenLogprobs = unmodifiableList(tokenLogprobs);
			}
			return this;
		}

		public Builder topLogprobs(List<Map<String, Double>> topLogprobs) {
			if (topLogprobs != null) {
				List<Map<String, Double>> topLogprobsCopy = new ArrayList<>();
				for (Map<String, Double> map : topLogprobs) {
					topLogprobsCopy.add(unmodifiableMap(map));
				}
				this.topLogprobs = unmodifiableList(topLogprobsCopy);
			}

			return this;
		}

		public Builder textOffset(List<Integer> textOffset) {
			if (textOffset != null) {
				this.textOffset = unmodifiableList(textOffset);
			}
			return this;
		}

		public Logprobs build() {
			return new Logprobs(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.ContentType.IMAGE_URL;
import static io.github.pigmesh.ai.deepseek.core.chat.ContentType.TEXT;
import static io.github.pigmesh.ai.deepseek.core.chat.Role.USER;
import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = UserMessage.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class UserMessage implements Message {

	@JsonProperty
	private final Role role = USER;

	@JsonProperty
	private final Object content;

	@JsonProperty
	private final String name;

	private UserMessage(Builder builder) {
		this.content = builder.stringContent != null ? builder.stringContent : builder.content;
		this.name = builder.name;
	}

	public Role role() {
		return role;
	}

	public Object content() {
		return content;
	}

	public String name() {
		return name;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof UserMessage && equalTo((UserMessage) another);
	}

	private boolean equalTo(UserMessage another) {
		return Objects.equals(role, another.role) && Objects.equals(content, another.content)
				&& Objects.equals(name, another.name);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(role);
		h += (h << 5) + Objects.hashCode(content);
		h += (h << 5) + Objects.hashCode(name);
		return h;
	}

	@Override
	public String toString() {
		return "UserMessage{" + "role=" + role + ", content=" + content + ", name=" + name + "}";
	}

	public static UserMessage from(String text) {
		return UserMessage.builder().content(text).build();
	}

	public static UserMessage from(String text, String... imageUrls) {
		return UserMessage.builder().addText(text).addImageUrls(imageUrls).build();
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String stringContent; // keeping it for compatibility with other
										// OpenAI-like APIs

		private List<Content> content;

		private String name;

		private Builder() {
		}

		public Builder addText(String text) {
			initializeContent();
			Content content = Content.builder().type(TEXT).text(text).build();
			this.content.add(content);
			return this;
		}

		public Builder addImageUrl(String imageUrl) {
			return addImageUrl(imageUrl, null);
		}

		public Builder addImageUrl(String imageUrl, ImageDetail imageDetail) {
			initializeContent();
			Content content = Content.builder().type(IMAGE_URL)
					.imageUrl(ImageUrl.builder().url(imageUrl).detail(imageDetail).build()).build();
			this.content.add(content);
			return this;
		}

		public Builder addImageUrls(String... imageUrls) {
			for (String imageUrl : imageUrls) {
				addImageUrl(imageUrl);
			}
			return this;
		}

		public Builder addInputAudio(InputAudio inputAudio) {
			initializeContent();
			this.content.add(Content.builder().type(ContentType.AUDIO).inputAudio(inputAudio).build());

			return this;
		}

		public Builder content(List<Content> content) {
			if (content != null) {
				this.content = unmodifiableList(content);
			}
			return this;
		}

		public Builder content(String content) {
			this.stringContent = content;
			return this;
		}

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public UserMessage build() {
			return new UserMessage(this);
		}

		private void initializeContent() {
			if (this.content == null) {
				this.content = new ArrayList<>();
			}
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.List;
import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.Role.ASSISTANT;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = AssistantMessage.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class AssistantMessage implements Message {

	@JsonProperty
	private final Role role = ASSISTANT;

	@JsonProperty
	private final String content;

	@JsonProperty
	private final String reasoningContent;

	@JsonProperty
	private final String name;

	@JsonProperty
	private final List<ToolCall> toolCalls;

	@JsonProperty
	private final Boolean refusal;

	@JsonProperty
	@Deprecated
	private final FunctionCall functionCall;

	private AssistantMessage(Builder builder) {
		this.content = builder.content;
		this.reasoningContent = builder.reasoningContent;
		this.name = builder.name;
		this.toolCalls = builder.toolCalls;
		this.refusal = builder.refusal;
		this.functionCall = builder.functionCall;
	}

	public Role role() {
		return role;
	}

	public String content() {
		return content;
	}

	public String name() {
		return name;
	}

	public String reasoningContent() {
		return reasoningContent;
	}

	public List<ToolCall> toolCalls() {
		return toolCalls;
	}

	public Boolean refusal() {
		return refusal;
	}

	@Deprecated
	public FunctionCall functionCall() {
		return functionCall;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof AssistantMessage && equalTo((AssistantMessage) another);
	}

	private boolean equalTo(AssistantMessage another) {
		return Objects.equals(role, another.role) && Objects.equals(content, another.content)
				&& Objects.equals(name, another.name) && Objects.equals(toolCalls, another.toolCalls)
				&& Objects.equals(refusal, another.refusal) && Objects.equals(functionCall, another.functionCall);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(role);
		h += (h << 5) + Objects.hashCode(content);
		h += (h << 5) + Objects.hashCode(name);
		h += (h << 5) + Objects.hashCode(toolCalls);
		h += (h << 5) + Objects.hashCode(refusal);
		h += (h << 5) + Objects.hashCode(functionCall);
		return h;
	}

	@Override
	public String toString() {
		return "AssistantMessage{" + "role=" + role + ", content=" + content + ", name=" + name + ", toolCalls="
				+ toolCalls + ", refusal=" + refusal + ", functionCall=" + functionCall + "}";
	}

	public static AssistantMessage from(String content) {
		return AssistantMessage.builder().content(content).build();
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String content;

		private String reasoningContent;

		private String name;

		private List<ToolCall> toolCalls;

		private Boolean refusal;

		@Deprecated
		private FunctionCall functionCall;

		private Builder() {
		}

		public Builder content(String content) {
			this.content = content;
			return this;
		}

		public Builder reasoningContent(String reasoningContent) {
			this.reasoningContent = reasoningContent;
			return this;
		}

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public Builder toolCalls(ToolCall... toolCalls) {
			return toolCalls(asList(toolCalls));
		}

		@JsonSetter
		public Builder toolCalls(List<ToolCall> toolCalls) {
			if (toolCalls != null) {
				this.toolCalls = unmodifiableList(toolCalls);
			}
			return this;
		}

		public Builder refusal(Boolean refusal) {
			this.refusal = refusal;
			return this;
		}

		@Deprecated
		public Builder functionCall(FunctionCall functionCall) {
			this.functionCall = functionCall;
			return this;
		}

		public AssistantMessage build() {
			return new AssistantMessage(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.List;
import java.util.Objects;

import static java.util.Collections.unmodifiableList;

@JsonDeserialize(builder = Delta.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class Delta {

	@JsonProperty
	private final Role role;

	@JsonProperty
	private final String content;

	@JsonProperty
	private final String reasoningContent;

	@JsonProperty
	private final List<ToolCall> toolCalls;

	@JsonProperty
	@Deprecated
	private final FunctionCall functionCall;

	private Delta(Builder builder) {
		this.role = builder.role;
		this.content = builder.content;
		this.reasoningContent = builder.reasoningContent;
		this.toolCalls = builder.toolCalls;
		this.functionCall = builder.functionCall;
	}

	public Role role() {
		return role;
	}

	public String content() {
		return content;
	}

	public String reasoningContent() {
		return reasoningContent;
	}

	public List<ToolCall> toolCalls() {
		return toolCalls;
	}

	@Deprecated
	public FunctionCall functionCall() {
		return functionCall;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Delta && equalTo((Delta) another);
	}

	private boolean equalTo(Delta another) {
		return Objects.equals(role, another.role) && Objects.equals(content, another.content)
				&& Objects.equals(toolCalls, another.toolCalls) && Objects.equals(functionCall, another.functionCall);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(role);
		h += (h << 5) + Objects.hashCode(content);
		h += (h << 5) + Objects.hashCode(toolCalls);
		h += (h << 5) + Objects.hashCode(functionCall);
		return h;
	}

	@Override
	public String toString() {
		return "Delta{" + "role=" + role + ", content=" + content + ", toolCalls=" + toolCalls + ", functionCall="
				+ functionCall + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Role role;

		private String content;

		private String reasoningContent;

		private List<ToolCall> toolCalls;

		@Deprecated
		private FunctionCall functionCall;

		private Builder() {
		}

		public Builder role(Role role) {
			this.role = role;
			return this;
		}

		public Builder content(String content) {
			this.content = content;
			return this;
		}

		public Builder reasoningContent(String reasoningContent) {
			this.reasoningContent = reasoningContent;
			return this;
		}

		public Builder toolCalls(List<ToolCall> toolCalls) {
			if (toolCalls != null) {
				this.toolCalls = unmodifiableList(toolCalls);
			}
			return this;
		}

		@Deprecated
		public Builder functionCall(FunctionCall functionCall) {
			this.functionCall = functionCall;
			return this;
		}

		public Delta build() {
			return new Delta(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.*;

@JsonDeserialize(builder = JsonObjectSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonObjectSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	@JsonProperty
	private final Map<String, JsonSchemaElement> properties;

	@JsonProperty
	private final List<String> required;

	@JsonProperty("additionalProperties")
	private final Boolean additionalProperties;

	@JsonProperty("$defs")
	private final Map<String, JsonSchemaElement> definitions;

	public JsonObjectSchema(Builder builder) {
		super("object");
		this.description = builder.description;
		this.properties = new LinkedHashMap<>(builder.properties);
		this.required = new ArrayList<>(builder.required);
		this.additionalProperties = builder.additionalProperties;
		this.definitions = builder.definitions == null ? null : new LinkedHashMap<>(builder.definitions);
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonObjectSchema && equalTo((JsonObjectSchema) another);
	}

	private boolean equalTo(JsonObjectSchema another) {
		return Objects.equals(description, another.description) && Objects.equals(properties, another.properties)
				&& Objects.equals(required, another.required)
				&& Objects.equals(additionalProperties, another.additionalProperties)
				&& Objects.equals(definitions, another.definitions);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		h += (h << 5) + Objects.hashCode(properties);
		h += (h << 5) + Objects.hashCode(required);
		h += (h << 5) + Objects.hashCode(additionalProperties);
		h += (h << 5) + Objects.hashCode(definitions);
		return h;
	}

	@Override
	public String toString() {
		return "JsonObjectSchema{" + "description=" + description + ", properties=" + properties + ", required="
				+ required + ", additionalProperties=" + additionalProperties + ", definitions=" + definitions + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		private Map<String, JsonSchemaElement> properties = new LinkedHashMap<>();

		private List<String> required = new ArrayList<>();

		private Boolean additionalProperties;

		private Map<String, JsonSchemaElement> definitions;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public Builder properties(Map<String, JsonSchemaElement> properties) {
			this.properties = properties;
			return this;
		}

		public Builder required(List<String> required) {
			this.required = required;
			return this;
		}

		public Builder additionalProperties(Boolean additionalProperties) {
			this.additionalProperties = additionalProperties;
			return this;
		}

		public Builder definitions(Map<String, JsonSchemaElement> definitions) {
			this.definitions = definitions;
			return this;
		}

		public JsonObjectSchema build() {
			return new JsonObjectSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;

@JsonDeserialize(builder = JsonEnumSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonEnumSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	@JsonProperty("enum")
	private final List<String> enumValues;

	public JsonEnumSchema(Builder builder) {
		super("string");
		this.description = builder.description;
		this.enumValues = new ArrayList<>(builder.enumValues);
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonEnumSchema && equalTo((JsonEnumSchema) another);
	}

	private boolean equalTo(JsonEnumSchema another) {
		return Objects.equals(description, another.description) && Objects.equals(enumValues, another.enumValues);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		h += (h << 5) + Objects.hashCode(enumValues);
		return h;
	}

	@Override
	public String toString() {
		return "JsonEnumSchema{" + "description=" + description + ", enumValues=" + enumValues + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		private List<String> enumValues;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public Builder enumValues(List<String> enumValues) {
			this.enumValues = enumValues;
			return this;
		}

		public Builder enumValues(Class<?> enumClass) {
			if (!enumClass.isEnum()) {
				throw new RuntimeException("Class " + enumClass.getName() + " must be enum");
			}

			List<String> enumValues = stream(enumClass.getEnumConstants()).map(Object::toString).collect(toList());

			return enumValues(enumValues);
		}

		public JsonEnumSchema build() {
			return new JsonEnumSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = ToolCall.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ToolCall {

	@JsonProperty
	private final String id;

	@JsonProperty
	private final Integer index;

	@JsonProperty
	private final ToolType type;

	@JsonProperty
	private final FunctionCall function;

	private ToolCall(Builder builder) {
		this.id = builder.id;
		this.index = builder.index;
		this.type = builder.type;
		this.function = builder.function;
	}

	public String id() {
		return id;
	}

	public Integer index() {
		return index;
	}

	public ToolType type() {
		return type;
	}

	public FunctionCall function() {
		return function;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ToolCall && equalTo((ToolCall) another);
	}

	private boolean equalTo(ToolCall another) {
		return Objects.equals(id, another.id) && Objects.equals(index, another.index)
				&& Objects.equals(type, another.type) && Objects.equals(function, another.function);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(id);
		h += (h << 5) + Objects.hashCode(index);
		h += (h << 5) + Objects.hashCode(type);
		h += (h << 5) + Objects.hashCode(function);
		return h;
	}

	@Override
	public String toString() {
		return "ToolCall{" + "id=" + id + ", index=" + index + ", type=" + type + ", function=" + function + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String id;

		private Integer index;

		private ToolType type;

		private FunctionCall function;

		private Builder() {
		}

		public Builder id(String id) {
			this.id = id;
			return this;
		}

		public Builder index(Integer index) {
			this.index = index;
			return this;
		}

		public Builder type(ToolType type) {
			this.type = type;
			return this;
		}

		public Builder function(FunctionCall function) {
			this.function = function;
			return this;
		}

		public ToolCall build() {
			return new ToolCall(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = ChatCompletionChoice.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class ChatCompletionChoice {

	@JsonProperty
	private final Integer index;

	@JsonProperty
	private final AssistantMessage message;

	@JsonProperty
	private final Delta delta;

	@JsonProperty
	private final String finishReason;

	private ChatCompletionChoice(Builder builder) {
		this.index = builder.index;
		this.message = builder.message;
		this.delta = builder.delta;
		this.finishReason = builder.finishReason;
	}

	public Integer index() {
		return index;
	}

	public AssistantMessage message() {
		return message;
	}

	public Delta delta() {
		return delta;
	}

	public String finishReason() {
		return finishReason;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ChatCompletionChoice && equalTo((ChatCompletionChoice) another);
	}

	private boolean equalTo(ChatCompletionChoice another) {
		return Objects.equals(index, another.index) && Objects.equals(message, another.message)
				&& Objects.equals(delta, another.delta) && Objects.equals(finishReason, another.finishReason);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(index);
		h += (h << 5) + Objects.hashCode(message);
		h += (h << 5) + Objects.hashCode(delta);
		h += (h << 5) + Objects.hashCode(finishReason);
		return h;
	}

	@Override
	public String toString() {
		return "ChatCompletionChoice{" + "index=" + index + ", message=" + message + ", delta=" + delta
				+ ", finishReason=" + finishReason + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Integer index;

		private AssistantMessage message;

		private Delta delta;

		private String finishReason;

		private Builder() {
		}

		public Builder index(Integer index) {
			this.index = index;
			return this;
		}

		public Builder message(AssistantMessage message) {
			this.message = message;
			return this;
		}

		public Builder delta(Delta delta) {
			this.delta = delta;
			return this;
		}

		public Builder finishReason(String finishReason) {
			this.finishReason = finishReason;
			return this;
		}

		public ChatCompletionChoice build() {
			return new ChatCompletionChoice(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.completion;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = CompletionChoice.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class CompletionChoice {

	@JsonProperty
	private final String text;

	@JsonProperty
	private final Integer index;

	@JsonProperty
	private final Logprobs logprobs;

	@JsonProperty
	private final String finishReason;

	private CompletionChoice(Builder builder) {
		this.text = builder.text;
		this.index = builder.index;
		this.logprobs = builder.logprobs;
		this.finishReason = builder.finishReason;
	}

	public String text() {
		return text;
	}

	public Integer index() {
		return index;
	}

	public Logprobs logprobs() {
		return logprobs;
	}

	public String finishReason() {
		return finishReason;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof CompletionChoice && equalTo((CompletionChoice) another);
	}

	private boolean equalTo(CompletionChoice another) {
		return Objects.equals(text, another.text) && Objects.equals(index, another.index)
				&& Objects.equals(logprobs, another.logprobs) && Objects.equals(finishReason, another.finishReason);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(text);
		h += (h << 5) + Objects.hashCode(index);
		h += (h << 5) + Objects.hashCode(logprobs);
		h += (h << 5) + Objects.hashCode(finishReason);
		return h;
	}

	@Override
	public String toString() {
		return "CompletionChoice{" + "text=" + text + ", index=" + index + ", logprobs=" + logprobs + ", finishReason="
				+ finishReason + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		@JsonProperty
		private String text;

		@JsonProperty
		private Integer index;

		@JsonProperty
		private Logprobs logprobs;

		@JsonProperty
		private String finishReason;

		private Builder() {
		}

		public Builder text(String text) {
			this.text = text;
			return this;
		}

		public Builder index(Integer index) {
			this.index = index;
			return this;
		}

		public Builder logprobs(Logprobs logprobs) {
			this.logprobs = logprobs;
			return this;
		}

		public Builder finishReason(String finishReason) {
			this.finishReason = finishReason;
			return this;
		}

		public CompletionChoice build() {
			return new CompletionChoice(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = Function.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Function {

	@JsonProperty
	private final String name;

	@JsonProperty
	private final String description;

	@JsonProperty
	private final Boolean strict;

	@JsonProperty
	private final JsonObjectSchema parameters;

	private Function(Builder builder) {
		this.name = builder.name;
		this.description = builder.description;
		this.strict = builder.strict;
		this.parameters = builder.parameters;
	}

	public String name() {
		return name;
	}

	public String description() {
		return description;
	}

	public Boolean strict() {
		return strict;
	}

	public JsonObjectSchema parameters() {
		return parameters;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Function && equalTo((Function) another);
	}

	private boolean equalTo(Function another) {
		return Objects.equals(name, another.name) && Objects.equals(description, another.description)
				&& Objects.equals(strict, another.strict) && Objects.equals(parameters, another.parameters);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(name);
		h += (h << 5) + Objects.hashCode(description);
		h += (h << 5) + Objects.hashCode(strict);
		h += (h << 5) + Objects.hashCode(parameters);
		return h;
	}

	@Override
	public String toString() {
		return "Function{" + "name=" + name + ", description=" + description + ", strict=" + strict + ", parameters="
				+ parameters + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String name;

		private String description;

		private Boolean strict;

		private JsonObjectSchema parameters = JsonObjectSchema.builder().build();

		private Builder() {
		}

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public Builder strict(Boolean strict) {
			this.strict = strict;
			return this;
		}

		public Builder parameters(JsonObjectSchema parameters) {
			this.parameters = parameters;
			return this;
		}

		public Function build() {
			return new Function(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonSchema {

	@JsonProperty
	private final String name;

	@JsonProperty
	private final Boolean strict;

	@JsonProperty
	private final JsonObjectSchema schema;

	public JsonSchema(Builder builder) {
		this.name = builder.name;
		this.strict = builder.strict;
		this.schema = builder.schema;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonSchema && equalTo((JsonSchema) another);
	}

	private boolean equalTo(JsonSchema another) {
		return Objects.equals(name, another.name) && Objects.equals(strict, another.strict)
				&& Objects.equals(schema, another.schema);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(name);
		h += (h << 5) + Objects.hashCode(strict);
		h += (h << 5) + Objects.hashCode(schema);
		return h;
	}

	@Override
	public String toString() {
		return "JsonSchema{" + "name=" + name + ", strict=" + strict + ", schema=" + schema + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String name;

		private Boolean strict;

		private JsonObjectSchema schema;

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public Builder strict(Boolean strict) {
			this.strict = strict;
			return this;
		}

		public Builder schema(JsonObjectSchema schema) {
			this.schema = schema;
			return this;
		}

		public JsonSchema build() {
			return new JsonSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.shared;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = Usage.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class Usage {

	@JsonProperty
	private final Integer totalTokens;

	@JsonProperty
	private final Integer promptTokens;

	@JsonProperty
	private final PromptTokensDetails promptTokensDetails;

	@JsonProperty
	private final Integer completionTokens;

	@JsonProperty
	private final CompletionTokensDetails completionTokensDetails;

	private Usage(Builder builder) {
		this.totalTokens = builder.totalTokens;
		this.promptTokens = builder.promptTokens;
		this.promptTokensDetails = builder.promptTokensDetails;
		this.completionTokens = builder.completionTokens;
		this.completionTokensDetails = builder.completionTokensDetails;
	}

	public Integer totalTokens() {
		return totalTokens;
	}

	public Integer promptTokens() {
		return promptTokens;
	}

	public PromptTokensDetails promptTokensDetails() {
		return promptTokensDetails;
	}

	public Integer completionTokens() {
		return completionTokens;
	}

	public CompletionTokensDetails completionTokensDetails() {
		return completionTokensDetails;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Usage && equalTo((Usage) another);
	}

	private boolean equalTo(Usage another) {
		return Objects.equals(totalTokens, another.totalTokens) && Objects.equals(promptTokens, another.promptTokens)
				&& Objects.equals(promptTokensDetails, another.promptTokensDetails)
				&& Objects.equals(completionTokens, another.completionTokens)
				&& Objects.equals(completionTokensDetails, another.completionTokensDetails);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(totalTokens);
		h += (h << 5) + Objects.hashCode(promptTokens);
		h += (h << 5) + Objects.hashCode(promptTokensDetails);
		h += (h << 5) + Objects.hashCode(completionTokens);
		h += (h << 5) + Objects.hashCode(completionTokensDetails);
		return h;
	}

	@Override
	public String toString() {
		return "Usage{" + "totalTokens=" + totalTokens + ", promptTokens=" + promptTokens + ", promptTokensDetails="
				+ promptTokensDetails + ", completionTokens=" + completionTokens + ", completionTokensDetails="
				+ completionTokensDetails + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Integer totalTokens;

		private Integer promptTokens;

		private PromptTokensDetails promptTokensDetails;

		private Integer completionTokens;

		private CompletionTokensDetails completionTokensDetails;

		private Builder() {
		}

		public Builder totalTokens(Integer totalTokens) {
			this.totalTokens = totalTokens;
			return this;
		}

		public Builder promptTokens(Integer promptTokens) {
			this.promptTokens = promptTokens;
			return this;
		}

		public Builder promptTokensDetails(PromptTokensDetails promptTokensDetails) {
			this.promptTokensDetails = promptTokensDetails;
			return this;
		}

		public Builder completionTokens(Integer completionTokens) {
			this.completionTokens = completionTokens;
			return this;
		}

		public Builder completionTokensDetails(CompletionTokensDetails completionTokensDetails) {
			this.completionTokensDetails = completionTokensDetails;
			return this;
		}

		public Usage build() {
			return new Usage(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.List;
import java.util.Objects;

@JsonDeserialize(builder = JsonAnyOfSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonAnyOfSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	@JsonProperty("anyOf")
	private final List<JsonSchemaElement> anyOf;

	public JsonAnyOfSchema(Builder builder) {
		super(null);
		this.description = builder.description;
		this.anyOf = builder.anyOf;
	}

	@Override
	public boolean equals(final Object another) {
		if (this == another)
			return true;
		return another instanceof JsonAnyOfSchema && equalTo((JsonAnyOfSchema) another);
	}

	private boolean equalTo(final JsonAnyOfSchema another) {
		return Objects.equals(description, another.description) && Objects.equals(anyOf, another.anyOf);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		h += (h << 5) + Objects.hashCode(anyOf);
		return h;
	}

	@Override
	public String toString() {
		return "JsonAnyOfSchema{" + "description='" + description + '\'' + ", anyOf=" + anyOf + '}';
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		private List<JsonSchemaElement> anyOf;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public Builder anyOf(List<JsonSchemaElement> anyOf) {
			this.anyOf = anyOf;
			return this;
		}

		public JsonAnyOfSchema build() {
			return new JsonAnyOfSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.Role.SYSTEM;

@JsonDeserialize(builder = SystemMessage.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class SystemMessage implements Message {

	@JsonProperty
	private final Role role = SYSTEM;

	@JsonProperty
	private final String content;

	@JsonProperty
	private final String name;

	private SystemMessage(Builder builder) {
		this.content = builder.content;
		this.name = builder.name;
	}

	public Role role() {
		return role;
	}

	public String content() {
		return content;
	}

	public String name() {
		return name;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof SystemMessage && equalTo((SystemMessage) another);
	}

	private boolean equalTo(SystemMessage another) {
		return Objects.equals(role, another.role) && Objects.equals(content, another.content)
				&& Objects.equals(name, another.name);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(role);
		h += (h << 5) + Objects.hashCode(content);
		h += (h << 5) + Objects.hashCode(name);
		return h;
	}

	@Override
	public String toString() {
		return "SystemMessage{" + "role=" + role + ", content=" + content + ", name=" + name + "}";
	}

	public static SystemMessage from(String content) {
		return SystemMessage.builder().content(content).build();
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String content;

		private String name;

		private Builder() {
		}

		public Builder content(String content) {
			this.content = content;
			return this;
		}

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public SystemMessage build() {
			return new SystemMessage(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.Role.FUNCTION;

@JsonDeserialize(builder = FunctionMessage.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Deprecated
public final class FunctionMessage implements Message {

	@JsonProperty
	private final Role role = FUNCTION;

	@JsonProperty
	private final String name;

	@JsonProperty
	private final String content;

	private FunctionMessage(Builder builder) {
		this.name = builder.name;
		this.content = builder.content;
	}

	public Role role() {
		return role;
	}

	public String name() {
		return name;
	}

	public String content() {
		return content;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof FunctionMessage && equalTo((FunctionMessage) another);
	}

	private boolean equalTo(FunctionMessage another) {
		return Objects.equals(role, another.role) && Objects.equals(name, another.name)
				&& Objects.equals(content, another.content);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(role);
		h += (h << 5) + Objects.hashCode(name);
		h += (h << 5) + Objects.hashCode(content);
		return h;
	}

	@Override
	public String toString() {
		return "FunctionMessage{" + "role=" + role + ", name=" + name + ", content=" + content + "}";
	}

	@Deprecated
	public static FunctionMessage from(String name, String content) {
		return FunctionMessage.builder().name(name).content(content).build();
	}

	@Deprecated
	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String name;

		private String content;

		private Builder() {
		}

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public Builder content(String content) {
			this.content = content;
			return this;
		}

		public FunctionMessage build() {
			return new FunctionMessage(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core;

import io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingRequest;
import io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingResponse;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 支持向量话的客户端
 *
 * @author lengleng
 * @date 2025/02/12
 */
public class EmbeddingClient extends OpenAiClient {

	private static final Logger log = LoggerFactory.getLogger(EmbeddingClient.class);

	private final String baseUrl;

	private final String model;

	private final String apiVersion;

	private final OkHttpClient okHttpClient;

	private final OpenAiApi openAiApi;

	private final boolean logStreamingResponses;

	public EmbeddingClient(String apiKey) {
		this(new Builder().openAiApiKey(apiKey));
	}

	private EmbeddingClient(Builder serviceBuilder) {
		this.baseUrl = serviceBuilder.baseUrl;
		this.apiVersion = serviceBuilder.apiVersion;
		this.model = serviceBuilder.model;

		OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder().callTimeout(serviceBuilder.callTimeout)
				.connectTimeout(serviceBuilder.connectTimeout).readTimeout(serviceBuilder.readTimeout)
				.writeTimeout(serviceBuilder.writeTimeout);

		if (serviceBuilder.dispatcher != null) {
			okHttpClientBuilder.dispatcher(serviceBuilder.dispatcher);
		}

		if (serviceBuilder.openAiApiKey != null) {
			okHttpClientBuilder.addInterceptor(new AuthorizationHeaderInjector(serviceBuilder.openAiApiKey));
		}
		else {
			okHttpClientBuilder.addInterceptor(new ApiKeyHeaderInjector(serviceBuilder.azureApiKey));
		}

		Map<String, String> headers = new HashMap<>();
		if (serviceBuilder.organizationId != null) {
			headers.put("OpenAI-Organization", serviceBuilder.organizationId);
		}
		if (serviceBuilder.userAgent != null) {
			headers.put("User-Agent", serviceBuilder.userAgent);
		}
		if (serviceBuilder.customHeaders != null) {
			headers.putAll(serviceBuilder.customHeaders);
		}
		if (!headers.isEmpty()) {
			okHttpClientBuilder.addInterceptor(new GenericHeaderInjector(headers));
		}

		if (serviceBuilder.proxy != null) {
			okHttpClientBuilder.proxy(serviceBuilder.proxy);
		}

		if (serviceBuilder.logRequests) {
			okHttpClientBuilder.addInterceptor(new RequestLoggingInterceptor(serviceBuilder.logLevel));
		}

		if (serviceBuilder.logResponses) {
			okHttpClientBuilder.addInterceptor(new ResponseLoggingInterceptor(serviceBuilder.logLevel));
		}
		this.logStreamingResponses = serviceBuilder.logStreamingResponses;

		this.okHttpClient = okHttpClientBuilder.build();

		Retrofit.Builder retrofitBuilder = new Retrofit.Builder().baseUrl(serviceBuilder.baseUrl).client(okHttpClient);

		if (serviceBuilder.persistTo != null) {
			retrofitBuilder.addConverterFactory(new PersistorConverterFactory(serviceBuilder.persistTo));
		}

		retrofitBuilder.addConverterFactory(JacksonConverterFactory.create(Json.OBJECT_MAPPER));
		this.openAiApi = retrofitBuilder.build().create(OpenAiApi.class);
	}

	public void shutdown() {
		okHttpClient.dispatcher().executorService().shutdown();

		okHttpClient.connectionPool().evictAll();

		Cache cache = okHttpClient.cache();
		if (cache != null) {
			try {
				cache.close();
			}
			catch (IOException e) {
				log.error("Failed to close cache", e);
			}
		}
	}

	public static Builder builder() {
		return new Builder();
	}

	public static class Builder extends OpenAiClient.Builder<EmbeddingClient, Builder> {

		public EmbeddingClient build() {
			return new EmbeddingClient(this);
		}

	}

	public EmbeddingResponse embed(EmbeddingRequest request) {
		return this.embedding(new OpenAiClientContext(), request).execute();
	}

	public List<Float> embed(String input) {
		return this.embedding(new OpenAiClientContext(), input).execute();
	}

	@Override
	public SyncOrAsync<EmbeddingResponse> embedding(OpenAiClientContext context, EmbeddingRequest request) {

		if (Objects.isNull(request.getModel())) {
			request.setModel(this.model);
		}

		return new RequestExecutor<>(openAiApi.embeddings(context.headers(), request, apiVersion), r -> r);
	}

	@Override
	public SyncOrAsync<List<Float>> embedding(OpenAiClientContext context, String input) {
		EmbeddingRequest.Builder builder = EmbeddingRequest.builder().input(input);
		if (Objects.nonNull(this.model)) {
			builder.model(this.model);
		}

		return new RequestExecutor<>(openAiApi.embeddings(context.headers(), builder.build(), apiVersion),
				EmbeddingResponse::embedding);
	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = ImageUrl.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ImageUrl {

	@JsonProperty
	private final String url;

	@JsonProperty
	private final ImageDetail detail;

	private ImageUrl(Builder builder) {
		this.url = builder.url;
		this.detail = builder.detail;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ImageUrl && equalTo((ImageUrl) another);
	}

	private boolean equalTo(ImageUrl another) {
		return Objects.equals(url, another.url) && Objects.equals(detail, another.detail);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(url);
		h += (h << 5) + Objects.hashCode(detail);
		return h;
	}

	@Override
	public String toString() {
		return "ImageUrl{" + "url=" + url + ", detail=" + detail + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String url;

		private ImageDetail detail;

		private Builder() {
		}

		public Builder url(String url) {
			this.url = url;
			return this;
		}

		public Builder detail(ImageDetail detail) {
			this.detail = detail;
			return this;
		}

		public ImageUrl build() {
			return new ImageUrl(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = FunctionCall.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class FunctionCall {

	@JsonProperty
	private final String name;

	@JsonProperty
	private final String arguments;

	private FunctionCall(Builder builder) {
		this.name = builder.name;
		this.arguments = builder.arguments;
	}

	public String name() {
		return name;
	}

	public String arguments() {
		return arguments;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof FunctionCall && equalTo((FunctionCall) another);
	}

	private boolean equalTo(FunctionCall another) {
		return Objects.equals(name, another.name) && Objects.equals(arguments, another.arguments);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(name);
		h += (h << 5) + Objects.hashCode(arguments);
		return h;
	}

	@Override
	public String toString() {
		return "FunctionCall{" + "name=" + name + ", arguments=" + arguments + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String name;

		private String arguments;

		private Builder() {
		}

		public Builder name(String name) {
			this.name = name;
			return this;
		}

		public Builder arguments(String arguments) {
			this.arguments = arguments;
			return this;
		}

		public FunctionCall build() {
			return new FunctionCall(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.moderation;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = ModerationResult.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class ModerationResult {

	@JsonProperty
	private final Categories categories;

	@JsonProperty
	private final CategoryScores categoryScores;

	@JsonProperty
	private final Boolean flagged;

	private ModerationResult(Builder builder) {
		this.categories = builder.categories;
		this.categoryScores = builder.categoryScores;
		this.flagged = builder.flagged;
	}

	public Categories categories() {
		return categories;
	}

	public CategoryScores categoryScores() {
		return categoryScores;
	}

	public Boolean isFlagged() {
		return flagged;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ModerationResult && equalTo((ModerationResult) another);
	}

	private boolean equalTo(ModerationResult another) {
		return Objects.equals(categories, another.categories) && Objects.equals(categoryScores, another.categoryScores)
				&& Objects.equals(flagged, another.flagged);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(categories);
		h += (h << 5) + Objects.hashCode(categoryScores);
		h += (h << 5) + Objects.hashCode(flagged);
		return h;
	}

	@Override
	public String toString() {
		return "ModerationResult{" + "categories=" + categories + ", categoryScores=" + categoryScores + ", flagged="
				+ flagged + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Categories categories;

		private CategoryScores categoryScores;

		private Boolean flagged;

		private Builder() {
		}

		public Builder categories(Categories categories) {
			this.categories = categories;
			return this;
		}

		public Builder categoryScores(CategoryScores categoryScores) {
			this.categoryScores = categoryScores;
			return this;
		}

		public Builder flagged(Boolean flagged) {
			this.flagged = flagged;
			return this;
		}

		public ModerationResult build() {
			return new ModerationResult(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.Role.TOOL;

@JsonDeserialize(builder = ToolMessage.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class ToolMessage implements Message {

	@JsonProperty
	private final Role role = TOOL;

	@JsonProperty
	private final String toolCallId;

	@JsonProperty
	private final String content;

	private ToolMessage(Builder builder) {
		this.toolCallId = builder.toolCallId;
		this.content = builder.content;
	}

	public Role role() {
		return role;
	}

	public String toolCallId() {
		return toolCallId;
	}

	public String content() {
		return content;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ToolMessage && equalTo((ToolMessage) another);
	}

	private boolean equalTo(ToolMessage another) {
		return Objects.equals(role, another.role) && Objects.equals(toolCallId, another.toolCallId)
				&& Objects.equals(content, another.content);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(role);
		h += (h << 5) + Objects.hashCode(toolCallId);
		h += (h << 5) + Objects.hashCode(content);
		return h;
	}

	@Override
	public String toString() {
		return "ToolMessage{" + "role=" + role + ", toolCallId=" + toolCallId + ", content=" + content + "}";
	}

	public static ToolMessage from(String toolCallId, String content) {
		return ToolMessage.builder().toolCallId(toolCallId).content(content).build();
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String toolCallId;

		private String content;

		private Builder() {
		}

		public Builder toolCallId(String toolCallId) {
			this.toolCallId = toolCallId;
			return this;
		}

		public Builder content(String content) {
			this.content = content;
			return this;
		}

		public ToolMessage build() {
			return new ToolMessage(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonArraySchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonArraySchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	@JsonProperty
	private final JsonSchemaElement items;

	public JsonArraySchema(Builder builder) {
		super("array");
		this.description = builder.description;
		this.items = builder.items;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonArraySchema && equalTo((JsonArraySchema) another);
	}

	private boolean equalTo(JsonArraySchema another) {
		return Objects.equals(description, another.description) && Objects.equals(items, another.items);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		h += (h << 5) + Objects.hashCode(items);
		return h;
	}

	@Override
	public String toString() {
		return "JsonArraySchema{" + "description=" + description + ", items=" + items + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		private JsonSchemaElement items;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public Builder items(JsonSchemaElement items) {
			this.items = items;
			return this;
		}

		public JsonArraySchema build() {
			return new JsonArraySchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.ToolType.FUNCTION;

@JsonDeserialize(builder = Tool.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Tool {

	@JsonProperty
	private final ToolType type = FUNCTION;

	@JsonProperty
	private final Function function;

	private Tool(Builder builder) {
		this.function = builder.function;
	}

	public ToolType type() {
		return this.type;
	}

	public Function function() {
		return this.function;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Tool && equalTo((Tool) another);
	}

	private boolean equalTo(Tool another) {
		return Objects.equals(type, another.type) && Objects.equals(function, another.function);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(type);
		h += (h << 5) + Objects.hashCode(function);
		return h;
	}

	@Override
	public String toString() {
		return "Tool{" + "type=" + type + ", function=" + function + "}";
	}

	public static Tool from(Function function) {
		return new Builder().function(function).build();
	}

	public static Tool.Builder builder() {
		return new Tool.Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Function function;

		private Builder() {
		}

		public Tool.Builder function(Function function) {
			this.function = function;
			return this;
		}

		public Tool build() {
			return new Tool(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

import static io.github.pigmesh.ai.deepseek.core.chat.ToolType.FUNCTION;

@JsonDeserialize(builder = ToolChoice.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ToolChoice {

	@JsonProperty
	private final ToolType type = FUNCTION;

	@JsonProperty
	private final Function function;

	private ToolChoice(Builder builder) {
		function = builder.function;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ToolChoice && equalTo((ToolChoice) another);
	}

	private boolean equalTo(ToolChoice another) {
		return Objects.equals(type, another.type) && Objects.equals(function, another.function);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(type);
		h += (h << 5) + Objects.hashCode(function);
		return h;
	}

	@Override
	public String toString() {
		return "ToolChoice{" + "type=" + type + ", function=" + function + "}";
	}

	public static ToolChoice from(String functionName) {
		return new Builder().function(Function.builder().name(functionName).build()).build();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Function function;

		private Builder() {
		}

		public ToolChoice.Builder function(Function function) {
			this.function = function;
			return this;
		}

		public ToolChoice build() {
			return new ToolChoice(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.shared;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = CompletionTokensDetails.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class CompletionTokensDetails {

	@JsonProperty
	private final Integer reasoningTokens;

	private CompletionTokensDetails(Builder builder) {
		this.reasoningTokens = builder.reasoningTokens;
	}

	public Integer reasoningTokens() {
		return reasoningTokens;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof CompletionTokensDetails && equalTo((CompletionTokensDetails) another);
	}

	private boolean equalTo(CompletionTokensDetails another) {
		return Objects.equals(reasoningTokens, another.reasoningTokens);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(reasoningTokens);
		return h;
	}

	@Override
	public String toString() {
		return "CompletionTokensDetails{" + "reasoningTokens=" + reasoningTokens + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Integer reasoningTokens;

		private Builder() {
		}

		public Builder reasoningTokens(Integer reasoningTokens) {
			this.reasoningTokens = reasoningTokens;
			return this;
		}

		public CompletionTokensDetails build() {
			return new CompletionTokensDetails(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.shared;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = PromptTokensDetails.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class PromptTokensDetails {

	@JsonProperty
	private final Integer cachedTokens;

	private PromptTokensDetails(Builder builder) {
		this.cachedTokens = builder.cachedTokens;
	}

	public Integer cachedTokens() {
		return cachedTokens;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof PromptTokensDetails && equalTo((PromptTokensDetails) another);
	}

	private boolean equalTo(PromptTokensDetails another) {
		return Objects.equals(cachedTokens, another.cachedTokens);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(cachedTokens);
		return h;
	}

	@Override
	public String toString() {
		return "PromptTokensDetails{" + "cachedTokens=" + cachedTokens + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Integer cachedTokens;

		private Builder() {
		}

		public Builder cachedTokens(Integer cachedTokens) {
			this.cachedTokens = cachedTokens;
			return this;
		}

		public PromptTokensDetails build() {
			return new PromptTokensDetails(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = ResponseFormat.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ResponseFormat {

	@JsonProperty
	private final ResponseFormatType type;

	@JsonProperty
	private final JsonSchema jsonSchema;

	@JsonCreator
	public ResponseFormat(Builder builder) {
		this.type = builder.type;
		this.jsonSchema = builder.jsonSchema;
	}

	public ResponseFormatType type() {
		return type;
	}

	public JsonSchema jsonSchema() {
		return jsonSchema;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof ResponseFormat && equalTo((ResponseFormat) another);
	}

	private boolean equalTo(ResponseFormat another) {
		return Objects.equals(type, another.type) && Objects.equals(jsonSchema, another.jsonSchema);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(type);
		h += (h << 5) + Objects.hashCode(jsonSchema);
		return h;
	}

	@Override
	public String toString() {
		return "ResponseFormat{" + "type=" + type + ", jsonSchema=" + jsonSchema + "}";
	}

	public static ResponseFormat.Builder builder() {
		return new ResponseFormat.Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private ResponseFormatType type;

		private JsonSchema jsonSchema;

		public ResponseFormat.Builder type(ResponseFormatType type) {
			this.type = type;
			return this;
		}

		public ResponseFormat.Builder jsonSchema(JsonSchema jsonSchema) {
			this.jsonSchema = jsonSchema;
			return this;
		}

		public ResponseFormat build() {
			return new ResponseFormat(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonIntegerSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonIntegerSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	public JsonIntegerSchema(Builder builder) {
		super("integer");
		this.description = builder.description;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonIntegerSchema && equalTo((JsonIntegerSchema) another);
	}

	private boolean equalTo(JsonIntegerSchema another) {
		return Objects.equals(description, another.description);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		return h;
	}

	@Override
	public String toString() {
		return "JsonIntegerSchema{" + "description=" + description + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public JsonIntegerSchema build() {
			return new JsonIntegerSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonBooleanSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonBooleanSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	public JsonBooleanSchema(Builder builder) {
		super("boolean");
		this.description = builder.description;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonBooleanSchema && equalTo((JsonBooleanSchema) another);
	}

	private boolean equalTo(JsonBooleanSchema another) {
		return Objects.equals(description, another.description);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		return h;
	}

	@Override
	public String toString() {
		return "JsonBooleanSchema{" + "description=" + description + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public JsonBooleanSchema build() {
			return new JsonBooleanSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonNumberSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonNumberSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	public JsonNumberSchema(Builder builder) {
		super("number");
		this.description = builder.description;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonNumberSchema && equalTo((JsonNumberSchema) another);
	}

	private boolean equalTo(JsonNumberSchema another) {
		return Objects.equals(description, another.description);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		return h;
	}

	@Override
	public String toString() {
		return "JsonNumberSchema{" + "description=" + description + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public JsonNumberSchema build() {
			return new JsonNumberSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonStringSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonStringSchema extends JsonSchemaElement {

	@JsonProperty
	private final String description;

	public JsonStringSchema(Builder builder) {
		super("string");
		this.description = builder.description;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonStringSchema && equalTo((JsonStringSchema) another);
	}

	private boolean equalTo(JsonStringSchema another) {
		return Objects.equals(description, another.description);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(description);
		return h;
	}

	@Override
	public String toString() {
		return "JsonStringSchema{" + "description=" + description + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String description;

		public Builder description(String description) {
			this.description = description;
			return this;
		}

		public JsonStringSchema build() {
			return new JsonStringSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = JsonReferenceSchema.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class JsonReferenceSchema extends JsonSchemaElement {

	@JsonProperty("$ref")
	private final String reference;

	public JsonReferenceSchema(Builder builder) {
		super(null);
		this.reference = builder.reference;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof JsonReferenceSchema && equalTo((JsonReferenceSchema) another);
	}

	private boolean equalTo(JsonReferenceSchema another) {
		return Objects.equals(reference, another.reference);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(reference);
		return h;
	}

	@Override
	public String toString() {
		return "JsonReferenceSchema{" + "reference=" + reference + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private String reference;

		public Builder reference(String reference) {
			this.reference = reference;
			return this;
		}

		public JsonReferenceSchema build() {
			return new JsonReferenceSchema(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import java.util.Objects;

@JsonDeserialize(builder = InputAudio.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class InputAudio {

	private final String data;

	private final String format;

	private InputAudio(Builder builder) {
		data = builder.data;
		format = builder.format;
	}

	public String getData() {
		return data;
	}

	public String getFormat() {
		return format;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof InputAudio && equalTo((InputAudio) another);
	}

	private boolean equalTo(InputAudio another) {
		return Objects.equals(data, another.data) && Objects.equals(format, another.format);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(data);
		h += (h << 5) + Objects.hashCode(format);
		return h;
	}

	@Override
	public String toString() {
		return "InputAudio{" + "data=" + data + ", format=" + format + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	public static final class Builder {

		private String data;

		private String format;

		public Builder data(String data) {
			this.data = data;
			return this;
		}

		public Builder format(String format) {
			this.format = format;
			return this;
		}

		public static Builder builder() {
			return new Builder();
		}

		public InputAudio build() {
			return new InputAudio(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.moderation;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = Categories.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class Categories {

	@JsonProperty
	private final Boolean hate;

	@JsonProperty("hate/threatening")
	private final Boolean hateThreatening;

	@JsonProperty("self-harm")
	private final Boolean selfHarm;

	@JsonProperty
	private final Boolean sexual;

	@JsonProperty("sexual/minors")
	private final Boolean sexualMinors;

	@JsonProperty
	private final Boolean violence;

	@JsonProperty("violence/graphic")
	private final Boolean violenceGraphic;

	private Categories(Builder builder) {
		this.hate = builder.hate;
		this.hateThreatening = builder.hateThreatening;
		this.selfHarm = builder.selfHarm;
		this.sexual = builder.sexual;
		this.sexualMinors = builder.sexualMinors;
		this.violence = builder.violence;
		this.violenceGraphic = builder.violenceGraphic;
	}

	public Boolean hate() {
		return hate;
	}

	public Boolean hateThreatening() {
		return hateThreatening;
	}

	public Boolean selfHarm() {
		return selfHarm;
	}

	public Boolean sexual() {
		return sexual;
	}

	public Boolean sexualMinors() {
		return sexualMinors;
	}

	public Boolean violence() {
		return violence;
	}

	public Boolean violenceGraphic() {
		return violenceGraphic;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof Categories && equalTo((Categories) another);
	}

	private boolean equalTo(Categories another) {
		return Objects.equals(hate, another.hate) && Objects.equals(hateThreatening, another.hateThreatening)
				&& Objects.equals(selfHarm, another.selfHarm) && Objects.equals(sexual, another.sexual)
				&& Objects.equals(sexualMinors, another.sexualMinors) && Objects.equals(violence, another.violence)
				&& Objects.equals(violenceGraphic, another.violenceGraphic);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(hate);
		h += (h << 5) + Objects.hashCode(hateThreatening);
		h += (h << 5) + Objects.hashCode(selfHarm);
		h += (h << 5) + Objects.hashCode(sexual);
		h += (h << 5) + Objects.hashCode(sexualMinors);
		h += (h << 5) + Objects.hashCode(violence);
		h += (h << 5) + Objects.hashCode(violenceGraphic);
		return h;
	}

	@Override
	public String toString() {
		return "Categories{" + "hate=" + hate + ", hateThreatening=" + hateThreatening + ", selfHarm=" + selfHarm
				+ ", sexual=" + sexual + ", sexualMinors=" + sexualMinors + ", violence=" + violence
				+ ", violenceGraphic=" + violenceGraphic + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Boolean hate;

		private Boolean hateThreatening;

		private Boolean selfHarm;

		private Boolean sexual;

		private Boolean sexualMinors;

		private Boolean violence;

		private Boolean violenceGraphic;

		private Builder() {
		}

		public Builder hate(Boolean hate) {
			this.hate = hate;
			return this;
		}

		@JsonSetter("hate/threatening")
		public Builder hateThreatening(Boolean hateThreatening) {
			this.hateThreatening = hateThreatening;
			return this;
		}

		@JsonSetter("self-harm")
		public Builder selfHarm(Boolean selfHarm) {
			this.selfHarm = selfHarm;
			return this;
		}

		public Builder sexual(Boolean sexual) {
			this.sexual = sexual;
			return this;
		}

		@JsonSetter("sexual/minors")
		public Builder sexualMinors(Boolean sexualMinors) {
			this.sexualMinors = sexualMinors;
			return this;
		}

		public Builder violence(Boolean violence) {
			this.violence = violence;
			return this;
		}

		@JsonSetter("violence/graphic")
		public Builder violenceGraphic(Boolean violenceGraphic) {
			this.violenceGraphic = violenceGraphic;
			return this;
		}

		public Categories build() {
			return new Categories(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.moderation;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = CategoryScores.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class CategoryScores {

	@JsonProperty
	private final Double hate;

	@JsonProperty("hate/threatening")
	private final Double hateThreatening;

	@JsonProperty("self-harm")
	private final Double selfHarm;

	@JsonProperty
	private final Double sexual;

	@JsonProperty("sexual/minors")
	private final Double sexualMinors;

	@JsonProperty
	private final Double violence;

	@JsonProperty("violence/graphic")
	private final Double violenceGraphic;

	private CategoryScores(Builder builder) {
		this.hate = builder.hate;
		this.hateThreatening = builder.hateThreatening;
		this.selfHarm = builder.selfHarm;
		this.sexual = builder.sexual;
		this.sexualMinors = builder.sexualMinors;
		this.violence = builder.violence;
		this.violenceGraphic = builder.violenceGraphic;
	}

	public Double hate() {
		return hate;
	}

	public Double hateThreatening() {
		return hateThreatening;
	}

	public Double selfHarm() {
		return selfHarm;
	}

	public Double sexual() {
		return sexual;
	}

	public Double sexualMinors() {
		return sexualMinors;
	}

	public Double violence() {
		return violence;
	}

	public Double violenceGraphic() {
		return violenceGraphic;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof CategoryScores && equalTo((CategoryScores) another);
	}

	private boolean equalTo(CategoryScores another) {
		return Objects.equals(hate, another.hate) && Objects.equals(hateThreatening, another.hateThreatening)
				&& Objects.equals(selfHarm, another.selfHarm) && Objects.equals(sexual, another.sexual)
				&& Objects.equals(sexualMinors, another.sexualMinors) && Objects.equals(violence, another.violence)
				&& Objects.equals(violenceGraphic, another.violenceGraphic);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(hate);
		h += (h << 5) + Objects.hashCode(hateThreatening);
		h += (h << 5) + Objects.hashCode(selfHarm);
		h += (h << 5) + Objects.hashCode(sexual);
		h += (h << 5) + Objects.hashCode(sexualMinors);
		h += (h << 5) + Objects.hashCode(violence);
		h += (h << 5) + Objects.hashCode(violenceGraphic);
		return h;
	}

	@Override
	public String toString() {
		return "CategoryScores{" + "hate=" + hate + ", hateThreatening=" + hateThreatening + ", selfHarm=" + selfHarm
				+ ", sexual=" + sexual + ", sexualMinors=" + sexualMinors + ", violence=" + violence
				+ ", violenceGraphic=" + violenceGraphic + "}";
	}

	public static Builder builder() {
		return new Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private Double hate;

		private Double hateThreatening;

		private Double selfHarm;

		private Double sexual;

		private Double sexualMinors;

		private Double violence;

		private Double violenceGraphic;

		private Builder() {
		}

		public Builder hate(Double hate) {
			this.hate = hate;
			return this;
		}

		@JsonSetter("hate/threatening")
		public Builder hateThreatening(Double hateThreatening) {
			this.hateThreatening = hateThreatening;
			return this;
		}

		@JsonSetter("self-harm")
		public Builder selfHarm(Double selfHarm) {
			this.selfHarm = selfHarm;
			return this;
		}

		public Builder sexual(Double sexual) {
			this.sexual = sexual;
			return this;
		}

		@JsonSetter("sexual/minors")
		public Builder sexualMinors(Double sexualMinors) {
			this.sexualMinors = sexualMinors;
			return this;
		}

		public Builder violence(Double violence) {
			this.violence = violence;
			return this;
		}

		@JsonSetter("violence/graphic")
		public Builder violenceGraphic(Double violenceGraphic) {
			this.violenceGraphic = violenceGraphic;
			return this;
		}

		public CategoryScores build() {
			return new CategoryScores(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core.shared;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.util.Objects;

@JsonDeserialize(builder = StreamOptions.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final class StreamOptions {

	@JsonProperty
	private final Boolean includeUsage;

	public StreamOptions(Builder builder) {
		this.includeUsage = builder.includeUsage;
	}

	public Boolean includeUsage() {
		return includeUsage;
	}

	@Override
	public boolean equals(Object another) {
		if (this == another)
			return true;
		return another instanceof StreamOptions && equalTo((StreamOptions) another);
	}

	private boolean equalTo(StreamOptions another) {
		return Objects.equals(includeUsage, another.includeUsage);
	}

	@Override
	public int hashCode() {
		int h = 5381;
		h += (h << 5) + Objects.hashCode(includeUsage);
		return h;
	}

	@Override
	public String toString() {
		return "StreamOptions{" + "includeUsage=" + includeUsage + "}";
	}

	public static StreamOptions.Builder builder() {
		return new StreamOptions.Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static class Builder {

		private Boolean includeUsage;

		public StreamOptions.Builder includeUsage(Boolean includeUsage) {
			this.includeUsage = includeUsage;
			return this;
		}

		public StreamOptions build() {
			return new StreamOptions(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core;

import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionRequest;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
import io.github.pigmesh.ai.deepseek.core.completion.CompletionRequest;
import io.github.pigmesh.ai.deepseek.core.completion.CompletionResponse;
import io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingRequest;
import io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingResponse;
import io.github.pigmesh.ai.deepseek.core.models.ModelsResponse;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationRequest;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationResponse;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationResult;
import okhttp3.Dispatcher;
import reactor.core.publisher.Flux;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static io.github.pigmesh.ai.deepseek.core.LogLevel.DEBUG;

public abstract class OpenAiClient {

	public SyncOrAsyncOrStreaming<CompletionResponse> completion(CompletionRequest request) {
		return completion(new OpenAiClientContext(), request);
	}

	public SyncOrAsyncOrStreaming<CompletionResponse> completion(OpenAiClientContext clientContext,
			CompletionRequest request) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsyncOrStreaming<String> completion(String prompt) {
		return completion(new OpenAiClientContext(), prompt);
	}

	public SyncOrAsyncOrStreaming<String> completion(OpenAiClientContext clientContext, String prompt) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatCompletion(ChatCompletionRequest request) {
		return chatCompletion(new OpenAiClientContext(), request);
	}

	public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatCompletion(OpenAiClientContext clientContext,
			ChatCompletionRequest request) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsyncOrStreaming<String> chatCompletion(String userMessage) {
		return chatCompletion(new OpenAiClientContext(), userMessage);
	}

	public SyncOrAsyncOrStreaming<String> chatCompletion(OpenAiClientContext clientContext, String userMessage) {
		throw new UnsupportedOperationException();
	}

	public Flux<ChatCompletionResponse> chatFluxCompletion(ChatCompletionRequest request) {
		throw new UnsupportedOperationException();
	}

	public Flux<ChatCompletionResponse> chatFluxCompletion(String userMessage) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsync<EmbeddingResponse> embedding(EmbeddingRequest request) {
		return embedding(new OpenAiClientContext(), request);
	}

	public SyncOrAsync<EmbeddingResponse> embedding(OpenAiClientContext clientContext, EmbeddingRequest request) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsync<List<Float>> embedding(String input) {
		return embedding(new OpenAiClientContext(), input);
	}

	public SyncOrAsync<List<Float>> embedding(OpenAiClientContext clientContext, String input) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsync<ModerationResponse> moderation(ModerationRequest request) {
		return moderation(new OpenAiClientContext(), request);
	}

	public SyncOrAsync<ModerationResponse> moderation(OpenAiClientContext clientContext, ModerationRequest request) {
		throw new UnsupportedOperationException();
	}

	public SyncOrAsync<ModerationResult> moderation(String input) {
		return moderation(new OpenAiClientContext(), input);
	}

	public SyncOrAsync<ModerationResult> moderation(OpenAiClientContext clientContext, String input) {
		throw new UnsupportedOperationException();
	}

	public ModelsResponse models() {
		throw new UnsupportedOperationException();
	}

	public abstract void shutdown();

	public static class OpenAiClientContext {

		private final Map<String, String> headers = new HashMap<>();

		public OpenAiClientContext addHeaders(Map<String, String> headers) {
			this.headers.putAll(headers);
			return this;
		}

		public OpenAiClientContext addHeader(String key, String value) {
			headers.put(key, value);
			return this;
		}

		public Map<String, String> headers() {
			return headers;
		}

		public static OpenAiClientContext create() {
			return new OpenAiClientContext();
		}

	}

	@SuppressWarnings("unchecked")
	public abstract static class Builder<T extends OpenAiClient, B extends Builder<T, B>> {

		public String baseUrl = "https://api.deepseek.com/v1/";

		public String organizationId;

		public String model;

		public String apiVersion;

		public String openAiApiKey;

		public String azureApiKey;

		public Duration callTimeout = Duration.ofSeconds(60);

		public Duration connectTimeout = Duration.ofSeconds(60);

		public Duration readTimeout = Duration.ofSeconds(60);

		public Duration writeTimeout = Duration.ofSeconds(60);

		public Dispatcher dispatcher;

		public Proxy proxy;

		public String userAgent;

		public boolean logRequests;

		public boolean logResponses;

		public LogLevel logLevel = DEBUG;

		public boolean logStreamingResponses;

		public Path persistTo;

		public Map<String, String> customHeaders;

		public String systemMessage;

		public abstract T build();

		/**
		 * @param baseUrl Base URL of OpenAI API. For OpenAI (default):
		 * "https://api.openai.com/v1/" For Azure OpenAI:
		 * "https://{resource-name}.openai.azure.com/openai/deployments/{deployment-id}/"
		 * @return builder
		 */
		public B baseUrl(String baseUrl) {
			if (baseUrl == null || baseUrl.trim().isEmpty()) {
				throw new IllegalArgumentException("baseUrl cannot be null or empty");
			}
			this.baseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
			return (B) this;
		}

		/**
		 * @param organizationId The organizationId for OpenAI:
		 * https://platform.openai.com/docs/api-reference/organization-optional
		 * @return builder
		 */
		public B organizationId(String organizationId) {
			this.organizationId = organizationId;
			return (B) this;
		}

		/**
		 * @param apiVersion Version of the API in the YYYY-MM-DD format. Applicable only
		 * for Azure OpenAI.
		 * @return builder
		 */
		public B apiVersion(String apiVersion) {
			this.apiVersion = apiVersion;
			return (B) this;
		}

		/**
		 * @param openAiApiKey OpenAI API key. Will be injected in HTTP headers like this:
		 * "Authorization: Bearer ${openAiApiKey}"
		 * @return builder
		 */
		public B openAiApiKey(String openAiApiKey) {
			if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) {
				throw new IllegalArgumentException(
						"ApiKey 不能为空，请访问 DeepSeek 开放平台获取: https://platform.deepseek.com/api_keys");
			}
			this.openAiApiKey = openAiApiKey;
			return (B) this;
		}

		public B model(String model) {
			this.model = model;
			return (B) this;
		}

		public B systemMessage(String systemMessage) {
			this.systemMessage = systemMessage;
			return (B) this;
		}

		/**
		 * @param azureApiKey Azure API key. Will be injected in HTTP headers like this:
		 * "api-key: ${azureApiKey}"
		 * @return builder
		 */
		public B azureApiKey(String azureApiKey) {
			if (azureApiKey == null || azureApiKey.trim().isEmpty()) {
				throw new IllegalArgumentException("azureApiKey cannot be null or empty");
			}
			this.azureApiKey = azureApiKey;
			return (B) this;
		}

		public B callTimeout(Duration callTimeout) {
			if (callTimeout == null) {
				throw new IllegalArgumentException("callTimeout cannot be null");
			}
			this.callTimeout = callTimeout;
			return (B) this;
		}

		public B connectTimeout(Duration connectTimeout) {
			if (connectTimeout == null) {
				throw new IllegalArgumentException("connectTimeout cannot be null");
			}
			this.connectTimeout = connectTimeout;
			return (B) this;
		}

		public B readTimeout(Duration readTimeout) {
			if (readTimeout == null) {
				throw new IllegalArgumentException("readTimeout cannot be null");
			}
			this.readTimeout = readTimeout;
			return (B) this;
		}

		public B writeTimeout(Duration writeTimeout) {
			if (writeTimeout == null) {
				throw new IllegalArgumentException("writeTimeout cannot be null");
			}
			this.writeTimeout = writeTimeout;
			return (B) this;
		}

		public B dispatcher(Dispatcher dispatcher) {
			this.dispatcher = dispatcher;
			return (B) this;
		}

		public B proxy(Proxy.Type type, String ip, int port) {
			this.proxy = new Proxy(type, new InetSocketAddress(ip, port));
			return (B) this;
		}

		public B proxy(Proxy proxy) {
			this.proxy = proxy;
			return (B) this;
		}

		public B userAgent(String userAgent) {
			this.userAgent = userAgent;
			return (B) this;
		}

		public B logRequests() {
			return logRequests(true);
		}

		public B logRequests(Boolean logRequests) {
			if (logRequests == null) {
				logRequests = false;
			}
			this.logRequests = logRequests;
			return (B) this;
		}

		public B logLevel(LogLevel logLevel) {
			if (logLevel == null) {
				logLevel = DEBUG;
			}
			this.logLevel = logLevel;
			return (B) this;
		}

		public B logResponses() {
			return logResponses(true);
		}

		public B logResponses(Boolean logResponses) {
			if (logResponses == null) {
				logResponses = false;
			}
			this.logResponses = logResponses;
			return (B) this;
		}

		public B logStreamingResponses() {
			return logStreamingResponses(true);
		}

		public B logStreamingResponses(Boolean logStreamingResponses) {
			if (logStreamingResponses == null) {
				logStreamingResponses = false;
			}
			this.logStreamingResponses = logStreamingResponses;
			return (B) this;
		}

		/**
		 * Generated response will be persisted under java.io.tmpdir. Used
		 * with images generation for the moment only. The URL within
		 * dev.ai4j.openai4j.image.GenerateImagesResponse will contain the
		 * URL to local images then.
		 * @return builder
		 */
		public B withPersisting() {
			persistTo = Paths.get(System.getProperty("java.io.tmpdir"));
			return (B) this;
		}

		/**
		 * Generated response will be persisted under provided path. Used with images
		 * generation for the moment only. The URL within
		 * dev.ai4j.openai4j.image.GenerateImagesResponse will contain the
		 * URL to local images then.
		 * @param persistTo path
		 * @return builder
		 */
		public B persistTo(Path persistTo) {
			this.persistTo = persistTo;
			return (B) this;
		}

		/**
		 * Custom headers to be added to each HTTP request.
		 * @param customHeaders a map of headers
		 * @return builder
		 */
		public B customHeaders(Map<String, String> customHeaders) {
			this.customHeaders = customHeaders;
			return (B) this;
		}

	}

}

package io.github.pigmesh.ai.deepseek.core;

import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionRequest;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
import io.github.pigmesh.ai.deepseek.core.completion.CompletionRequest;
import io.github.pigmesh.ai.deepseek.core.completion.CompletionResponse;
import io.github.pigmesh.ai.deepseek.core.models.ModelsResponse;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationRequest;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationResponse;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationResult;
import io.github.pigmesh.ai.deepseek.core.search.SearchApi;
import io.github.pigmesh.ai.deepseek.core.search.SearchRequest;
import io.github.pigmesh.ai.deepseek.core.search.SearchResponse;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Deep Seek 客户端
 *
 * @author lengleng
 * @date 2025/02/06
 */
public class DeepSeekClient extends OpenAiClient {

	private static final Logger log = LoggerFactory.getLogger(DeepSeekClient.class);

	private final String baseUrl;

	private final String apiVersion;

	private final String model;

	private final OkHttpClient okHttpClient;

	private final OpenAiApi openAiApi;

	private final SearchApi searchApi;

	private final boolean logStreamingResponses;

	private final String systemMessage;

	public DeepSeekClient(String apiKey) {
		this(new Builder().openAiApiKey(apiKey));
	}

	public DeepSeekClient(Builder serviceBuilder) {
		this.baseUrl = serviceBuilder.baseUrl;
		this.apiVersion = serviceBuilder.apiVersion;
		this.model = serviceBuilder.model;
		this.systemMessage = serviceBuilder.systemMessage;

		OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder().callTimeout(serviceBuilder.callTimeout)
				.connectTimeout(serviceBuilder.connectTimeout).readTimeout(serviceBuilder.readTimeout)
				.writeTimeout(serviceBuilder.writeTimeout);

		if (serviceBuilder.dispatcher != null) {
			okHttpClientBuilder.dispatcher(serviceBuilder.dispatcher);
		}

		if (serviceBuilder.openAiApiKey == null && serviceBuilder.azureApiKey == null) {
			throw new IllegalArgumentException("openAiApiKey OR azureApiKey must be defined");
		}
		if (serviceBuilder.openAiApiKey != null && serviceBuilder.azureApiKey != null) {
			throw new IllegalArgumentException("openAiApiKey AND azureApiKey cannot both be defined at the same time");
		}
		if (serviceBuilder.openAiApiKey != null) {
			okHttpClientBuilder.addInterceptor(new AuthorizationHeaderInjector(serviceBuilder.openAiApiKey));
		}
		else {
			okHttpClientBuilder.addInterceptor(new ApiKeyHeaderInjector(serviceBuilder.azureApiKey));
		}

		Map<String, String> headers = new HashMap<>();
		if (serviceBuilder.organizationId != null) {
			headers.put("OpenAI-Organization", serviceBuilder.organizationId);
		}
		if (serviceBuilder.userAgent != null) {
			headers.put("User-Agent", serviceBuilder.userAgent);
		}
		if (serviceBuilder.customHeaders != null) {
			headers.putAll(serviceBuilder.customHeaders);
		}
		if (!headers.isEmpty()) {
			okHttpClientBuilder.addInterceptor(new GenericHeaderInjector(headers));
		}

		if (serviceBuilder.proxy != null) {
			okHttpClientBuilder.proxy(serviceBuilder.proxy);
		}

		if (serviceBuilder.logRequests) {
			okHttpClientBuilder.addInterceptor(new RequestLoggingInterceptor(serviceBuilder.logLevel));
		}

		if (serviceBuilder.logResponses) {
			okHttpClientBuilder.addInterceptor(new ResponseLoggingInterceptor(serviceBuilder.logLevel));
		}
		this.logStreamingResponses = serviceBuilder.logStreamingResponses;

		this.okHttpClient = okHttpClientBuilder.build();

		Retrofit.Builder retrofitBuilder = new Retrofit.Builder().baseUrl(serviceBuilder.baseUrl).client(okHttpClient);

		if (serviceBuilder.persistTo != null) {
			retrofitBuilder.addConverterFactory(new PersistorConverterFactory(serviceBuilder.persistTo));
		}

		retrofitBuilder.addConverterFactory(JacksonConverterFactory.create(Json.OBJECT_MAPPER));

		this.openAiApi = retrofitBuilder.build().create(OpenAiApi.class);

		// Search API
		OkHttpClient searchOkHttpClient = new OkHttpClient.Builder().callTimeout(serviceBuilder.callTimeout)
				.connectTimeout(serviceBuilder.connectTimeout).readTimeout(serviceBuilder.readTimeout)
				.writeTimeout(serviceBuilder.writeTimeout)
				.addInterceptor(new RequestLoggingInterceptor(serviceBuilder.logLevel))
				.addInterceptor(new ResponseLoggingInterceptor(serviceBuilder.logLevel))
				.addInterceptor(new AuthorizationHeaderInjector(serviceBuilder.searchApiKey)).build();
		Retrofit.Builder searchRetrofitBuilder = new Retrofit.Builder().baseUrl(serviceBuilder.searchEndpoint)
				.client(searchOkHttpClient);
		searchRetrofitBuilder.addConverterFactory(JacksonConverterFactory.create(Json.OBJECT_MAPPER));
		this.searchApi = searchRetrofitBuilder.build().create(SearchApi.class);
	}

	public void shutdown() {
		okHttpClient.dispatcher().executorService().shutdown();

		okHttpClient.connectionPool().evictAll();

		Cache cache = okHttpClient.cache();
		if (cache != null) {
			try {
				cache.close();
			}
			catch (IOException e) {
				log.error("Failed to close cache", e);
			}
		}
	}

	public static Builder builder() {
		return new Builder();
	}

	public static class Builder extends OpenAiClient.Builder<DeepSeekClient, Builder> {

		private String searchEndpoint = "https://api.bochaai.com/v1/";

		private String searchApiKey;

		private SearchApi searchApi;

		public Builder searchApi(SearchApi searchApi) {
			this.searchApi = searchApi;
			return this;
		}

		public Builder searchApiKey(String searchApiKey) {
			this.searchApiKey = searchApiKey;
			return this;
		}

		public Builder searchEndpoint(String searchEndpoint) {
			this.searchEndpoint = searchEndpoint;
			return this;
		}

		public DeepSeekClient build() {
			return new DeepSeekClient(this);
		}

	}

	@Override
	public SyncOrAsyncOrStreaming<CompletionResponse> completion(OpenAiClientContext context,
			CompletionRequest request) {
		CompletionRequest syncRequest = CompletionRequest.builder().from(request).stream(false).build();

		return new RequestExecutor<>(openAiApi.completions(context.headers(), syncRequest, apiVersion), r -> r,
				okHttpClient, formatUrl("completions"),
				() -> CompletionRequest.builder().from(request).stream(true).build(), CompletionResponse.class, r -> r,
				logStreamingResponses);
	}

	@Override
	public SyncOrAsyncOrStreaming<String> completion(OpenAiClientContext context, String prompt) {
		CompletionRequest request = CompletionRequest.builder().prompt(prompt).build();

		CompletionRequest syncRequest = CompletionRequest.builder().from(request).stream(false).build();

		return new RequestExecutor<>(openAiApi.completions(context.headers(), syncRequest, apiVersion),
				CompletionResponse::text, okHttpClient, formatUrl("completions"),
				() -> CompletionRequest.builder().from(request).stream(true).build(), CompletionResponse.class,
				CompletionResponse::text, logStreamingResponses);
	}

	@Override
	public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatCompletion(OpenAiClientContext context,
			ChatCompletionRequest request) {

		if (Objects.isNull(request.getModel())) {
			request.setModel(this.model);
		}

		ChatCompletionRequest syncRequest = ChatCompletionRequest.builder().from(request).stream(false).build();

		return new RequestExecutor<>(openAiApi.chatCompletions(context.headers(), syncRequest, apiVersion), r -> r,
				okHttpClient, formatUrl("chat/completions"),
				() -> ChatCompletionRequest.builder().from(request).stream(true).build(), ChatCompletionResponse.class,
				r -> r, logStreamingResponses);
	}

	@Override
	public SyncOrAsyncOrStreaming<String> chatCompletion(OpenAiClientContext context, String userMessage) {
		ChatCompletionRequest request = ChatCompletionRequest.builder().addUserMessage(userMessage).build();

		ChatCompletionRequest syncRequest = ChatCompletionRequest.builder().from(request).stream(false).build();

		return new RequestExecutor<>(openAiApi.chatCompletions(context.headers(), syncRequest, apiVersion),
				ChatCompletionResponse::content, okHttpClient, formatUrl("chat/completions"),
				() -> ChatCompletionRequest.builder().from(request).stream(true).build(), ChatCompletionResponse.class,
				r -> r.choices().get(0).delta().content(), logStreamingResponses);
	}

	@Override
	public Flux<ChatCompletionResponse> chatFluxCompletion(ChatCompletionRequest request) {
		if (Objects.isNull(request.getModel())) {
			request.setModel(this.model);
		}

		return Flux.create(emitter -> {
			this.chatCompletion(new OpenAiClientContext(), request).onPartialResponse(emitter::next)
					.onComplete(emitter::complete).onError(emitter::error).execute();
		});
	}

	/**
	 * 聊天搜索
	 * @param userMessage 用户消息
	 * @return {@link Flux }<{@link ChatCompletionResponse }>
	 */
	public Flux<ChatCompletionResponse> chatSearchCompletion(String userMessage) {
		SearchResponse searchResponse = new SyncRequestExecutor<>(
				searchApi.webSearch(SearchRequest.builder().enable(true).query(userMessage).build()),
				searchResponse1 -> searchResponse1).execute();
		if (200 == searchResponse.getCode()) {
			String formatted = Utils.format(userMessage, searchResponse.getData().getWebPages().getValue());
			if (Objects.nonNull(formatted)) {
				return this.chatFluxCompletion(formatted);
			}
			return this.chatFluxCompletion(userMessage);
		}

		return this.chatFluxCompletion(userMessage);
	}

	/**
	 * 聊天搜索
	 * @param userMessage 用户消息
	 * @return {@link Flux }<{@link ChatCompletionResponse }>
	 */
	public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatSearchStreamingCompletion(String userMessage) {
		SearchResponse searchResponse = new SyncRequestExecutor<>(
				searchApi.webSearch(SearchRequest.builder().enable(true).query(userMessage).build()),
				searchResponse1 -> searchResponse1).execute();
		if (200 == searchResponse.getCode()) {
			String formatted = Utils.format(userMessage, searchResponse.getData().getWebPages().getValue());
			if (Objects.nonNull(formatted)) {
				return this.chatCompletion(new OpenAiClientContext(), ChatCompletionRequest.builder().stream(true)
						.model(this.model).addUserMessage(formatted).build());
			}
		}
		return this.chatCompletion(new OpenAiClientContext(),
				ChatCompletionRequest.builder().stream(true).model(this.model).addUserMessage(userMessage).build());
	}

	/**
	 * 聊天搜索完成
	 * @param userMessage 用户消息
	 * @param searchRequest 搜索请求
	 * @return {@link Flux }<{@link ChatCompletionResponse }>
	 */
	public Flux<ChatCompletionResponse> chatSearchCompletion(String userMessage, SearchRequest searchRequest) {

		if (Objects.isNull(searchRequest.getQuery())) {
			searchRequest.setQuery(userMessage);
		}

		SearchResponse searchResponse = new SyncRequestExecutor<>(searchApi.webSearch(searchRequest),
				searchResponse1 -> searchResponse1).execute();
		if (200 == searchResponse.getCode()) {
			String formatted = Utils.format(userMessage, searchResponse.getData().getWebPages().getValue());
			if (Objects.nonNull(formatted)) {
				return this.chatFluxCompletion(formatted);
			}
			return this.chatFluxCompletion(userMessage);
		}

		return this.chatFluxCompletion(userMessage);
	}

	@Override
	public Flux<ChatCompletionResponse> chatFluxCompletion(String userMessage) {
		ChatCompletionRequest.Builder builder = ChatCompletionRequest.builder();

		if (Objects.nonNull(this.systemMessage)) {
			builder.addSystemMessage(this.systemMessage);
		}

		if (Objects.nonNull(this.model)) {
			builder.model(this.model);
		}

		builder.addUserMessage(userMessage);
		return Flux.create(emitter -> {
			this.chatCompletion(new OpenAiClientContext(), builder.build()).onPartialResponse(emitter::next)
					.onComplete(emitter::complete).onError(emitter::error).execute();
		});
	}

	@Override
	public SyncOrAsync<ModerationResponse> moderation(OpenAiClientContext context, ModerationRequest request) {
		return new RequestExecutor<>(openAiApi.moderations(context.headers(), request, apiVersion), r -> r);
	}

	@Override
	public SyncOrAsync<ModerationResult> moderation(OpenAiClientContext context, String input) {
		ModerationRequest request = ModerationRequest.builder().input(input).build();

		return new RequestExecutor<>(openAiApi.moderations(context.headers(), request, apiVersion),
				r -> r.results().get(0));
	}

	public ModelsResponse models() {

		return new RequestExecutor<>(this.openAiApi.models(new HashMap<>(), apiVersion), r -> r, okHttpClient, null,
				null, ModelsResponse.class, null, logStreamingResponses).execute();
	}

	private String formatUrl(String endpoint) {
		return baseUrl + endpoint + apiVersionQueryParam();
	}

	private String apiVersionQueryParam() {
		if (apiVersion == null || apiVersion.trim().isEmpty()) {
			return "";
		}
		return "?api-version=" + apiVersion;
	}

}

package io.github.pigmesh.ai.deepseek.core.models;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Data;

import java.util.List;
import java.util.Objects;

@Data
@JsonDeserialize(builder = ModelsResponse.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ModelsResponse {

	@JsonProperty("object")
	private String object;

	@JsonProperty("data")
	private List<Model> data;

	public ModelsResponse(Builder builder) {
		this.object = builder.object;
		this.data = builder.data;
	}

	public static ModelsResponse.Builder builder() {
		return new ModelsResponse.Builder();
	}

	@JsonPOJOBuilder(withPrefix = "")
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
	public static final class Builder {

		private String object; // 这里可能会与 Java 的 Object 冲突，可能需要重命名

		private List<Model> data;

		private Builder() {
		}

		public ModelsResponse.Builder object(String object) {
			this.object = object;
			return this;
		}

		public ModelsResponse.Builder data(List<Model> data) {
			this.data = data;
			return this;
		}

		public ModelsResponse build() {
			return new ModelsResponse(this);
		}

	}

}

package io.github.pigmesh.ai.deepseek.core;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static io.github.pigmesh.ai.deepseek.core.Utils.toException;

class StreamingRequestExecutor<Request, Response, ResponseContent> {

	private static final Logger log = LoggerFactory.getLogger(StreamingRequestExecutor.class);

	private final OkHttpClient okHttpClient;

	private final String endpointUrl;

	private final Supplier<Request> requestWithStreamSupplier;

	private final Class<Response> responseClass;

	private final Function<Response, ResponseContent> streamEventContentExtractor;

	private final boolean logStreamingResponses;

	private final ResponseLoggingInterceptor responseLogger = new ResponseLoggingInterceptor();

	StreamingRequestExecutor(OkHttpClient okHttpClient, String endpointUrl, Supplier<Request> requestWithStreamSupplier,
			Class<Response> responseClass, Function<Response, ResponseContent> streamEventContentExtractor,
			boolean logStreamingResponses) {
		this.okHttpClient = okHttpClient;
		this.endpointUrl = endpointUrl;
		this.requestWithStreamSupplier = requestWithStreamSupplier;
		this.responseClass = responseClass;
		this.streamEventContentExtractor = streamEventContentExtractor;
		this.logStreamingResponses = logStreamingResponses;
	}

	StreamingResponseHandling onPartialResponse(Consumer<ResponseContent> partialResponseHandler) {

		return new StreamingResponseHandling() {

			@Override
			public StreamingCompletionHandling onComplete(Runnable streamingCompletionCallback) {
				return new StreamingCompletionHandling() {

					@Override
					public ErrorHandling onError(Consumer<Throwable> errorHandler) {
						return new ErrorHandling() {

							@Override
							public ResponseHandle execute() {
								return stream(partialResponseHandler, streamingCompletionCallback, errorHandler);
							}
						};
					}

					@Override
					public ErrorHandling ignoreErrors() {
						return new ErrorHandling() {

							@Override
							public ResponseHandle execute() {
								return stream(partialResponseHandler, streamingCompletionCallback, (e) -> {
									// intentionally ignoring because user called
									// ignoreErrors()
								});
							}
						};
					}
				};
			}

			@Override
			public ErrorHandling onError(Consumer<Throwable> errorHandler) {
				return new ErrorHandling() {

					@Override
					public ResponseHandle execute() {
						return stream(partialResponseHandler, () -> {
							// intentionally ignoring because user did not provide
							// callback
						}, errorHandler);
					}
				};
			}

			@Override
			public ErrorHandling ignoreErrors() {
				return new ErrorHandling() {

					@Override
					public ResponseHandle execute() {
						return stream(partialResponseHandler, () -> {
							// intentionally ignoring because user did not provide
							// callback
						}, (e) -> {
							// intentionally ignoring because user called ignoreErrors()
						});
					}
				};
			}
		};
	}

	private ResponseHandle stream(Consumer<ResponseContent> partialResponseHandler,
			Runnable streamingCompletionCallback, Consumer<Throwable> errorHandler) {

		Request request = requestWithStreamSupplier.get();

		String requestJson = Json.toJson(request);

		okhttp3.Request okHttpRequest = new okhttp3.Request.Builder().url(endpointUrl)
				.post(RequestBody.create(requestJson, MediaType.get("application/json; charset=utf-8"))).build();

		ResponseHandle responseHandle = new ResponseHandle();

		EventSourceListener eventSourceListener = new EventSourceListener() {

			@Override
			public void onOpen(EventSource eventSource, okhttp3.Response response) {
				if (responseHandle.cancelled) {
					eventSource.cancel();
					return;
				}

				if (logStreamingResponses) {
					responseLogger.log(response);
				}
			}

			@Override
			public void onEvent(EventSource eventSource, String id, String type, String data) {
				if (responseHandle.cancelled) {
					eventSource.cancel();
					return;
				}

				if (logStreamingResponses) {
					log.debug("onEvent() {}", data);
				}

				if ("[DONE]".equals(data)) {
					return;
				}

				try {
					Response response = Json.fromJson(data, responseClass);
					ResponseContent responseContent = streamEventContentExtractor.apply(response);
					if (responseContent != null) {
						partialResponseHandler.accept(responseContent); // do not handle
																		// exception,
																		// fail-fast
					}
				}
				catch (Exception e) {
					errorHandler.accept(e);
				}
			}

			@Override
			public void onClosed(EventSource eventSource) {
				if (responseHandle.cancelled) {
					eventSource.cancel();
					return;
				}

				if (logStreamingResponses) {
					log.debug("onClosed()");
				}

				streamingCompletionCallback.run();
			}

			@Override
			public void onFailure(EventSource eventSource, Throwable t, okhttp3.Response response) {
				if (responseHandle.cancelled) {
					return;
				}

				// TODO remove this when migrating from okhttp
				if (t instanceof IllegalArgumentException && "byteCount < 0: -1".equals(t.getMessage())) {
					streamingCompletionCallback.run();
					return;
				}

				if (logStreamingResponses) {
					log.debug("onFailure()", t);
					responseLogger.log(response);
				}

				if (t != null) {
					errorHandler.accept(t); // TODO also include information from
											// response?
				}
				else {
					try {
						errorHandler.accept(toException(response));
					}
					catch (IOException e) {
						errorHandler.accept(e); // TODO right thing to do?
					}
				}
			}
		};

		EventSources.createFactory(okHttpClient).newEventSource(okHttpRequest, eventSourceListener);

		return responseHandle;
	}

}

package io.github.pigmesh.ai.deepseek.solon;

import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
import io.github.pigmesh.ai.deepseek.core.EmbeddingClient;
import lombok.SneakyThrows;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.core.util.ResourceUtil;

import java.time.Duration;
import java.util.Objects;

/**
 * 装配 DeepSeekClient 和 EmbeddingClient
 *
 * @author songyinyin
 * @since 2025/2/16 16:11
 */
@Configuration
public class AutoConfiguration {

	@Bean
	@SneakyThrows
	@Condition(onMissingBean = DeepSeekClient.class, onProperty = "deepseek.api-key")
	public DeepSeekClient deepSeekClient(DeepSeekProperties deepSeekProperties) {

		DeepSeekClient.Builder builder = DeepSeekClient.builder().baseUrl(deepSeekProperties.getBaseUrl())
				.model(deepSeekProperties.getModel()).openAiApiKey(deepSeekProperties.getApiKey())
				.logRequests(deepSeekProperties.isLogRequests()).logResponses(deepSeekProperties.isLogResponses());

		if (Objects.nonNull(deepSeekProperties.getProxy())) {
			builder.proxy(deepSeekProperties.getProxy());
		}

		if (Objects.nonNull(deepSeekProperties.getConnectTimeout())) {
			builder.connectTimeout(Duration.ofSeconds(deepSeekProperties.getConnectTimeout()));
		}

		if (Objects.nonNull(deepSeekProperties.getReadTimeout())) {
			builder.readTimeout(Duration.ofSeconds(deepSeekProperties.getReadTimeout()));
		}

		if (Objects.nonNull(deepSeekProperties.getCallTimeout())) {
			builder.callTimeout(Duration.ofSeconds(deepSeekProperties.getCallTimeout()));
		}

		builder.logLevel(deepSeekProperties.getLogLevel());

		// 注入R1 提示词
		if (deepSeekProperties.isDefaultSystemPrompt()) {
			String systemMessage = ResourceUtil.getResourceAsString("prompts/system.pt");
			builder.systemMessage(systemMessage);
		}

		builder.searchApiKey(deepSeekProperties.getSearchApiKey());
		return builder.build();
	}

	@Bean
	@SneakyThrows
	@Condition(onMissingBean = EmbeddingClient.class, onProperty = "embedding.api-key")
	public EmbeddingClient embeddingClient(EmbeddingProperties embeddingProperties) {

		EmbeddingClient.Builder builder = EmbeddingClient.builder().baseUrl(embeddingProperties.getBaseUrl())
				.model(embeddingProperties.getModel()).openAiApiKey(embeddingProperties.getApiKey())
				.logRequests(embeddingProperties.isLogRequests()).logResponses(embeddingProperties.isLogResponses());

		if (Objects.nonNull(embeddingProperties.getProxy())) {
			builder.proxy(embeddingProperties.getProxy());
		}

		if (Objects.nonNull(embeddingProperties.getConnectTimeout())) {
			builder.connectTimeout(Duration.ofSeconds(embeddingProperties.getConnectTimeout()));
		}

		if (Objects.nonNull(embeddingProperties.getReadTimeout())) {
			builder.readTimeout(Duration.ofSeconds(embeddingProperties.getReadTimeout()));
		}

		if (Objects.nonNull(embeddingProperties.getCallTimeout())) {
			builder.callTimeout(Duration.ofSeconds(embeddingProperties.getCallTimeout()));
		}

		builder.logLevel(embeddingProperties.getLogLevel());

		return builder.build();
	}

}

package io.github.pigmesh.ai.deepseek.config;

import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
import io.github.pigmesh.ai.deepseek.core.EmbeddingClient;
import io.github.pigmesh.ai.deepseek.core.OpenAiClient;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Objects;

/**
 * Deep Seek 自动配置
 *
 * @author lengleng
 * @date 2025/02/06
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ DeepSeekProperties.class, EmbeddingProperties.class })
public class DeepSeekAutoConfiguration {

	@Value("classpath:/prompts/system.pt")
	private Resource systemResource;

	/**
	 * Deep Seek 客户端
	 * @param deepSeekProperties Deep Seek 属性
	 * @return {@link OpenAiClient }
	 */
	@SneakyThrows
	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnProperty(prefix = "deepseek", name = "api-key")
	public DeepSeekClient deepSeekClient(DeepSeekProperties deepSeekProperties) {

		DeepSeekClient.Builder builder = DeepSeekClient.builder().baseUrl(deepSeekProperties.getBaseUrl())
				.model(deepSeekProperties.getModel()).openAiApiKey(deepSeekProperties.getApiKey())
				.logRequests(deepSeekProperties.isLogRequests()).logResponses(deepSeekProperties.isLogResponses());

		if (Objects.nonNull(deepSeekProperties.getProxy())) {
			builder.proxy(deepSeekProperties.getProxy());
		}

		if (Objects.nonNull(deepSeekProperties.getConnectTimeout())) {
			builder.connectTimeout(Duration.ofSeconds(deepSeekProperties.getConnectTimeout()));
		}

		if (Objects.nonNull(deepSeekProperties.getReadTimeout())) {
			builder.readTimeout(Duration.ofSeconds(deepSeekProperties.getReadTimeout()));
		}

		if (Objects.nonNull(deepSeekProperties.getCallTimeout())) {
			builder.callTimeout(Duration.ofSeconds(deepSeekProperties.getCallTimeout()));
		}

		builder.logLevel(deepSeekProperties.getLogLevel());

		// 注入R1 提示词
		if (deepSeekProperties.isDefaultSystemPrompt()) {
			String systemMessage = StreamUtils.copyToString(systemResource.getInputStream(), StandardCharsets.UTF_8);
			builder.systemMessage(systemMessage);
		}

		builder.searchApiKey(deepSeekProperties.getSearchApiKey());
		return builder.build();
	}

	/**
	 * Embedding 客户端
	 * @param embeddingProperties Embedding 属性
	 * @return {@link OpenAiClient }
	 */
	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnProperty(prefix = "embedding", name = "api-key")
	public EmbeddingClient embeddingClient(EmbeddingProperties embeddingProperties) {

		EmbeddingClient.Builder builder = EmbeddingClient.builder().baseUrl(embeddingProperties.getBaseUrl())
				.model(embeddingProperties.getModel()).openAiApiKey(embeddingProperties.getApiKey())
				.logRequests(embeddingProperties.isLogRequests()).logResponses(embeddingProperties.isLogResponses());

		if (Objects.nonNull(embeddingProperties.getProxy())) {
			builder.proxy(embeddingProperties.getProxy());
		}

		if (Objects.nonNull(embeddingProperties.getConnectTimeout())) {
			builder.connectTimeout(Duration.ofSeconds(embeddingProperties.getConnectTimeout()));
		}

		if (Objects.nonNull(embeddingProperties.getReadTimeout())) {
			builder.readTimeout(Duration.ofSeconds(embeddingProperties.getReadTimeout()));
		}

		if (Objects.nonNull(embeddingProperties.getCallTimeout())) {
			builder.callTimeout(Duration.ofSeconds(embeddingProperties.getCallTimeout()));
		}

		builder.logLevel(embeddingProperties.getLogLevel());

		return builder.build();
	}

}

package io.github.pigmesh.ai.deepseek.core.embedding;

public enum EmbeddingModel {

	BGE_M3("bge-m3:latest");

	private final String value;

	EmbeddingModel(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return value;
	}

}

package io.github.pigmesh.ai.deepseek.core;

import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okio.Buffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
import static java.util.stream.StreamSupport.stream;

class RequestLoggingInterceptor implements Interceptor {

	private static final Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class);

	private static final Set<String> COMMON_SECRET_HEADERS = new HashSet<>(
			asList("authorization", "x-api-key", "x-auth-token"));

	private static final String BEARER = "Bearer";

	private LogLevel logLevel = LogLevel.DEBUG;

	public RequestLoggingInterceptor() {
	}

	public RequestLoggingInterceptor(LogLevel logLevel) {
		this.logLevel = logLevel;
	}

	@Override
	public Response intercept(Chain chain) throws IOException {
		Request request = chain.request();

		log(request);

		return chain.proceed(request);
	}

	private void log(Request request) {
		String message = "Request:\n- method: {}\n- url: {}\n- headers: {}\n- body: {}";

		try {
			switch (logLevel) {
			case INFO:
				logInfo(request, message);
				break;
			case WARN:
				logWarn(request, message);
				break;
			case ERROR:
				logError(request, message);
				break;
			default:
				logDebug(request, message);
			}
		}
		catch (Exception e) {
			log.warn("Failed to log request", e);
		}
	}

	private void logInfo(Request request, String message) {
		log.info(message, request.method(), request.url(), inOneLine(request.headers()), getBody(request));
	}

	private void logWarn(Request request, String message) {
		log.warn(message, request.method(), request.url(), inOneLine(request.headers()), getBody(request));
	}

	private void logError(Request request, String message) {
		log.error(message, request.method(), request.url(), inOneLine(request.headers()), getBody(request));
	}

	private void logDebug(Request request, String message) {
		log.debug(message, request.method(), request.url(), inOneLine(request.headers()), getBody(request));
	}

	static String inOneLine(Headers headers) {

		return stream(headers.spliterator(), false).map(header -> format(header.component1(), header.component2()))
				.collect(joining(", "));
	}

	static String format(String headerKey, String headerValue) {
		if (COMMON_SECRET_HEADERS.contains(headerKey.toLowerCase())) {
			headerValue = maskSecretKey(headerValue);
		}
		return String.format("[%s: %s]", headerKey, headerValue);
	}

	static String maskSecretKey(String key) {
		if (key == null || key.trim().isEmpty()) {
			return key;
		}

		try {
			if (key.startsWith(BEARER)) {
				return BEARER + " " + mask(key.substring(BEARER.length() + 1));
			}
			else {
				return mask(key);
			}
		}
		catch (Exception e) {
			return "Failed to mask the API key.";
		}
	}

	private static String mask(String key) {
		if (key.length() >= 7) {
			return key.substring(0, 5) + "..." + key.substring(key.length() - 2);
		}
		else {
			return "...";
		}
	}

	private static String getBody(Request request) {
		try {
			Buffer buffer = new Buffer();
			if (request.body() != null) {
				request.body().writeTo(buffer);
			}
			return buffer.readUtf8();
		}
		catch (Exception e) {
			log.warn("Exception happened while reading request body", e);
			return "[Exception happened while reading request body. Check logs for more details.]";
		}
	}

}

package io.github.pigmesh.ai.deepseek.example.ollama;

import io.github.pigmesh.ai.deepseek.config.DeepSeekProperties;
import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
import io.github.pigmesh.ai.deepseek.core.Json;
import io.github.pigmesh.ai.deepseek.core.SyncOrAsyncOrStreaming;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionChoice;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionRequest;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
import io.github.pigmesh.ai.deepseek.core.models.ModelsResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/ollama")
@RequiredArgsConstructor
public class OllamaLocalController {

	@Resource
	private DeepSeekClient deepSeekClient;

	@Resource
	private DeepSeekProperties deepSeekProperties;

	@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<ChatCompletionResponse> chat(String prompt) {
		return deepSeekClient.chatFluxCompletion(prompt);
	}

	// 同步
	@GetMapping(value = "/sync/chat")
	public ChatCompletionResponse syncChat(String prompt) {
		ChatCompletionRequest request = ChatCompletionRequest.builder()
				// 根据渠道模型名称动态修改这个参数
				.model(deepSeekProperties.getModel()).addUserMessage(prompt).build();

		return deepSeekClient.chatCompletion(request).execute();
	}

	// 异步
	@GetMapping(value = "/async/chat")
	public void asyncChat(String prompt) {
		ChatCompletionRequest request = ChatCompletionRequest.builder().model(deepSeekProperties.getModel())
				.addUserMessage(prompt).build();
		SyncOrAsyncOrStreaming<ChatCompletionResponse> asyncOrStreaming = deepSeekClient.chatCompletion(request);
		asyncOrStreaming.onResponse((responseContent) -> {
			log.info("处理返回数据：{}", Json.toJson(responseContent));
		}, exception -> {
			log.info("处理异常数据：{}", exception.getMessage());
		});
	}

	@GetMapping(value = "/models", produces = MediaType.APPLICATION_JSON_VALUE)
	public ModelsResponse models() {
		return deepSeekClient.models();
	}

	public final static HashMap<String, String> cache = new HashMap<>();

	@GetMapping(value = "/chat/advanced", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<ChatCompletionResponse> chatAdvanced(String prompt, String cacheCode) {
		log.info("cacheCode {}", cacheCode);

		ChatCompletionRequest request = ChatCompletionRequest.builder().model(deepSeekProperties.getModel())
				.addUserMessage(prompt).addAssistantMessage(elt.apply(cache.getOrDefault(cacheCode, "")))
				.addSystemMessage("你是一个专业的助手").maxCompletionTokens(5000).build();
		log.info("request {}", Json.toJson(request));
		// 只保留上一次回答内容
		cache.remove(cacheCode);
		return deepSeekClient.chatFluxCompletion(request).doOnNext(i -> {
			String content = choicesProcess.apply(i.choices());
			// 其他ELT流程
			cache.merge(cacheCode, content, String::concat);
		}).doOnError(e -> log.error("/chat/advanced error:{}", e.getMessage()));
	}

	Function<List<ChatCompletionChoice>, String> choicesProcess = list -> list.stream().map(e -> e.delta().content())
			.collect(Collectors.joining());

	Function<String, String> elt = s -> s.replaceAll("<think>[\\s\\S]*?</think>", "").replaceAll("\n", "");

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public abstract class JsonSchemaElement {

	@JsonProperty
	private final String type;

	protected JsonSchemaElement(String type) {
		this.type = type;
	}

	public String type() {
		return type;
	}

}

package io.github.pigmesh.ai.deepseek.core;

import io.github.pigmesh.ai.deepseek.core.search.SearchResponse;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;

public class Utils {

	static RuntimeException toException(retrofit2.Response<?> response) throws IOException {
		return new OpenAiHttpException(response.code(), response.errorBody().string());
	}

	static RuntimeException toException(okhttp3.Response response) throws IOException {
		return new OpenAiHttpException(response.code(), response.body().string());
	}

	static <T> T getOrDefault(T value, T defaultValue) {
		return value != null ? value : defaultValue;
	}

	public static String format(String user, SearchResponse.Value[] values) {
		if (Objects.isNull(values) || values.length == 0) {
			return null;
		}

		String results = Arrays
				.stream(values).map(
						organicResult -> "Title: " + organicResult.getName() + "\n" + "Source: "
								+ organicResult.getUrl() + "\n"
								+ (organicResult.getSummary() != null ? "Content:" + "\n" + organicResult.getSummary()
										: "Snippet:" + "\n" + organicResult.getSnippet()))
				.collect(Collectors.joining("\n\n"));
		return String.format("用户输入提问: %s\n\n 当前时间:%s \n\n 参考如下内容进行推理回答:%s", user,
				LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME), results);
	}

}

package io.github.pigmesh.ai.deepseek.core;

import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionRequest;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
import io.github.pigmesh.ai.deepseek.core.completion.CompletionRequest;
import io.github.pigmesh.ai.deepseek.core.completion.CompletionResponse;
import io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingRequest;
import io.github.pigmesh.ai.deepseek.core.embedding.EmbeddingResponse;
import io.github.pigmesh.ai.deepseek.core.models.ModelsResponse;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationRequest;
import io.github.pigmesh.ai.deepseek.core.moderation.ModerationResponse;
import retrofit2.Call;
import retrofit2.http.*;

import java.util.Map;

interface OpenAiApi {

	@POST("completions")
	@Headers("Content-Type: application/json")
	Call<CompletionResponse> completions(@Body CompletionRequest request, @Query("api-version") String apiVersion);

	@POST("completions")
	@Headers("Content-Type: application/json")
	Call<CompletionResponse> completions(@HeaderMap Map<String, String> headers, @Body CompletionRequest request,
			@Query("api-version") String apiVersion);

	@POST("chat/completions")
	@Headers("Content-Type: application/json")
	Call<ChatCompletionResponse> chatCompletions(@Body ChatCompletionRequest request,
			@Query("api-version") String apiVersion);

	@POST("chat/completions")
	@Headers("Content-Type: application/json")
	Call<ChatCompletionResponse> chatCompletions(@HeaderMap Map<String, String> headers,
			@Body ChatCompletionRequest request, @Query("api-version") String apiVersion);

	@POST("moderations")
	@Headers("Content-Type: application/json")
	Call<ModerationResponse> moderations(@Body ModerationRequest request, @Query("api-version") String apiVersion);

	@POST("moderations")
	@Headers("Content-Type: application/json")
	Call<ModerationResponse> moderations(@HeaderMap Map<String, String> headers, @Body ModerationRequest request,
			@Query("api-version") String apiVersion);

	@GET("models")
	@Headers("Content-Type: application/json")
	Call<ModelsResponse> models(@HeaderMap Map<String, String> headers, @Query("api-version") String apiVersion);

	@POST("embeddings")
	@Headers("Content-Type: application/json")
	Call<EmbeddingResponse> embeddings(@Body EmbeddingRequest request, @Query("api-version") String apiVersion);

	@POST("embeddings")
	@Headers("Content-Type: application/json")
	Call<EmbeddingResponse> embeddings(@HeaderMap Map<String, String> headers, @Body EmbeddingRequest request,
			@Query("api-version") String apiVersion);

}

package io.github.pigmesh.ai.deepseek.core;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.type.LogicalType;

import java.util.Map;

public class Json {

	public static final ObjectMapper OBJECT_MAPPER;

	static {
		OBJECT_MAPPER = (new ObjectMapper()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
				.enable(SerializationFeature.INDENT_OUTPUT);
		OBJECT_MAPPER.coercionConfigFor(LogicalType.Enum).setCoercion(CoercionInputShape.EmptyString,
				CoercionAction.AsNull);
	}

	public static String toJson(Object o) {
		try {
			return OBJECT_MAPPER.writeValueAsString(o);
		}
		catch (JsonProcessingException jpe) {
			throw new RuntimeException(jpe);
		}
	}

	public static <T> T fromJson(String json, Class<T> type) {
		try {
			return OBJECT_MAPPER.readValue(json, type);
		}
		catch (JsonProcessingException jpe) {
			throw new RuntimeException(jpe);
		}
	}

	public static <T> T fromJson(String json, Class<T> type, Map<String, String> dynamicFieldNames) {
		try {
			return OBJECT_MAPPER.readValue(json, type);
		}
		catch (JsonProcessingException jpe) {
			throw new RuntimeException(jpe);
		}
	}

}

package io.github.pigmesh.ai.deepseek.core;

import okhttp3.OkHttpClient;
import retrofit2.Call;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class RequestExecutor<Request, Response, ResponseContent> implements SyncOrAsyncOrStreaming<ResponseContent> {

	private final Call<Response> call;

	private final Function<Response, ResponseContent> responseContentExtractor;

	private final OkHttpClient okHttpClient;

	private final String endpointUrl;

	private final Supplier<Request> requestWithStreamSupplier;

	private final Class<Response> responseClass;

	private final Function<Response, ResponseContent> streamEventContentExtractor;

	private final boolean logStreamingResponses;

	RequestExecutor(Call<Response> call, Function<Response, ResponseContent> responseContentExtractor,

			OkHttpClient okHttpClient, String endpointUrl, Supplier<Request> requestWithStreamSupplier,
			Class<Response> responseClass, Function<Response, ResponseContent> streamEventContentExtractor,
			boolean logStreamingResponses) {
		this.call = call;
		this.responseContentExtractor = responseContentExtractor;

		this.okHttpClient = okHttpClient;
		this.endpointUrl = endpointUrl;
		this.requestWithStreamSupplier = requestWithStreamSupplier;
		this.responseClass = responseClass;
		this.streamEventContentExtractor = streamEventContentExtractor;
		this.logStreamingResponses = logStreamingResponses;
	}

	public RequestExecutor(Call<Response> call, Function<Response, ResponseContent> responseContentExtractor) {
		this.call = call;
		this.responseContentExtractor = responseContentExtractor;

		this.okHttpClient = null;
		this.endpointUrl = null;
		this.requestWithStreamSupplier = null;
		this.responseClass = null;
		this.streamEventContentExtractor = null;
		this.logStreamingResponses = false;
	}

	@Override
	public ResponseContent execute() {

		return new SyncRequestExecutor<>(call, responseContentExtractor).execute();
	}

	@Override
	public void onResponse(Consumer<ResponseContent> responseHandler, Consumer<Throwable> errorHandler) {

		new AsyncRequestExecutor<>(call, responseContentExtractor).onResponse(responseHandler, errorHandler);
	}

	@Override
	public StreamingResponseHandling onPartialResponse(Consumer<ResponseContent> partialResponseHandler) {

		return new StreamingRequestExecutor<>(okHttpClient, endpointUrl, requestWithStreamSupplier, responseClass,
				streamEventContentExtractor, logStreamingResponses).onPartialResponse(partialResponseHandler);
	}

}

package io.github.pigmesh.ai.deepseek.core;

import retrofit2.Call;

import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Function;

import static io.github.pigmesh.ai.deepseek.core.Utils.toException;

class AsyncRequestExecutor<Response, ResponseContent> {

	private final Call<Response> call;

	private final Function<Response, ResponseContent> responseContentExtractor;

	AsyncRequestExecutor(Call<Response> call, Function<Response, ResponseContent> responseContentExtractor) {
		this.call = call;
		this.responseContentExtractor = responseContentExtractor;
	}

	void onResponse(Consumer<ResponseContent> responseHandler, Consumer<Throwable> errorHandler) {
		call.enqueue(new retrofit2.Callback<Response>() {
			@Override
			public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
				if (response.isSuccessful()) {
					responseHandler.accept(responseContentExtractor.apply(response.body()));
				}
				else {
					try {
						errorHandler.accept(toException(response));
					}
					catch (IOException e) {
						throw new RuntimeException(e);
					}
				}
			}

			@Override
			public void onFailure(Call<Response> call, Throwable t) {
				errorHandler.accept(t);
			}
		});
	}

}

package io.github.pigmesh.ai.deepseek.core;

import retrofit2.Call;

import java.io.IOException;
import java.util.function.Function;

import static io.github.pigmesh.ai.deepseek.core.Utils.toException;

class SyncRequestExecutor<Response, ResponseContent> {

	private final Call<Response> call;

	private final Function<Response, ResponseContent> responseContentExtractor;

	SyncRequestExecutor(Call<Response> call, Function<Response, ResponseContent> responseContentExtractor) {
		this.call = call;
		this.responseContentExtractor = responseContentExtractor;
	}

	ResponseContent execute() {
		try {
			retrofit2.Response<Response> retrofitResponse = call.execute();
			if (retrofitResponse.isSuccessful()) {
				Response response = retrofitResponse.body();
				return responseContentExtractor.apply(response);
			}
			else {
				throw toException(retrofitResponse);
			}
		}
		catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

}

package io.github.pigmesh.ai.deepseek.core;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

import static io.github.pigmesh.ai.deepseek.core.RequestLoggingInterceptor.inOneLine;

class ResponseLoggingInterceptor implements Interceptor {

	private static final Logger log = LoggerFactory.getLogger(ResponseLoggingInterceptor.class);

	private LogLevel logLevel = LogLevel.DEBUG;

	public ResponseLoggingInterceptor() {
	}

	public ResponseLoggingInterceptor(LogLevel logLevel) {
		this.logLevel = logLevel;
	}

	@Override
	public Response intercept(Chain chain) throws IOException {
		Request request = chain.request();
		Response response = chain.proceed(request);

		log(response);

		return response;
	}

	void log(Response response) {
		String message = "Response:\n- status code: {}\n- headers: {}\n- body: {}";

		try {
			switch (logLevel) {
			case INFO:
				logInfo(response, message);
				break;
			case WARN:
				logWarn(response, message);
				break;
			case ERROR:
				logError(response, message);
				break;
			default:
				logDebug(response, message);
			}

		}
		catch (IOException e) {
			log.warn("Failed to log response", e);
		}
	}

	private void logError(Response response, String message) throws IOException {
		log.error(message, response.code(), inOneLine(response.headers()), getBody(response));
	}

	private void logWarn(Response response, String message) throws IOException {
		log.warn(message, response.code(), inOneLine(response.headers()), getBody(response));
	}

	private void logInfo(Response response, String message) throws IOException {
		log.info(message, response.code(), inOneLine(response.headers()), getBody(response));
	}

	private void logDebug(Response response, String message) throws IOException {
		log.debug(message, response.code(), inOneLine(response.headers()), getBody(response));
	}

	private String getBody(Response response) throws IOException {
		if (isEventStream(response)) {
			return "[skipping response body due to streaming]";
		}
		else {
			return response.peekBody(Long.MAX_VALUE).string();
		}
	}

	private boolean isEventStream(Response response) {
		String contentType = response.header("content-type");
		return contentType != null && contentType.contains("event-stream");
	}

}

package io.github.pigmesh.ai.deepseek.core;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

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

class GenericHeaderInjector implements Interceptor {

	private final Map<String, String> headers = new HashMap<>();

	GenericHeaderInjector(Map<String, String> headers) {
		Optional.ofNullable(headers).ifPresent(this.headers::putAll);
	}

	@Override
	public Response intercept(Chain chain) throws IOException {
		Builder builder = chain.request().newBuilder();

		// Add headers
		this.headers.forEach(builder::addHeader);

		return chain.proceed(builder.build());
	}

}

package io.github.pigmesh.ai.deepseek.core;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Base64;
import java.util.UUID;

class FilePersistor {

	static Path persistFromUri(URI uri, Path destinationFolder) {
		try {
			Path fileName = Paths.get(uri.getPath()).getFileName();
			Path destinationFilePath = destinationFolder.resolve(fileName);
			try (InputStream inputStream = uri.toURL().openStream()) {
				java.nio.file.Files.copy(inputStream, destinationFilePath, StandardCopyOption.REPLACE_EXISTING);
			}

			return destinationFilePath;
		}
		catch (IOException e) {
			throw new RuntimeException("Error persisting file from URI: " + uri, e);
		}
	}

	public static Path persistFromBase64String(String base64EncodedString, Path destinationFolder) throws IOException {
		byte[] decodedBytes = Base64.getDecoder().decode(base64EncodedString);
		Path destinationFile = destinationFolder.resolve(randomFileName());

		Files.write(destinationFile, decodedBytes, StandardOpenOption.CREATE);

		return destinationFile;
	}

	private static String randomFileName() {
		return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
	}

}

package io.github.pigmesh.ai.deepseek.core;

import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.file.Path;

class PersistorConverterFactory extends Converter.Factory {

	private final Path persistTo;

	PersistorConverterFactory(Path persistTo) {
		this.persistTo = persistTo;
	}

	@Override
	public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
		return new PersistorConverter<>(retrofit.nextResponseBodyConverter(this, type, annotations));
	}

	private class PersistorConverter<T> implements Converter<ResponseBody, T> {

		private final Converter<ResponseBody, T> delegate;

		PersistorConverter(Converter<ResponseBody, T> delegate) {
			this.delegate = delegate;
		}

		@Override
		public T convert(ResponseBody value) throws IOException {
			T response = delegate.convert(value);

			return response;
		}

	}

}

package io.github.pigmesh.ai.deepseek.config;

import io.github.pigmesh.ai.deepseek.core.DeepSeekConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author lengleng
 * @date 2025/2/6
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties(prefix = "embedding")
public class EmbeddingProperties extends DeepSeekConfig {

	/**
	 * 基本 URL
	 */
	private String baseUrl = "http://127.0.0.1:11434/v1";

}

package io.github.pigmesh.ai.deepseek.solon;

import io.github.pigmesh.ai.deepseek.core.DeepSeekConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.noear.solon.annotation.BindProps;
import org.noear.solon.annotation.Configuration;

/**
 * @author songyinyin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@BindProps(prefix = "embedding")
@Configuration
public class EmbeddingProperties extends DeepSeekConfig {

	/**
	 * 基本 URL
	 */
	private String baseUrl = "http://127.0.0.1:11434/v1";

}

package io.github.pigmesh.ai.deepseek.core.chat;

import lombok.Getter;

public enum ChatCompletionModel {

	DEEPSEEK_CHAT("deepseek-chat"), //
	// alias
	DEEPSEEK_REASONER("deepseek-reasoner");

	@Getter
	private final String value;

	ChatCompletionModel(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return value;
	}

}

package io.github.pigmesh.ai.deepseek.example.deepseek;

import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
import io.github.pigmesh.ai.deepseek.core.models.ModelsResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import javax.annotation.Resource;

@RestController
@RequestMapping
public class DeepSeekLocalController {

	@Resource
	private DeepSeekClient deepSeekClient;

	@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE + "; charset=UTF-8")
	public Flux<ChatCompletionResponse> chat(String prompt) {
		return deepSeekClient.chatFluxCompletion(prompt);
	}

	@GetMapping(value = "/models", produces = MediaType.APPLICATION_JSON_VALUE)
	public ModelsResponse models() {
		return deepSeekClient.models();
	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import io.github.pigmesh.ai.deepseek.core.Json;

import java.util.Map;

public class FunctionCallUtil {

	public static <T> T argument(String name, FunctionCall function) {
		Map<String, Object> arguments = argumentsAsMap(function.arguments()); // TODO
																				// cache?
		return (T) arguments.get(name);
	}

	public static Map<String, Object> argumentsAsMap(String arguments) {
		return Json.fromJson(arguments, Map.class);
	}

}

package io.github.pigmesh.ai.deepseek.core.completion;

public enum CompletionModel {

	GPT_3_5_TURBO_INSTRUCT("gpt-3.5-turbo-instruct");

	private final String value;

	CompletionModel(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return value;
	}

}

package io.github.pigmesh.ai.deepseek.core.search;

import lombok.Builder;
import lombok.Data;
import lombok.Setter;

/**
 * 搜索请求
 *
 * @author lengleng
 * @date 2025/2/9
 */
@Builder
@Data
public class SearchRequest {

	private final boolean enable;

	@Setter
	private String query;

	@Builder.Default
	private final FreshnessEnums freshness = FreshnessEnums.NO_LIMIT;

	@Builder.Default
	private final boolean summary = true;

	@Builder.Default
	private final int count = 10;

	@Builder.Default
	private final int page = 1;

}

package io.github.pigmesh.ai.deepseek.core.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
public class Model {

	@JsonProperty("id")
	private String id;

	@JsonProperty("object")
	private String modelObject;

	@JsonProperty("owned_by")
	private String ownedBy;

}

package io.github.pigmesh.ai.deepseek.core.moderation;

public enum ModerationModel {

	TEXT_MODERATION_STABLE("text-moderation-stable"), TEXT_MODERATION_LATEST("text-moderation-latest");

	private final String value;

	ModerationModel(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return value;
	}

}

package io.github.pigmesh.ai.deepseek.core;

import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionModel;
import lombok.Data;

import java.net.Proxy;

import static io.github.pigmesh.ai.deepseek.core.LogLevel.DEBUG;

/**
 * 公共配置
 *
 * @author songyinyin
 * @since 2025/2/16 15:15
 */
@Data
public class DeepSeekConfig {

	/**
	 * 基本 URL
	 */
	protected String baseUrl = "https://api.deepseek.com/v1";

	/**
	 * API 密钥
	 */
	protected String apiKey;

	/**
	 * 搜索 API 密钥
	 */
	protected String searchApiKey;

	/**
	 * 模型名称
	 */
	protected String model = ChatCompletionModel.DEEPSEEK_REASONER.getValue();

	/**
	 * 默认系统提示词
	 */
	protected boolean defaultSystemPrompt = true;

	/**
	 * 日志请求
	 */
	protected boolean logRequests;

	/**
	 * 日志响应
	 */
	protected boolean logResponses;

	/**
	 * 代理
	 */
	protected Proxy proxy;

	/**
	 * 连接超时 S
	 */
	protected Integer connectTimeout;

	/**
	 * 读取超时 S
	 */
	protected Integer readTimeout;

	/**
	 * 呼叫超时 S
	 */
	protected Integer callTimeout;

	/**
	 * 日志级别
	 */
	protected LogLevel logLevel = DEBUG;

}

package io.github.pigmesh.ai.deepseek.core;

public class OpenAiHttpException extends RuntimeException {

	private final int code;

	public OpenAiHttpException(int code, String message) {
		super(message);
		this.code = code;
	}

	public int code() {
		return code;
	}

}

package io.github.pigmesh.ai.deepseek.core.search;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
 * @author lengleng
 * @date 2025/2/9
 *
 * 查询响应
 */
@Data
public class SearchResponse {

	private Integer code;

	@JsonProperty("log_id")
	private String logId;

	private String msg;

	private ResponsData data;

	@Data
	public static class ResponsData {

		private String _type;

		private QueryContext queryContext;

		private WebPages webPages;

		private Images images;

	}

	@Data
	public static class QueryContext {

		private String originalQuery;

	}

	@Data
	public static class WebPages {

		private String webSearchUrl;

		private Integer totalEstimatedMatches;

		private Value[] value;

		private Boolean someResultsRemoved;

	}

	@Data
	public static class Value {

		private String id;

		private String name;

		private String url;

		private String displayUrl;

		private String snippet;

		private String summary;

		private String siteName;

		private String siteIcon;

		private String dateLastCrawled;

		private String cachedPageUrl;

		private String language;

		private Boolean isFamilyFriendly;

		private Boolean isNavigational;

	}

	@Data
	public static class Images {

		private String id;

		private String readLink;

		private String webSearchUrl;

		private Value[] value;

		private Boolean isFamilyFriendly;

	}

}

package io.github.pigmesh.ai.deepseek.solon;

import org.noear.solon.core.AppContext;
import org.noear.solon.core.Plugin;

/**
 * solon deepseek4j plugin
 *
 * @author songyinyin
 * @since 2025/2/16 13:03
 */
public class DeepSeekPlugin implements Plugin {

	@Override
	public void start(AppContext context) throws Throwable {
		// 扫描当前包
		context.beanScan(DeepSeekPlugin.class);
	}

}

package io.github.pigmesh.ai.deepseek.core.search;

/**
 * @author lengleng
 * @date 2025/2/9
 */
public enum FreshnessEnums {

	ONE_DAY("oneDay"), ONE_WEEK("oneWeek"), ONE_MONTH("oneMonth"), ONE_YEAR("oneYear"), NO_LIMIT("noLimit");

	private final String value;

	FreshnessEnums(String value) {
		this.value = value;
	}

}

package io.github.pigmesh.ai.deepseek.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DeepseekExampleApplication {

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

}

package io.github.pigmesh.ai.deepseek.core.search;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Headers;
import retrofit2.http.POST;

/**
 * @author lengleng
 * @date 2025/2/9
 */
public interface SearchApi {

	@POST("web-search")
	@Headers("Content-Type: application/json")
	Call<SearchResponse> webSearch(@Body SearchRequest request);

}

package io.github.pigmesh.ai.deepseek.core;

import java.util.Collections;

public class AuthorizationHeaderInjector extends GenericHeaderInjector {

	public AuthorizationHeaderInjector(String apiKey) {
		super(Collections.singletonMap("Authorization", "Bearer " + apiKey));
	}

}

package io.github.pigmesh.ai.deepseek.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE")
				.allowedHeaders("*");
	}

}

package io.github.pigmesh.ai.deepseek.core;

/**
 * Provides a mechanism to cancel the response after a request has been initiated.
 */
public class ResponseHandle {

	volatile boolean cancelled = false;

	/**
	 * Cancels the response. Currently, this only works for streaming. It has no effect on
	 * regular asynchronous responses.
	 */
	public void cancel() {
		cancelled = true;
	}

}

package io.github.pigmesh.ai.deepseek.config;

import io.github.pigmesh.ai.deepseek.core.DeepSeekConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author lengleng
 * @date 2025/2/6
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties(prefix = "deepseek")
public class DeepSeekProperties extends DeepSeekConfig {

}

package io.github.pigmesh.ai.deepseek.solon;

import io.github.pigmesh.ai.deepseek.core.DeepSeekConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.noear.solon.annotation.BindProps;
import org.noear.solon.annotation.Configuration;

/**
 * @author songyinyin
 */
@Data
@EqualsAndHashCode(callSuper = true)
@BindProps(prefix = "deepseek")
@Configuration
public class DeepSeekProperties extends DeepSeekConfig {

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum ContentType {

	@JsonProperty("text")
	TEXT, @JsonProperty("image_url")
	IMAGE_URL, @JsonProperty("input_audio")
	AUDIO

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum Role {

	@JsonProperty("system")
	SYSTEM, @JsonProperty("user")
	USER, @JsonProperty("assistant")
	ASSISTANT, @JsonProperty("tool")
	TOOL, @JsonProperty("function")
	@Deprecated
	FUNCTION

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum ResponseFormatType {

	@JsonProperty("text")
	TEXT, @JsonProperty("json_object")
	JSON_OBJECT, @JsonProperty("json_schema")
	JSON_SCHEMA

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum ImageDetail {

	@JsonProperty("low")
	LOW, @JsonProperty("high")
	HIGH, @JsonProperty("auto")
	AUTO

}

package io.github.pigmesh.ai.deepseek.core;

import java.util.Collections;

class ApiKeyHeaderInjector extends GenericHeaderInjector {

	ApiKeyHeaderInjector(String apiKey) {
		super(Collections.singletonMap("api-key", apiKey));
	}

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum ToolChoiceMode {

	@JsonProperty("none")
	NONE, @JsonProperty("auto")
	AUTO, @JsonProperty("required")
	REQUIRED

}

package io.github.pigmesh.ai.deepseek.core.chat;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum ToolType {

	@JsonProperty("function")
	FUNCTION

}

package io.github.pigmesh.ai.deepseek.core;

import java.util.function.Consumer;

public interface SyncOrAsyncOrStreaming<ResponseContent> extends SyncOrAsync<ResponseContent> {

	StreamingResponseHandling onPartialResponse(Consumer<ResponseContent> partialResponseHandler);

}

package io.github.pigmesh.ai.deepseek.core;

import java.util.function.Consumer;

public interface SyncOrAsync<ResponseContent> {

	ResponseContent execute();

	void onResponse(Consumer<ResponseContent> responseHandler, Consumer<Throwable> errorHandler);

}

package io.github.pigmesh.ai.deepseek.core;

import java.util.function.Consumer;

public interface StreamingCompletionHandling {

	ErrorHandling onError(Consumer<Throwable> errorHandler);

	ErrorHandling ignoreErrors();

}

package io.github.pigmesh.ai.deepseek.core;

import java.util.function.Consumer;

public interface AsyncResponseHandling {

	ErrorHandling onError(Consumer<Throwable> errorHandler);

	ErrorHandling ignoreErrors();

}

package io.github.pigmesh.ai.deepseek.core;

public enum LogLevel {

	INFO, WARN, ERROR, DEBUG

}

package io.github.pigmesh.ai.deepseek.core.chat;

public interface Message {

	Role role();

}

package io.github.pigmesh.ai.deepseek.core;

public interface StreamingResponseHandling extends AsyncResponseHandling {

	StreamingCompletionHandling onComplete(Runnable streamingCompletionCallback);

}

package io.github.pigmesh.ai.deepseek.core;

public interface ErrorHandling {

	ResponseHandle execute();

}

@JsonDeserialize(builder = EmbeddingRequest.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public final
class
EmbeddingRequest
{
@JsonProperty
	@Getter
	@Setter
	private String model;
@JsonProperty
	private final List<String> input;
@JsonProperty
	private final Integer dimensions;
@JsonProperty
	private final String user;
public String model (){}
public List<String> input (){}
public Integer dimensions (){}
public String user (){}
@Override
	public boolean equals (Object another){}
private boolean equalTo (EmbeddingRequest another){}
@Override
	public int hashCode (){}
@Override
	public String toString (){}
public static Builder builder (){}
}

This code segment functions to: Define a data structure for serialized API requests with configurable parameters, enforce data immutability after construction, and provide validation through builder pattern implementation while maintaining JSON compatibility with specific naming conventions.

```java
[TASK_BEGIN]
    public String model() {
        return this.model;
    }

    public List<String> input() {
        return unmodifiableList(this.input);
    }

    public Integer dimensions() {
        return this.dimensions;
    }

    public String user() {
        return this.user;
    }

    @Override
    public boolean equals(Object another) {
        if (this == another) return true;
        return another instanceof EmbeddingRequest
            && equalTo((EmbeddingRequest) another);
    }

    private boolean equalTo(EmbeddingRequest another) {
        return Objects.equals(model, another.model)
            && Objects.equals(input, another.input)
            && Objects.equals(dimensions, another.dimensions)
            && Objects.equals(user, another.user);
    }

    @Override
    public int hashCode() {
        int result = Objects.hashCode(model);
        result = 31 * result + Objects.hashCode(input);
        result = 31 * result + Objects.hashCode(dimensions);
        result = 31 * result + Objects.hashCode(user);
        return result;
    }

    @Override
    public String toString() {
        return "EmbeddingRequest{"
            + "model=" + model
            + ", input=" + input
            + ", dimensions=" + dimensions
            + ", user=" + user
            + "}";
    }

    public static Builder builder() {
        return new Builder();
    }

    @JsonPOJOBuilder(withPrefix = "")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class Builder {
        private String model = BGE_M3.toString();
        private List<String> input;
        private Integer dimensions;
        private String user;

        public Builder model(String model) {
            this.model = model;
            return this;
        }

        public Builder input(String... input) {
            return input(asList(input));
        }

        public Builder input(List<String> input) {
            this.input = input;
            return this;
        }

        public Builder dimensions(Integer dimensions) {
            this.dimensions = dimensions;
            return this;
        }

        public Builder user(String user) {
            this.user = user;
            return this;
        }

        public EmbeddingRequest build() {
            return new EmbeddingRequest(this);
        }
    }

    private EmbeddingRequest(Builder builder) {
        this.model = builder.model;
        this.input = builder.input != null ? builder.input : List.of();
        this.dimensions = builder.dimensions;
        this.user = builder.user;
    }
[TASK_END]
```