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
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@
/** legacy 라우팅용 Intent 기본값 */
public class IntentDefaultsResolver {

private final IntentDictionary intentDictionary;

public IntentDefaultsResolver(IntentDictionary intentDictionary) {
this.intentDictionary = Objects.requireNonNull(intentDictionary, "intentDictionary");
}

public IntentDefaults resolve(UnifiedGeneratePromptCommand command) {
Objects.requireNonNull(command, "command must not be null");
ActionIntent intent = command.intent();
if (intent == null) {
intent = ActionIntent.GENERATE;
}

var defaults = IntentDictionary.getResolutionDefaults(intent);
var defaults = intentDictionary.getResolutionDefaults(intent);
Optional<TaskDomain> domainAffinity = Optional.empty();

EngineProfile recommendedProfile = EngineProfile.QUALITY_PIPELINE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@
import org.example.sharedprompts.domain.prompt.domain.value.objective.PromptObjective;
import org.springframework.stereotype.Component;

import java.util.Objects;

/** Intent·프로필·jsonSchema 기반 objective/outputNeeds/taskDomain 해석 (단일 책임) */
@Component
public class IntentBasedAxisDefaultsResolver {

private final IntentDictionary intentDictionary;

public IntentBasedAxisDefaultsResolver(IntentDictionary intentDictionary) {
this.intentDictionary = Objects.requireNonNull(intentDictionary, "intentDictionary");
}

public PromptObjective resolveObjective(ActionIntent intent, boolean hasJsonSchema, boolean extractionRequestMode) {
var defaults = IntentDictionary.getResolutionDefaults(intent);
var defaults = intentDictionary.getResolutionDefaults(intent);
org.example.sharedprompts.domain.prompt.common.enums.semantic.PromptObjective apiObjective = defaults.defaultObjective();
if (hasJsonSchema && (extractionRequestMode || intent == ActionIntent.EXTRACT)) {
apiObjective = org.example.sharedprompts.domain.prompt.common.enums.semantic.PromptObjective.EXTRACTION;
Expand All @@ -23,7 +31,7 @@ public PromptObjective resolveObjective(ActionIntent intent, boolean hasJsonSche
}

public OutputNeeds resolveOutputNeeds(ActionIntent intent, boolean hasJsonSchema) {
var defaults = IntentDictionary.getResolutionDefaults(intent);
var defaults = intentDictionary.getResolutionDefaults(intent);
if (hasJsonSchema) {
return OutputNeeds.JSON_SCHEMA_REQUIRED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,41 @@

import org.example.sharedprompts.domain.prompt.common.enums.action.ActionTypeInterface;

import java.util.Objects;
import java.util.Optional;

/** ActionType → ActionGroup(capability) 매핑 레지스트리. */
public interface CanonicalActionRegistry {

/** ActionType을 ActionGroup으로 변환. */
/**
* Permissive lookup: empty when {@code actionType} is null or no group can be resolved
* (e.g. unknown stable key with no definition). Prefer {@link #requireActionGroup(ActionTypeInterface)}
* for curated policy/seed lists where absence is a configuration error.
*/
Optional<ActionGroup> toCanonical(ActionTypeInterface actionType);

/** stable key로 ActionGroup 조회. */
/**
* Permissive: stable key may be missing from {@link org.example.sharedprompts.domain.prompt.common.enums.action.registry.ActionTypeRegistry}.
*/
Optional<ActionGroup> findByKey(String stableKey);

/**
* Strict: curated taxonomy / seed / policy paths must resolve a capability. Failure is an
* {@link IllegalArgumentException} with the action stable key in the message.
*/
default ActionGroup requireActionGroup(ActionTypeInterface actionType) {
Objects.requireNonNull(actionType, "actionType");
return toCanonical(actionType)
.orElseThrow(
() ->
new IllegalArgumentException(
"Missing ActionGroup for action type stableKey="
+ actionType.key()
+ " (class="
+ actionType.getClass().getName()
+ "). Definition-first resolution failed; ensure getActionGroup() is non-null or the stable key is registered."));
}

/** 두 ActionType이 동일 capability(ActionGroup)인지 비교. */
default boolean sameCanonicalCapability(ActionTypeInterface a, ActionTypeInterface b) {
if (a == null || b == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public DefaultCanonicalActionRegistry(ActionTypeRegistry actionTypeRegistry) {

/**
* Definition-first: canonical meaning comes from the ActionType's own {@link ActionTypeInterface#getActionGroup()}.
* No registry re-query — registry is used only for {@link #findByKey(String)} when resolving by stable key.
* Registry is used only for {@link #findByKey(String)} when the definition has no group. Empty result is allowed
* for open-ended lookups; curated seeds/policies should use {@link CanonicalActionRegistry#requireActionGroup(ActionTypeInterface)}.
*/
@Override
public Optional<ActionGroup> toCanonical(ActionTypeInterface actionType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,28 @@ default SemanticFitLevel getIntentFitLevel(ActionIntent intent) {
* so profiles without action group data remain valid.
*/
default List<ActionGroup> getCompatibleActionGroupsForIntent(ActionIntent intent) {
if (intent == null) {
return Collections.emptyList();
}
List<ActionTypeInterface> actions = getCompatibleActionsForIntent(intent);
if (actions == null || actions.isEmpty()) {
return Collections.emptyList();
}
return actions.stream()
.map(ActionTypeInterface::getActionGroup)
.filter(g -> g != null)
.map(
a -> {
ActionGroup g = a.getActionGroup();
if (g == null) {
throw new IllegalStateException(
"Compatible action without ActionGroup for category "
+ getCategory().name()
+ ", intent "
+ intent.name()
+ ", actionStableKey="
+ a.key());
}
return g;
})
.distinct()
.collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import org.example.sharedprompts.domain.prompt.common.enums.semantic.PromptCategory;
import org.example.sharedprompts.domain.prompt.domain.semantic.seed.CategorySemanticProfileSeedDefinitions;

import java.util.Objects;
import java.util.Optional;
import java.util.Set;

/**
Expand All @@ -14,26 +12,16 @@
public interface CategorySemanticProfileSeedSource {

/**
* Categories the registry will assemble. Each must succeed {@link #requireSeed(PromptCategory)}.
* Default: full {@link PromptCategory#canonicalSemanticProfileCategories()}. Override together with {@link #getSeed}
* when intentionally wiring a subset (otherwise default iteration will call {@code requireSeed} for missing categories).
* Categories the registry will assemble. Each must return a non-null seed from {@link #requireSeed(PromptCategory)}.
* Default: {@link CategorySemanticProfileSeedDefinitions#canonicalProfileCategories()}. Override to assemble a subset only.
*/
default Set<PromptCategory> profileCategoriesForRegistry() {
return CategorySemanticProfileSeedDefinitions.canonicalProfileCategories();
}

/**
* Resolves seed for {@code category} (after {@link PromptCategory#canonical()}). Missing registration is a configuration error.
* Required seed for {@code category} (after {@link PromptCategory#canonical()}).
* Absence is a configuration error, not an optional outcome.
*/
default CategorySemanticProfileSeed requireSeed(PromptCategory category) {
Objects.requireNonNull(category, "category");
PromptCategory canonical = category.canonical();
return getSeed(canonical).orElseThrow(() -> new IllegalStateException(
"Missing CategorySemanticProfileSeed for category: " + canonical.name()));
}

/**
* Optional seed when the category may legitimately lack one (e.g. not in this source). Prefer {@link #requireSeed(PromptCategory)} for profile assembly.
*/
Optional<CategorySemanticProfileSeed> getSeed(PromptCategory category);
CategorySemanticProfileSeed requireSeed(PromptCategory category);
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,14 @@ public record ConfirmedSemanticAxes(
recommendationHints = recommendationHints != null ? List.copyOf(recommendationHints) : List.of();
}

/** Resolve action group for prompt assembly / resolution. Use registry when building prompts. */
public Optional<ActionGroup> actionGroup(CanonicalActionRegistry registry) {
/**
* Permissive lookup: optional axes may omit {@link #actionType}; then no capability group exists for rendering.
* Uses {@link CanonicalActionRegistry#toCanonical(ActionTypeInterface)} — empty when type is absent or lookup fails.
*
* <p>{@link org.example.sharedprompts.domain.prompt.domain.service.spec.PromptSpecFactory} applies a stricter rule:
* when {@link #actionType} is present, the registry must resolve a group (no direct enum read there).
*/
public Optional<ActionGroup> findActionGroup(CanonicalActionRegistry registry) {
Objects.requireNonNull(registry, "registry");
return actionType.flatMap(registry::toCanonical);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,18 @@
import java.util.Objects;

/**
* Aggregates intent definitions/defaults provided by smaller providers.
* Aggregates intent definitions/defaults from injected providers (merge, duplicate detection).
* <p>
* IntentDictionary keeps responsibility for fail-fast completeness validation and indexing.
* Inject custom provider lists via the constructor for tests or alternate wiring; production
* static aggregation uses {@link #definitionsEntries()} / {@link #resolutionDefaultEntries()}.
* Does not own provider registration; use {@link IntentDefinitionProviderAssembly} for the
* production catalog or pass custom lists for tests/alternate wiring.
* {@link IntentDictionary} performs completeness validation and indexing on top of collected entries.
*/
public final class IntentDefinitionDataSource {

record IntentDefinitionEntry(ActionIntent intent, IntentDefinition definition) {}

record IntentResolutionDefaultEntry(ActionIntent intent, IntentResolutionDefaults defaults) {}

private static final List<IntentDefinitionEntriesProvider> DEFAULT_DEFINITIONS_PROVIDERS = List.of(
new IntentCreationIntentDefinitionsProvider(),
new IntentModificationIntentDefinitionsProvider(),
new IntentAnalysisIntentDefinitionsProvider(),
new IntentExplanationIntentDefinitionsProvider(),
new IntentPlanningIntentDefinitionsProvider(),
new IntentDecisionIntentDefinitionsProvider(),
new IntentResearchIntentDefinitionsProvider(),
new IntentExtractionIntentDefinitionsProvider()
);

private static final List<IntentResolutionDefaultsEntriesProvider> DEFAULT_RESOLUTION_DEFAULTS_PROVIDERS = List.of(
new IntentCreationIntentResolutionDefaultsProvider(),
new IntentModificationIntentResolutionDefaultsProvider(),
new IntentAnalysisIntentResolutionDefaultsProvider(),
new IntentExplanationIntentResolutionDefaultsProvider(),
new IntentPlanningIntentResolutionDefaultsProvider(),
new IntentDecisionIntentResolutionDefaultsProvider(),
new IntentResearchIntentResolutionDefaultsProvider(),
new IntentExtractionIntentResolutionDefaultsProvider()
);

private static final IntentDefinitionDataSource DEFAULT = new IntentDefinitionDataSource(
DEFAULT_DEFINITIONS_PROVIDERS, DEFAULT_RESOLUTION_DEFAULTS_PROVIDERS);

private final List<IntentDefinitionEntriesProvider> definitionsProviders;
private final List<IntentResolutionDefaultsEntriesProvider> resolutionDefaultsProviders;

Expand All @@ -55,14 +30,6 @@ public IntentDefinitionDataSource(
this.resolutionDefaultsProviders = List.copyOf(Objects.requireNonNull(resolutionDefaultsProviders));
}

static List<IntentDefinitionEntry> definitionsEntries() {
return DEFAULT.collectDefinitionsEntries();
}

static List<IntentResolutionDefaultEntry> resolutionDefaultEntries() {
return DEFAULT.collectResolutionDefaultEntries();
}

public List<IntentDefinitionEntry> collectDefinitionsEntries() {
Map<ActionIntent, IntentDefinition> defs = new HashMap<>();
for (IntentDefinitionEntriesProvider provider : definitionsProviders) {
Expand Down Expand Up @@ -97,4 +64,3 @@ public List<IntentResolutionDefaultEntry> collectResolutionDefaultEntries() {
.toList();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
/**
* Provides intent definition entries.
* <p>
* Data ownership lives in providers; {@link IntentDefinitionDataSource} only aggregates.
* Data ownership lives in providers; {@link IntentDefinitionDataSource} merges entries.
* Production registration: {@link IntentDefinitionProviderAssembly}.
*/
public interface IntentDefinitionEntriesProvider {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.example.sharedprompts.domain.prompt.domain.semantic;

import java.util.List;

/**
* Production composition of intent definition / resolution-defaults providers.
* <p>
* {@link IntentDefinitionDataSource} only merges and validates; this class is the single
* explicit registration point for which providers participate in the shipped catalog.
* Order is stable: creation → modification → analysis → explanation → planning → decision →
* research → extraction (matches prior {@code IntentDefinitionDataSource} static lists).
*/
public final class IntentDefinitionProviderAssembly {

private IntentDefinitionProviderAssembly() {}

public static List<IntentDefinitionEntriesProvider> productionDefinitionsProviders() {
return List.of(
new IntentCreationIntentDefinitionsProvider(),
new IntentModificationIntentDefinitionsProvider(),
new IntentAnalysisIntentDefinitionsProvider(),
new IntentExplanationIntentDefinitionsProvider(),
new IntentPlanningIntentDefinitionsProvider(),
new IntentDecisionIntentDefinitionsProvider(),
new IntentResearchIntentDefinitionsProvider(),
new IntentExtractionIntentDefinitionsProvider());
}

public static List<IntentResolutionDefaultsEntriesProvider> productionResolutionDefaultsProviders() {
return List.of(
new IntentCreationIntentResolutionDefaultsProvider(),
new IntentModificationIntentResolutionDefaultsProvider(),
new IntentAnalysisIntentResolutionDefaultsProvider(),
new IntentExplanationIntentResolutionDefaultsProvider(),
new IntentPlanningIntentResolutionDefaultsProvider(),
new IntentDecisionIntentResolutionDefaultsProvider(),
new IntentResearchIntentResolutionDefaultsProvider(),
new IntentExtractionIntentResolutionDefaultsProvider());
}
Comment on lines +17 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: ActionIntent enum 값 개수와 provider 개수 비교

echo "=== ActionIntent enum values ==="
rg -n "^\s+[A-Z_]+[,;]?\s*$" --type=java -A 0 | grep -E "ActionIntent\.java" | head -20

echo ""
echo "=== Counting providers in assembly ==="
rg -c "new Intent.*Provider\(\)" sharedPrompts/src/main/java/org/example/sharedprompts/domain/prompt/domain/semantic/IntentDefinitionProviderAssembly.java

Repository: rabitis99/prompts-backend

Length of output: 2627


🏁 Script executed:

#!/bin/bash
# Get the complete ActionIntent enum file
cat sharedPrompts/src/main/java/org/example/sharedprompts/domain/prompt/common/enums/semantic/ActionIntent.java

Repository: rabitis99/prompts-backend

Length of output: 1893


🏁 Script executed:

#!/bin/bash
# Count the exact number of enum values in ActionIntent
rg "^\s+[A-Z_]+," sharedPrompts/src/main/java/org/example/sharedprompts/domain/prompt/common/enums/semantic/ActionIntent.java | wc -l

Repository: rabitis99/prompts-backend

Length of output: 71


ActionIntent enum의 모든 카테고리가 provider로 커버되는지 확인하세요.

ActionIntent enum은 28개의 값을 8개 카테고리로 구성하고 있으며, 8개의 definition providers와 8개의 resolution defaults providers가 각 카테고리를 담당합니다. 현재는 카테고리별 매핑이므로 커버되어 있으나, enum 값이 새로운 카테고리에 추가되거나 카테고리 구조가 변경될 경우 provider 누락 가능성이 있습니다. IntentDictionary 생성 시 fail-fast로 감지되지만, 코드 유지보수 시 주의가 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sharedPrompts/src/main/java/org/example/sharedprompts/domain/prompt/domain/semantic/IntentDefinitionProviderAssembly.java`
around lines 17 - 39, Ensure all ActionIntent categories are covered by the
providers by adding a fail-fast validation in IntentDefinitionProviderAssembly:
when building productionDefinitionsProviders() and
productionResolutionDefaultsProviders() (or when constructing IntentDictionary),
enumerate ActionIntent.values(), derive their category, and verify that every
category has a corresponding IntentDefinitionEntriesProvider and
IntentResolutionDefaultsEntriesProvider; if any category is missing, throw an
IllegalStateException with a clear message listing missing categories so
misconfiguration is detected immediately (use the existing
productionDefinitionsProviders, productionResolutionDefaultsProviders and
IntentDictionary construction paths to locate where to run this validation).


/**
* Full production {@link IntentDefinitionDataSource} (same merge result as before this refactor).
*/
public static IntentDefinitionDataSource productionIntentDefinitionDataSource() {
return new IntentDefinitionDataSource(
productionDefinitionsProviders(), productionResolutionDefaultsProviders());
}

/**
* Production {@link IntentDictionary}: indexes and validates completeness over
* {@link #productionIntentDefinitionDataSource()}.
*/
public static IntentDictionary productionIntentDictionary() {
return new IntentDictionary(productionIntentDefinitionDataSource());
}
}
Loading