Skip to content

fix: filter channels by protocol endpoint compatibility in routing#477

Merged
fengyizhu merged 1 commit intoLianjiaTech:developfrom
Jiakaic:fix_channel_filter_by_entity_code
Jan 27, 2026
Merged

fix: filter channels by protocol endpoint compatibility in routing#477
fengyizhu merged 1 commit intoLianjiaTech:developfrom
Jiakaic:fix_channel_filter_by_entity_code

Conversation

@Jiakaic
Copy link
Member

@Jiakaic Jiakaic commented Jan 27, 2026

route时,过滤,仅选择endpoint适配的Adaptor
解决的问题:假设某厂商同模型既支持/v1/chat/completions和/v1/responses,此时会存在俩Adaptor的channelDB行记录,过滤选择endpoint匹配的channel

Add endpoint matching validation in ChannelRouter to prevent routing to incompatible protocol adaptors.

Problem:

  • When routing by model, all channels for that model were considered
  • No validation that channel's protocol supports the requested endpoint
  • Could route /v1/responses request to OpenAIAdaptor (only supports /v1/chat/completions)
  • Could route /v1/chat/completions to OpenAIResponsesAdaptor (only supports /v1/responses)

Solution:

  • Inject AdaptorManager into ChannelRouter (with @lazy to avoid circular dependency)
  • Filter channels by checking adaptorManager.support(endpoint, protocol)
  • Only consider channels whose protocol is registered for the current endpoint
  • Use EndpointContext.getProcessData().getEndpoint() for normalized endpoint value

Changes:

  • Add @lazy AdaptorManager dependency injection
  • Add endpoint compatibility filter at start of filter() method
  • Use endpointMatched list for subsequent safety/visibility filtering
  • Throw clear exception when no compatible channels found

Impact:

  • Prevents protocol mismatch errors at runtime
  • Ensures correct channel selection for multi-protocol models
  • No breaking changes to existing functionality

🤖 Generated with Claude Code

@szl97
Copy link
Collaborator

szl97 commented Jan 27, 2026

@claude 检查提交的代码,使用中文回复

@szl97
Copy link
Collaborator

szl97 commented Jan 27, 2026

Claude finished @szl97's task —— View job


代码审查完成

  • 读取和分析 ChannelRouter.java 的修改内容
  • 检查代码实现的正确性和潜在问题
  • 提供中文代码审查反馈

代码审查结果

总体来说,这次修改解决了一个重要的路由问题,实现质量良好。以下是详细的审查意见:

✅ 优点

  1. 问题定位准确: 正确识别了路由器在选择通道时缺乏端点兼容性检查的问题,避免了将请求路由到不支持的协议适配器。

  2. 解决方案合理: 通过注入 AdaptorManager 并使用 support() 方法进行端点兼容性验证,这是一个直接有效的解决方案。

  3. 循环依赖处理得当: 使用 @Lazy 注解避免了 ChannelRouterAdaptorManager 之间的循环依赖问题。

  4. 代码集成良好:

    • filter() 方法的开始处添加端点过滤逻辑,保证了现有过滤流程的完整性
    • 使用 EndpointContext.getProcessData().getEndpoint() 获取标准化的端点值是正确的做法
  5. 错误处理完善: 当没有找到兼容通道时,抛出清晰的异常信息,便于调试。

⚠️ 需要关注的点

  1. 性能考虑:

    • 对每个通道都调用 adaptorManager.support() 可能会带来一定的性能开销
    • 建议在 AdaptorManager 中对支持的端点-协议组合进行缓存优化
  2. 代码一致性:

    • 第 239 行的另一个 route() 方法没有添加相同的端点兼容性检查
    • 建议保持所有路由方法的一致性,或者说明为什么某些方法不需要此检查

🔧 具体代码分析

修改位置: api/server/src/main/java/com/ke/bella/openapi/protocol/ChannelRouter.java:109-116

String currentEndpoint = EndpointContext.getProcessData().getEndpoint();
List<ChannelDB> endpointMatched = channels.stream()
        .filter(channel -> adaptorManager.support(currentEndpoint, channel.getProtocol()))
        .collect(Collectors.toList());

if(CollectionUtils.isEmpty(endpointMatched)) {
    throw new BizParamCheckException("没有支持当前endpoint的可用渠道: " + currentEndpoint);
}
  • ✅ 正确获取当前端点
  • ✅ 过滤逻辑清晰
  • ✅ 异常消息有助于调试

依赖注入: api/server/src/main/java/com/ke/bella/openapi/protocol/ChannelRouter.java:42-44

@Autowired
@Lazy
private AdaptorManager adaptorManager;
  • @Lazy 注解正确解决循环依赖问题

📝 建议

  1. 性能优化: 考虑在 AdaptorManager 中缓存端点-协议支持关系,减少重复计算。

  2. 方法一致性: 检查第 239 行的 route(String endpoint, String model, ApikeyInfo apikey, Integer queueMode) 方法是否也需要添加端点兼容性检查。

  3. 单元测试: 建议添加测试用例覆盖以下场景:

    • 端点与协议不兼容时的异常处理
    • @Lazy 注解的循环依赖解决
    • 端点兼容性过滤的正确性

总结

这是一个有价值的修复,正确解决了协议路由兼容性问题。代码实现规范,错误处理得当,对现有功能无破坏性影响。建议合并,但可以考虑上述性能优化和一致性建议。

@Jiakaic Jiakaic force-pushed the fix_channel_filter_by_entity_code branch from 830ae82 to d280b00 Compare January 27, 2026 08:58
@Jiakaic
Copy link
Member Author

Jiakaic commented Jan 27, 2026

@claude 已修复你所说的route方法潜在不一致问题,再次review

@Jiakaic Jiakaic requested a review from fengyizhu January 27, 2026 08:58
@szl97
Copy link
Collaborator

szl97 commented Jan 27, 2026

Claude finished @Jiakaic's task —— View job


代码重新审查完成

  • 读取更新后的 ChannelRouter.java 文件
  • 检查是否修复了 route 方法的一致性问题
  • 分析代码实现的正确性和潜在问题
  • 提供中文代码审查反馈

代码审查结果

非常好!您已经成功修复了我之前提到的方法一致性问题。以下是详细的审查意见:

✅ 修复确认

问题已解决: 第二个 route() 方法(第239-274行)现在也正确实现了端点兼容性检查:

// 第252-256行:新增的端点兼容性过滤
List<ChannelDB> endpointMatched = Optional.ofNullable(channels)
        .orElse(Collections.emptyList())
        .stream()
        .filter(channel -> adaptorManager.support(endpoint, channel.getProtocol()))
        .collect(Collectors.toList());

一致性达成: 两个 route() 方法现在都在开始阶段进行端点兼容性检查,保证了代码的一致性。

✅ 实现优点

  1. 端点兼容性验证完整: 两个路由方法都正确实现了 adaptorManager.support() 检查

  2. 错误处理一致: 使用了相似的异常消息格式:

    • 第一个方法: "没有支持当前endpoint的可用渠道: " + currentEndpoint
    • 第二个方法: "没有支持当前endpoint的可用通道: " + endpoint
  3. 代码健壮性提升: 第二个方法使用了 Optional.ofNullable().orElse(Collections.emptyList()) 来避免空指针异常

  4. 过滤逻辑清晰: 端点兼容性检查作为第一步过滤条件,逻辑清晰

✅ 技术实现细节

第239行方法的改进:

  • 在第252-256行添加了端点兼容性过滤
  • 在第258-260行添加了相应的异常处理
  • 后续的队列模式、访问权限、安全合规过滤都基于 endpointMatched 列表

代码结构:

channelsendpointMatchedfilteredChannelspickMaxPriority

这个过滤链条逻辑清晰,每一步都有明确的职责。

✅ 最佳实践

  1. 防御性编程: 使用 Optional.ofNullable() 避免空指针异常
  2. 早期过滤: 端点兼容性检查放在最前面,避免不必要的计算
  3. 一致的异常消息: 两个方法的异常消息风格保持一致
  4. Stream API 良好使用: 链式调用清晰表达了过滤逻辑

📝 建议 (可选优化)

  1. 异常消息统一: 可以考虑将两个方法的异常消息完全统一:

    // 建议统一为
    "没有支持当前endpoint的可用渠道: " + endpoint
  2. 代码复用: 两个方法都有端点兼容性检查逻辑,可以考虑提取为私有方法:

    private List<ChannelDB> filterByEndpointCompatibility(List<ChannelDB> channels, String endpoint) {
        return channels.stream()
                .filter(channel -> adaptorManager.support(endpoint, channel.getProtocol()))
                .collect(Collectors.toList());
    }

总结

🎉 修复质量: 优秀
🎯 问题解决: 完全解决了方法一致性问题
🔒 代码质量: 高质量,遵循良好的编程实践
✅ 建议状态: 可以合并

这次修复完美解决了之前识别的问题,两个 route() 方法现在都正确实现了端点兼容性检查,避免了将请求路由到不支持的协议适配器的风险。代码质量很高,建议合并!


@Jiakaic Jiakaic force-pushed the fix_channel_filter_by_entity_code branch from d280b00 to ab2884d Compare January 27, 2026 09:56
… singleton

Refactor AdaptorManager to static singleton pattern to resolve circular
dependency issue exposed by @lazy injection in previous commit.

Problem:
- Previous commit used @lazy to inject AdaptorManager into ChannelRouter
- This exposed circular dependency: ChannelRouter → AdaptorManager ← ChannelService → ChannelRouter
- @lazy is a workaround that masks the architectural issue
- Inner layer (ChannelRouter) should not depend on outer context (AdaptorManager bean)

Solution:
- Convert AdaptorManager to static singleton pattern with getInstance()
- Remove all Spring dependency injection for AdaptorManager in ChannelRouter and ChannelService
- BellaAutoConf now initializes the singleton instead of creating new instance
- Direct static access eliminates circular dependency without @lazy workaround

Changes:
- AdaptorManager: Add private constructor, static INSTANCE field, and getInstance() method
- BellaAutoConf: Call AdaptorManager.getInstance() in @bean adaptorManager()
- ChannelRouter: Remove @lazy @Autowired, use AdaptorManager.getInstance().support()
- ChannelService: Remove @Autowired, use AdaptorManager.getInstance().support()

Impact:
- Eliminates circular dependency without workarounds
- Minimal code changes (15 lines)
- No new components or files added
- Zero performance impact (still in-memory lookup)
- AdaptorManager naturally fits singleton utility pattern

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@Jiakaic Jiakaic force-pushed the fix_channel_filter_by_entity_code branch from ab2884d to 8fa802e Compare January 27, 2026 09:59
@fengyizhu fengyizhu merged commit 2928d3d into LianjiaTech:develop Jan 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants