Class PaymentRole
java.lang.Object
com.tnsai.roles.Role
com.tnsai.integration.scop.examples.PaymentRole
@RoleSpec(name="PaymentAgent",
description="Charges accounts via a flaky external API; falls back to queueing on failure.",
version="0.1.0",
llm=@LLMSpec(provider=OPENAI,model="gpt-4o-mini",temperature=0.0f))
public class PaymentRole
extends com.tnsai.roles.Role
Reference example: declarative observability + recovery decorator
stack —
@Traced + @Metered + @Fallback
(issue #173, Phase 1).
Demonstrates the canonical resilience pattern from the issue:
a flaky external call (here simulated with a random failure) wrapped
in three decorators that the framework's ActionExecutor
applies automatically — no manual try/catch in the action body.
Decorator stack (outer → inner)
@Traced— opens a span, pushes a trace id into MDC, emitsspan.start/span.endlog lines with duration + outcome. Optional args/result inclusion.@Metered— records call count, success/failure counters, and a latency histogram inResilienceMetrics. Tests can snapshot the registry to assert metrics fired correctly.@Fallback— when the primary action throws, retries up tomaxRetriestimes, then invokes the matching fallback method on this role with the original args + the captured exception. Suppresses the original failure if the fallback returns normally.
The fallback method convention
Fallback methods live on the same role class as the action they cover, marked with@Fallback(forAction = "primaryActionName"). They take the
primary action's parameters in the same declaration order, with a
trailing Throwable (or Exception) parameter that
receives the captured failure. The method's return value becomes
the action's return value when the framework invokes the fallback.
Why this example is "flaky" by design
Thecharge method has a 50% random failure rate. This
isn't a bug — it's the cookbook's whole point: a real consumer
code calling charge once gets either the tool's result
or the fallback's recovery value, transparently. The integration
test (PaymentRoleResilienceIntegrationTest) seeds the JVM's
ThreadLocalRandom via repeated invocations to assert
both branches fire.- Since:
- 0.5.4 (issue #173, Phase 1 — resilience cookbook)
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionPrimary action — calls the (simulated) external payment API.chargeFallback(String requestId, int amount, Throwable failure) Recovery handler forcharge(java.lang.String, int).com.tnsai.models.role.RoleIdentityMethods inherited from class com.tnsai.roles.Role
create, getAction, getActionCount, getActions, getDomain, getGoal, getInputGuardrailConfig, getLLM, getMinimalPrompt, getName, getOutputGuardrailConfig, getResilienceConfig, getSpec, getSystemPrompt, hasAction, hasLLM, identity, setLLM, toString
-
Constructor Details
-
PaymentRole
public PaymentRole()
-
-
Method Details
-
getIdentity
public com.tnsai.models.role.RoleIdentity getIdentity()- Specified by:
getIdentityin classcom.tnsai.roles.Role
-
charge
@ActionSpec(type=LOCAL, description="Charge a customer\'s account.") @Traced(operationName="payment.charge", includeArgs=true) @Metered(name="payment.charge", histogram=true) public String charge(String requestId, int amount) Primary action — calls the (simulated) external payment API. Fails ~50% of the time to demonstrate the fallback path.Three decorators stack here:
@Traced(operationName = "payment.charge", includeArgs = true)— every call gets a trace id; args (requestId, amount) appear in the span.start log line.@Metered(name = "payment.charge", histogram = true)— every call ticks a counter and records latency. The integration test snapshots the registry to assert both success and failure paths increment the right counters.@Fallbackdeclared onchargeFallback(java.lang.String, int, java.lang.Throwable)below — handles failures via the queue path.
-
chargeFallback
@Fallback(forAction="charge", maxRetries=2) public String chargeFallback(String requestId, int amount, Throwable failure) Recovery handler forcharge(java.lang.String, int). Marked with@Fallback(forAction = "charge", maxRetries = 2)— the framework will:- Catch any exception thrown by
charge - Re-invoke
chargeup tomaxRetries = 2additional times - If all retries also fail, invoke this method with the original (requestId, amount) + the final exception
- Return this method's value as if
chargehad returned it normally
Method name doesn't matter for resolution — the
forActionvalue is the binding key. Multiple fallbacks with differentforActionvalues can coexist on the same role class. - Catch any exception thrown by
-