Resolve a ProviderCredential everywhere a provider is called, not a string
LLMCredentialResolver.resolve now returns ProviderCredential instead of a bare String, so the endpoint travels with the value from the vault all the way to the provider that needs it. Every one of the eight call sites had to change to compile - there was no way to touch only one - so all of them now pass the resolved credential straight through instead of unwrapping it first. That turned out to be the right amount of change, not more than necessary. Where an endpoint-aware provider is not actually reachable yet (the interaction simulator and the bias judge choose their descriptor after the execution already exists, and never had their authorization requirement computed up front to begin with - a separate, pre-existing gap this does not close), a missing credential fails exactly as it always did: LLMCredentialResolver still throws "Missing saved credential" when the authorizations map has no entry for the provider's key, whether the caller then unwraps .value() or keeps the whole ProviderCredential makes no difference to that failure. The only place behaviour actually changes is the success case, and only for a provider that reads the endpoint at all - every existing provider still only reads .value() through the interface's own default unwrapping, so Gemini, InternalOllama and every test stub keep behaving exactly as before. LLMCredentialResolverTest is new: this resolver was previously exercised only indirectly, through a full execution. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b32e5e7b12
commit
247de4485b
|
|
@ -30,6 +30,7 @@ import it.cnr.isti.workflow.manager.flows.model.bias.BlockBiasAnnotation;
|
|||
import it.cnr.isti.workflow.manager.flows.validation.ValidationErrorCode;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ public class BiasImpactJudge {
|
|||
public BiasJudgeSummary evaluate(BiasImpactReport comparison, LLMDescriptor descriptor, ExecutionObject baseline,
|
||||
ExecutionObject biased) {
|
||||
LLMProvider provider = resolveProvider(descriptor.provider());
|
||||
String authorization = resolveAuthorization(provider, baseline);
|
||||
ProviderCredential authorization = resolveAuthorization(provider, baseline);
|
||||
String interventions = describeInterventions(biased, comparison.annotationIds());
|
||||
|
||||
List<BiasJudgeTarget> targets = BiasJudgeTarget.collect(comparison);
|
||||
|
|
@ -117,7 +118,7 @@ public class BiasImpactJudge {
|
|||
errors, verdicts);
|
||||
}
|
||||
|
||||
private BiasJudgeVerdict judgeTarget(LLMProvider provider, LLMDescriptor descriptor, String authorization,
|
||||
private BiasJudgeVerdict judgeTarget(LLMProvider provider, LLMDescriptor descriptor, ProviderCredential authorization,
|
||||
String interventions, BiasJudgeTarget target, List<String> errors) {
|
||||
String prompt = pairPrompt(interventions, target);
|
||||
try {
|
||||
|
|
@ -150,7 +151,7 @@ public class BiasImpactJudge {
|
|||
* this, where the answer is the real one - and the extractor below pulls the object out of
|
||||
* whatever prose it arrives wrapped in.
|
||||
*/
|
||||
private String askForJson(LLMProvider provider, LLMDescriptor descriptor, String authorization, String prompt) {
|
||||
private String askForJson(LLMProvider provider, LLMDescriptor descriptor, ProviderCredential authorization, String prompt) {
|
||||
String json = null;
|
||||
try {
|
||||
json = provider.generateJson(descriptor.model(), prompt, authorization, descriptor.parameters());
|
||||
|
|
@ -183,7 +184,7 @@ public class BiasImpactJudge {
|
|||
* per-pair verdicts in code, so that re-reading a report cannot show a different headline than
|
||||
* the pairs it is made of.
|
||||
*/
|
||||
private String narrate(LLMProvider provider, LLMDescriptor descriptor, String authorization, String interventions,
|
||||
private String narrate(LLMProvider provider, LLMDescriptor descriptor, ProviderCredential authorization, String interventions,
|
||||
BiasImpactReport comparison, Map<String, BiasJudgeVerdict> verdicts, BiasJudgeImpactLevel impact,
|
||||
BiasJudgeAttribution attribution, List<String> errors) {
|
||||
String prompt = """
|
||||
|
|
@ -504,7 +505,7 @@ public class BiasImpactJudge {
|
|||
* run is reached the same way as the models that produced it, and the internal provider - the
|
||||
* one a local install has - needs no credential at all.
|
||||
*/
|
||||
private String resolveAuthorization(LLMProvider provider, ExecutionObject baseline) {
|
||||
private ProviderCredential resolveAuthorization(LLMProvider provider, ExecutionObject baseline) {
|
||||
try {
|
||||
return credentialResolver.resolve(provider, baseline.getProvidedAuthorizations(),
|
||||
baseline.getContext().getResolvedExecutionVariables());
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import it.cnr.isti.workflow.manager.llms.ChatMessage;
|
|||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptorInputBinding;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
|
||||
@Component
|
||||
|
|
@ -65,7 +66,7 @@ public class ChatInteractionExecutor implements BlockExecutor<ChatInteractionBlo
|
|||
|
||||
LLMDescriptor chatDescriptor = LLMDescriptorInputBinding.resolve(configuration.getLlmDescriptor(), inputs, executionVariables);
|
||||
LLMProvider chatProvider = SimulatedChatSupport.resolveProvider(llmProviders, chatDescriptor.provider());
|
||||
String chatAuthorization = SimulatedChatSupport.resolveAuthorization(credentialResolver, chatProvider, authorizations,
|
||||
ProviderCredential chatCredential = SimulatedChatSupport.resolveAuthorization(credentialResolver, chatProvider, authorizations,
|
||||
executionVariables);
|
||||
if (eventLogger != null) {
|
||||
eventLogger.info(ExecutionEventType.LLM_REQUEST,
|
||||
|
|
@ -74,7 +75,7 @@ public class ChatInteractionExecutor implements BlockExecutor<ChatInteractionBlo
|
|||
}
|
||||
|
||||
LLMProvider simulatorProvider = SimulatedChatSupport.resolveProvider(llmProviders, simulatorDescriptor.provider());
|
||||
String simulatorAuthorization = SimulatedChatSupport.resolveAuthorization(credentialResolver, simulatorProvider,
|
||||
ProviderCredential simulatorCredential = SimulatedChatSupport.resolveAuthorization(credentialResolver, simulatorProvider,
|
||||
authorizations, executionVariables);
|
||||
|
||||
String resolvedGoal = ExecutionTemplateResolver.resolve(configuration.getGoalDescription(), inputs, executionVariables);
|
||||
|
|
@ -82,7 +83,7 @@ public class ChatInteractionExecutor implements BlockExecutor<ChatInteractionBlo
|
|||
|
||||
for (int turn = 1; turn <= MAX_SIMULATED_INTERACTIONS; turn++) {
|
||||
String simulatedMessage = SimulatedChatSupport.generateSimulatorMessage(simulatorProvider, simulatorDescriptor,
|
||||
simulatorAuthorization, resolvedGoal, inputs, history, turn, MAX_SIMULATED_INTERACTIONS, "ChatInteraction");
|
||||
simulatorCredential, resolvedGoal, inputs, history, turn, MAX_SIMULATED_INTERACTIONS, "ChatInteraction");
|
||||
history.add(SimulatedChatSupport.formatConversationLine(ChatMessage.Role.USER.name(), simulatedMessage));
|
||||
|
||||
List<ChatMessage> messages = history.stream().map(this::parseHistoryLine).collect(Collectors.toList());
|
||||
|
|
@ -91,12 +92,12 @@ public class ChatInteractionExecutor implements BlockExecutor<ChatInteractionBlo
|
|||
messages.addFirst(new ChatMessage(ChatMessage.Role.SYSTEM, experimentalDirective));
|
||||
}
|
||||
String assistantResponse = chatProvider.chat(chatDescriptor.model(), List.copyOf(messages),
|
||||
chatAuthorization, chatDescriptor.parameters());
|
||||
chatCredential, chatDescriptor.parameters());
|
||||
history.add(SimulatedChatSupport.formatConversationLine(ChatMessage.Role.ASSISTANT.name(), assistantResponse));
|
||||
}
|
||||
|
||||
String finalResponse = SimulatedChatSupport.generateSimulatorFinalResponse(simulatorProvider, simulatorDescriptor,
|
||||
simulatorAuthorization, resolvedGoal, inputs, history, "ChatInteraction");
|
||||
simulatorCredential, resolvedGoal, inputs, history, "ChatInteraction");
|
||||
return buildResult(configuration, finalResponse, List.copyOf(history));
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +125,7 @@ public class ChatInteractionExecutor implements BlockExecutor<ChatInteractionBlo
|
|||
"Calling chat model " + llmDescriptor.model(),
|
||||
Map.of("provider", llmDescriptor.provider(), "model", llmDescriptor.model()));
|
||||
}
|
||||
String authorization = SimulatedChatSupport.resolveAuthorization(credentialResolver, llmProvider, authorizations,
|
||||
ProviderCredential credential = SimulatedChatSupport.resolveAuthorization(credentialResolver, llmProvider, authorizations,
|
||||
executionVariables);
|
||||
|
||||
if (interaction.containsKey(ChatInteractionBlockFactory.INTERACTION_FIELD)) {
|
||||
|
|
@ -142,7 +143,7 @@ public class ChatInteractionExecutor implements BlockExecutor<ChatInteractionBlo
|
|||
.collect(Collectors.toList());
|
||||
messages.add(new ChatMessage(ChatMessage.Role.USER, resolvedMessage));
|
||||
|
||||
String assistantResponse = llmProvider.chat(llmDescriptor.model(), List.copyOf(messages), authorization,
|
||||
String assistantResponse = llmProvider.chat(llmDescriptor.model(), List.copyOf(messages), credential,
|
||||
llmDescriptor.parameters());
|
||||
|
||||
List<String> updatedHistory = new ArrayList<>(history);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import it.cnr.isti.workflow.manager.executions.bias.runtime.BiasRuntimeSupport;
|
|||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptorInputBinding;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
|
||||
@Component
|
||||
|
|
@ -84,12 +85,12 @@ public class ConditionalExecutor implements BlockExecutor<ConditionalBlockType>
|
|||
ExecutionEventLogger eventLogger) {
|
||||
LLMDescriptor llmDescriptor = LLMDescriptorInputBinding.resolve(config.getLlmDescriptor(), inputValues, executionVariables);
|
||||
LLMProvider llmProvider = SimulatedChatSupport.resolveProvider(llmProviders, llmDescriptor.provider());
|
||||
String authorization = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
ProviderCredential credential = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
|
||||
String prompt = buildLlmPrompt(config, inputValues, executionVariables);
|
||||
ModelParameterReporting.reportUnsupported(llmProvider, llmDescriptor.parameters(), llmDescriptor.model(),
|
||||
eventLogger);
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, authorization,
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, credential,
|
||||
llmDescriptor.parameters());
|
||||
try {
|
||||
return parseBooleanResponse(response);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import it.cnr.isti.workflow.manager.executions.steps.Input;
|
|||
import it.cnr.isti.workflow.manager.executions.bias.runtime.BiasRuntimeSupport;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
|
||||
@Component
|
||||
|
|
@ -64,7 +65,7 @@ public class HumanDecisionExecutor implements BlockExecutor<HumanDecisionBlockTy
|
|||
if (llmProvider == null) {
|
||||
throw new IllegalArgumentException("Provider not found: " + simulatorDescriptor.provider());
|
||||
}
|
||||
String authorization = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
ProviderCredential credential = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
|
||||
String context = inputs.stream()
|
||||
.map(input -> "%s= %s".formatted(input.getDescriptor().getName(), input.getValue()))
|
||||
|
|
@ -87,7 +88,7 @@ public class HumanDecisionExecutor implements BlockExecutor<HumanDecisionBlockTy
|
|||
|
||||
ModelParameterReporting.reportUnsupported(llmProvider, simulatorDescriptor.parameters(),
|
||||
simulatorDescriptor.model(), eventLogger);
|
||||
String response = llmProvider.generate(simulatorDescriptor.model(), prompt, authorization,
|
||||
String response = llmProvider.generate(simulatorDescriptor.model(), prompt, credential,
|
||||
simulatorDescriptor.parameters());
|
||||
if (!StringUtils.hasText(response)) {
|
||||
throw new IllegalArgumentException("Simulated HumanDecision produced an empty response");
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import it.cnr.isti.workflow.manager.executions.steps.Input;
|
|||
import it.cnr.isti.workflow.manager.executions.bias.runtime.BiasRuntimeSupport;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
|
||||
@Component
|
||||
|
|
@ -68,11 +69,11 @@ public class HumanInteractionExecutor implements BlockExecutor<HumanInteractionB
|
|||
if (llmProvider == null) {
|
||||
throw new IllegalArgumentException("Provider not found: " + llmDescriptor.provider());
|
||||
}
|
||||
String authorization = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
ProviderCredential credential = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
|
||||
ModelParameterReporting.reportUnsupported(llmProvider, llmDescriptor.parameters(), llmDescriptor.model(),
|
||||
eventLogger);
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, authorization,
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, credential,
|
||||
llmDescriptor.parameters());
|
||||
|
||||
return Map.of("output", response);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import it.cnr.isti.workflow.manager.executions.bias.runtime.BiasRuntimeSupport;
|
|||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptorInputBinding;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
import it.cnr.isti.workflow.manager.skills.SkillPromptService;
|
||||
|
||||
|
|
@ -77,11 +78,11 @@ public class LLMExecutor implements BlockExecutor<LLMBlockType> {
|
|||
if (llmProvider == null) {
|
||||
throw new IllegalArgumentException("Provider not found: " + llmDescriptor.provider());
|
||||
}
|
||||
String authorization = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
ProviderCredential credential = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
|
||||
ModelParameterReporting.reportUnsupported(llmProvider, llmDescriptor.parameters(), llmDescriptor.model(),
|
||||
eventLogger);
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, authorization,
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, credential,
|
||||
llmDescriptor.parameters());
|
||||
|
||||
if (eventLogger != null) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import it.cnr.isti.workflow.manager.executions.bias.runtime.BiasRuntimeSupport;
|
|||
import it.cnr.isti.workflow.manager.llms.ConfigurableInputBinding;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
import it.cnr.isti.workflow.manager.mcp.MCPAgentService;
|
||||
import it.cnr.isti.workflow.manager.mcp.MCPSharedSessionRegistry;
|
||||
|
|
@ -74,7 +75,7 @@ public class MCPAgentChatExecutor implements BlockExecutor<MCPAgentChatBlockType
|
|||
throw new IllegalArgumentException("Missing simulation descriptor for MCPAgentChat execution");
|
||||
}
|
||||
LLMProvider simulatorProvider = SimulatedChatSupport.resolveProvider(llmProviders, simulatorDescriptor.provider());
|
||||
String simulatorAuthorization = SimulatedChatSupport.resolveAuthorization(credentialResolver, simulatorProvider,
|
||||
ProviderCredential simulatorCredential = SimulatedChatSupport.resolveAuthorization(credentialResolver, simulatorProvider,
|
||||
authorizations, executionVariables);
|
||||
String resolvedGoal = ExecutionTemplateResolver.resolve(configuration.getGoalDescription(), inputs, executionVariables);
|
||||
resolvedGoal = BiasRuntimeSupport.decoratePrompt(resolvedGoal, executionVariables);
|
||||
|
|
@ -92,7 +93,7 @@ public class MCPAgentChatExecutor implements BlockExecutor<MCPAgentChatBlockType
|
|||
try {
|
||||
for (int turn = 1; turn <= MAX_SIMULATED_INTERACTIONS; turn++) {
|
||||
String simulatedMessage = SimulatedChatSupport.generateSimulatorMessage(simulatorProvider, simulatorDescriptor,
|
||||
simulatorAuthorization, resolvedGoal, inputs, history, turn, MAX_SIMULATED_INTERACTIONS, "MCPAgentChat");
|
||||
simulatorCredential, resolvedGoal, inputs, history, turn, MAX_SIMULATED_INTERACTIONS, "MCPAgentChat");
|
||||
history.add(SimulatedChatSupport.formatConversationLine("USER", simulatedMessage));
|
||||
|
||||
String assistantResponse = mcpAgentService.querySession(sessionId, simulatedMessage);
|
||||
|
|
@ -100,7 +101,7 @@ public class MCPAgentChatExecutor implements BlockExecutor<MCPAgentChatBlockType
|
|||
}
|
||||
|
||||
String finalResponse = SimulatedChatSupport.generateSimulatorFinalResponse(simulatorProvider, simulatorDescriptor,
|
||||
simulatorAuthorization, resolvedGoal, inputs, history, "MCPAgentChat");
|
||||
simulatorCredential, resolvedGoal, inputs, history, "MCPAgentChat");
|
||||
return buildResult(configuration, finalResponse, List.copyOf(history), null);
|
||||
} finally {
|
||||
if (!managedSharedSession) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import it.cnr.isti.workflow.manager.executions.ExecutionTemplateResolver;
|
|||
import it.cnr.isti.workflow.manager.executions.steps.Input;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
|
||||
final class SimulatedChatSupport {
|
||||
|
|
@ -32,7 +33,7 @@ final class SimulatedChatSupport {
|
|||
.orElseThrow(() -> new IllegalArgumentException("Provider not found: " + providerName));
|
||||
}
|
||||
|
||||
static String resolveAuthorization(LLMCredentialResolver credentialResolver, LLMProvider provider,
|
||||
static ProviderCredential resolveAuthorization(LLMCredentialResolver credentialResolver, LLMProvider provider,
|
||||
Map<String, Object> authorizations, Map<String, Object> executionVariables) {
|
||||
return credentialResolver.resolve(provider, authorizations, executionVariables);
|
||||
}
|
||||
|
|
@ -72,7 +73,7 @@ final class SimulatedChatSupport {
|
|||
}
|
||||
|
||||
static String generateSimulatorMessage(LLMProvider simulatorProvider, LLMDescriptor simulatorDescriptor,
|
||||
String simulatorAuthorization, String goalDescription, List<Input> inputs, List<String> history, int turn,
|
||||
ProviderCredential simulatorCredential, String goalDescription, List<Input> inputs, List<String> history, int turn,
|
||||
int maxTurns, String blockLabel) {
|
||||
String prompt = """
|
||||
###SIMULATED_CHAT_MESSAGE###
|
||||
|
|
@ -89,7 +90,7 @@ final class SimulatedChatSupport {
|
|||
|
||||
Return only the next user message for the conversation.
|
||||
""".formatted(goalDescription, turn, maxTurns, formatInputs(inputs), formatHistory(history));
|
||||
String response = simulatorProvider.generate(simulatorDescriptor.model(), prompt, simulatorAuthorization,
|
||||
String response = simulatorProvider.generate(simulatorDescriptor.model(), prompt, simulatorCredential,
|
||||
simulatorDescriptor.parameters());
|
||||
if (!StringUtils.hasText(response)) {
|
||||
throw new IllegalArgumentException("Simulated " + blockLabel + " produced an empty message");
|
||||
|
|
@ -98,7 +99,7 @@ final class SimulatedChatSupport {
|
|||
}
|
||||
|
||||
static String generateSimulatorFinalResponse(LLMProvider simulatorProvider, LLMDescriptor simulatorDescriptor,
|
||||
String simulatorAuthorization, String goalDescription, List<Input> inputs, List<String> history, String blockLabel) {
|
||||
ProviderCredential simulatorCredential, String goalDescription, List<Input> inputs, List<String> history, String blockLabel) {
|
||||
String prompt = """
|
||||
###SIMULATED_CHAT_FINAL###
|
||||
Goal:
|
||||
|
|
@ -112,7 +113,7 @@ final class SimulatedChatSupport {
|
|||
|
||||
Return only the final response value that the simulated user would submit.
|
||||
""".formatted(goalDescription, formatInputs(inputs), formatHistory(history));
|
||||
String response = simulatorProvider.generate(simulatorDescriptor.model(), prompt, simulatorAuthorization,
|
||||
String response = simulatorProvider.generate(simulatorDescriptor.model(), prompt, simulatorCredential,
|
||||
simulatorDescriptor.parameters());
|
||||
if (!StringUtils.hasText(response)) {
|
||||
throw new IllegalArgumentException("Simulated " + blockLabel + " produced an empty final response");
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import it.cnr.isti.workflow.manager.ios.IOType;
|
|||
import it.cnr.isti.workflow.manager.llms.LLMDescriptor;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMDescriptorInputBinding;
|
||||
import it.cnr.isti.workflow.manager.llms.LLMCredentialResolver;
|
||||
import it.cnr.isti.workflow.manager.llms.ProviderCredential;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
|
||||
@Component
|
||||
|
|
@ -112,12 +113,12 @@ public class SwitchExecutor implements BlockExecutor<SwitchBlockType> {
|
|||
ExecutionEventLogger eventLogger) {
|
||||
LLMDescriptor llmDescriptor = LLMDescriptorInputBinding.resolve(config.getLlmDescriptor(), inputValues, executionVariables);
|
||||
LLMProvider llmProvider = SimulatedChatSupport.resolveProvider(llmProviders, llmDescriptor.provider());
|
||||
String authorization = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
ProviderCredential credential = credentialResolver.resolve(llmProvider, authorizations, executionVariables);
|
||||
|
||||
String prompt = buildLlmPrompt(config, inputValues, executionVariables, allowedOutputs);
|
||||
ModelParameterReporting.reportUnsupported(llmProvider, llmDescriptor.parameters(), llmDescriptor.model(),
|
||||
eventLogger);
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, authorization,
|
||||
String response = llmProvider.generate(llmDescriptor.model(), prompt, credential,
|
||||
llmDescriptor.parameters());
|
||||
return parseSelectedOutput(response, allowedOutputs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,13 @@ public class LLMCredentialResolver {
|
|||
this.userSecretService = userSecretService;
|
||||
}
|
||||
|
||||
public String resolve(LLMProvider provider, Map<String, Object> authorizations,
|
||||
/**
|
||||
* Returns a {@link ProviderCredential}, carrying the endpoint alongside the secret value for a
|
||||
* provider whose {@code requiresEndpoint()} is true. Not yet called from every executor: see
|
||||
* {@code docs/llm-providers-openai-remote-ollama-plan-2026-09-17.md} step 11 for which call
|
||||
* sites still resolve through the plain secret value only, and why.
|
||||
*/
|
||||
public ProviderCredential resolve(LLMProvider provider, Map<String, Object> authorizations,
|
||||
Map<String, Object> executionVariables) {
|
||||
if (!provider.requiresAuthorization()) {
|
||||
return null;
|
||||
|
|
@ -38,7 +44,7 @@ public class LLMCredentialResolver {
|
|||
throw new IllegalArgumentException(
|
||||
"A user-owned execution is required to resolve credential for provider: " + provider.getName());
|
||||
}
|
||||
return userSecretService.resolveValue(owner, credentialId.trim(), provider.getName());
|
||||
return userSecretService.resolveCredential(owner, credentialId.trim(), provider.getName());
|
||||
}
|
||||
|
||||
private String asText(Object value) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
|
||||
package it.cnr.isti.workflow.manager.llms;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import it.cnr.isti.workflow.manager.executions.ExecutionRuntimeContextSupport;
|
||||
import it.cnr.isti.workflow.manager.llms.providers.LLMProvider;
|
||||
import it.cnr.isti.workflow.manager.vault.UserSecretService;
|
||||
|
||||
/**
|
||||
* Not previously covered on its own - only ever exercised indirectly through a full execution.
|
||||
* This pins the branching {@code resolve} itself does (no authorization required, no credential
|
||||
* id, no owner) before it becomes even harder to isolate from what calls it.
|
||||
*/
|
||||
class LLMCredentialResolverTest {
|
||||
|
||||
private final UserSecretService userSecretService = mock(UserSecretService.class);
|
||||
private final LLMCredentialResolver resolver = new LLMCredentialResolver(userSecretService);
|
||||
|
||||
private LLMProvider provider(boolean requiresAuthorization) {
|
||||
return new LLMProvider() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRegisteredModels() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate(String model, String prompt) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresAuthorization() {
|
||||
return requiresAuthorization;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsNullWithoutTouchingTheVaultWhenTheProviderNeedsNoAuthorization() {
|
||||
ProviderCredential result = resolver.resolve(provider(false), Map.of(), Map.of());
|
||||
|
||||
assertNull(result);
|
||||
verify(userSecretService, never()).resolveCredential(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenTheAuthorizationsMapHasNoEntryForTheProvidersKey() {
|
||||
LLMProvider provider = provider(true);
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> resolver.resolve(provider, Map.of(), Map.of(ExecutionRuntimeContextSupport.EXECUTION_OWNER, "alice")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenTheCredentialIdIsBlank() {
|
||||
LLMProvider provider = provider(true);
|
||||
Map<String, Object> authorizations = Map.of(provider.authorizationKey(), " ");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> resolver.resolve(provider, authorizations,
|
||||
Map.of(ExecutionRuntimeContextSupport.EXECUTION_OWNER, "alice")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenThereIsNoOwnedExecutionContext() {
|
||||
LLMProvider provider = provider(true);
|
||||
Map<String, Object> authorizations = Map.of(provider.authorizationKey(), "secret-1");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> resolver.resolve(provider, authorizations, Map.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesThroughUserSecretServiceAndReturnsItsCredentialAsIs() {
|
||||
LLMProvider provider = provider(true);
|
||||
Map<String, Object> authorizations = Map.of(provider.authorizationKey(), " secret-1 ");
|
||||
Map<String, Object> executionVariables = Map.of(ExecutionRuntimeContextSupport.EXECUTION_OWNER, "alice");
|
||||
ProviderCredential expected = new ProviderCredential("api-key", "https://gateway.example.com");
|
||||
when(userSecretService.resolveCredential("alice", "secret-1", "Test")).thenReturn(expected);
|
||||
|
||||
ProviderCredential result = resolver.resolve(provider, authorizations, executionVariables);
|
||||
|
||||
assertEquals(expected, result);
|
||||
// The id is trimmed before being used - a pasted value with surrounding whitespace must
|
||||
// still resolve.
|
||||
verify(userSecretService).resolveCredential(eq("alice"), eq("secret-1"), eq("Test"));
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue