GitHub user weiqingy added a comment to the discussion: [Discussion] Pluggable 
model routing — API & design (PR #852 follow-up)

+1 to @wenjin272's questions, especially around the runtime/event contract and 
whether caching is still needed in the framework-owned design.

Thanks for writing this up. I like the direction of treating routing as a 
framework capability rather than a specialized `ChatModelSetup`. That seems 
like a promising way to address the observability and metrics concerns raised 
on #852, while keeping the agent-side usage simple.

A few additional questions came to mind while reading:

1. **Could we make the event/control-flow contract more explicit?**  
   If a user sends `ChatRequestEvent("router", ...)`, would `ChatModelAction` 
resolve the selected model and call it directly, or would the framework 
create/record a selected-model chat request as a separate step? If it is a 
direct call, I wonder whether the selected backend invocation would still be 
visible as an ordinary model invocation in the EventLog. If it is represented 
as a second/internal chat request, it may be helpful to define request IDs, 
parent/original request IDs, and whether user-defined actions listening to chat 
requests can observe it.

2. **What role should `ModelRoutingEvent` play?**  
   Should it be purely an observability event for logging/tracing/evaluation, 
or is it expected to drive another framework step that dispatches the request 
to the selected model? I think either shape can work, but making this explicit 
would help clarify the runtime call path and avoid ambiguity for users who 
consume events.

3. **Would a structured `RoutingDecision` be more useful than returning only a 
`String`?**  
   A selected model name may be enough for execution, but the routing event may 
want to record why the model was selected, whether the strategy abstained, 
whether default/fallback was used, score/confidence, strategy metadata, or an 
LLM judge response. Maybe the common custom case can still stay lightweight 
with helpers, but the SPI could return a structured decision so this 
information is not lost.

4. **Is router-level caching still needed once routing is owned by the 
framework?**  
   In #852, caching helped because the router was itself a chat model and could 
be invoked again on each tool-call round. If routing moves into 
`ChatModelAction`, could it route once for the initial request, associate the 
selected concrete model with `initialRequestId`, and reuse that model for 
subsequent tool-call rounds? That might avoid a separate LRU keyed by the first 
user message, which could be tricky when different conversations share the same 
first message but differ in prompt args, tools, tenant, or metadata.

5. **How should LLM-as-router be represented?**  
   A rule-based strategy feels like a pure selection function, while an LLM 
judge is itself a model invocation with latency, retries, metrics, durability, 
and EventLog implications. In the current shape, a resource/strategy does not 
have `RunnerContext.sendEvent(...)`, and a direct `judge.chat(...)` call would 
bypass the current action-level EventLog and token-recording path. Would it 
make sense for the judge call to go through a framework-managed observable path 
from the beginning, or should the first version focus on the basic routing 
contract and leave fully observable LLM routing as a follow-up?

6. **How should routing decisions behave during recovery/replay?**  
   Routing makes the selected backend a runtime decision. If a job fails after 
the original decision but before the run finishes, should recovery replay the 
original selected model, or is it acceptable to re-run the strategy and 
possibly choose a different backend? This seems especially important because 
the current chat durable call identity is generic to the chat operation rather 
than to the selected model/decision. My intuition is that the decision should 
become replayable data, not re-derived data, so a recovered run reaches the 
same backend or can detect that it would not. Would `RoutingDecision`, 
`ModelRoutingEvent`, or the existing action/durable state be the right place to 
persist that selected model and related metadata?

7. **Could fallback semantics be clarified relative to existing retry/error 
handling?**  
   `ChatModelAction` already has retry/ignore/fail behavior. It would be useful 
to know whether fallback happens before retries, after retries are exhausted, 
or only for certain exception categories. For observability, it may also help 
if the routing/response data records both the initially selected model and the 
model that ultimately produced the response.

8. **What is the deployable shape for custom strategies?**  
   The lambda example is very ergonomic for local Java code, but I wonder how 
that function reaches TaskManagers once the agent plan is serialized. Existing 
function/resource paths tend to move named classes or descriptors rather than 
closures. Is the lambda form intended mainly as an embedded/local convenience, 
with a named `RoutingStrategy` class as the deployable shape? Or should custom 
routing follow a named-method hook similar to actions/tools?

9. **What `ResourceType` should a framework router use?**  
   If `ChatModelAction` needs to detect the router, should the router still be 
registered as `CHAT_MODEL`, or should there be a distinct router resource type? 
Keeping it as `CHAT_MODEL` preserves the agent-side experience but may keep 
some of the type-level conflation that the framework design is trying to 
remove, and it also raises questions like whether routers can point to other 
routers. A distinct type would make the contract clearer, but it would need the 
usual Java/Python/YAML parity work.

10. **Should abstain and invalid candidate be treated differently?**  
   Using `defaultModel` when a strategy returns `null` seems intuitive. If a 
strategy returns a non-candidate model name, though, could that indicate a typo 
or buggy strategy? I wonder whether failing clearly, or at least recording it 
as an invalid decision, would be safer than treating it the same as abstain.

11. **Could we record the Python/YAML scope explicitly?**  
   On #852, the Python story was intentionally narrow: Python users could 
reference the Java router/strategies by FQCN, while a native Python router/SPI 
was deferred. Does that remain the intended v1 scope for the framework-level 
design? If so, it may be worth recording as a deliberate non-goal, together 
with the trigger for when native Python routing should be added.

Overall, I'm supportive of the framework-level router direction and the single 
`RoutingStrategy` extension point with built-in factories. The main thing I'd 
love to understand better is the runtime/event contract: when routing runs, how 
the selected model is stored for tool-call rounds and recovery, what exactly 
`ModelRoutingEvent` represents, and how selected backend calls remain 
observable with clear IDs and metrics attribution.

GitHub link: 
https://github.com/apache/flink-agents/discussions/897#discussioncomment-17695346

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]

Reply via email to