Conversation
jimador
added this pull request to stack #2067
September 27, 2026 16:33
jimador
marked this pull request as ready for review
September 27, 2026 16:33
jimador
force-pushed
the
feature/decision-ai-integration
branch
3 times, most recently
from
September 27, 2026 20:48
d57b0da to
e78256e
Compare
This was referenced Sep 27, 2026
Contributor
|
@jimador - PR is very large, could you please try to split into smaller PRs. Thank you |
jimador
force-pushed
the
feature/decision-ai-integration
branch
from
September 27, 2026 22:10
1ebc6eb to
82498ee
Compare
jimador
removed this pull request from stack #2067
September 27, 2026 22:10
jimador
changed the base branch from
feature/decision-spec-contracts
to
feature/decision-execution
September 27, 2026 22:10
jimador
added this pull request to stack #2081
September 27, 2026 22:11
jimador
force-pushed
the
feature/decision-ai-integration
branch
from
September 28, 2026 01:37
82498ee to
79bd0ca
Compare
jimador
force-pushed
the
feature/decision-ai-integration
branch
from
September 28, 2026 02:15
79bd0ca to
af76c08
Compare
jimador
force-pushed
the
feature/decision-ai-integration
branch
from
September 28, 2026 03:42
af76c08 to
6b4f17c
Compare
jimador
force-pushed
the
feature/decision-ai-integration
branch
3 times, most recently
from
September 28, 2026 05:19
6b6793b to
37a2906
Compare
jimador
force-pushed
the
feature/decision-ai-integration
branch
from
September 28, 2026 16:07
37a2906 to
5e1a478
Compare
… role or instance Ai.decisions() and Ai.classifications() return a ServiceSelector over a DecisionServiceRegistry. Each selector resolves the family default, a registration name, a role or a given instance, and throws ServiceSelectionException with the reason and the fix when it cannot. Inside a workflow a selected service runs its calls under the selecting operation's observation. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
The Spring context builds the decision service registry from the ClassificationService beans, the embabel.models.decision and embabel.models.classification defaults and roles, and the default candidates. Entries under the TypeSafe services property register named Jev services. An application bean with an entry's name replaces that entry. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…ion services Adds service selection, the registry, configuration, plain Java assembly and the worked examples to the decision execution page, and named services to the TypeSafe page. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…mples Responses no longer carry a spec id, so the examples stop storing one. - The support triage and grounding evidence records drop the id field. - The operation-bound native test checks the response with requireMatches. - The reference page no longer says the stored evidence holds an id. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
An application that only classifies registers a classification service under a role and routes tickets with a ClassificationSpec. - ClassificationOnlyExampleTest selects the service by role, default, name and instance, switches over all four outcomes, and maps a selection to an enum through CategoryMapping.fromEnum. - ClassificationOnlyKotlinExampleTest builds the spec with the DSL. - The example YAML adds a prompted kind: classification entry bound to the ticket-routing classification role. The Spring example checks that it resolves as a classification-only service and shows the spec as a bean. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
- The decision execution page shows how to select a service through classifications(), classify with a ClassificationSpec, map to an enum, and configure a prompted classification-only entry. - The classification page links to it. - The sample registry summary lists CHOICE for a prompted service. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
|
❌ The last analysis has failed. |
…ctory Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
jimador
force-pushed
the
feature/decision-ai-integration
branch
from
September 28, 2026 16:48
5e1a478 to
2f563b5
Compare
jimador
removed this pull request from stack #2081
September 28, 2026 16:48
jimador
added this pull request to stack #2087
September 28, 2026 16:49
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



This PR selects, binds and registers decision services. Workflow code selects a service through
Ai.decisions()orAi.classifications()by family default, registration name, role or a supplied instance, and asks it a whole spec. Inside a workflowusing(service)wraps a service inObservedDecisionService. Spring configuration registers several Jev and prompted services with family defaults and roles. Plain Java builds the same registry by hand. Running a spec against one service, the four service families and their telemetry come from #2080.Selecting a service and asking
ai.decisions()returns aServiceSelector<DecisionService>. Each terminal returns the service itself, soaskfollows directly. In the support-triage exampleURGENTandDEPARTMENTare named questions andTRIAGEisDecisionSpec.of(URGENT, DEPARTMENT):defaultService()is the fourth terminal and returns the family default.classifications()selects from every registered service, because a decision service also classifies.DEPARTMENTSis aClassificationSpec, shown under Classification only:Inside a workflow operation each terminal returns a service bound to that operation. Its provider calls run under the operation's observation, including calls made from another thread, and the ask span is a child of the action span. A name, role or default that the registry cannot resolve throws
ServiceSelectionExceptionwith areasonand a message that lists the family's services, roles and default and says how to fix the selection.The operation binding forwards
capabilities()and every hook, adds no kind, and implementsDelegatingDecisionService. A decorator stacked on the binding therefore routes the way the provider does.Reading typed answers
Named questions give typed results. Each result keeps answered, inconclusive and failed apart, and a choice also keeps no-match. A Spring
Convertermaps a response to an application route:A request that failed as a whole carries its reason, and every answer is then a failure with that reason:
Evidence is present only when the provider reports it:
The application stores the response JSON and each answer's provenance:
The stored JSON holds no input and reads back to an equal response.
Grounding evidence
The grounding example asks whether a source excerpt supports a claim through the role
dice-grounding. An application policy reads the answer kind and its own source rule. It recordspTruewith the provenance and a policy version, and it writes the grounding link only forSUPPORTED:The revision example asks one choice per scoped candidate and one evidence-strength rating in a single spec, and rechecks candidate versions before it applies a proposal. The application applies each proposal itself.
Kotlin
The DSL builds the spec, and the application handles every outcome:
Execution is blocking and needs no coroutine scope.
Classification only
An application that only sorts text into categories needs
Ai.classifications()and aClassificationSpec. Select the service by role, as the family default, by registration name, or pass an instance:Describe the categories once:
Classify with the spec and switch over the four outcomes:
CategoryMapping.fromEnumbuilds the spec from an enum, andmapturns a selection into the constant:In a Spring application the spec can be a bean:
A prompted service that only classifies sets
kind: classificationon its entry and binds to a classification role. The relevant entries fromdecision-services-example.yml:That service is not a decision service, so a decision default or role cannot name it, and
decisions().named("ticket-classifier")throwsServiceSelectionExceptionwithWRONG_CAPABILITY. A decision service answers aClassificationSpectoo, since the spec is a decision spec with one choice question.ClassificationOnlyExampleTestandClassificationOnlyKotlinExampleTestcover the selectors, the four outcomes and the enum mapping, with the Kotlin spec built byclassificationSpec { }.Spring configuration
Two Jev services, a prompted decision service, a prompted classification-only service and roles in both families:
The entry key under
servicesis the bean name and the registration name. A service's ownnameis its model name. Named Jev services share the credential, base URL and response limit underembabel.agent.platform.models.typesafe, and the defaulttypeSafeDecisionServicebean stays registered next to them.With no explicit default, the TypeSafe auto-configuration offers
typeSafeDecisionServiceas the default candidate, so Jev is the default of both families when TypeSafe is configured. Prompted services contribute no candidate. A default resolves to the explicit default, then the single eligible candidate, then the single eligible service. Zero or several of each throwNO_DEFAULTorAMBIGUOUS_DEFAULTat lookup.Application beans take precedence over configuration:
DecisionServiceRegistrybeantypeSafeDecisionServiceservicesstill register. With none configured, the TypeSafe auto-configuration does not run and needs no credential.Startup fails when a default or role names a missing service, when a decision default or role names a classification-only service, when a Jev entry has a blank model or an unknown key, when a Jev and a prompted entry share a key, when a configured key names an application bean that is not a decision or classification service, or when a service's
capabilities()throws. Each error names the property or the registration.Lazy and non-singleton service beans are not registered, and one INFO line names each. One instance under several bean names registers once, under its
@Primaryname.The registry is an immutable snapshot taken when the
decisionServiceRegistrybean is created. A service created later, such as a per-user service, goes throughusing(service).Plain Java
Plain Java builds Jev, stub and no-op services and the registry:
An application with a Spring context can register services from the
LlmDecisionServiceFactorybean in a registry it builds.Diagnosing
build()logs one INFO summary onDecisionServiceRegistrythat maps each registration name to its service name, provider, type and capabilities, with the family defaults, roles and candidates. For the YAML above, with two capability lists shortened:An unresolved selection throws
ServiceSelectionException, described above. Execution, provider and assembler log lines are described in #2080.embabel.agent.platform.decisions.capture-content: trueturns onDecisionContentCapturewhile the context runs. Spring then logs one WARN at startup onDecisionServiceRegistryConfiguration. Captured lines hold the input and provider output.Selection and binding
flowchart TD Ai["context.ai().decisions() / classifications()"] --> Sel[operation-bound selector] Sel -->|"named / byRole / defaultService"| Reg[DecisionServiceRegistry] Sel -->|"using(service)"| Wrap["ObservedDecisionService, unless already observed"] Reg -->|unresolved| SSE[ServiceSelectionException] Reg -->|resolved| Bind[operation binding] Wrap --> Bind Bind --> Ask["ask: preflight and execution (execution PR)"]Configuration
embabel.models.decision.defaultembabel.models.decision.roles.<role>embabel.models.classification.defaultembabel.models.classification.roles.<role>embabel.agent.platform.models.typesafe.services.<name>.model<name>for the given modelembabel.agent.platform.decisions.llm.services.<name>.llm<name>, with the entry keys from #2074embabel.agent.platform.decisions.capture-contentfalseRoles and defaults are scoped to one family. A role bound under
embabel.models.decision.rolesis unknown toclassifications().byRole(...).Compatibility
Every member added to
AiandPlatformServiceshas a default body, and no abstract member is added to an existing interface.ClassificationService,ModelProviderand the existing LLM and embedding selection are unchanged.Experimental status
Every type added here carries
@ApiStatus.Experimental. The owner is James Dunnam (@jimador). Promotion to stable needs resolution by role, name, default and instance used by a consumer application, the four families working through one registry, a compatibility review ofAiandPlatformServices, privacy checks on logs, tags and exception messages, a runnable consumer proof, and a recorded run against the hosted Jev service. Promotion is revisited at the next release review after the consumer proof.Docs: the
decision-executionreference page gains sections on selecting services, classification only, the registry, configuration, plain Java and the examples, with samples from the tagged tests. The TypeSafe page covers named services.Stacked on #2080.