feat(jev): add TypeSafe System One client, Spring Boot starter, and example middlewares - #3240
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Chunked selection and response-validation paths contain correctness gaps, including invalid __none__ handling and malformed score responses.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 5
Open (8)
Guard against null deserialization results · New Validate legend values against question criteria levels · New Treat blank primary API keys as unset · New Prevent none from colliding with user-defined candidates · New Do not select representatives when no-match probability is highest · New Translate the non-English source comment · New Preserve API key fallback in the quickstart · New 避免快速示例绕过 API 密钥回退 · New
What changed in this PR
Adds a TypeSafe System One Jev client, typed decision models, selection middlewares, tests, documentation, and distribution integration.
Changes:
- Added HTTP client with retries, timeouts, authentication, and response validation.
- Added skill suggestion and tool selection middlewares with chunking and fail-open behavior.
- Registered the extension in Maven aggregators, BOM, all-in-one distribution, and bilingual docs.
| File | Description |
|---|---|
docs/v2/zh/integration/ecosystem/jev.md |
Chinese Jev integration guide |
docs/v2/en/integration/ecosystem/jev.md |
English Jev integration guide |
docs/docs.json |
Documentation navigation and redirects |
agentscope-extensions/pom.xml |
Registers the extension module |
agentscope-extensions/agentscope-extensions-jev/pom.xml |
Jev module dependencies |
agentscope-distribution/agentscope-bom/pom.xml |
BOM registration |
agentscope-distribution/agentscope-all/pom.xml |
All-in-one distribution registration |
.../JevClient.java |
HTTP client and validation |
.../JevException.java |
Jev exception type |
.../JevRetryPolicy.java |
Retry configuration |
.../SystemOneRequest.java |
Request model and builder |
.../SystemOneResult.java |
Response model |
.../Usage.java |
Token usage model |
.../Question.java |
Question polymorphism |
.../Answer.java |
Answer polymorphism |
.../NoulQuestion.java |
Noul question model |
.../NoulAnswer.java |
Noul answer model |
.../ChoiceQuestion.java |
Choice question model |
.../ChoiceAnswer.java |
Choice answer model |
.../ScoreQuestion.java |
Score question model |
.../ScoreAnswer.java |
Score answer model |
.../JevSelectionSupport.java |
Shared selection helpers |
.../JevSkillSuggestionMiddleware.java |
Skill suggestion middleware |
.../JevToolSelectionMiddleware.java |
Tool selection middleware |
.../JevClientTest.java |
Client behavior tests |
.../JevDtoTest.java |
DTO serialization tests |
.../JevExceptionTest.java |
Exception tests |
.../JevRetryPolicyTest.java |
Retry policy tests |
.../JevSkillSuggestionMiddlewareTest.java |
Skill middleware tests |
.../JevToolSelectionMiddlewareTest.java |
Tool middleware tests |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
oss-maintainer
left a comment
There was a problem hiding this comment.
Summary
Adds a new agentscope-extensions-jev module: a typed client for TypeSafe's System One endpoint (JevClient + sealed Question/Answer DTOs + JevRetryPolicy), plus two middlewares that use it to rank skills (JevSkillSuggestionMiddleware) and shrink the tool schema sent to the primary model (JevToolSelectionMiddleware), with docs in both languages. ~3.3k lines, all additive, with 1.2k lines of tests — good coverage of validation, retry and fail-open paths, and the module is registered in the extensions aggregator, agentscope-all and the BOM.
Overall this looks well-built and I'd land it after a round of fixes on the runtime-behaviour items below. My main theme: the remote call is on the reasoning hot path — one (or two, for >254 candidates) round trips per reasoning step with a per-attempt timeout that is restarted by retryWhen, so worst-case latency added to a ReAct turn is well over a minute before fail-open engages, and a low-confidence answer degrades to zero optional tools rather than to "no filtering".
Findings
- [Critical]
middleware/JevToolSelectionMiddleware.java:160— empty selection (Set.of(), also from theconfidenceThresholdpath inJevSelectionSupport.selectedNames) removes every optional tool for that step, which is the opposite of thefailOpenintent. - [Warning]
middleware/JevToolSelectionMiddleware.java:106— latency budget: 2 sequentialsystemOne()calls per step, each3 x timeout + backoff(defaultJevRetryPolicygives ~19.5s, i.e. ~39s worst case). Suggest an outer total deadline plus per-turn memoization. - [Warning]
middleware/JevSelectionSupport.java:57— rawMsgobjects are serialized through the SNAKE_CASEJevClient.MAPPER, so message JSON on the wire is not the canonical AgentScope shape; it also ships full conversation content (incl. tool results) to a third party with no projection/redaction hook. - [Warning]
middleware/JevToolSelectionMiddleware.java:52—load_skill_through_path/reset_tools/generate_responseare literals owned bySkillToolFactoryandReActAgent.STRUCTURED_OUTPUT_TOOL_NAME; a rename upstream silently removes them from the schema. - [Warning]
middleware/JevSkillSuggestionMiddleware.java:223—repository.getAllSkills()runs per agent invocation; blocking I/O fan-out on the hot path, worth caching. - [Warning]
JevClient.java:310— nullusageaborts an otherwise-valid response; it is a reporting field, treat it as optional. - [Warning]
JevClient.java:403— absolute1e-6probability-sum tolerance over up to 255 options will reject legitimately rounded responses. Scale the tolerance with option count or normalize before comparing. - [Info]
JevClient.java:95—systemOneBlockingusesblock(), which throws onNonBlockingthreads; guard or document. - [Info]
middleware/JevSkillSuggestionMiddleware.java:231— swallowedRuntimeExceptionwith no logger makes a permanently broken repository indistinguishable from "no skills". - [Info]
docs/docs.json:331— the page is registered but missing from the Ecosystem list indocs/v2/{en,zh}/integration/overview.md.
Suggestions
For the hot-path items, a shape that would address both at once:
return selectTools(state, optionalTools)
.timeout(totalBudget) // hard ceiling across retries
.onErrorResume(e -> failOpen ? Mono.just(allNames) : Mono.error(e))
.map(names -> names.isEmpty() ? allNames : names) // "unsure" => no filteringand in retrySpec, Retry.backoff(...).handle(...)-style accounting or an outer .timeout() so timeout bounds the whole call rather than each attempt.
Notes
- I did not build or run the module locally (no local checkout sync in this run), so the review is static: the findings above come from reading the diff against
agentscope-core'sHttpTransport/MiddlewareBase/ skill contracts. - Nice touches: sealed DTOs with explicit Jackson subtype names, strict request/response validation,
failOpenas a builder flag,builder(Function)seams that let the middleware tests avoid a real transport, and matched en/zh docs.
Automated review by github-manager-bot
|
这个 PR 提供了有价值的 Jev 集成。我建议分别评估 Skill 推荐和 Tool 筛选,因为两者的错误代价不同:Skill 中间件保留完整目录、追加相关性建议;Tool 中间件则会真正移除发送给主模型的候选。
|
Review notesReviewed at head
VerificationThe focused reactor tests passed: mvn -pl agentscope-extensions/agentscope-extensions-judge/agentscope-extensions-jev,agentscope-extensions/agentscope-spring-boot-starters/agentscope-jev-spring-boot-starter -am testThis covered the eight-module reactor, including 49 Jev tests and 5 starter tests. |
c20f0cd to
33b5cb9
Compare
There was a problem hiding this comment.
The overall approach looks good: Jev is exposed as a standalone structured-decision client. The following runtime-contract issues should be addressed before merging.
Blocking issues
-
Null
RuntimeContextcauses model routing to fail. Both branches ofJevModelRouterMiddleware.onAgent()callctx.put(), but the defaultagent.call()/streamEvents()paths allow a null context. Support those entry points and propagate the routing decision to the same invocation’sonModelCall(). Creating a method-local context without propagating it is insufficient. -
Denied events do not reach the public event stream.
ReActAgent.acting()only handlesRequestStopEventand consumes the remaining events throughthen(). External publication happens insideactingStream(), which middleware-synthesized events bypass. Connect the synthesized events to the publication path without duplicating core-emitted events. Currently, conversation state recordsDENIED, but external consumers receive no corresponding tool-completion event. -
Partial denial reorders the remaining tool calls. Collecting all unguarded calls before approved guarded calls turns
[approved guarded A, unguarded B, denied C]into[B, A]. This can break order-dependent execution, such as writing a file before reading it. Filter the original list while preserving the relative order of retained calls. -
A valid
__none__decision is treated as fallback. WhenselectedNames.isEmpty(), ToolSelection returns the original tool list. Consequently, even a high-confidence__none__decision sends all tools to the model. Distinguish a successful zero-tool selection, which should retain only always-included tools, from an unusable decision that requires fallback. -
Filtering can invalidate an explicit
tool_choice. Tool schemas are filtered while generation options pass through unchanged. This can leaveToolChoice.Specific("search")pointing to a removed tool. Preserve the explicitly requested tool or bypass automatic filtering when a specific tool is required. -
The manual runners lack their required model-provider dependency. All three call
ModelRegistry.resolve("openai:..."), but the module does not includeagentscope-extensions-model-openai, and core does not provide that provider. Add the dependency in the appropriate scope and update the documented execution commands.
Additional improvements
-
Consider moving the three manual runners out of
src/main, either intosrc/testas*ManualRunneror into an example module. This avoids shipping manual entry points containingSystem.exitin the production JAR and avoids duplicate simple class names with unit tests. If moved to test sources, update dependency scopes and theexec:javaclasspath configuration. -
Clarify the status of
jev.example: supported public API with an explicit stability contract, or reference implementations intended for copying and customization with documented compatibility expectations. The documentation already describes them as references; align packaging and API expectations with that positioning. -
Make the missing-key error identify
agentscope.jev.api-keyand theTYPESAFE_API_KEY/JEV_API_KEYfallback options. Do not reject an absent property before accounting for environment variables and builder customizers. -
Validate explicitly null retry subfields or define a default-value fallback. This avoids obscure failures from
Integerunboxing or theJevRetryPolicyconstructor. Existing defaults already cover ordinary omitted or partially specified configuration. -
Consider making
JevClient.MAPPERprivate and letting tests construct their own mapper. It is currently package-private, and no reconfiguration path was found, so this is an encapsulation improvement rather than an established concurrency defect. -
Consider defensively copying the maps in
ChoiceAnswerandScoreAnswer, and make the mutability contract ofSystemOneResult.answersconsistent. If iteration order matters, use an unmodifiableLinkedHashMapcopy. -
AutoMode’s Javadoc should state that fail-open is enabled by default and that the
state == nullbranch allows calls unconditionally, even whenfailOpen(false)is configured.
Please also add integration coverage through real ReActAgent.call() / streamEvents() entry points for default-context handling, denied-event visibility, mixed-tool ordering, and explicit tool-choice preservation. Direct hook tests with a stubbed next function do not exercise these lifecycle interactions.


Summary
This PR adds a dedicated Jev extension for TypeSafe System One (https://docs.typesafe.ai/introduction),
along with a Spring Boot starter and three reference middlewares.
Jev is intentionally not registered as a ChatModel provider. It is a structured decision model,
not a chat model, so the extension exposes it as a typed decision primitive (
Noul,Choice,Score) for middleware and application logic.Changes
agentscope-extensions-jevJevClient: HTTP client forPOST /v1/systemone, configurable base URL /model / transport / retry / timeout, and response validation for answer keys, types,
probabilities, and score legends
NoulQuestion,ChoiceQuestion,ScoreQuestionand theircorresponding answers
JevExceptionwith HTTP status, response body, and retryabilityagentscope-jev-spring-boot-starterJevClientbean fromagentscope.jev.*propertiesJevClientBuilderCustomizerfor advanced programmatic configurationTYPESAFE_API_KEY/JEV_API_KEYenvironment variables when no explicit keyis configured
io.agentscope.extensions.judge.jev.example(for reference only):JevToolSelectionMiddleware(onReasoning)__none__threshold to theprimary model, up to
maxToolsload_skill_through_path,reset_tools,generate_response)JevModelRouterMiddleware(onAgent/onModelCall)per-candidate routing criteria
or no user text is available
ModelCallInput.model; messages, tools, and options pass throughJevAutoModeMiddleware(onActing)bash)safetyThresholdby writing a syntheticDENIEDToolResultBlockand emitting tool-result events, without invoking the toolkitALLOWEDvia HITL, so human approval takes precedenceJevSelectionSupport) for message extraction, criteriabuilding, and chunking/reranking
middlewares:
Manual Testing
Three manual runners are included for end-to-end validation against the real Jev and DashScope
APIs. They are plain Java classes with
main()methods (not JUnit tests), located inagentscope-java\agentscope-examples\documentation\src\main\java\io\agentscope\examples\documentation2\jev\:JevAutoModeMiddlewareTest— runs a ReActAgent with a simulated bash tool; verifies thatlspasses the Jev safety check whilerm -rf .gets deniedJevModelRouterMiddlewareTest— runs a ReActAgent that routes between two models based ontask complexity
JevToolSelectionMiddlewareTest— runs a ReActAgent with a larger tool registry; verifiesthat only relevant tools are sent to the model
To run them: