Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The Explain Error Plugin is a Jenkins plugin that provides AI-powered explanatio

- **GlobalConfigurationImpl**: Main plugin configuration class with `@Symbol("explainError")` for Configuration as Code support, handles migration from legacy enum-based configuration
- **BaseAIProvider**: Abstract base class for AI provider implementations with nested `Assistant` interface and `BaseProviderDescriptor` for extensibility
- **OpenAIProvider** / **GeminiProvider** / **BedrockProvider** / **OllamaProvider**: LangChain4j-based AI service implementations with provider-specific configurations
- **OpenAIProvider** / **GeminiProvider** / **AnthropicProvider** / **BedrockProvider** / **DeepSeekProvider** / **OllamaProvider**: LangChain4j-based AI service implementations with provider-specific configurations
- **ExplainErrorStep**: Pipeline step implementation for `explainError()` function
- **ConsoleExplainErrorAction**: Adds "Explain Error" button to console output for manual triggering
- **ConsoleExplainErrorActionFactory**: TransientActionFactory that dynamically injects ConsoleExplainErrorAction into all runs (new and existing)
Expand Down Expand Up @@ -39,6 +39,8 @@ src/main/java/io/jenkins/plugins/explain_error/
├── BaseAIProvider.java # Abstract AI service with Assistant interface
├── OpenAIProvider.java # OpenAI/LangChain4j implementation
├── GeminiProvider.java # Google Gemini/LangChain4j implementation
├── AnthropicProvider.java # Anthropic Claude/LangChain4j implementation
├── DeepSeekProvider.java # DeepSeek/LangChain4j implementation
├── BedrockProvider.java # AWS Bedrock/LangChain4j implementation
└── OllamaProvider.java # Ollama/LangChain4j implementation
```
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,30 @@ unclassified:
enableExplanation: true
```

**Anthropic Configuration:**
```yaml
unclassified:
explainError:
aiProvider:
anthropic:
apiKey: "${AI_API_KEY}"
model: "claude-3-5-sonnet-20241022"
# url: "" # Optional, leave empty for default
enableExplanation: true
```

**DeepSeek Configuration:**
```yaml
unclassified:
explainError:
aiProvider:
deepseek:
apiKey: "${AI_API_KEY}"
model: "deepseek-chat"
# url: "" # Optional, leave empty for default (https://api.deepseek.com)
enableExplanation: true
```

**Ollama Configuration:**
```yaml
unclassified:
Expand Down Expand Up @@ -170,6 +194,18 @@ This allows you to manage the plugin configuration alongside your other Jenkins
- **Endpoint**: Leave empty for official Google AI API, or specify custom URL for Gemini-compatible services
- **Best for**: Fast, efficient analysis with competitive quality

### Anthropic
- **Models**: `claude-3-5-sonnet-20241022`, `claude-3-5-haiku-20241022`, `claude-3-opus-20240229`, etc.
- **API Key**: Get from [Anthropic Console](https://console.anthropic.com/)
- **Endpoint**: Leave empty for official Anthropic API, or specify custom URL for Claude-compatible services
- **Best for**: Advanced reasoning, complex error analysis with Claude models

### DeepSeek
- **Models**: `deepseek-chat`, `deepseek-coder`, `deepseek-reasoner`
- **API Key**: Get from [DeepSeek Platform](https://platform.deepseek.com/)
- **Endpoint**: Leave empty for official DeepSeek API (`https://api.deepseek.com`), or specify custom URL
- **Best for**: Cost-effective, coding-focused analysis

### AWS Bedrock
- **Models**: `anthropic.claude-3-5-sonnet-20240620-v1:0`, `eu.anthropic.claude-3-5-sonnet-20240620-v1:0` (EU cross-region), `meta.llama3-8b-instruct-v1:0`, `us.amazon.nova-lite-v1:0`, etc.
- **API Key**: Not required — uses AWS credential chain (instance profiles, environment variables, etc.)
Expand Down
72 changes: 64 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,19 @@
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>*</artifactId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
Expand All @@ -173,11 +181,19 @@
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>*</artifactId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
Expand All @@ -189,11 +205,19 @@
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>*</artifactId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
Expand All @@ -205,11 +229,43 @@
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>*</artifactId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-anthropic</artifactId>
<version>${langchain4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.jenkins.plugins.explain_error.provider;

import dev.langchain4j.model.anthropic.AnthropicChatModel;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.service.AiServices;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
import hudson.model.AutoCompletionCandidates;
import hudson.model.TaskListener;
import hudson.util.FormValidation;
import hudson.util.Secret;
import io.jenkins.plugins.explain_error.ExplanationException;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.verb.GET;
import org.kohsuke.stapler.verb.POST;

public class AnthropicProvider extends BaseAIProvider {

private static final Logger LOGGER = Logger.getLogger(AnthropicProvider.class.getName());

protected Secret apiKey;

@DataBoundConstructor
public AnthropicProvider(String url, String model, Secret apiKey) {
super(url, model);
this.apiKey = apiKey;
}

public Secret getApiKey() {
return apiKey;
}

@Override
public Assistant createAssistant() {
ChatModel model = AnthropicChatModel.builder()
.baseUrl(Util.fixEmptyAndTrim(getUrl())) // Will use default if null
.apiKey(getApiKey().getPlainText())
.modelName(getModel())
.temperature(0.3)
.logRequests(LOGGER.isLoggable(Level.FINE))
.logResponses(LOGGER.isLoggable(Level.FINE))
.build();

return AiServices.create(Assistant.class, model);

Check warning on line 51 in src/main/java/io/jenkins/plugins/explain_error/provider/AnthropicProvider.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 42-51 are not covered by tests
}

@Override
public boolean isNotValid(@CheckForNull TaskListener listener) {
if (listener != null) {

Check warning on line 56 in src/main/java/io/jenkins/plugins/explain_error/provider/AnthropicProvider.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 56 is only partially covered, one branch is missing
if (Util.fixEmptyAndTrim(Secret.toString(getApiKey())) == null) {
listener.getLogger().println("No Api key configured for Anthropic.");
} else if (Util.fixEmptyAndTrim(getModel()) == null) {
listener.getLogger().println("No Model configured for Anthropic.");

Check warning on line 60 in src/main/java/io/jenkins/plugins/explain_error/provider/AnthropicProvider.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 57-60 are not covered by tests
}
}
return Util.fixEmptyAndTrim(Secret.toString(getApiKey())) == null ||
Util.fixEmptyAndTrim(getModel()) == null;

Check warning on line 64 in src/main/java/io/jenkins/plugins/explain_error/provider/AnthropicProvider.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 64 is only partially covered, one branch is missing
}

@Extension
@Symbol("anthropic")
public static class DescriptorImpl extends BaseProviderDescriptor {

private static final String[] MODELS = new String[]{
"claude-3-5-sonnet-20241022",
"claude-3-5-sonnet-20240620",
"claude-3-5-haiku-20241022",
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307"
};

@NonNull
@Override
public String getDisplayName() {
return "Anthropic";
}

public String getDefaultModel() {
return "claude-3-5-sonnet-20241022";
}

@GET
@SuppressWarnings("lgtm[jenkins/no-permission-check]")
public AutoCompletionCandidates doAutoCompleteModel(@QueryParameter String value) {
AutoCompletionCandidates c = new AutoCompletionCandidates();
for (String model : MODELS) {
if (model.toLowerCase().startsWith(value.toLowerCase())) {
c.add(model);
}
}
return c;
}

/**
* Method to test the AI API configuration.
* This is called when the "Test Configuration" button is clicked.
*/
@POST
public FormValidation doTestConfiguration(@QueryParameter("apiKey") Secret apiKey,
@QueryParameter("url") String url,
@QueryParameter("model") String model) throws ExplanationException {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);

AnthropicProvider provider = new AnthropicProvider(url, model, apiKey);
try {
provider.explainError("Send 'Configuration test successful' to me.", null);
return FormValidation.ok("Configuration test successful! API connection is working properly.");
} catch (ExplanationException e) {
return FormValidation.error("Configuration test failed: " + e.getMessage(), e);

Check warning on line 117 in src/main/java/io/jenkins/plugins/explain_error/provider/AnthropicProvider.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 87-117 are not covered by tests
}
}
}
}
Loading
Loading