├── .gitee ├── ISSUE_TEMPLATE.zh-CN.md ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ └── feature.yml └── PULL_REQUEST_TEMPLATE.md ├── .github ├── ISSUE_TEMPLATE.zh-CN.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── problem_support.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE.template ├── README.md ├── TEST.md ├── UPDATE_LOG.md ├── __release ├── README.md ├── pom.xml ├── solon-ai-bundle1 │ ├── NOTICE │ └── pom.xml └── solon-ai-bundle2 │ └── pom.xml ├── pom.xml ├── solon-ai-a2a ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── a2a_preview │ ├── client │ ├── A2AClient.java │ ├── HostAgent.java │ ├── HostAgentAssistantTools.java │ └── StreamingEventListener.java │ ├── model │ ├── A2AError.java │ ├── AgentAuthentication.java │ ├── AgentCapabilities.java │ ├── AgentCard.java │ ├── AgentProvider.java │ ├── AgentSkill.java │ ├── Artifact.java │ ├── CancelTaskRequest.java │ ├── CancelTaskResponse.java │ ├── DataPart.java │ ├── ErrorCode.java │ ├── FileContent.java │ ├── FileContentBase.java │ ├── FileContentBytes.java │ ├── FileContentURI.java │ ├── FilePart.java │ ├── GetTaskHistoryResponse.java │ ├── GetTaskPushNotificationRequest.java │ ├── GetTaskPushNotificationResponse.java │ ├── GetTaskRequest.java │ ├── GetTaskResponse.java │ ├── JSONRPCError.java │ ├── JSONRPCErrorResponse.java │ ├── JSONRPCMessage.java │ ├── JSONRPCMessageIdentifier.java │ ├── JSONRPCRequest.java │ ├── JSONRPCResponse.java │ ├── Message.java │ ├── MessageSendConfiguration.java │ ├── MessageSendParams.java │ ├── Part.java │ ├── PushNotificationAuthenticationInfo.java │ ├── PushNotificationConfig.java │ ├── SendTaskRequest.java │ ├── SendTaskResponse.java │ ├── SendTaskStreamingRequest.java │ ├── SendTaskStreamingResponse.java │ ├── SetTaskPushNotificationRequest.java │ ├── SetTaskPushNotificationResponse.java │ ├── Task.java │ ├── TaskArtifactUpdateEvent.java │ ├── TaskHistory.java │ ├── TaskIDParams.java │ ├── TaskPushNotificationConfig.java │ ├── TaskQueryParams.java │ ├── TaskResubscriptionRequest.java │ ├── TaskSendParams.java │ ├── TaskState.java │ ├── TaskStatus.java │ ├── TaskStatusUpdateEvent.java │ └── TextPart.java │ └── server │ ├── A2AServer.java │ ├── A2AServerEndpoint.java │ └── TaskHandler.java ├── solon-ai-anp └── README.md ├── solon-ai-core ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ ├── AiConfig.java │ │ │ ├── AiHandler.java │ │ │ ├── AiMedia.java │ │ │ ├── AiModel.java │ │ │ ├── AiModelDialect.java │ │ │ ├── AiUsage.java │ │ │ ├── annotation │ │ │ └── ToolMapping.java │ │ │ ├── chat │ │ │ ├── ChatChoice.java │ │ │ ├── ChatConfig.java │ │ │ ├── ChatConfigReadonly.java │ │ │ ├── ChatException.java │ │ │ ├── ChatModel.java │ │ │ ├── ChatOptions.java │ │ │ ├── ChatRequest.java │ │ │ ├── ChatRequestDesc.java │ │ │ ├── ChatRequestDescDefault.java │ │ │ ├── ChatResponse.java │ │ │ ├── ChatResponseDefault.java │ │ │ ├── ChatRole.java │ │ │ ├── ChatSession.java │ │ │ ├── ChatSessionDefault.java │ │ │ ├── ChatSubscriberProxy.java │ │ │ ├── dialect │ │ │ │ ├── AbstractChatDialect.java │ │ │ │ ├── ChatDialect.java │ │ │ │ └── ChatDialectManager.java │ │ │ ├── interceptor │ │ │ │ ├── CallChain.java │ │ │ │ ├── ChatInterceptor.java │ │ │ │ ├── StreamChain.java │ │ │ │ ├── ToolChain.java │ │ │ │ └── ToolRequest.java │ │ │ ├── message │ │ │ │ ├── AssistantMessage.java │ │ │ │ ├── ChatMessage.java │ │ │ │ ├── ChatMessageBase.java │ │ │ │ ├── SystemMessage.java │ │ │ │ ├── SystemMessageTemplate.java │ │ │ │ ├── ToolMessage.java │ │ │ │ ├── UserMessage.java │ │ │ │ └── UserMessageTemplate.java │ │ │ ├── prompt │ │ │ │ ├── ChatPrompt.java │ │ │ │ └── Prompt.java │ │ │ ├── session │ │ │ │ └── InMemoryChatSession.java │ │ │ └── tool │ │ │ │ ├── ChatTool.java │ │ │ │ ├── FunctionTool.java │ │ │ │ ├── FunctionToolDesc.java │ │ │ │ ├── MethodExecuteHandler.java │ │ │ │ ├── MethodFunctionTool.java │ │ │ │ ├── MethodToolProvider.java │ │ │ │ ├── ToolCall.java │ │ │ │ ├── ToolCallBuilder.java │ │ │ │ ├── ToolCallException.java │ │ │ │ ├── ToolCallResultConverter.java │ │ │ │ ├── ToolCallResultConverterDefault.java │ │ │ │ ├── ToolHandler.java │ │ │ │ ├── ToolProvider.java │ │ │ │ └── ToolSchemaUtil.java │ │ │ ├── embedding │ │ │ ├── Embedding.java │ │ │ ├── EmbeddingConfig.java │ │ │ ├── EmbeddingException.java │ │ │ ├── EmbeddingModel.java │ │ │ ├── EmbeddingOptions.java │ │ │ ├── EmbeddingRequestDesc.java │ │ │ ├── EmbeddingResponse.java │ │ │ └── dialect │ │ │ │ ├── AbstractEmbeddingDialect.java │ │ │ │ ├── EmbeddingDialect.java │ │ │ │ └── EmbeddingDialectManager.java │ │ │ ├── generate │ │ │ ├── GenerateConfig.java │ │ │ ├── GenerateContent.java │ │ │ ├── GenerateException.java │ │ │ ├── GenerateModel.java │ │ │ ├── GenerateOptions.java │ │ │ ├── GeneratePrompt.java │ │ │ ├── GeneratePromptDefault.java │ │ │ ├── GenerateRequestDesc.java │ │ │ ├── GenerateResponse.java │ │ │ └── dialect │ │ │ │ ├── AbstractGenerateDialect.java │ │ │ │ ├── GenerateDialect.java │ │ │ │ └── GenerateDialectManager.java │ │ │ ├── image │ │ │ ├── ImageConfig.java │ │ │ ├── ImageException.java │ │ │ ├── ImageModel.java │ │ │ ├── ImageOptions.java │ │ │ ├── ImagePrompt.java │ │ │ ├── ImagePromptDefault.java │ │ │ ├── ImageRequestDesc.java │ │ │ ├── ImageResponse.java │ │ │ └── dialect │ │ │ │ ├── AbstractImageDialect.java │ │ │ │ ├── ImageDialect.java │ │ │ │ └── ImageDialectManager.java │ │ │ ├── integration │ │ │ ├── AiPlugin.java │ │ │ └── AiProperties.java │ │ │ ├── media │ │ │ ├── AbstractMedia.java │ │ │ ├── Audio.java │ │ │ ├── Image.java │ │ │ ├── Text.java │ │ │ └── Video.java │ │ │ ├── rag │ │ │ ├── Document.java │ │ │ ├── DocumentLoader.java │ │ │ ├── DocumentSplitter.java │ │ │ ├── Repository.java │ │ │ ├── RepositoryLifecycle.java │ │ │ ├── RepositoryStorable.java │ │ │ ├── loader │ │ │ │ ├── AbstractDocumentLoader.java │ │ │ │ ├── AbstractOptionsDocumentLoader.java │ │ │ │ └── TextLoader.java │ │ │ ├── repository │ │ │ │ ├── InMemoryRepository.java │ │ │ │ └── WebSearchRepository.java │ │ │ ├── splitter │ │ │ │ ├── JsonSplitter.java │ │ │ │ ├── RegexTextSplitter.java │ │ │ │ ├── SplitterPipeline.java │ │ │ │ ├── TextSplitter.java │ │ │ │ └── TokenSizeTextSplitter.java │ │ │ └── util │ │ │ │ ├── Freshness.java │ │ │ │ ├── HybridSearchParams.java │ │ │ │ ├── ListUtil.java │ │ │ │ ├── QueryCondition.java │ │ │ │ ├── SearchType.java │ │ │ │ └── SimilarityUtil.java │ │ │ ├── reranking │ │ │ ├── Reranking.java │ │ │ ├── RerankingConfig.java │ │ │ ├── RerankingException.java │ │ │ ├── RerankingModel.java │ │ │ ├── RerankingOptions.java │ │ │ ├── RerankingRequestDesc.java │ │ │ ├── RerankingResponse.java │ │ │ └── dialect │ │ │ │ ├── AbstractRerankingDialect.java │ │ │ │ ├── RerankingDialect.java │ │ │ │ └── RerankingDialectManager.java │ │ │ └── util │ │ │ ├── ParamDesc.java │ │ │ └── ProxyDesc.java │ └── resources │ │ └── META-INF │ │ └── solon │ │ └── solon-ai-core.properties │ └── test │ └── java │ └── features │ └── ai │ └── core │ ├── CaseBo.java │ ├── CaseTool.java │ ├── InMemoryChatSessionTest.java │ └── ToolSchemaUtilTest.java ├── solon-ai-flow ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── flow │ │ │ ├── components │ │ │ ├── AbsAiComponent.java │ │ │ ├── AiComponent.java │ │ │ ├── AiIoComponent.java │ │ │ ├── AiPropertyComponent.java │ │ │ ├── Attrs.java │ │ │ ├── inputs │ │ │ │ ├── ConsoleInputCom.java │ │ │ │ ├── VarCopyCom.java │ │ │ │ ├── VarInputCom.java │ │ │ │ └── WebInputCom.java │ │ │ ├── models │ │ │ │ ├── ChatModelCom.java │ │ │ │ ├── EmbeddingModelCom.java │ │ │ │ └── ImageModelCom.java │ │ │ ├── outputs │ │ │ │ ├── ConsoleOutputCom.java │ │ │ │ ├── VarOutputCom.java │ │ │ │ └── WebOutputCom.java │ │ │ └── repositorys │ │ │ │ ├── AbsRepositoryCom.java │ │ │ │ ├── InMemoryRepositoryCom.java │ │ │ │ ├── RedisConfig.java │ │ │ │ └── RedisRepositoryCom.java │ │ │ ├── events │ │ │ ├── Events.java │ │ │ └── NodeEvent.java │ │ │ └── integration │ │ │ └── AiFlowPlugin.java │ └── resources │ │ └── META-INF │ │ └── solon │ │ └── solon-ai-flow.properties │ └── test │ ├── java │ └── features │ │ └── ai │ │ └── flow │ │ ├── ChatTest.java │ │ ├── ChatTest3.java │ │ ├── McpTest.java │ │ ├── PkTest.java │ │ ├── RagTest.java │ │ ├── ToolDemo.java │ │ ├── ToolTest.java │ │ └── app │ │ ├── DemoApp.java │ │ ├── DemoController.java │ │ └── DemoMcpServer.java │ └── resources │ ├── app.yml │ └── flow │ ├── chat_case1.chain.json │ ├── chat_case1.chain.yml │ ├── chat_case2.chain.json │ ├── chat_case2.chain.yml │ ├── chat_case3.chain.json │ ├── chat_case3.chain.yml │ ├── mcp_case1.chain.json │ ├── mcp_case1.chain.yml │ ├── pk_case1.chain.json │ ├── pk_case1.chain.yml │ ├── pk_case2.chain.yml │ ├── rag_case1.chain.json │ ├── rag_case1.chain.yml │ ├── rag_case2.chain.yml │ ├── rept_case2.chain.yml │ ├── tool_case1.chain.json │ ├── tool_case1.chain.yml │ └── tool_case2.chain.yml ├── solon-ai-llm-dialects ├── solon-ai-dialect-dashscope │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── llm │ │ │ └── dialect │ │ │ └── dashscope │ │ │ ├── DashscopeChatDialect.java │ │ │ ├── DashscopeEmbeddingDialect.java │ │ │ ├── DashscopeGenerateDialect.java │ │ │ ├── DashscopeImageDialect.java │ │ │ └── DashscopeRerankingDialect.java │ │ └── resources │ │ └── META-INF │ │ └── native-image │ │ └── org.noear.solon.ai.llm.dialect.dashscope │ │ └── reflect-config.json ├── solon-ai-dialect-ollama │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── llm │ │ │ └── dialect │ │ │ └── ollama │ │ │ ├── OllamaChatDialect.java │ │ │ ├── OllamaEmbeddingDialect.java │ │ │ ├── OllamaGenerateDialect.java │ │ │ └── OllamaImageDialect.java │ │ └── resources │ │ └── META-INF │ │ └── native-image │ │ └── org.noear.solon.ai.llm.dialect.ollama │ │ └── reflect-config.json └── solon-ai-dialect-openai │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── org │ │ └── noear │ │ └── solon │ │ └── ai │ │ └── llm │ │ └── dialect │ │ └── openai │ │ ├── OpenaiChatDialect.java │ │ ├── OpenaiEmbeddingDialect.java │ │ ├── OpenaiGenerateDialect.java │ │ ├── OpenaiImageDialect.java │ │ └── OpenaiRerankingDialect.java │ └── resources │ └── META-INF │ └── native-image │ └── org.noear.solon.ai.llm.dialect.openai │ └── reflect-config.json ├── solon-ai-mcp ├── pom.xml ├── reconnect.md └── src │ ├── main │ ├── java │ │ ├── io │ │ │ └── modelcontextprotocol │ │ │ │ ├── client │ │ │ │ ├── LifecycleInitializer.java │ │ │ │ ├── McpAsyncClient.java │ │ │ │ ├── McpClient.java │ │ │ │ ├── McpClientFeatures.java │ │ │ │ ├── McpSyncClient.java │ │ │ │ └── transport │ │ │ │ │ ├── ServerParameters.java │ │ │ │ │ ├── StdioClientTransport.java │ │ │ │ │ ├── WebRxSseClientTransport.java │ │ │ │ │ └── WebRxStreamableHttpTransport.java │ │ │ │ ├── server │ │ │ │ ├── DefaultMcpStatelessServerHandler.java │ │ │ │ ├── DefaultMcpTransportContext.java │ │ │ │ ├── McpAsyncServer.java │ │ │ │ ├── McpAsyncServerExchange.java │ │ │ │ ├── McpInitRequestHandler.java │ │ │ │ ├── McpNotificationHandler.java │ │ │ │ ├── McpRequestHandler.java │ │ │ │ ├── McpServer.java │ │ │ │ ├── McpServerFeatures.java │ │ │ │ ├── McpStatelessAsyncServer.java │ │ │ │ ├── McpStatelessNotificationHandler.java │ │ │ │ ├── McpStatelessRequestHandler.java │ │ │ │ ├── McpStatelessServerFeatures.java │ │ │ │ ├── McpStatelessServerHandler.java │ │ │ │ ├── McpStatelessSyncServer.java │ │ │ │ ├── McpSyncServer.java │ │ │ │ ├── McpSyncServerExchange.java │ │ │ │ ├── McpTransportContext.java │ │ │ │ ├── McpTransportContextExtractor.java │ │ │ │ └── transport │ │ │ │ │ ├── IMcpHttpServerTransport.java │ │ │ │ │ ├── StdioServerTransportProvider.java │ │ │ │ │ ├── WebRxSseServerTransportProvider.java │ │ │ │ │ ├── WebRxStatelessServerTransport.java │ │ │ │ │ └── WebRxStreamableServerTransportProvider.java │ │ │ │ ├── spec │ │ │ │ ├── DefaultJsonSchemaValidator.java │ │ │ │ ├── DefaultMcpStreamableServerSessionFactory.java │ │ │ │ ├── DefaultMcpTransportSession.java │ │ │ │ ├── DefaultMcpTransportStream.java │ │ │ │ ├── HttpHeaders.java │ │ │ │ ├── JsonSchemaValidator.java │ │ │ │ ├── McpClientSession.java │ │ │ │ ├── McpClientTransport.java │ │ │ │ ├── McpError.java │ │ │ │ ├── McpLoggableSession.java │ │ │ │ ├── McpSchema.java │ │ │ │ ├── McpServerSession.java │ │ │ │ ├── McpServerTransport.java │ │ │ │ ├── McpServerTransportProvider.java │ │ │ │ ├── McpServerTransportProviderBase.java │ │ │ │ ├── McpSession.java │ │ │ │ ├── McpStatelessServerTransport.java │ │ │ │ ├── McpStreamableServerSession.java │ │ │ │ ├── McpStreamableServerTransport.java │ │ │ │ ├── McpStreamableServerTransportProvider.java │ │ │ │ ├── McpTransport.java │ │ │ │ ├── McpTransportException.java │ │ │ │ ├── McpTransportSession.java │ │ │ │ ├── McpTransportSessionNotFoundException.java │ │ │ │ ├── McpTransportStream.java │ │ │ │ ├── MissingMcpTransportSession.java │ │ │ │ └── ProtocolVersions.java │ │ │ │ └── util │ │ │ │ ├── Assert.java │ │ │ │ ├── DeafaultMcpUriTemplateManagerFactory.java │ │ │ │ ├── DefaultMcpUriTemplateManager.java │ │ │ │ ├── KeepAliveScheduler.java │ │ │ │ ├── McpUriTemplateManager.java │ │ │ │ ├── McpUriTemplateManagerFactory.java │ │ │ │ └── Utils.java │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ ├── annotation │ │ │ ├── PromptMapping.java │ │ │ └── ResourceMapping.java │ │ │ └── mcp │ │ │ ├── McpChannel.java │ │ │ ├── client │ │ │ ├── McpClientProperties.java │ │ │ ├── McpClientProvider.java │ │ │ ├── McpProviders.java │ │ │ └── McpServerParameters.java │ │ │ ├── exception │ │ │ └── McpException.java │ │ │ ├── integration │ │ │ ├── McpPlugin.java │ │ │ └── McpProperties.java │ │ │ └── server │ │ │ ├── IMcpServerEndpoint.java │ │ │ ├── McpServerContext.java │ │ │ ├── McpServerEndpointProvider.java │ │ │ ├── McpServerProperties.java │ │ │ ├── annotation │ │ │ └── McpServerEndpoint.java │ │ │ ├── manager │ │ │ ├── McpServerManager.java │ │ │ ├── PromptMcpServerManager.java │ │ │ ├── ResourceMcpServerManager.java │ │ │ └── ToolMcpServerManager.java │ │ │ ├── prompt │ │ │ ├── FunctionPrompt.java │ │ │ ├── FunctionPromptDesc.java │ │ │ ├── MethodFunctionPrompt.java │ │ │ ├── MethodPromptProvider.java │ │ │ └── PromptProvider.java │ │ │ └── resource │ │ │ ├── FunctionResource.java │ │ │ ├── FunctionResourceDesc.java │ │ │ ├── MethodFunctionResource.java │ │ │ ├── MethodResourceProvider.java │ │ │ └── ResourceProvider.java │ └── resources │ │ └── META-INF │ │ └── solon │ │ └── solon-ai-mcp.properties │ └── test │ ├── java │ ├── demo │ │ └── ai │ │ │ └── mcp │ │ │ ├── client │ │ │ ├── McpClientApp.java │ │ │ ├── McpClientConfig.java │ │ │ └── McpClientDemo.java │ │ │ ├── client_auth │ │ │ └── AuthDemo.java │ │ │ ├── client_ssl │ │ │ └── SslDemo.java │ │ │ ├── demo │ │ │ └── DemoApp.java │ │ │ ├── server │ │ │ ├── ChatConfig.java │ │ │ ├── ChatController.java │ │ │ ├── FilterImpl.java │ │ │ ├── McpRedirectServer5.java │ │ │ ├── McpServerApp.java │ │ │ ├── McpServerAuth.java │ │ │ ├── McpServerConfig.java │ │ │ ├── McpServerTool.java │ │ │ ├── McpServerTool2.java │ │ │ ├── McpServerTool3.java │ │ │ ├── McpServerTool4.java │ │ │ ├── McpServerTool6_sse.java │ │ │ ├── McpServerTool7_sse.java │ │ │ ├── SseController.java │ │ │ ├── _Constants.java │ │ │ ├── art1 │ │ │ │ ├── CalculatorTools.java │ │ │ │ └── WeatherTools.java │ │ │ └── outputschema │ │ │ │ ├── OutputSchemaMcpTool.java │ │ │ │ └── dataobject │ │ │ │ ├── CityInfo.java │ │ │ │ ├── Result.java │ │ │ │ └── UserInfo.java │ │ │ ├── server_context_path │ │ │ ├── McpServerApp.java │ │ │ ├── McpServerTool2.java │ │ │ └── McpServerTool3.java │ │ │ └── server_resume │ │ │ ├── McpServerApp.java │ │ │ ├── McpServerTool.java │ │ │ └── McpServerTool2.java │ ├── features │ │ └── ai │ │ │ └── mcp │ │ │ └── client │ │ │ ├── ChatTest.java │ │ │ ├── ConfigTest.java │ │ │ ├── McpChangeSyncTest.java │ │ │ ├── McpClientErrTest.java │ │ │ ├── McpContextPathTest.java │ │ │ ├── McpContextPathTest3.java │ │ │ ├── McpHttpAuthTest.java │ │ │ ├── McpHttpClientMixTest2.java │ │ │ ├── McpHttpClientMixTest4.java │ │ │ ├── McpHttpClientMixTest6_sse.java │ │ │ ├── McpHttpClientMixTest7_sse.java │ │ │ ├── McpHttpClientTest.java │ │ │ ├── McpInitClientTest.java │ │ │ ├── McpPropsTest.java │ │ │ ├── McpPropsTest2.java │ │ │ ├── McpRedirectTest5.java │ │ │ ├── McpStartTest.java │ │ │ ├── McpToolMixTest.java │ │ │ ├── OutputSchemaMcpToolTest.java │ │ │ └── SseClientTest.java │ └── lab │ │ └── ai │ │ ├── a2a │ │ ├── AgentDispatcher.java │ │ ├── AgentTaskHandler.java │ │ └── demo │ │ │ ├── a2a │ │ │ ├── Tools1.java │ │ │ └── Tools2.java │ │ │ ├── a2a_local │ │ │ └── A2ALocalTest.java │ │ │ ├── a2a_remote │ │ │ ├── A2ARemoteTest.java │ │ │ ├── App.java │ │ │ ├── Server1.java │ │ │ └── Server2.java │ │ │ └── tool_only │ │ │ ├── ToolOnlyTest.java │ │ │ ├── Tools1.java │ │ │ └── Tools2.java │ │ └── mcp │ │ ├── case1 │ │ └── BrowserMcpTest.java │ │ ├── client │ │ ├── DashscopeTest.java │ │ ├── McpServerTool.java │ │ ├── McpSseClientCloseTest.java │ │ ├── McpSseClientRetryTest.java │ │ ├── McpSseClientRetryTest2.java │ │ ├── McpStdioClientTest.java │ │ ├── ModelscopeTest.java │ │ └── server │ │ │ ├── McpSseToStdioServerDemo.java │ │ │ └── McpStdioToSseServerDemo.java │ │ └── debug │ │ ├── client │ │ ├── McpClientLab.java │ │ └── McpClientTest.java │ │ └── server │ │ ├── McpApp.java │ │ └── McpServerEndpointDemo.java │ └── resources │ ├── app-client-serverparams.yml │ ├── app-client.yml │ ├── app-server.yml │ ├── app-server2.yml │ ├── app.yml │ ├── mcpServers.json │ └── mcpServers2.json ├── solon-ai-parent └── pom.xml ├── solon-ai-rag-loaders ├── solon-ai-load-ddl │ ├── NOTICE │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── loader │ │ │ ├── DdlLoadConfig.java │ │ │ └── DdlLoader.java │ │ └── test │ │ ├── java │ │ └── features │ │ │ └── ai │ │ │ └── load │ │ │ └── jdbc │ │ │ ├── MysqlDdlLoaderTest.java │ │ │ └── SqliteDdlLoaderTest.java │ │ └── resources │ │ └── sqlite.db ├── solon-ai-load-excel │ ├── NOTICE │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── loader │ │ │ └── ExcelLoader.java │ │ └── test │ │ ├── java │ │ └── features │ │ │ └── ai │ │ │ └── load │ │ │ └── excel │ │ │ └── ExcelLoaderTest.java │ │ └── resources │ │ ├── demo.xls │ │ ├── demo.xlsx │ │ └── demo2.xlsx ├── solon-ai-load-html │ ├── NOTICE │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── loader │ │ │ └── HtmlSimpleLoader.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── load │ │ └── html │ │ └── HtmlTest.java ├── solon-ai-load-markdown │ ├── NOTICE │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── loader │ │ │ └── MarkdownLoader.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── read │ │ └── md │ │ └── MarkdownLoaderTest.java ├── solon-ai-load-pdf │ ├── NOTICE │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── loader │ │ │ └── PdfLoader.java │ │ └── test │ │ ├── java │ │ └── features │ │ │ └── ai │ │ │ └── load │ │ │ └── pdf │ │ │ └── PdfLoaderTest.java │ │ └── resources │ │ ├── PdfLoaderTest.pdf │ │ └── PdfWithImg.pdf ├── solon-ai-load-ppt │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── loader │ │ │ └── PptLoader.java │ │ └── test │ │ ├── java │ │ └── features │ │ │ └── ai │ │ │ └── load │ │ │ └── ppt │ │ │ └── PptLoaderTest.java │ │ └── resources │ │ ├── demo.ppt │ │ └── demo.pptx └── solon-ai-load-word │ ├── NOTICE │ ├── pom.xml │ └── src │ ├── main │ └── java │ │ └── org │ │ └── noear │ │ └── solon │ │ └── ai │ │ └── rag │ │ └── loader │ │ └── WordLoader.java │ └── test │ ├── java │ └── features │ │ └── ai │ │ └── load │ │ └── word │ │ └── WordLoaderTest.java │ └── resources │ ├── demo.doc │ └── demo.docx ├── solon-ai-rag-repositorys ├── solon-ai-repo-chroma │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── ChromaRepository.java │ │ │ └── chroma │ │ │ ├── ChromaClient.java │ │ │ ├── ChromaResponse.java │ │ │ ├── CollectionResponse.java │ │ │ ├── CollectionsResponse.java │ │ │ ├── FilterTransformer.java │ │ │ ├── GetResponse.java │ │ │ ├── QueryResponse.java │ │ │ └── StringSerializer.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── repo │ │ └── chroma │ │ └── ChromaRepositoryTest.java ├── solon-ai-repo-dashvector │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── DashVectorRepository.java │ │ │ └── dashvector │ │ │ ├── AddDocResponse.java │ │ │ ├── CreateCollectionResponse.java │ │ │ ├── DashVectorClient.java │ │ │ ├── DashVectorResponse.java │ │ │ ├── DeleteCollectionResponse.java │ │ │ ├── DeleteDocResponse.java │ │ │ ├── Doc.java │ │ │ ├── DocOpResult.java │ │ │ ├── FieldType.java │ │ │ ├── FilterTransformer.java │ │ │ ├── ListCollectionsResponse.java │ │ │ ├── MetadataField.java │ │ │ ├── QueryByIdResponse.java │ │ │ ├── QueryResponse.java │ │ │ └── StringSerializer.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── repo │ │ └── dashvector │ │ └── DashVectorRepositoryTest.java ├── solon-ai-repo-elasticsearch │ ├── NOTICE │ ├── README.md │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── ElasticsearchRepository.java │ │ │ └── elasticsearch │ │ │ ├── FilterTransformer.java │ │ │ └── MetadataField.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── repo │ │ └── elasticsearch │ │ └── ElasticsearchRepositoryTest.java ├── solon-ai-repo-mariadb │ └── pom.xml ├── solon-ai-repo-milvus │ ├── NOTICE │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── MilvusRepository.java │ │ │ └── milvus │ │ │ └── FilterTransformer.java │ │ └── test │ │ └── java │ │ ├── demo │ │ └── ai │ │ │ └── repo │ │ │ └── milvus │ │ │ └── AsyncInsertDemoTest.java │ │ ├── features │ │ └── ai │ │ │ └── repo │ │ │ └── milvus │ │ │ ├── MilvusRepositoryTest.java │ │ │ └── TestUtils.java │ │ └── test.md ├── solon-ai-repo-mysql │ ├── README.md │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── MySqlRepository.java │ │ │ └── mysql │ │ │ ├── FilterTransformer.java │ │ │ └── MetadataField.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── repo │ │ └── mysql │ │ └── MySqlRepositoryTest.java ├── solon-ai-repo-opensearch │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── OpenSearchRepository.java │ │ │ └── opensearch │ │ │ ├── FilterTransformer.java │ │ │ └── MetadataField.java │ │ └── test │ │ └── java │ │ └── org │ │ └── noear │ │ └── solon │ │ └── ai │ │ └── rag │ │ └── repository │ │ └── OpenSearchRepositoryTest.java ├── solon-ai-repo-pgvector │ ├── README.md │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── PgVectorRepository.java │ │ │ └── pgvector │ │ │ ├── FilterTransformer.java │ │ │ └── MetadataField.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── repo │ │ └── pgvector │ │ └── PgVectorRepositoryTest.java ├── solon-ai-repo-qdrant │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── QdrantRepository.java │ │ │ └── qdrant │ │ │ ├── FilterTransformer.java │ │ │ └── QdrantValueUtil.java │ │ └── test │ │ ├── java │ │ └── features │ │ │ └── ai │ │ │ └── repo │ │ │ └── qdrant │ │ │ ├── QdrantRepositoryTest.java │ │ │ └── TestUtils.java │ │ └── resources │ │ └── app.yml ├── solon-ai-repo-redis │ ├── NOTICE │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── RedisRepository.java │ │ │ └── redis │ │ │ ├── FilterTransformer.java │ │ │ └── MetadataField.java │ │ └── test │ │ ├── java │ │ └── features │ │ │ └── ai │ │ │ └── repo │ │ │ └── redis │ │ │ ├── RedisRepositoryTest.java │ │ │ └── TestUtils.java │ │ └── resources │ │ └── app.yml ├── solon-ai-repo-tcvectordb │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── noear │ │ │ └── solon │ │ │ └── ai │ │ │ └── rag │ │ │ └── repository │ │ │ ├── TcVectorDbRepository.java │ │ │ └── tcvectordb │ │ │ ├── EmbeddingModelEnum.java │ │ │ ├── FilterTransformer.java │ │ │ └── MetadataField.java │ │ └── test │ │ └── java │ │ └── features │ │ └── ai │ │ └── repo │ │ └── vectordb │ │ └── TcVectorDBRepositoryTest.java └── solon-ai-repo-vectorex │ ├── pom.xml │ └── src │ ├── main │ └── java │ │ └── org │ │ └── noear │ │ └── solon │ │ └── ai │ │ └── rag │ │ └── repository │ │ ├── VectoRexRepository.java │ │ └── vectorex │ │ ├── FilterTransformer.java │ │ └── MetadataField.java │ └── test │ └── java │ └── features │ └── ai │ └── repo │ └── vectorex │ └── VectoRexRepositoryTest.java ├── solon-ai-rag-searchs └── solon-ai-search-baidu │ ├── README.md │ ├── pom.xml │ └── src │ ├── main │ └── java │ │ └── org │ │ └── noear │ │ └── solon │ │ └── ai │ │ └── rag │ │ └── search │ │ └── BaiduWebSearchRepository.java │ └── test │ └── java │ └── features │ └── ai │ ├── BaiduAiSearchTest.java │ ├── TestUtils.java │ └── example │ └── BaiduAiSearchExample.java ├── solon-ai ├── NOTICE ├── UPDATE_USE.md ├── pom.xml └── src │ └── test │ ├── java │ ├── demo │ │ └── ai │ │ │ ├── App.java │ │ │ ├── Config.java │ │ │ └── DemoController.java │ └── features │ │ ├── LoadTest.java │ │ ├── ai │ │ ├── chat │ │ │ ├── AbsChatTest.java │ │ │ ├── AbsThinkTest.java │ │ │ ├── AbsVisionTest.java │ │ │ ├── App.java │ │ │ ├── BigmodelTest.java │ │ │ ├── DashscopeOpenAiTest.java │ │ │ ├── DashscopeTest.java │ │ │ ├── DashscopeVisionTest.java │ │ │ ├── DeepSeekR1Test.java │ │ │ ├── DeepSeekTest.java │ │ │ ├── GiteeaiDsTest.java │ │ │ ├── GiteeaiR1Test.java │ │ │ ├── GiteeaiTest.java │ │ │ ├── GiteeaiVisionTest.java │ │ │ ├── ModelscopeR1Test.java │ │ │ ├── ModelscopeTest.java │ │ │ ├── OllamaR1Test.java │ │ │ ├── OllamaTest.java │ │ │ ├── OllamaVisionTest.java │ │ │ ├── OpenRouterR1Test.java │ │ │ ├── SiliconflowR1Test.java │ │ │ ├── SiliconflowTest.java │ │ │ ├── interceptor │ │ │ │ └── ChatInterceptorTest.java │ │ │ └── tool │ │ │ │ ├── Case10Tools.java │ │ │ │ ├── Case8Tools.java │ │ │ │ ├── Case9Tools.java │ │ │ │ ├── EntityTools.java │ │ │ │ ├── ReturnTools.java │ │ │ │ └── Tools.java │ │ ├── config │ │ │ └── ConfigTest.java │ │ ├── embedding │ │ │ ├── DashscopeTest.java │ │ │ ├── GiteeaiTest.java │ │ │ └── OllamaTest.java │ │ ├── generate │ │ │ ├── DashcsopeTest.java │ │ │ ├── GiteeaiTest.java │ │ │ └── OllamaTest.java │ │ ├── image │ │ │ ├── DashcsopeTest.java │ │ │ └── GiteeaiTest.java │ │ ├── rag │ │ │ ├── RagRepositoryTest.java │ │ │ ├── TestUtils.java │ │ │ ├── TextLoaderTest.java │ │ │ └── WebSearchTest.java │ │ ├── reranking │ │ │ ├── DashscopeTest.java │ │ │ └── GiteeaiTest.java │ │ └── util │ │ │ └── ListUtilTest.java │ │ └── expr │ │ ├── PrintUtil.java │ │ ├── Query1Test.java │ │ ├── Query2Test.java │ │ └── Query3Test.java │ └── resources │ ├── app.yml │ └── flow │ ├── case4.yml │ ├── case5.yml │ └── demo │ ├── ai-flow.yml │ └── ai-flow2.yml └── solon_icon.png /.gitee/ISSUE_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 问题描述 2 | 3 | 4 | ### 我当前使用 Solon 版本是? 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 问题反馈 2 | description: 在使用中发现了本项目的问题 3 | title: "[问题]: " 4 | labels: [ ] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: 如何复现: 9 | description: 请详细告诉我们如何复现您遇到的问题,并附上可复现的代码示例 10 | placeholder: | 11 | 1. ... 12 | 2. ... 13 | 3. ... 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: 预期结果: 19 | description: 请告诉我们您预期会发生什么。 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: 实际结果: 25 | description: 请告诉我们实际发生了什么。 26 | validations: 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: 截图或视频: 31 | description: 如果可以的话,上传任何关于 Bug 的截图。 32 | placeholder: | 33 | [在这里上传图片] 34 | - type: input 35 | id: version 36 | attributes: 37 | label: 关联版本: 38 | description: 您当前正在使用我们框架的哪个版本? 39 | validations: 40 | required: true 41 | -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # 鼓励用户使用 Issue 模板(不允许用户创建空白 Issue) 2 | blank_issues_enabled: true 3 | 4 | # 引导用户浏览相关文档 5 | contact_links: 6 | - name: Solon 官网 7 | url: https://solon.noear.org/ 8 | about: 查阅官网以获得基本功能使用、介绍和常见问题解答 9 | -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: 功能建议 2 | description: 对本项目提出一个功能建议 3 | title: "[建议]: " 4 | labels: [ "enhancement" ] 5 | body: 6 | - type: textarea 7 | id: related-problem 8 | attributes: 9 | label: 您的功能建议: 10 | validations: 11 | required: false 12 | - type: textarea 13 | id: desired-solution 14 | attributes: 15 | label: 您希望看到什么解决方案: 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: alternatives 20 | attributes: 21 | label: 您考虑过哪些替代方案: 22 | validations: 23 | required: false 24 | -------------------------------------------------------------------------------- /.gitee/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 这个PR有什么用 / 我们为什么需要它? 2 | 3 | 4 | ### 总结您的更改 5 | 6 | 7 | #### 请注明您已完成以下工作: 8 | 9 | - [ ] 确保测试通过,并在需要时添加测试覆盖率。 10 | - [ ] 确保提交消息遵循 [常规提交规范](https://www.conventionalcommits.org/) 的规则。 11 | - [ ] 考虑文档的影响,如果需要,打开一个新的文档问题或文档更改的PR。 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 问题描述 2 | 3 | 4 | ### 我当前使用 Solon 版本是? 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG 提交 3 | about: 提交问题缺陷帮助我们更好的改进 4 | title: '[BUG]' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 问题描述 11 | *简要描述您碰到的问题。* 12 | 13 | 14 | #### 关联版本 15 | *您当前正在使用我们框架的哪个版本?* 16 | 17 | ### 如何复现 18 | *请详细告诉我们如何复现您遇到的问题,并附上可复现的代码示例* 19 | 20 | 1. 21 | 2. 22 | 3. 23 | ```java 24 | //可在此输入示例代码 25 | ``` 26 | 27 | ### 预期结果 28 | *请告诉我们您预期会发生什么。* 29 | 30 | ### 实际结果 31 | *请告诉我们实际发生了什么。* 32 | 33 | 34 | ### 截图或视频 35 | *如果可以的话,上传任何关于 Bug 的截图。* 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 需求建议 3 | about: 提出针对本项目的想法和建议 4 | title: '[FEATURE]' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 请描述您的需求或者改进建议 11 | *对您想要需求或建议的清晰简洁的描述。* 12 | 13 | 14 | ### 请描述你建议的实现方案 15 | *对您想要需求或建议的实现方案的详细描述。* 16 | 17 | 18 | ### 描述您考虑过的替代方案 19 | *对您考虑过的任何替代解决方案或功能的描述。* 20 | 21 | 22 | #### 附加信息 23 | *如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。* 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/problem_support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题支持 3 | about: 提出针对本项目使用及其他方面的问题 4 | title: '[QUESTION]' 5 | labels: 'question' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 请描述您的问题 11 | *询问有关本项目的使用和其他方面的相关问题。* 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 这个PR有什么用 / 我们为什么需要它? 2 | 3 | 4 | ### 总结您的更改 5 | 6 | 7 | #### 请注明您已完成以下工作: 8 | 9 | - [ ] 确保测试通过,并在需要时添加测试覆盖率。 10 | - [ ] 确保提交消息遵循 [常规提交规范](https://www.conventionalcommits.org/) 的规则。 11 | - [ ] 考虑文档的影响,如果需要,打开一个新的文档问题或文档更改的PR。 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | *.log 18 | *.flattened-pom.xml 19 | 20 | ### NetBeans ### 21 | nbproject/private/ 22 | build/ 23 | nbbuild/ 24 | dist/ 25 | nbdist/ 26 | .nb-gradle/ 27 | 28 | ### Mac files ### 29 | *.DS_Store 30 | .vscode 31 | -------------------------------------------------------------------------------- /NOTICE.template: -------------------------------------------------------------------------------- 1 | Copyright 2017-2025 noear.org and authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | This project includes: 16 | #GENERATED_NOTICES# 17 | -------------------------------------------------------------------------------- /TEST.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 开发与测试情况 4 | 5 | | | 开发 | 测试 | 备注 | 6 | |------------------------------|----|----|----| 7 | | solon-ai-load-excel | ok | ok | | 8 | | solon-ai-load-html | ok | ok | | 9 | | solon-ai-load-markdown | ok | ok | | 10 | | solon-ai-load-pdf | ok | ok | | 11 | | solon-ai-load-ppt | ok | ok | | 12 | | solon-ai-load-word | ok | ok | | 13 | | | | | | 14 | | solon-ai-repo-chroma | ok | ok | | 15 | | solon-ai-repo-dashvector | ok | ok | | 16 | | solon-ai-repo-elasticsearch | ok | ok | | 17 | | solon-ai-repo-mariadb | | | | 18 | | solon-ai-repo-milvus | ok | ok | | 19 | | solon-ai-repo-mysql | ok | | | 20 | | solon-ai-repo-opensearch | ok | | | 21 | | solon-ai-repo-pgvector | ok | | | 22 | | solon-ai-repo-qdrant | ok | ok | | 23 | | solon-ai-repo-redis | ok | ok | | 24 | | solon-ai-repo-tcvectordb | ok | ok | | 25 | | solon-ai-repo-vectorex | ok | | | 26 | -------------------------------------------------------------------------------- /__release/README.md: -------------------------------------------------------------------------------- 1 | # Solon 发布包 2 | 3 | 因为项目太多,经常发布超时。所以拆成了多个发布包。 4 | -------------------------------------------------------------------------------- /__release/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.noear.solon-ai 8 | __release 9 | dev 10 | pom 11 | 12 | __release 13 | Java project for solon 14 | 15 | 16 | UTF-8 17 | UTF-8 18 | 19 | 20 | 21 | solon-ai-bundle1 22 | solon-ai-bundle2 23 | 24 | -------------------------------------------------------------------------------- /__release/solon-ai-bundle2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.noear 9 | solon-ai-parent 10 | 3.6.0 11 | ../../solon-ai-parent/pom.xml 12 | 13 | 14 | org.noear.bundle 15 | solon-ai-bundle2 16 | ${project.artifactId} 17 | pom 18 | 19 | 20 | ../../solon-ai-rag-repositorys/solon-ai-repo-mariadb 21 | ../../solon-ai-rag-repositorys/solon-ai-repo-mysql 22 | ../../solon-ai-rag-repositorys/solon-ai-repo-vectorex 23 | 24 | -------------------------------------------------------------------------------- /solon-ai-a2a/README.md: -------------------------------------------------------------------------------- 1 | 敬请期待 -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/client/StreamingEventListener.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.client; 2 | 3 | /** 4 | * Listener interface for streaming task events 5 | */ 6 | public interface StreamingEventListener { 7 | 8 | /** 9 | * Called when a streaming event is received 10 | * 11 | * @param event the event object (could be TaskStatusUpdateEvent or TaskArtifactUpdateEvent) 12 | */ 13 | void onEvent(Object event); 14 | 15 | /** 16 | * Called when an error occurs during streaming 17 | * 18 | * @param exception the exception that occurred 19 | */ 20 | void onError(Exception exception); 21 | 22 | /** 23 | * Called when the stream is completed 24 | */ 25 | void onComplete(); 26 | } -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/A2AError.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author by HaiTao.Wang on 2025/8/21. 8 | */ 9 | @AllArgsConstructor 10 | @Data 11 | public class A2AError { 12 | 13 | /** 14 | * Code is a number indicating the error type that occurred 15 | */ 16 | ErrorCode code; 17 | 18 | /** 19 | * Message is a string providing a short description of the error 20 | */ 21 | String message; 22 | 23 | /** 24 | * Data is optional additional data about the error 25 | */ 26 | Object data; 27 | } 28 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/AgentAuthentication.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author by HaiTao.Wang on 2025/8/21. 10 | */ 11 | @AllArgsConstructor 12 | @Data 13 | public class AgentAuthentication { 14 | 15 | /** 16 | * Schemes is a list of supported authentication schemes 17 | */ 18 | List schemes; 19 | 20 | /** 21 | * Credentials for authentication. Can be a string (e.g., token) or null if not required initially 22 | */ 23 | String credentials; 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/AgentCapabilities.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Builder 11 | @AllArgsConstructor 12 | @Data 13 | public class AgentCapabilities { 14 | 15 | /** 16 | * Streaming indicates if the agent supports streaming responses 17 | */ 18 | Boolean streaming; 19 | 20 | /** 21 | * PushNotifications indicates if the agent supports push notification mechanisms 22 | */ 23 | Boolean pushNotifications; 24 | 25 | /** 26 | * StateTransitionHistory indicates if the agent supports providing state transition history 27 | */ 28 | Boolean stateTransitionHistory; 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/AgentProvider.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Builder 11 | @AllArgsConstructor 12 | @Data 13 | public class AgentProvider { 14 | 15 | /** 16 | * Organization is the name of the organization providing the agent 17 | */ 18 | String organization; 19 | 20 | /** 21 | * URL associated with the agent provider 22 | */ 23 | String url; 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/AgentSkill.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author by HaiTao.Wang on 2025/8/21. 11 | */ 12 | @Builder 13 | @AllArgsConstructor 14 | @Data 15 | public class AgentSkill { 16 | 17 | /** 18 | * ID is the unique identifier for the skill 19 | */ 20 | String id; 21 | 22 | /** 23 | * Name is the human-readable name of the skill 24 | */ 25 | String name; 26 | 27 | /** 28 | * Description is an optional description of the skill 29 | */ 30 | String description; 31 | 32 | /** 33 | * Tags is an optional list of tags associated with the skill for categorization 34 | */ 35 | List tags; 36 | 37 | /** 38 | * Examples is an optional list of example inputs or use cases for the skill 39 | */ 40 | List examples; 41 | 42 | /** 43 | * InputModes is an optional list of input modes supported by this skill 44 | */ 45 | List inputModes; 46 | 47 | /** 48 | * OutputModes is an optional list of output modes supported by this skill 49 | */ 50 | List outputModes; 51 | } 52 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/Artifact.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author by HaiTao.Wang on 2025/8/21. 10 | */ 11 | @Data 12 | public class Artifact { 13 | 14 | /** 15 | * ArtifactId is the unique identifier for the artifact 16 | */ 17 | String artifactId; 18 | 19 | /** 20 | * Name is an optional name for the artifact 21 | */ 22 | String name; 23 | 24 | /** 25 | * Description is an optional description of the artifact 26 | */ 27 | String description; 28 | 29 | /** 30 | * Parts are the constituent parts of the artifact 31 | */ 32 | List parts; 33 | 34 | /** 35 | * Index is an optional index for ordering artifacts 36 | */ 37 | Integer index; 38 | 39 | /** 40 | * Append indicates if this artifact content should append to previous content 41 | */ 42 | Boolean append; 43 | 44 | /** 45 | * Metadata is optional metadata associated with the artifact 46 | */ 47 | Map metadata; 48 | 49 | /** 50 | * LastChunk indicates if this is the last chunk of data for this artifact 51 | */ 52 | Boolean lastChunk; 53 | } 54 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/CancelTaskRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class CancelTaskRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskIDParams params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/CancelTaskResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class CancelTaskResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Result is the result of the method invocation. Required on success. 25 | * Should be null or omitted if an error occurred. 26 | */ 27 | Task result; 28 | 29 | /** 30 | * Error is an error object if an error occurred during the request. 31 | * Required on failure. Should be null or omitted if the request was successful. 32 | */ 33 | A2AError error; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/DataPart.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * DataPart represents a structured data segment within a message part 10 | * @author by HaiTao.Wang on 2025/8/21. 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | public class DataPart implements Part { 15 | /** 16 | * Kind is the part type - data for DataParts 17 | */ 18 | String kind; 19 | 20 | /** 21 | * Data is the structured data content 22 | */ 23 | Map data; 24 | 25 | /** 26 | * Metadata is optional metadata associated with the part 27 | */ 28 | Map metadata; 29 | 30 | 31 | public DataPart(Map data, Map metadata) { 32 | this("data", data, metadata); 33 | } 34 | 35 | public DataPart(Map data) { 36 | this("data", data, null); 37 | } 38 | 39 | 40 | 41 | } -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Getter 9 | public enum ErrorCode { 10 | 11 | PARSE_ERROR(-32700), 12 | INVALID_REQUEST(-32600), 13 | METHOD_NOT_FOUND(-32601), 14 | INVALID_PARAMS(-32602), 15 | INTERNAL_ERROR(-32603), 16 | TASK_NOT_FOUND(-32000), 17 | TASK_NOT_CANCELABLE(-32001), 18 | PUSH_NOTIFICATION_NOT_SUPPORTED(-32002), 19 | UNSUPPORTED_OPERATION(-32003), 20 | INVALID_AGENT_RESPONSE(-32004), 21 | CONTENT_TYPE_NOT_SUPPORTED(-32005); 22 | 23 | private final int value; 24 | 25 | ErrorCode(int value) { 26 | this.value = value; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/FileContent.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | /** 4 | * @author by HaiTao.Wang on 2025/8/21. 5 | */ 6 | public interface FileContent { 7 | } 8 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/FileContentBase.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class FileContentBase { 10 | 11 | /** 12 | * Name is the optional name of the file 13 | */ 14 | String name; 15 | 16 | /** 17 | * MimeType is the optional MIME type of the file content 18 | */ 19 | String mimeType; 20 | } 21 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/FileContentBytes.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class FileContentBytes implements FileContent { 10 | 11 | /** 12 | * Name is the optional name of the file 13 | */ 14 | String name; 15 | 16 | /** 17 | * MimeType is the optional MIME type of the file content 18 | */ 19 | String mimeType; 20 | 21 | /** 22 | * Bytes is the file content encoded as a Base64 string 23 | */ 24 | String bytes; 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/FileContentURI.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class FileContentURI implements FileContent { 10 | 11 | /** 12 | * Name is the optional name of the file 13 | */ 14 | String name; 15 | 16 | /** 17 | * MimeType is the optional MIME type of the file content 18 | */ 19 | String mimeType; 20 | 21 | /** 22 | * URI is the URI pointing to the file content 23 | */ 24 | String uri; 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/FilePart.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * FilePart represents a File segment within parts 10 | * @author by HaiTao.Wang on 2025/8/21. 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | public class FilePart implements Part { 15 | /** 16 | * Kind is the part type - file for FileParts 17 | */ 18 | String kind; 19 | 20 | /** 21 | * File is the file content either as url or bytes 22 | */ 23 | FileContent file; 24 | 25 | /** 26 | * Metadata is optional metadata associated with the part 27 | */ 28 | Map metadata; 29 | 30 | 31 | public FilePart(FileContent file, Map metadata) { 32 | this("file", file, metadata); 33 | } 34 | 35 | public FilePart(FileContent file) { 36 | this("file", file, null); 37 | } 38 | 39 | 40 | } -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/GetTaskHistoryResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class GetTaskHistoryResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Result is the result of the method invocation. Required on success. 25 | * Should be null or omitted if an error occurred. 26 | */ 27 | TaskHistory result; 28 | 29 | /** 30 | * Error is an error object if an error occurred during the request. 31 | * Required on failure. Should be null or omitted if the request was successful. 32 | */ 33 | A2AError error; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/GetTaskPushNotificationRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class GetTaskPushNotificationRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskIDParams params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/GetTaskPushNotificationResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class GetTaskPushNotificationResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Result is the result of the method invocation. Required on success. 25 | * Should be null or omitted if an error occurred. 26 | */ 27 | PushNotificationConfig result; 28 | 29 | /** 30 | * Error is an error object if an error occurred during the request. 31 | * Required on failure. Should be null or omitted if the request was successful. 32 | */ 33 | A2AError error; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/GetTaskRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class GetTaskRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskQueryParams params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/GetTaskResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class GetTaskResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Result is the result of the method invocation. Required on success. 25 | * Should be null or omitted if an error occurred. 26 | */ 27 | Task result; 28 | 29 | /** 30 | * Error is an error object if an error occurred during the request. 31 | * Required on failure. Should be null or omitted if the request was successful. 32 | */ 33 | A2AError error; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/JSONRPCError.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author by HaiTao.Wang on 2025/8/21. 8 | */ 9 | @AllArgsConstructor 10 | @Data 11 | public class JSONRPCError { 12 | 13 | /** 14 | * Code is a number indicating the error type that occurred 15 | */ 16 | int code; 17 | 18 | /** 19 | * Message is a string providing a short description of the error 20 | */ 21 | String message; 22 | 23 | /** 24 | * Data is optional additional data about the error 25 | */ 26 | Object data; 27 | } 28 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/JSONRPCErrorResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class JSONRPCErrorResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | */ 15 | Object id; 16 | /** 17 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 18 | */ 19 | String jsonrpc; 20 | 21 | /** 22 | * Error is the error object 23 | */ 24 | A2AError error; 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/JSONRPCMessage.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class JSONRPCMessage { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | } 23 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/JSONRPCMessageIdentifier.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class JSONRPCMessageIdentifier { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | } 18 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/JSONRPCRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Accessors; 7 | 8 | /** 9 | * @author by HaiTao.Wang on 2025/8/21. 10 | */ 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Accessors(chain = true) 14 | @Data 15 | public class JSONRPCRequest { 16 | 17 | /** 18 | * ID is the request identifier. Can be a string, number, or null. 19 | * Responses must have the same ID as the request they relate to. 20 | * Notifications (requests without an expected response) should omit the ID or use null. 21 | */ 22 | Object id; 23 | 24 | /** 25 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 26 | */ 27 | String jsonrpc; 28 | 29 | /** 30 | * Method is the name of the method to be invoked 31 | */ 32 | String method; 33 | 34 | /** 35 | * Params are the parameters for the method 36 | */ 37 | Object params; 38 | } 39 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/JSONRPCResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author by HaiTao.Wang on 2025/8/21. 8 | */ 9 | @AllArgsConstructor 10 | @Data 11 | public class JSONRPCResponse { 12 | 13 | /** 14 | * ID is the request identifier. Can be a string, number, or null. 15 | * Responses must have the same ID as the request they relate to. 16 | * Notifications (requests without an expected response) should omit the ID or use null. 17 | */ 18 | Object id; 19 | 20 | /** 21 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 22 | */ 23 | String jsonrpc; 24 | 25 | /** 26 | * Result is the result of the method invocation. Required on success. 27 | * Should be null or omitted if an error occurred. 28 | */ 29 | Object result; 30 | 31 | /** 32 | * Error is an error object if an error occurred during the request. 33 | * Required on failure. Should be null or omitted if the request was successful. 34 | */ 35 | JSONRPCError error; 36 | } 37 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/MessageSendConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Data 11 | public class MessageSendConfiguration { 12 | 13 | /** 14 | * AcceptedOutputModes are the accepted output modalities by the client 15 | */ 16 | List acceptedOutputModes; 17 | 18 | /** 19 | * Blocking indicates if the server should treat the client as a blocking request 20 | */ 21 | Boolean blocking; 22 | 23 | /** 24 | * HistoryLength is the number of recent messages to be retrieved 25 | */ 26 | Integer historyLength; 27 | 28 | /** 29 | * PushNotificationConfig is where the server should send notifications when disconnected 30 | */ 31 | PushNotificationConfig pushNotificationConfig; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/MessageSendParams.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Data 11 | public class MessageSendParams { 12 | 13 | /** 14 | * Message is the message being sent to the server 15 | */ 16 | Message message; 17 | 18 | /** 19 | * Configuration is the send message configuration 20 | */ 21 | MessageSendConfiguration configuration; 22 | 23 | /** 24 | * Metadata is extension metadata 25 | */ 26 | Map metadata; 27 | } 28 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/Part.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | public interface Part { 4 | } -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/PushNotificationAuthenticationInfo.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Data 11 | public class PushNotificationAuthenticationInfo { 12 | 13 | /** 14 | * Schemes are the supported authentication schemes (e.g. Basic, Bearer) 15 | */ 16 | List schemes; 17 | 18 | /** 19 | * Credentials are optional credentials 20 | */ 21 | String credentials; 22 | } 23 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/PushNotificationConfig.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class PushNotificationConfig { 10 | 11 | /** 12 | * URL is the endpoint where the agent should send notifications 13 | */ 14 | String url; 15 | 16 | /** 17 | * Token is a token to be included in push notification requests for verification 18 | */ 19 | String token; 20 | 21 | /** 22 | * Authentication is optional authentication details needed by the agent 23 | */ 24 | PushNotificationAuthenticationInfo authentication; 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/SendTaskRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class SendTaskRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskSendParams params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/SendTaskResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class SendTaskResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Result is the result of the method invocation. Required on success. 25 | * Should be null or omitted if an error occurred. 26 | */ 27 | Task result; 28 | 29 | /** 30 | * Error is an error object if an error occurred during the request. 31 | * Required on failure. Should be null or omitted if the request was successful. 32 | */ 33 | A2AError error; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/SendTaskStreamingRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class SendTaskStreamingRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskSendParams params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/SendTaskStreamingResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author by HaiTao.Wang on 2025/8/21. 8 | */ 9 | @AllArgsConstructor 10 | @Data 11 | public class SendTaskStreamingResponse { 12 | 13 | /** 14 | * ID is the request identifier. Can be a string, number, or null. 15 | * Responses must have the same ID as the request they relate to. 16 | * Notifications (requests without an expected response) should omit the ID or use null. 17 | */ 18 | Object id; 19 | 20 | /** 21 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 22 | */ 23 | String jsonrpc; 24 | 25 | /** 26 | * Result is the result of the method invocation. Can be TaskStatusUpdateEvent or TaskArtifactUpdateEvent 27 | * Should be null or omitted if an error occurred. 28 | */ 29 | Object result; 30 | 31 | /** 32 | * Error is an error object if an error occurred during the request. 33 | * Required on failure. Should be null or omitted if the request was successful. 34 | */ 35 | A2AError error; 36 | } 37 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/SetTaskPushNotificationRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class SetTaskPushNotificationRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskPushNotificationConfig params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/SetTaskPushNotificationResponse.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class SetTaskPushNotificationResponse { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Result is the result of the method invocation. Required on success. 25 | * Should be null or omitted if an error occurred. 26 | */ 27 | PushNotificationConfig result; 28 | 29 | /** 30 | * Error is an error object if an error occurred during the request. 31 | * Required on failure. Should be null or omitted if the request was successful. 32 | */ 33 | A2AError error; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/Task.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author by HaiTao.Wang on 2025/8/21. 11 | */ 12 | @AllArgsConstructor 13 | @Data 14 | public class Task { 15 | 16 | 17 | /** 18 | * ID is the unique identifier for the task 19 | */ 20 | String id; 21 | 22 | /** 23 | * ContextId is the server-generated id for contextual alignment across interactions 24 | */ 25 | String contextId; 26 | 27 | /** 28 | * Kind is the event type - task for Tasks 29 | */ 30 | String kind; 31 | 32 | /** 33 | * Status is the current status of the task 34 | */ 35 | TaskStatus status; 36 | 37 | /** 38 | * Artifacts is the collection of artifacts created by the agent 39 | */ 40 | List artifacts; 41 | 42 | /** 43 | * History is the message history for the task 44 | */ 45 | List history; 46 | 47 | /** 48 | * Metadata is extension metadata 49 | */ 50 | Map metadata; 51 | } 52 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskArtifactUpdateEvent.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Data 11 | public class TaskArtifactUpdateEvent { 12 | 13 | /** 14 | * ID is the ID of the task being updated 15 | */ 16 | String id; 17 | 18 | /** 19 | * Artifact is the new or updated artifact for the task 20 | */ 21 | Artifact artifact; 22 | 23 | /** 24 | * Final indicates if this is the final update for the task 25 | */ 26 | Boolean finalUpdate; 27 | 28 | /** 29 | * Metadata is optional metadata associated with this update event 30 | */ 31 | Map metadata; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskHistory.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Data 11 | public class TaskHistory { 12 | 13 | /** 14 | * MessageHistory is the list of messages in chronological order 15 | */ 16 | List messageHistory; 17 | } 18 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskIDParams.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author by HaiTao.Wang on 2025/8/21. 9 | */ 10 | @Data 11 | public class TaskIDParams { 12 | 13 | /** 14 | * ID is the unique identifier of the task 15 | */ 16 | String id; 17 | 18 | /** 19 | * Metadata is optional metadata to include with the operation 20 | */ 21 | Map metadata; 22 | } 23 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskPushNotificationConfig.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class TaskPushNotificationConfig { 10 | 11 | /** 12 | * ID is the ID of the task the notification config is associated with 13 | */ 14 | String id; 15 | 16 | /** 17 | * PushNotificationConfig is the push notification configuration details 18 | */ 19 | PushNotificationConfig pushNotificationConfig; 20 | } 21 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskQueryParams.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * @author by HaiTao.Wang on 2025/8/21. 12 | */ 13 | @Accessors(chain = true) 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Data 17 | public class TaskQueryParams { 18 | 19 | /** 20 | * ID is the unique identifier of the task 21 | */ 22 | String id; 23 | 24 | /** 25 | * Metadata is optional metadata to include with the operation 26 | */ 27 | Map metadata; 28 | 29 | /** 30 | * HistoryLength is an optional parameter to specify how much history to retrieve 31 | */ 32 | Integer historyLength; 33 | } 34 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskResubscriptionRequest.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Data 9 | public class TaskResubscriptionRequest { 10 | 11 | /** 12 | * ID is the request identifier. Can be a string, number, or null. 13 | * Responses must have the same ID as the request they relate to. 14 | * Notifications (requests without an expected response) should omit the ID or use null. 15 | */ 16 | Object id; 17 | 18 | /** 19 | * JSONRPC specifies the JSON-RPC version. Must be "2.0" 20 | */ 21 | String jsonrpc; 22 | 23 | /** 24 | * Method is the name of the method to be invoked 25 | */ 26 | String method; 27 | 28 | /** 29 | * Params are the parameters for the method 30 | */ 31 | TaskQueryParams params; 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskState.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author by HaiTao.Wang on 2025/8/21. 7 | */ 8 | @Getter 9 | public enum TaskState { 10 | 11 | SUBMITTED("submitted"), 12 | WORKING("working"), 13 | INPUT_REQUIRED("input-required"), 14 | COMPLETED("completed"), 15 | CANCELED("canceled"), 16 | FAILED("failed"), 17 | REJECTED("rejected"), 18 | AUTH_REQUIRED("auth-required"), 19 | UNKNOWN("unknown"); 20 | 21 | private final String value; 22 | 23 | TaskState(String value) { 24 | this.value = value; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskStatus.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author by HaiTao.Wang on 2025/8/21. 8 | */ 9 | @AllArgsConstructor 10 | @Data 11 | public class TaskStatus { 12 | 13 | /** 14 | * State is the current state of the task 15 | */ 16 | TaskState state; 17 | 18 | /** 19 | * Message is an additional status update for the client 20 | */ 21 | Message message; 22 | 23 | /** 24 | * Timestamp is the ISO 8601 datetime string when the status was recorded 25 | */ 26 | String timestamp; 27 | } 28 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TaskStatusUpdateEvent.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * @author by HaiTao.Wang on 2025/8/21. 10 | */ 11 | @AllArgsConstructor 12 | @Data 13 | public class TaskStatusUpdateEvent { 14 | 15 | /** 16 | * ID is the ID of the task being updated 17 | */ 18 | String id; 19 | 20 | /** 21 | * Status is the new status of the task 22 | */ 23 | TaskStatus status; 24 | 25 | /** 26 | * Final indicates if this is the final update for the task 27 | */ 28 | Boolean finalUpdate; 29 | 30 | /** 31 | * Metadata is optional metadata associated with this update event 32 | */ 33 | Map metadata; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/model/TextPart.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * TextPart represents a text segment within parts 11 | * @author by HaiTao.Wang on 2025/8/21. 12 | */ 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Data 16 | public class TextPart implements Part { 17 | /** 18 | * Kind is the part type - text for TextParts 19 | */ 20 | String kind; 21 | 22 | /** 23 | * Text is the text content 24 | */ 25 | String text; 26 | 27 | Map metadata; 28 | 29 | public TextPart(String text) { 30 | this("text", text, null); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /solon-ai-a2a/src/main/java/io/a2a_preview/server/TaskHandler.java: -------------------------------------------------------------------------------- 1 | package io.a2a_preview.server; 2 | 3 | 4 | import io.a2a_preview.model.Message; 5 | import io.a2a_preview.model.Task; 6 | 7 | /** 8 | * TaskHandler is a functional interface for handling tasks 9 | */ 10 | @FunctionalInterface 11 | public interface TaskHandler { 12 | /** 13 | * Handle a task 14 | * 15 | * @param task the task to handle 16 | * @param message the message content 17 | * @return the processed task 18 | * @throws Exception exceptions during processing 19 | */ 20 | Task handle(Task task, Message message) throws Exception; 21 | } -------------------------------------------------------------------------------- /solon-ai-anp/README.md: -------------------------------------------------------------------------------- 1 | 敬请期待 -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/AiHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * Ai 处理器 22 | * 23 | * @author noear 24 | * @since 3.3 25 | */ 26 | public interface AiHandler { 27 | /** 28 | * 处理 29 | * 30 | * @param req 请求 31 | */ 32 | Resp handle(Req req) throws Err; 33 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/AiMedia.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai; 17 | 18 | import java.util.Map; 19 | 20 | /** 21 | * Ai 媒体 22 | * 23 | * @author noear 24 | * @since 3.1 25 | */ 26 | public interface AiMedia { 27 | /** 28 | * 转为数据字符串 29 | */ 30 | String toDataString(boolean useMime); 31 | 32 | /** 33 | * 转为数据 34 | */ 35 | Map toData(boolean useMime); 36 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/AiModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai; 17 | 18 | /** 19 | * Ai 模型 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public interface AiModel { 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/AiModelDialect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai; 17 | 18 | /** 19 | * Ai 模型方言 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public interface AiModelDialect { 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/chat/ChatRole.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.chat; 17 | 18 | /** 19 | * 聊天角色 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public enum ChatRole { 25 | /** 26 | * 系统 27 | */ 28 | SYSTEM, 29 | /** 30 | * 用户 31 | */ 32 | USER, 33 | /** 34 | * 工具 35 | */ 36 | TOOL, 37 | /** 38 | * 助理 39 | */ 40 | ASSISTANT; 41 | } 42 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/chat/tool/ChatTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.chat.tool; 17 | 18 | /** 19 | * 聊天工具 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public interface ChatTool { 25 | /** 26 | * 工具类型 27 | */ 28 | String type(); 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/chat/tool/ToolCallBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.chat.tool; 17 | 18 | /** 19 | * 工具调用构建器 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public class ToolCallBuilder { 25 | public final StringBuffer idBuilder = new StringBuffer(); 26 | public final StringBuffer nameBuilder = new StringBuffer(); 27 | public final StringBuffer argumentsBuilder = new StringBuffer(); 28 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/chat/tool/ToolCallException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.chat.tool; 17 | 18 | import org.noear.solon.ai.chat.ChatException; 19 | 20 | /** 21 | * 聊天异常 22 | * 23 | * @author noear 24 | * @since 3.3 25 | */ 26 | public class ToolCallException extends ChatException { 27 | public ToolCallException(String message) { 28 | super(message); 29 | } 30 | 31 | public ToolCallException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | public ToolCallException(Throwable cause) { 36 | super(cause); 37 | } 38 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/chat/tool/ToolHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.chat.tool; 17 | 18 | import java.util.Map; 19 | 20 | /** 21 | * 工具处理器 22 | * 23 | * @author noear 24 | * @since 3.5 25 | */ 26 | @FunctionalInterface 27 | public interface ToolHandler { 28 | /** 29 | * 处理 30 | * 31 | * @param args 参数 32 | * 33 | */ 34 | String handle(Map args) throws Throwable; 35 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/chat/tool/ToolProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.chat.tool; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * 工具提供者 22 | * 23 | * @author noear 24 | * @since 3.1 25 | */ 26 | public interface ToolProvider { 27 | /** 28 | * 获取工具 29 | */ 30 | Collection getTools(); 31 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/generate/GeneratePromptDefault.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.generate; 17 | 18 | import org.noear.solon.lang.Preview; 19 | 20 | import java.util.Map; 21 | 22 | /** 23 | * 生成提示语默认实现 24 | * 25 | * @author noear 26 | * @since 3.5 27 | */ 28 | @Preview("3.5") 29 | public class GeneratePromptDefault implements GeneratePrompt { 30 | private Map map; 31 | 32 | public GeneratePromptDefault(Map map) { 33 | this.map = map; 34 | } 35 | 36 | @Override 37 | public Map toMap() { 38 | return map; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/image/ImagePrompt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.image; 17 | 18 | import org.noear.solon.Utils; 19 | 20 | import java.util.Map; 21 | 22 | /** 23 | * 图像提示语(方便强类型定义) 24 | * 25 | * @author noear 26 | * @since 3.5 27 | */ 28 | public interface ImagePrompt { 29 | static ImagePrompt ofKeyValues(Object... keyValues) { 30 | return new ImagePromptDefault(Utils.asMap(keyValues)); 31 | } 32 | 33 | static ImagePrompt ofMap(Map map) { 34 | return new ImagePromptDefault(map); 35 | } 36 | 37 | Map toMap(); 38 | } 39 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/image/ImagePromptDefault.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.image; 17 | 18 | import java.util.Map; 19 | 20 | /** 21 | * 图像提示语默认实现 22 | * 23 | * @author noear 24 | * @since 3.5 25 | */ 26 | public class ImagePromptDefault implements ImagePrompt { 27 | private Map map; 28 | 29 | public ImagePromptDefault(Map map) { 30 | this.map = map; 31 | } 32 | 33 | @Override 34 | public Map toMap() { 35 | return map; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/rag/RepositoryLifecycle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.rag; 17 | 18 | /** 19 | * 知识库生命周期 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public interface RepositoryLifecycle extends Repository { 25 | /** 26 | * 初始化仓库 27 | */ 28 | void initRepository() throws Exception; 29 | 30 | /** 31 | * 注销仓库 32 | */ 33 | void dropRepository() throws Exception; 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/rag/loader/AbstractDocumentLoader.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.loader; 2 | 3 | import org.noear.solon.ai.rag.DocumentLoader; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * 虚拟文档加载器 10 | * 11 | * @author noear 12 | * @since 3.1 13 | */ 14 | public abstract class AbstractDocumentLoader implements DocumentLoader { 15 | protected final Map additionalMetadata = new HashMap<>(); 16 | 17 | @Override 18 | public DocumentLoader additionalMetadata(String key, Object value) { 19 | this.additionalMetadata.put(key, value); 20 | return this; 21 | } 22 | 23 | @Override 24 | public DocumentLoader additionalMetadata(Map metadata) { 25 | this.additionalMetadata.putAll(metadata); 26 | return this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/rag/loader/AbstractOptionsDocumentLoader.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.loader; 2 | 3 | import java.util.function.Consumer; 4 | 5 | /** 6 | * 虚拟带选项的文档加载器 7 | * 8 | * @author noear 9 | * @since 3.1 10 | */ 11 | public abstract class AbstractOptionsDocumentLoader extends AbstractDocumentLoader { 12 | protected Opt options; 13 | 14 | /** 15 | * 配置选项 16 | */ 17 | public Slf options(Opt options) { 18 | if (options != null) { 19 | this.options = options; 20 | } 21 | return (Slf) this; 22 | } 23 | 24 | /** 25 | * 配置选项 26 | */ 27 | public Slf options(Consumer optionsBuilder) { 28 | optionsBuilder.accept(this.options); 29 | return (Slf) this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/rag/util/Freshness.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.rag.util; 17 | 18 | /** 19 | * 时间热度 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public enum Freshness { 25 | /** 26 | * 一天内 27 | */ 28 | ONE_DAY("oneDay"), 29 | /** 30 | * 一周内 31 | */ 32 | ONE_WEEK("oneWeek"), 33 | /** 34 | * 一月内 35 | */ 36 | ONE_MONTH("oneMonth"), 37 | /** 38 | * 一年内 39 | */ 40 | ONE_YEAR("oneYear"), 41 | /** 42 | * 不限 43 | */ 44 | NO_LIMIT("noLimit"); 45 | 46 | public final String value; 47 | 48 | Freshness(String value) { 49 | this.value = value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/rag/util/SearchType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.rag.util; 17 | 18 | /** 19 | * 检索类型枚举 20 | * 21 | * @author orz_zsy 22 | * @since 3.3 23 | */ 24 | public enum SearchType { 25 | /** 26 | * 全文检索 27 | */ 28 | FULL_TEXT, 29 | 30 | /** 31 | * 向量检索 32 | */ 33 | VECTOR, 34 | 35 | /** 36 | * 混合检索(全文 + 向量) 37 | */ 38 | HYBRID 39 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/java/org/noear/solon/ai/util/ProxyDesc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.util; 17 | 18 | import java.io.Serializable; 19 | import java.net.Proxy; 20 | 21 | /** 22 | * 简单代理描述 23 | * 24 | * @author noear 25 | * @since 3.3 26 | */ 27 | public class ProxyDesc implements Serializable { 28 | public Proxy.Type type; 29 | public String host; 30 | public int port; 31 | } -------------------------------------------------------------------------------- /solon-ai-core/src/main/resources/META-INF/solon/solon-ai-core.properties: -------------------------------------------------------------------------------- 1 | solon.plugin=org.noear.solon.ai.integration.AiPlugin 2 | solon.plugin.priority=20 -------------------------------------------------------------------------------- /solon-ai-core/src/test/java/features/ai/core/CaseTool.java: -------------------------------------------------------------------------------- 1 | package features.ai.core; 2 | 3 | import org.noear.solon.Utils; 4 | import org.noear.solon.ai.annotation.ToolMapping; 5 | import org.noear.solon.annotation.Param; 6 | 7 | import java.lang.reflect.Array; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** 12 | * @author noear 2025/6/11 created 13 | */ 14 | public class CaseTool { 15 | @ToolMapping(description = "查询案件列表 建议最多查询5条") 16 | public List getCaseList5( 17 | @Param(description = "查询条件") CaseBo caseBo, 18 | @Param(description = "页码") Integer pageNum, 19 | @Param(description = "每页条数") Integer pageSize 20 | ) { // @Header("X-User-Id") Long userId 21 | System.out.println("------------------查询案件列表"); 22 | //// caseBo.setInspectorId(userId); 23 | // caseBo.setCaseFiled(true); 24 | // // 一行代码,实现一个用户检索接口(MapUtils.flat 只是收集前端的请求参数) 25 | // PageQuery pageQuery = new PageQuery(); 26 | // pageQuery.setPageNum(pageNum); 27 | // pageQuery.setPageSize(pageSize); 28 | // return caseService.queryPageList(caseBo, pageQuery).getData().getRows(); 29 | 30 | return Arrays.asList("1", "2", "3", "4", "5"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-flow/src/main/java/org/noear/solon/ai/flow/components/AiComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.flow.components; 17 | 18 | import org.noear.solon.flow.TaskComponent; 19 | 20 | /** 21 | * Ai 组件 22 | * 23 | * @author noear 24 | * @since 3.3 25 | */ 26 | public interface AiComponent extends TaskComponent { 27 | /** 28 | * 获取组件描述 29 | */ 30 | default String getDescription() { 31 | return this.getClass().getSimpleName(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solon-ai-flow/src/main/java/org/noear/solon/ai/flow/events/Events.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.flow.events; 17 | 18 | /** 19 | * 事件名 20 | * 21 | * @author noear 22 | * @since 3.3 23 | */ 24 | public interface Events { 25 | /** 26 | * 节点开始事件 27 | */ 28 | String EVENT_FLOW_NODE_START = "flow.node.start"; 29 | /** 30 | * 节点结束事件 31 | */ 32 | String EVENT_FLOW_NODE_END = "flow.node.end"; 33 | } 34 | -------------------------------------------------------------------------------- /solon-ai-flow/src/main/java/org/noear/solon/ai/flow/integration/AiFlowPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.flow.integration; 17 | 18 | import org.noear.solon.ai.flow.components.Attrs; 19 | import org.noear.solon.core.AppContext; 20 | import org.noear.solon.core.Plugin; 21 | 22 | /** 23 | * AiFlow 插件 24 | * 25 | * @author noear 26 | * @since 3.3 27 | */ 28 | public class AiFlowPlugin implements Plugin { 29 | @Override 30 | public void start(AppContext context) throws Throwable { 31 | context.beanScan(Attrs.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solon-ai-flow/src/main/resources/META-INF/solon/solon-ai-flow.properties: -------------------------------------------------------------------------------- 1 | solon.plugin=org.noear.solon.ai.flow.integration.AiFlowPlugin 2 | solon.plugin.priority=20 -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/ChatTest3.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.flow.FlowEngine; 5 | 6 | /** 7 | * @author noear 2025/5/16 created 8 | */ 9 | public class ChatTest3 { 10 | public static class Yaml { 11 | public static void main(String[] args) { 12 | Solon.start(ChatTest3.class, args); 13 | 14 | FlowEngine flowEngine = Solon.context().getBean(FlowEngine.class); 15 | 16 | flowEngine.eval("chat_case3"); 17 | } 18 | } 19 | 20 | public static class Json { 21 | public static void main(String[] args) { 22 | Solon.start(ChatTest3.class, args); 23 | 24 | FlowEngine flowEngine = Solon.context().getBean(FlowEngine.class); 25 | 26 | flowEngine.eval("chat_case3_json"); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/McpTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow; 2 | 3 | import features.ai.flow.app.DemoApp; 4 | import org.junit.jupiter.api.Test; 5 | import org.noear.solon.annotation.Inject; 6 | import org.noear.solon.flow.FlowEngine; 7 | import org.noear.solon.test.HttpTester; 8 | import org.noear.solon.test.SolonTest; 9 | 10 | @SolonTest(DemoApp.class) 11 | public class McpTest extends HttpTester { 12 | @Inject 13 | FlowEngine flowEngine; 14 | 15 | @Test 16 | public void case1() { 17 | flowEngine.eval("mcp_case1"); 18 | } 19 | 20 | @Test 21 | public void case1_json() { 22 | flowEngine.eval("mcp_case1_json"); 23 | } 24 | } -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/RagTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow; 2 | 3 | import features.ai.flow.app.DemoApp; 4 | import org.junit.jupiter.api.Test; 5 | import org.noear.solon.annotation.Inject; 6 | import org.noear.solon.flow.FlowEngine; 7 | import org.noear.solon.test.HttpTester; 8 | import org.noear.solon.test.SolonTest; 9 | 10 | @SolonTest(DemoApp.class) 11 | public class RagTest extends HttpTester { 12 | @Inject 13 | FlowEngine flowEngine; 14 | 15 | @Test 16 | public void case1() { 17 | flowEngine.eval("rag_case1"); 18 | } 19 | 20 | @Test 21 | public void case1_json() { 22 | flowEngine.eval("rag_case1_json"); 23 | } 24 | 25 | @Test 26 | public void case2() { 27 | String rst = path("/rag_case2").data("message", "Solon 是谁开发的?").get(); 28 | System.out.println(rst); 29 | assert rst.contains("无耳"); 30 | } 31 | } -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/ToolDemo.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Param; 5 | 6 | /** 7 | * @author noear 2025/5/13 created 8 | */ 9 | public class ToolDemo { 10 | @ToolMapping(description = "获取指定城市的天气情况") 11 | public String get_weather(@Param(name = "location", description = "根据用户提到的地点推测城市") String location) { 12 | if (location == null) { 13 | throw new IllegalStateException("arguments location is null (Assistant recognition failure)"); 14 | } 15 | 16 | return "晴,24度";// + weatherService.get(location); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/ToolTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow; 2 | 3 | import features.ai.flow.app.DemoApp; 4 | import org.junit.jupiter.api.Test; 5 | import org.noear.solon.annotation.Inject; 6 | import org.noear.solon.flow.FlowEngine; 7 | import org.noear.solon.test.HttpTester; 8 | import org.noear.solon.test.SolonTest; 9 | 10 | @SolonTest(DemoApp.class) 11 | public class ToolTest extends HttpTester { 12 | @Inject 13 | FlowEngine flowEngine; 14 | 15 | @Test 16 | public void case1() { 17 | flowEngine.eval("tool_case1"); 18 | } 19 | 20 | @Test 21 | public void case1_json() { 22 | flowEngine.eval("tool_case1_json"); 23 | } 24 | 25 | @Test 26 | public void case2() { 27 | String rst = path("/tool_case2").data("message", "杭州今天天气怎么样?").get(); 28 | System.out.println(rst); 29 | assert rst.contains("晴"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/app/DemoApp.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow.app; 2 | 3 | import org.noear.solon.Solon; 4 | 5 | /** 6 | * @author noear 2025/5/13 created 7 | */ 8 | public class DemoApp { 9 | public static void main(String[] args) { 10 | Solon.start(DemoApp.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /solon-ai-flow/src/test/java/features/ai/flow/app/DemoMcpServer.java: -------------------------------------------------------------------------------- 1 | package features.ai.flow.app; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Param; 7 | 8 | /** 9 | * @author noear 2025/5/27 created 10 | */ 11 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/mcp/sse") 12 | public class DemoMcpServer { 13 | @ToolMapping(description = "获取指定城市的天气情况") 14 | public String get_weather(@Param(name = "location", description = "根据用户提到的地点推测城市") String location) { 15 | if (location == null) { 16 | throw new IllegalStateException("arguments location is null (Assistant recognition failure)"); 17 | } 18 | 19 | return "晴,24度";// + weatherService.get(location); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/app.yml: -------------------------------------------------------------------------------- 1 | solon.logging: 2 | appender: 3 | console: 4 | level: INFO 5 | logger: 6 | "org.noear.dami": 7 | level: ERROR 8 | 9 | 10 | solon.flow: 11 | - classpath:flow/* -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/chat_case1.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: chat_case1 3 | layout: 4 | - task: "@VarInput" 5 | meta: 6 | message: "你好" 7 | - task : "@VarCopy" 8 | meta: 9 | var2: message 10 | - task: "@ChatModel" 11 | meta: 12 | systemPrompt: "你是个聊天助手" 13 | stream: false 14 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 15 | provider: "ollama" 16 | model: "qwen2.5:1.5b" 17 | apiUrl: "http://127.0.0.1:11434/api/chat" 18 | - task: "@ConsoleOutput" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/chat_case2.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: chat_case2 3 | layout: 4 | - task: "@WebInput" 5 | - task: "@ChatModel" 6 | meta: 7 | systemPrompt: "你是个聊天助手" 8 | stream: false 9 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 10 | provider: "ollama" 11 | model: "qwen2.5:1.5b" 12 | apiUrl: "http://127.0.0.1:11434/api/chat" 13 | - task: "@WebOutput" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/chat_case3.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: chat_case3 3 | layout: 4 | - type: start 5 | - task: "@ConsoleInput" 6 | id: input 7 | - task: "@ChatModel" 8 | meta: 9 | systemPrompt: "你是个聊天助手" 10 | stream: false 11 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 12 | provider: "ollama" 13 | model: "qwen2.5:1.5b" 14 | apiUrl: "http://127.0.0.1:11434/api/chat" 15 | - task: "@ConsoleOutput" 16 | meta: 17 | format: "机器人:#{message}" 18 | - type: "exclusive" 19 | link: 20 | - nextId: input 21 | condition: 'context.incrAdd("demo", 1) < 10' 22 | - nextId: end 23 | - type: "end" 24 | id: "end" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/mcp_case1.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: mcp_case1 3 | layout: 4 | - task: "@VarInput" 5 | meta: 6 | message: "杭州今天天气怎么样?" 7 | - task: "@ChatModel" 8 | meta: 9 | systemPrompt: "你是个天气预报员" 10 | stream: false 11 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 12 | provider: "ollama" 13 | model: "qwen2.5:1.5b" 14 | apiUrl: "http://127.0.0.1:11434/api/chat" 15 | mcpServers: 16 | mcp1: 17 | type: streamable 18 | url: "http://127.0.0.1:8080/mcp/sse" 19 | - task: "@ConsoleOutput" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/rag_case1.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: rag_case1 3 | layout: 4 | - task: "@VarInput" 5 | meta: 6 | message: "Solon 是谁开发的?" 7 | - task: "@EmbeddingModel" 8 | meta: 9 | embeddingConfig: # "@type": "org.noear.solon.ai.embedding.EmbeddingConfig" 10 | provider: "ollama" 11 | model: "bge-m3" 12 | apiUrl: "http://127.0.0.1:11434/api/embed" 13 | - task: "@InMemoryRepository" 14 | meta: 15 | documentSources: 16 | - "https://solon.noear.org/article/about?format=md" 17 | splitPipeline: 18 | - "org.noear.solon.ai.rag.splitter.RegexTextSplitter" 19 | - "org.noear.solon.ai.rag.splitter.TokenSizeTextSplitter" 20 | - task: "@ChatModel" 21 | meta: 22 | systemPrompt: "你是个知识库" 23 | stream: false 24 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 25 | provider: "ollama" 26 | model: "qwen2.5:1.5b" 27 | apiUrl: "http://127.0.0.1:11434/api/chat" 28 | - task: "@ConsoleOutput" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/rag_case2.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: rag_case2 3 | layout: 4 | - task: "@WebInput" 5 | - task: "@EmbeddingModel" 6 | meta: 7 | embeddingConfig: # "@type": "org.noear.solon.ai.embedding.EmbeddingConfig" 8 | provider: "ollama" 9 | model: "bge-m3" 10 | apiUrl: "http://127.0.0.1:11434/api/embed" 11 | - task: "@InMemoryRepository" 12 | meta: 13 | documentSources: 14 | - "https://solon.noear.org/article/about?format=md" 15 | splitPipeline: 16 | - "org.noear.solon.ai.rag.splitter.RegexTextSplitter" 17 | - "org.noear.solon.ai.rag.splitter.TokenSizeTextSplitter" 18 | - task: "@ChatModel" 19 | meta: 20 | systemPrompt: "你是个知识库" 21 | stream: false 22 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 23 | provider: "ollama" 24 | model: "qwen2.5:1.5b" 25 | apiUrl: "http://127.0.0.1:11434/api/chat" 26 | - task: "@WebOutput" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/tool_case1.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: tool_case1 3 | layout: 4 | - task: "@VarInput" 5 | meta: 6 | message: "杭州今天天气怎么样?" 7 | - task: "@ChatModel" 8 | meta: 9 | systemPrompt: "你是个天气预报员" 10 | stream: false 11 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 12 | provider: "ollama" 13 | model: "qwen2.5:1.5b" 14 | apiUrl: "http://127.0.0.1:11434/api/chat" 15 | toolProviders: 16 | - "features.ai.flow.ToolDemo" 17 | - task: "@ConsoleOutput" -------------------------------------------------------------------------------- /solon-ai-flow/src/test/resources/flow/tool_case2.chain.yml: -------------------------------------------------------------------------------- 1 | 2 | id: tool_case2 3 | layout: 4 | - task: "@WebInput" 5 | - task: "@ChatModel" 6 | meta: 7 | systemPrompt: "你是个天气预报员" 8 | stream: false 9 | chatConfig: # "@type": "org.noear.solon.ai.chat.ChatConfig" 10 | provider: "ollama" 11 | model: "qwen2.5:1.5b" 12 | apiUrl: "http://127.0.0.1:11434/api/chat" 13 | toolProviders: 14 | - "features.ai.flow.ToolDemo" 15 | - task: "@WebOutput" -------------------------------------------------------------------------------- /solon-ai-llm-dialects/solon-ai-dialect-dashscope/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.noear 9 | solon-ai-parent 10 | 3.6.0 11 | ../../solon-ai-parent/pom.xml 12 | 13 | 14 | solon-ai-dialect-dashscope 15 | ${project.artifactId} 16 | jar 17 | 18 | 19 | 20 | org.noear 21 | solon-ai-core 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /solon-ai-llm-dialects/solon-ai-dialect-dashscope/src/main/resources/META-INF/native-image/org.noear.solon.ai.llm.dialect.dashscope/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "org.noear.solon.ai.llm.dialect.dashscope.DashscopeChatDialect", 4 | "allDeclaredConstructors": true 5 | }, 6 | { 7 | "name": "org.noear.solon.ai.llm.dialect.dashscope.DashscopeEmbeddingDialect", 8 | "allDeclaredConstructors": true 9 | }, 10 | { 11 | "name": "org.noear.solon.ai.llm.dialect.dashscope.DashscopeImageDialect", 12 | "allDeclaredConstructors": true 13 | }, 14 | { 15 | "name": "org.noear.solon.ai.llm.dialect.dashscope.DashscopeRerankingDialect", 16 | "allDeclaredConstructors": true 17 | } 18 | ] -------------------------------------------------------------------------------- /solon-ai-llm-dialects/solon-ai-dialect-ollama/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.noear 9 | solon-ai-parent 10 | 3.6.0 11 | ../../solon-ai-parent/pom.xml 12 | 13 | 14 | solon-ai-dialect-ollama 15 | ${project.artifactId} 16 | jar 17 | 18 | 19 | 20 | org.noear 21 | solon-ai-core 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /solon-ai-llm-dialects/solon-ai-dialect-ollama/src/main/resources/META-INF/native-image/org.noear.solon.ai.llm.dialect.ollama/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "org.noear.solon.ai.llm.dialect.ollama.OllamaChatDialect", 4 | "allDeclaredConstructors": true 5 | }, 6 | { 7 | "name": "org.noear.solon.ai.llm.dialect.ollama.OllamaEmbeddingDialect", 8 | "allDeclaredConstructors": true 9 | }, 10 | { 11 | "name": "org.noear.solon.ai.llm.dialect.ollama.OllamaImageDialect", 12 | "allDeclaredConstructors": true 13 | } 14 | ] -------------------------------------------------------------------------------- /solon-ai-llm-dialects/solon-ai-dialect-openai/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.noear 9 | solon-ai-parent 10 | 3.6.0 11 | ../../solon-ai-parent/pom.xml 12 | 13 | 14 | solon-ai-dialect-openai 15 | ${project.artifactId} 16 | jar 17 | 18 | 19 | 20 | org.noear 21 | solon-ai-core 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /solon-ai-llm-dialects/solon-ai-dialect-openai/src/main/resources/META-INF/native-image/org.noear.solon.ai.llm.dialect.openai/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "org.noear.solon.ai.llm.dialect.openai.OpenaiChatDialect", 4 | "allDeclaredConstructors": true 5 | }, 6 | { 7 | "name": "org.noear.solon.ai.llm.dialect.openai.OpenaiEmbeddingDialect", 8 | "allDeclaredConstructors": true 9 | }, 10 | { 11 | "name": "org.noear.solon.ai.llm.dialect.openai.OpenaiImageDialect", 12 | "allDeclaredConstructors": true 13 | }, 14 | { 15 | "name": "org.noear.solon.ai.llm.dialect.openai.OpenaiRerankingDialect", 16 | "allDeclaredConstructors": true 17 | } 18 | ] -------------------------------------------------------------------------------- /solon-ai-mcp/reconnect.md: -------------------------------------------------------------------------------- 1 | 2 | * 如果本次出错,下次会尝试重连 3 | * 每隔30秒会心跳,心跳如果失败,下次会尝试重连 -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/McpInitRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.server; 6 | 7 | import io.modelcontextprotocol.spec.McpSchema; 8 | import reactor.core.publisher.Mono; 9 | 10 | /** 11 | * Request handler for the initialization request. 12 | */ 13 | public interface McpInitRequestHandler { 14 | 15 | /** 16 | * Handles the initialization request. 17 | * @param initializeRequest the initialization request by the client 18 | * @return a Mono that will emit the result of the initialization 19 | */ 20 | Mono handle(McpSchema.InitializeRequest initializeRequest); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/McpNotificationHandler.java: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.server; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | /** 6 | * A handler for client-initiated notifications. 7 | */ 8 | public interface McpNotificationHandler { 9 | 10 | /** 11 | * Handles a notification from the client. 12 | * @param exchange the exchange associated with the client that allows calling back to 13 | * the connected client or inspecting its capabilities. 14 | * @param params the parameters of the notification. 15 | * @return a Mono that completes once the notification is handled. 16 | */ 17 | Mono handle(McpAsyncServerExchange exchange, Object params); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/McpRequestHandler.java: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.server; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | /** 6 | * A handler for client-initiated requests. 7 | * 8 | * @param the type of the response that is expected as a result of handling the 9 | * request. 10 | */ 11 | public interface McpRequestHandler { 12 | 13 | /** 14 | * Handles a request from the client. 15 | * @param exchange the exchange associated with the client that allows calling back to 16 | * the connected client or inspecting its capabilities. 17 | * @param params the parameters of the request. 18 | * @return a Mono that will emit the response to the request. 19 | */ 20 | Mono handle(McpAsyncServerExchange exchange, Object params); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessNotificationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.server; 6 | 7 | import reactor.core.publisher.Mono; 8 | 9 | /** 10 | * Handler for MCP notifications in a stateless server. 11 | * 12 | * @author Dariusz Jędrzejczyk 13 | */ 14 | public interface McpStatelessNotificationHandler { 15 | 16 | /** 17 | * Handle to notification and complete once done. 18 | * @param transportContext {@link McpTransportContext} associated with the transport 19 | * @param params the payload of the MCP notification 20 | * @return Mono which completes once the processing is done 21 | */ 22 | Mono handle(McpTransportContext transportContext, Object params); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.server; 6 | 7 | import reactor.core.publisher.Mono; 8 | 9 | /** 10 | * Handler for MCP requests in a stateless server. 11 | * 12 | * @param type of the MCP response 13 | * @author Dariusz Jędrzejczyk 14 | */ 15 | public interface McpStatelessRequestHandler { 16 | 17 | /** 18 | * Handle the request and complete with a result. 19 | * @param transportContext {@link McpTransportContext} associated with the transport 20 | * @param params the payload of the MCP request 21 | * @return Mono which completes with the response object 22 | */ 23 | Mono handle(McpTransportContext transportContext, Object params); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.server; 6 | 7 | /** 8 | * The contract for extracting metadata from a generic transport request of type 9 | * {@link T}. 10 | * 11 | * @param transport-specific representation of the request which allows extracting 12 | * metadata for use in the MCP features implementations. 13 | * @author Dariusz Jędrzejczyk 14 | */ 15 | public interface McpTransportContextExtractor { 16 | 17 | /** 18 | * Given an empty context, provides the means to fill it with transport-specific 19 | * metadata extracted from the request. 20 | * @param request the generic representation for the request in the context of a 21 | * specific transport implementation 22 | * @param transportContext the mutable context which can be filled in with metadata 23 | * @return the context filled in with metadata. It can be the same instance as 24 | * provided or a new one. 25 | */ 26 | McpTransportContext extract(T request, McpTransportContext transportContext); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/server/transport/IMcpHttpServerTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2024 the original author or authors. 3 | */ 4 | package io.modelcontextprotocol.server.transport; 5 | 6 | import org.noear.solon.SolonApp; 7 | 8 | /** 9 | * @author noear 10 | */ 11 | public interface IMcpHttpServerTransport { 12 | void toHttpHandler(SolonApp app); 13 | 14 | String getMcpEndpoint(); 15 | } 16 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/HttpHeaders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | /** 8 | * Names of HTTP headers in use by MCP HTTP transports. 9 | * 10 | * @author Dariusz Jędrzejczyk 11 | */ 12 | public interface HttpHeaders { 13 | 14 | /** 15 | * Identifies individual MCP sessions. 16 | */ 17 | String MCP_SESSION_ID = "mcp-session-id"; 18 | 19 | /** 20 | * Identifies events within an SSE Stream. 21 | */ 22 | String LAST_EVENT_ID = "Last-Event-ID"; 23 | 24 | /** 25 | * Identifies the MCP protocol version. 26 | */ 27 | String PROTOCOL_VERSION = "MCP-Protocol-Version"; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpLoggableSession.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | /** 8 | * An {@link McpSession} which is capable of processing logging notifications and keeping 9 | * track of a min logging level. 10 | * 11 | * @author Dariusz Jędrzejczyk 12 | */ 13 | public interface McpLoggableSession extends McpSession { 14 | 15 | /** 16 | * Set the minimum logging level for the client. Messages below this level will be 17 | * filtered out. 18 | * @param minLoggingLevel The minimum logging level 19 | */ 20 | void setMinLoggingLevel(McpSchema.LoggingLevel minLoggingLevel); 21 | 22 | /** 23 | * Allows checking whether a particular logging level is allowed. 24 | * @param loggingLevel the level to check 25 | * @return whether the logging at the specified level is permitted. 26 | */ 27 | boolean isNotificationForLevelAllowed(McpSchema.LoggingLevel loggingLevel); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpServerTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | /** 8 | * Marker interface for the server-side MCP transport. 9 | * 10 | * @author Christian Tzolov 11 | * @author Dariusz Jędrzejczyk 12 | */ 13 | public interface McpServerTransport extends McpTransport { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpServerTransportProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | /** 8 | * Classic implementation of {@link McpServerTransportProviderBase} for a single outgoing 9 | * stream in bidirectional communication (STDIO and the legacy HTTP SSE). 10 | * 11 | * @author Dariusz Jędrzejczyk 12 | */ 13 | public interface McpServerTransportProvider extends McpServerTransportProviderBase { 14 | 15 | /** 16 | * Sets the session factory that will be used to create sessions for new clients. An 17 | * implementation of the MCP server MUST call this method before any MCP interactions 18 | * take place. 19 | * @param sessionFactory the session factory to be used for initiating client sessions 20 | */ 21 | void setSessionFactory(McpServerSession.Factory sessionFactory); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | import java.util.List; 8 | 9 | import io.modelcontextprotocol.server.McpStatelessServerHandler; 10 | import io.modelcontextprotocol.util.Utils; 11 | import reactor.core.publisher.Mono; 12 | 13 | public interface McpStatelessServerTransport { 14 | 15 | void setMcpHandler(McpStatelessServerHandler mcpHandler); 16 | 17 | /** 18 | * Immediately closes all the transports with connected clients and releases any 19 | * associated resources. 20 | */ 21 | default void close() { 22 | this.closeGracefully().subscribe(); 23 | } 24 | 25 | /** 26 | * Gracefully closes all the transports with connected clients and releases any 27 | * associated resources asynchronously. 28 | * @return a {@link Mono} that completes when the connections have been closed. 29 | */ 30 | Mono closeGracefully(); 31 | 32 | default List protocolVersions() { 33 | return Utils.asList(ProtocolVersions.MCP_2025_03_26); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | import reactor.core.publisher.Mono; 8 | 9 | /** 10 | * Streamable HTTP server transport representing an individual SSE stream. 11 | * 12 | * @author Dariusz Jędrzejczyk 13 | */ 14 | public interface McpStreamableServerTransport extends McpServerTransport { 15 | 16 | /** 17 | * Send a message to the client with a message ID for use in the SSE event payload 18 | * @param message the JSON-RPC payload 19 | * @param messageId message id for SSE events 20 | * @return Mono which completes when done 21 | */ 22 | Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpTransportException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 - 2025 the original author or authors. 3 | */ 4 | package io.modelcontextprotocol.spec; 5 | 6 | /** 7 | * Exception thrown when there is an issue with the transport layer of the Model Context 8 | * Protocol (MCP). 9 | * 10 | *

11 | * This exception is used to indicate errors that occur during communication between the 12 | * MCP client and server, such as connection failures, protocol violations, or unexpected 13 | * responses. 14 | * 15 | * @author Christian Tzolov 16 | */ 17 | public class McpTransportException extends RuntimeException { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | public McpTransportException(String message) { 22 | super(message); 23 | } 24 | 25 | public McpTransportException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | public McpTransportException(Throwable cause) { 30 | super(cause); 31 | } 32 | 33 | public McpTransportException(String message, Throwable cause, boolean enableSuppression, 34 | boolean writableStackTrace) { 35 | super(message, cause, enableSuppression, writableStackTrace); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpTransportSessionNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.spec; 6 | 7 | /** 8 | * Exception that signifies that the server does not recognize the connecting client via 9 | * the presented transport session identifier. 10 | * 11 | * @author Dariusz Jędrzejczyk 12 | */ 13 | public class McpTransportSessionNotFoundException extends RuntimeException { 14 | 15 | /** 16 | * Construct an instance with a known {@link Exception cause}. 17 | * @param sessionId transport session identifier 18 | * @param cause the cause that was identified as a session not found error 19 | */ 20 | public McpTransportSessionNotFoundException(String sessionId, Exception cause) { 21 | super("Session " + sessionId + " not found on the server", cause); 22 | } 23 | 24 | /** 25 | * Construct an instance with the session identifier but without a {@link Exception 26 | * cause}. 27 | * @param sessionId transport session identifier 28 | */ 29 | public McpTransportSessionNotFoundException(String sessionId) { 30 | super("Session " + sessionId + " not found on the server"); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/ProtocolVersions.java: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.spec; 2 | 3 | public interface ProtocolVersions { 4 | 5 | /** 6 | * MCP protocol version for 2024-11-05. 7 | * https://modelcontextprotocol.io/specification/2024-11-05 8 | */ 9 | String MCP_2024_11_05 = "2024-11-05"; 10 | 11 | /** 12 | * MCP protocol version for 2025-03-26. 13 | * https://modelcontextprotocol.io/specification/2025-03-26 14 | */ 15 | String MCP_2025_03_26 = "2025-03-26"; 16 | 17 | /** 18 | * MCP protocol version for 2025-06-18. 19 | * https://modelcontextprotocol.io/specification/2025-06-18 20 | */ 21 | String MCP_2025_06_18 = "2025-06-18"; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/util/DeafaultMcpUriTemplateManagerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 - 2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.util; 6 | 7 | /** 8 | * @author Christian Tzolov 9 | */ 10 | public class DeafaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory { 11 | 12 | /** 13 | * Creates a new instance of {@link McpUriTemplateManager} with the specified URI 14 | * template. 15 | * @param uriTemplate The URI template to be used for variable extraction 16 | * @return A new instance of {@link McpUriTemplateManager} 17 | * @throws IllegalArgumentException if the URI template is null or empty 18 | */ 19 | @Override 20 | public McpUriTemplateManager create(String uriTemplate) { 21 | return new DefaultMcpUriTemplateManager(uriTemplate); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/io/modelcontextprotocol/util/McpUriTemplateManagerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 - 2025 the original author or authors. 3 | */ 4 | 5 | package io.modelcontextprotocol.util; 6 | 7 | /** 8 | * Factory interface for creating instances of {@link McpUriTemplateManager}. 9 | * 10 | * @author Christian Tzolov 11 | */ 12 | public interface McpUriTemplateManagerFactory { 13 | 14 | /** 15 | * Creates a new instance of {@link McpUriTemplateManager} with the specified URI 16 | * template. 17 | * @param uriTemplate The URI template to be used for variable extraction 18 | * @return A new instance of {@link McpUriTemplateManager} 19 | * @throws IllegalArgumentException if the URI template is null or empty 20 | */ 21 | McpUriTemplateManager create(String uriTemplate); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/org/noear/solon/ai/annotation/PromptMapping.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.annotation; 17 | 18 | import java.lang.annotation.*; 19 | 20 | /** 21 | * 提示语映射 22 | * 23 | * @author noear 24 | * @since 3.1 25 | */ 26 | @Target({ElementType.METHOD}) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Documented 29 | public @interface PromptMapping { 30 | /** 31 | * 名字 32 | */ 33 | String name() default ""; 34 | 35 | /** 36 | * 标题 37 | */ 38 | String title() default ""; 39 | 40 | /** 41 | * 描述 42 | */ 43 | String description(); 44 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/McpChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.mcp; 17 | 18 | /** 19 | * Mcp 通道 20 | * 21 | * @author noear 22 | * @since 3.1 23 | */ 24 | public interface McpChannel { 25 | String STDIO = "stdio"; 26 | String SSE = "sse"; 27 | String STREAMABLE = "streamable"; 28 | // String STATELESS = "Stateless"; 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/exception/McpException.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.mcp.exception; 2 | 3 | import org.noear.solon.exception.SolonException; 4 | 5 | /** 6 | * @author noear 7 | * @since 3.1 8 | */ 9 | public class McpException extends SolonException { 10 | public McpException(String message) { 11 | super(message); 12 | } 13 | 14 | public McpException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public McpException(Throwable cause) { 19 | super(cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/server/IMcpServerEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.mcp.server; 17 | 18 | /** 19 | * Mcp 服务端点声明(方便第三方框架批量获取后,构建 McpServerEndpointProvider) 20 | * 21 | * @author noear 22 | * @since 3.5 23 | */ 24 | public interface IMcpServerEndpoint { 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/server/prompt/PromptProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.mcp.server.prompt; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * 提示语提供者 22 | * 23 | * @author noear 24 | * @since 3.2 25 | */ 26 | public interface PromptProvider { 27 | /** 28 | * 获取提示语 29 | */ 30 | Collection getPrompts(); 31 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/server/resource/ResourceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 noear.org and authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.noear.solon.ai.mcp.server.resource; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * 资源提供者 22 | * 23 | * @author noear 24 | * @since 3.2 25 | */ 26 | public interface ResourceProvider { 27 | /** 28 | * 获取资源 29 | */ 30 | Collection getResources(); 31 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/main/resources/META-INF/solon/solon-ai-mcp.properties: -------------------------------------------------------------------------------- 1 | solon.plugin=org.noear.solon.ai.mcp.integration.McpPlugin 2 | solon.plugin.priority=20 -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/client/McpClientApp.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.client; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.annotation.Import; 5 | 6 | /** 7 | * @author noear 2025/4/12 created 8 | */ 9 | @Import(profiles = "app-client.yml") 10 | public class McpClientApp { 11 | public static void main(String[] args) { 12 | Solon.start(McpClientApp.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/client/McpClientConfig.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.client; 2 | 3 | import org.noear.solon.ai.chat.ChatConfig; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.ai.mcp.client.McpClientProvider; 6 | import org.noear.solon.annotation.Bean; 7 | import org.noear.solon.annotation.Configuration; 8 | import org.noear.solon.annotation.Inject; 9 | 10 | @Configuration 11 | public class McpClientConfig { 12 | @Bean 13 | public McpClientProvider clientWrapper(@Inject("${solon.ai.mcp.client.demo}") McpClientProvider client) { 14 | return client; 15 | } 16 | 17 | @Bean 18 | public ChatModel chatModel(@Inject("${solon.ai.chat.demo}") ChatConfig chatConfig, McpClientProvider toolProvider) { 19 | return ChatModel.of(chatConfig) 20 | .defaultToolsAdd(toolProvider) 21 | .build(); 22 | } 23 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/client_auth/AuthDemo.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.client_auth; 2 | 3 | import org.noear.solon.ai.mcp.client.McpClientProvider; 4 | 5 | /** 6 | * @author noear 2025/4/21 created 7 | */ 8 | public class AuthDemo { 9 | public void case1() { 10 | //通过 queryString 传递(需要 3.2.1-M1 或之后) 11 | McpClientProvider toolProvider = McpClientProvider.builder() 12 | .apiUrl("http://xxx.xxx.xxx/sse?key=yyy") 13 | .build(); 14 | } 15 | 16 | public void case2() { 17 | //通过 baisc auth 传递 18 | McpClientProvider toolProvider = McpClientProvider.builder() 19 | .apiUrl("http://xxx.xxx.xxx/sse") 20 | .apiKey("yyy") 21 | .build(); 22 | } 23 | 24 | public void case3() { 25 | //通过 baisc auth 传递 26 | McpClientProvider toolProvider = McpClientProvider.builder() 27 | .apiUrl("http://xxx.xxx.xxx/sse") 28 | .headerSet("tokey", "yyy") 29 | .build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/client_ssl/SslDemo.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.client_ssl; 2 | 3 | import io.modelcontextprotocol.spec.McpSchema; 4 | import org.noear.solon.Utils; 5 | import org.noear.solon.ai.mcp.client.McpClientProvider; 6 | import org.noear.solon.net.http.impl.HttpSslSupplierDefault; 7 | 8 | /** 9 | * @author noear 2025/7/24 created 10 | */ 11 | public class SslDemo { 12 | public void case1() { 13 | //通过 queryString 传递(需要 3.2.1-M1 或之后) 14 | McpClientProvider clientProvider = McpClientProvider.builder() 15 | .apiUrl("http://xxx.xxx.xxx/sse?key=yyy") 16 | .httpSsl(HttpSslSupplierDefault.getInstance()) 17 | .build(); 18 | 19 | 20 | clientProvider.getClient() 21 | .callTool(new McpSchema.CallToolRequest("demo", Utils.asMap("a", 1))) 22 | .doOnNext(rest ->{ 23 | System.out.println(rest); 24 | }) 25 | .subscribe(); 26 | } 27 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/ChatConfig.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.annotation.Bean; 5 | import org.noear.solon.annotation.Configuration; 6 | 7 | /** 8 | * 9 | * @author noear 2025/9/23 created 10 | * 11 | */ 12 | @Configuration 13 | public class ChatConfig { 14 | /** 15 | * 与大模型集成 16 | */ 17 | @Bean 18 | public ChatModel chatModel() throws Exception { 19 | return ChatModel.of(_Constants.chat_apiUrl) 20 | .provider(_Constants.chat_provider) 21 | .model(_Constants.chat_model) 22 | .build(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/ChatController.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.annotation.Controller; 6 | import org.noear.solon.annotation.Inject; 7 | import org.noear.solon.annotation.Mapping; 8 | import org.noear.solon.annotation.Produces; 9 | import org.noear.solon.core.util.MimeType; 10 | import reactor.core.publisher.Flux; 11 | 12 | /** 13 | * @author noear 2025/4/14 created 14 | */ 15 | @Slf4j 16 | @Controller 17 | public class ChatController { 18 | @Inject 19 | ChatModel chatModel; 20 | 21 | @Produces(MimeType.TEXT_EVENT_STREAM_VALUE) 22 | @Mapping("/test/stream") 23 | public Flux stream(String prompt) throws Exception { 24 | return Flux.from(chatModel.prompt(prompt).stream()) 25 | //.subscribeOn(Schedulers.boundedElastic()) //加这个打印效果更好 26 | .filter(resp -> resp.hasContent()) 27 | .map(resp -> resp.getContent()) 28 | .concatWithValues("[DONE]"); //有些前端框架,需要 [DONE] 实识用作识别 29 | } 30 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/FilterImpl.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.noear.solon.annotation.Component; 5 | import org.noear.solon.core.handle.Context; 6 | import org.noear.solon.core.handle.Filter; 7 | import org.noear.solon.core.handle.FilterChain; 8 | 9 | /** 10 | * @author noear 2025/5/28 created 11 | */ 12 | @Slf4j 13 | @Component 14 | public class FilterImpl implements Filter { 15 | @Override 16 | public void doFilter(Context ctx, FilterChain chain) throws Throwable { 17 | if (ctx.pathNew().equals("/demo2/sse") || ctx.pathNew().equals("/demo4/sse")) { 18 | log.warn(">> params: " + ctx.paramMap()); 19 | log.warn(">> headers: " + ctx.headerMap()); 20 | } 21 | 22 | chain.doFilter(ctx); 23 | } 24 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpRedirectServer5.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Component; 7 | import org.noear.solon.annotation.Controller; 8 | import org.noear.solon.annotation.Mapping; 9 | import org.noear.solon.annotation.Param; 10 | import org.noear.solon.core.handle.Context; 11 | import org.noear.solon.core.handle.Filter; 12 | import org.noear.solon.core.handle.FilterChain; 13 | 14 | /** 15 | * @author noear 2025/4/8 created 16 | */ 17 | @Mapping("/demo5/jump") 18 | @Controller 19 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/demo5/sse") 20 | public class McpRedirectServer5 { 21 | @ToolMapping(description = "查询天气预报", returnDirect = true) 22 | public String getWeather(@Param(description = "城市位置") String location, Context ctx) { 23 | System.out.println("------------: sessionId: " + ctx.sessionId()); 24 | 25 | ctx.realIp(); 26 | 27 | return "晴,14度"; 28 | } 29 | 30 | @Mapping("sse") 31 | public void sse(Context ctx) throws Exception { 32 | ctx.redirect("/demo5/sse", 307); 33 | } 34 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerApp.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.annotation.Import; 5 | import org.noear.solon.server.http.HttpServerConfigure; 6 | 7 | /** 8 | * @author noear 2025/4/8 created 9 | */ 10 | @Import(profiles = "app-server.yml") 11 | public class McpServerApp { 12 | public static void main(String[] args) { 13 | if (Solon.app() != null) { 14 | if (Solon.app().source() != McpServerApp.class) { 15 | Solon.stopBlock(); 16 | } 17 | } 18 | 19 | Solon.start(McpServerApp.class, args, app -> { 20 | app.onEvent(HttpServerConfigure.class, e -> { 21 | e.enableDebug(true); 22 | }); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerAuth.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Component; 7 | import org.noear.solon.annotation.Param; 8 | import org.noear.solon.core.handle.Context; 9 | import org.noear.solon.core.handle.Filter; 10 | import org.noear.solon.core.handle.FilterChain; 11 | 12 | /** 13 | * @author noear 2025/7/1 created 14 | */ 15 | @Component 16 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/auth/sse") 17 | public class McpServerAuth implements Filter { 18 | @ToolMapping(description = "查询天气预报") 19 | public String getWeather(@Param(description = "城市位置") String location) { 20 | return "晴,14度"; 21 | } 22 | 23 | @Override 24 | public void doFilter(Context ctx, FilterChain chain) throws Throwable { 25 | if (ctx.pathNew().endsWith("/auth/sse")) { 26 | if (ctx.param("user").equals("1") == false) { 27 | ctx.status(401); 28 | return; 29 | } 30 | } 31 | 32 | chain.doFilter(ctx); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerConfig.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.ai.chat.tool.MethodToolProvider; 4 | import org.noear.solon.ai.mcp.server.McpServerEndpointProvider; 5 | import org.noear.solon.annotation.Bean; 6 | import org.noear.solon.annotation.Configuration; 7 | import org.noear.solon.annotation.Inject; 8 | 9 | /** 10 | * @author noear 2025/4/17 created 11 | */ 12 | @Configuration 13 | public class McpServerConfig { 14 | //用配置构建 15 | @Bean 16 | public McpServerEndpointProvider demo1(@Inject("${solon.ai.mcp.server.demo1}") McpServerEndpointProvider serverEndpoint, 17 | McpServerTool serverTool) { 18 | serverEndpoint.addTool(new MethodToolProvider(serverTool)); 19 | 20 | return serverEndpoint; 21 | } 22 | 23 | //用构建器构建 24 | //@Bean 25 | public McpServerEndpointProvider demo2(McpServerTool2 serverTool) { 26 | McpServerEndpointProvider serverEndpoint = McpServerEndpointProvider.builder() 27 | .name("demo2") 28 | .mcpEndpoint("/demo2/sse") 29 | .build(); 30 | 31 | serverEndpoint.addTool(new MethodToolProvider(serverTool)); 32 | 33 | return serverEndpoint; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerTool.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Component; 5 | import org.noear.solon.annotation.Param; 6 | 7 | /** 8 | * @author noear 2025/4/8 created 9 | */ 10 | @Component 11 | public class McpServerTool { 12 | // 13 | // 建议开启编译参数:-parameters (否则,要再配置参数的 name) 14 | // 15 | @ToolMapping(description = "查询天气预报") 16 | public String getWeather(@Param(description = "城市位置") String location) { 17 | return "晴,14度"; 18 | } 19 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerTool3.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Param; 7 | 8 | /** 9 | * @author noear 2025/4/8 created 10 | */ 11 | @McpServerEndpoint(channel = McpChannel.STDIO) 12 | public class McpServerTool3 { 13 | // 14 | // 建议开启编译参数:-parameters (否则,要再配置参数的 name) 15 | // 16 | @ToolMapping(description = "查询天气预报") 17 | public String getWeather(@Param(description = "城市位置") String location) { 18 | return "晴,14度"; 19 | } 20 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/_Constants.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server; 2 | 3 | 4 | public class _Constants { 5 | //换成自己的模型配置(参考:https://solon.noear.org/article/918) 6 | public static final String chat_apiUrl = "http://127.0.0.1:11434/api/chat"; 7 | public static final String chat_provider = "ollama"; 8 | public static final String chat_model = "qwen2.5:1.5b"; //"llama3.2";//deepseek-r1:1.5b; 9 | 10 | 11 | 12 | //换成自己的模型配置(参考:https://solon.noear.org/article/934) 13 | public static final String embedding_apiUrl = "http://127.0.0.1:11434/api/embed"; 14 | public static final String embedding_provider = "ollama"; 15 | public static final String embedding_model = "bge-m3";// 16 | } 17 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/art1/CalculatorTools.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server.art1; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Param; 7 | 8 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/mcp/CalculatorTools/sse") 9 | public class CalculatorTools { 10 | @ToolMapping(description = "将两个数字相加") 11 | public int add(@Param int a, @Param int b) { 12 | return a + b; 13 | } 14 | 15 | @ToolMapping(description = "从第一个数中减去第二个数") 16 | public int subtract(@Param int a, @Param int b) { 17 | return a - b; 18 | } 19 | 20 | @ToolMapping(description = "将两个数相乘") 21 | public int multiply(@Param int a, @Param int b) { 22 | return a * b; 23 | } 24 | 25 | @ToolMapping(description = "将第一个数除以第二个数") 26 | public float divide(@Param float a, @Param float b) { 27 | return a / b; 28 | } 29 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/outputschema/dataobject/CityInfo.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server.outputschema.dataobject; 2 | 3 | import org.noear.solon.annotation.Param; 4 | 5 | /** 6 | * @Auther: ityangs@163.com 7 | * @Date: 2025/5/20 16:01 8 | * @Description: 9 | */ 10 | public class CityInfo { 11 | @Param(description = "城市名") 12 | private String name; 13 | 14 | @Param(description = "城市编码") 15 | private String code; 16 | 17 | // 构造器、getter、setter 省略 18 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/outputschema/dataobject/Result.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server.outputschema.dataobject; 2 | 3 | /** 4 | * @Auther: ityangs@163.com 5 | * @Date: 2025/5/20 15:59 6 | * @Description: 7 | */ 8 | public class Result { 9 | private String code; 10 | private String message; 11 | private T data; 12 | 13 | // 省略构造器、getter、setter 14 | 15 | public static Result ok(T data) { 16 | Result result = new Result<>(); 17 | return result; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server/outputschema/dataobject/UserInfo.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server.outputschema.dataobject; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import org.noear.solon.annotation.Param; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * @Auther: ityangs@163.com 10 | * @Date: 2025/5/20 15:59 11 | * @Description: 12 | */ 13 | public class UserInfo { 14 | @Param(description = "用户名") 15 | private String name; 16 | 17 | @Param(description = "年龄") 18 | private Integer age; 19 | 20 | @Param(description = "性别。0表示女,1表示男") 21 | private Integer gender; 22 | 23 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 24 | @Param(description = "创建时间") 25 | private Date created = new Date(1751798348208L); 26 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server_context_path/McpServerApp.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server_context_path; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.annotation.Import; 5 | import org.noear.solon.server.http.HttpServerConfigure; 6 | 7 | /** 8 | * @author noear 2025/4/8 created 9 | */ 10 | @Import(profiles = "app-server2.yml") 11 | public class McpServerApp { 12 | public static void main(String[] args) { 13 | if (Solon.app() != null) { 14 | if (Solon.app().source() != McpServerApp.class) { 15 | Solon.stopBlock(); 16 | } 17 | } 18 | 19 | Solon.start(McpServerApp.class, args, app -> { 20 | app.onEvent(HttpServerConfigure.class, e -> { 21 | e.enableDebug(true); 22 | }); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server_resume/McpServerApp.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server_resume; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.annotation.Import; 5 | import org.noear.solon.scheduling.annotation.EnableScheduling; 6 | 7 | /** 8 | * @author noear 2025/4/23 created 9 | */ 10 | @EnableScheduling 11 | @Import(profiles = "app-server.yml") 12 | public class McpServerApp { 13 | public static void main(String[] args) { 14 | Solon.start(McpServerApp.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server_resume/McpServerTool.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server_resume; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.McpServerEndpointProvider; 6 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 7 | import org.noear.solon.annotation.Inject; 8 | import org.noear.solon.annotation.Param; 9 | import org.noear.solon.scheduling.annotation.Scheduled; 10 | 11 | /** 12 | * @author noear 2025/4/8 created 13 | */ 14 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, name = "mcp-server1") 15 | public class McpServerTool { 16 | @ToolMapping(description = "查询天气预报") 17 | public String getWeather(@Param(description = "城市位置") String location) { 18 | return "晴,14度"; 19 | } 20 | 21 | //注入当前工具对应的端点提供者 22 | @Inject("mcp-server1") 23 | private McpServerEndpointProvider serverEndpointProvider; 24 | 25 | //30秒为间隔(暂停或恢复)//或者用 web 控制 26 | @Scheduled(fixedRate = 30_000) 27 | public void pauseAndResume() { 28 | if (serverEndpointProvider.pause() == false) { 29 | //如果暂停失败,说明之前已经暂停 30 | serverEndpointProvider.resume(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/demo/ai/mcp/server_resume/McpServerTool2.java: -------------------------------------------------------------------------------- 1 | package demo.ai.mcp.server_resume; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Param; 7 | 8 | /** 9 | * @author noear 2025/4/8 created 10 | */ 11 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/case2/sse") 12 | public class McpServerTool2 { 13 | @ToolMapping(description = "查询天气预报") 14 | public String getWeather(@Param(description = "城市位置") String location) { 15 | return "晴,14度"; 16 | } 17 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/features/ai/mcp/client/ConfigTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.mcp.client; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.Solon; 5 | import org.noear.solon.ai.mcp.client.McpClientProperties; 6 | import org.noear.solon.test.SolonTest; 7 | 8 | @SolonTest(args = "-cfg=app-client.yml") 9 | public class ConfigTest { 10 | @Test 11 | public void case1() { 12 | McpClientProperties config = Solon.cfg().toBean("solon.ai.mcp.client.proxy1", McpClientProperties.class); 13 | assert config != null; 14 | assert config.getHttpProxy() != null; 15 | } 16 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/features/ai/mcp/client/McpClientErrTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.mcp.client; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.client.McpClientProvider; 6 | 7 | /** 8 | * @author noear 2025/6/12 created 9 | */ 10 | public class McpClientErrTest { 11 | @Test 12 | public void connTest() throws Throwable { 13 | McpClientProvider clientProvider = McpClientProvider.builder() 14 | .channel(McpChannel.STREAMABLE) 15 | .apiUrl("https://mcp.map.baidu.com/sse") 16 | .build(); 17 | 18 | long start = System.currentTimeMillis(); 19 | try { 20 | clientProvider.getTools(); 21 | clientProvider.close(); 22 | } catch (Throwable e) { 23 | 24 | } 25 | long end = System.currentTimeMillis(); 26 | 27 | assert start - end < 1000; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/features/ai/mcp/client/McpRedirectTest5.java: -------------------------------------------------------------------------------- 1 | package features.ai.mcp.client; 2 | 3 | import demo.ai.mcp.server.McpServerApp; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.jupiter.api.Test; 6 | import org.noear.solon.Utils; 7 | import org.noear.solon.ai.mcp.McpChannel; 8 | import org.noear.solon.ai.mcp.client.McpClientProvider; 9 | import org.noear.solon.test.SolonTest; 10 | 11 | import java.util.Collections; 12 | 13 | /** 14 | * @author noear 2025/5/16 created 15 | */ 16 | @Slf4j 17 | @SolonTest(McpServerApp.class) 18 | public class McpRedirectTest5 { 19 | @Test 20 | public void tool1() throws Exception { 21 | McpClientProvider mcpClient = McpClientProvider.builder() 22 | .channel(McpChannel.STREAMABLE) 23 | .apiUrl("http://localhost:8081/demo5/jump/sse") 24 | .build(); 25 | 26 | String response = mcpClient.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent(); 27 | 28 | log.warn("{}", response); 29 | assert Utils.isNotEmpty(response); 30 | mcpClient.close(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/features/ai/mcp/client/McpToolMixTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.mcp.client; 2 | 3 | import demo.ai.mcp.server.McpServerApp; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.jupiter.api.Test; 6 | import org.noear.solon.ai.mcp.McpChannel; 7 | import org.noear.solon.ai.mcp.client.McpClientProvider; 8 | import org.noear.solon.ai.media.Text; 9 | import org.noear.solon.test.SolonTest; 10 | 11 | /** 12 | * @author noear 2025/5/11 created 13 | */ 14 | @Slf4j 15 | @SolonTest(McpServerApp.class) 16 | public class McpToolMixTest { 17 | 18 | 19 | @Test 20 | public void case1() throws Exception { 21 | McpClientProvider mcpClient = McpClientProvider.builder() 22 | .channel(McpChannel.STREAMABLE) 23 | .apiUrl("http://localhost:8081/mcp/WeatherTools/sse") 24 | .build(); 25 | 26 | Text mediaText = mcpClient.readResourceAsText("weather://cities"); 27 | 28 | System.out.println(mediaText); 29 | 30 | assert "[Tokyo, Sydney, Tokyo]".equals(mediaText.getContent()); 31 | mcpClient.close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/AgentTaskHandler.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a; 2 | 3 | /** 4 | * 智能体任务处理器 5 | * 6 | * @author noear 7 | * @since 3.5 8 | */ 9 | public interface AgentTaskHandler { 10 | /** 11 | * 智能体任务处理 12 | */ 13 | String handleTask(String message) throws Throwable; 14 | } 15 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a/Tools1.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.a2a; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Param; 5 | 6 | /** 7 | * @author haiTao.Wang on 2025/8/21. 8 | */ 9 | public class Tools1 { 10 | 11 | @ToolMapping(description = "查询天气预报", returnDirect = true) 12 | public String getWeather(@Param(description = "城市位置") String location) { 13 | return location + "天气晴"; 14 | } 15 | 16 | @ToolMapping(description = "查询温度", returnDirect = true) 17 | public String getTemperature(@Param(description = "城市位置") String location) { 18 | return location + "温度14度"; 19 | } 20 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a/Tools2.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.a2a; 2 | 3 | import org.noear.snack.core.utils.StringUtil; 4 | import org.noear.solon.ai.annotation.ToolMapping; 5 | import org.noear.solon.annotation.Param; 6 | 7 | /** 8 | * @author haiTao.Wang on 2025/8/21. 9 | */ 10 | public class Tools2 { 11 | 12 | @ToolMapping(description = "根据天气推荐旅游景点", returnDirect = true) 13 | public String recommendTourist(@Param(description = "天气") String weather) { 14 | if (StringUtil.isEmpty(weather)) { 15 | return "请输入天气"; 16 | } 17 | 18 | if (weather.contains("晴")) { 19 | return "公园、爬山等室外运动"; 20 | } 21 | return "海洋馆、科技馆等室内运动"; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a_remote/App.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.a2a_remote; 2 | 3 | import org.noear.solon.Solon; 4 | 5 | /** 6 | * @author noear 2025/8/31 created 7 | */ 8 | public class App { 9 | public static void main(String[] args) { 10 | Solon.start(App.class, new String[]{"--server.port=9001"}); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a_remote/Server1.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.a2a_remote; 2 | 3 | import lab.ai.a2a.AgentTaskHandler; 4 | import lab.ai.a2a.demo.a2a.Tools1; 5 | import org.noear.solon.ai.annotation.ToolMapping; 6 | import org.noear.solon.ai.chat.ChatModel; 7 | import org.noear.solon.ai.mcp.McpChannel; 8 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 9 | 10 | /** 11 | * @author noear 2025/8/31 created 12 | */ 13 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, mcpEndpoint = "mcp1") 14 | public class Server1 implements AgentTaskHandler { 15 | ChatModel chatModel = ChatModel.of("http://127.0.0.1:11434/api/chat") 16 | .model("qwen2.5:1.5b") 17 | .provider("ollama") 18 | .defaultToolsAdd(new Tools1()) 19 | .build(); 20 | 21 | @ToolMapping(name = "weather_agent", description = "专业的天气预报助手。主要任务是利用所提供的工具获取并传递天气信息") 22 | @Override 23 | public String handleTask(String message) throws Throwable { 24 | return chatModel.prompt(message).call().getMessage().getResultContent(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a_remote/Server2.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.a2a_remote; 2 | 3 | import lab.ai.a2a.AgentTaskHandler; 4 | import lab.ai.a2a.demo.a2a.Tools2; 5 | import org.noear.solon.ai.annotation.ToolMapping; 6 | import org.noear.solon.ai.chat.ChatModel; 7 | import org.noear.solon.ai.mcp.McpChannel; 8 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 9 | 10 | /** 11 | * @author noear 2025/8/31 created 12 | */ 13 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, mcpEndpoint = "mcp2") 14 | public class Server2 implements AgentTaskHandler { 15 | ChatModel chatModel = ChatModel.of("http://127.0.0.1:11434/api/chat") 16 | .model("qwen2.5:latest") 17 | .provider("ollama") 18 | .defaultToolsAdd(new Tools2()) 19 | .build(); 20 | 21 | @ToolMapping(name = "spot_agent", description = "专业的景区推荐助手。主要任务是推荐景点信息") 22 | @Override 23 | public String handleTask(String message) throws Throwable { 24 | return chatModel.prompt(message).call().getMessage().getResultContent(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/tool_only/Tools1.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.tool_only; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Param; 5 | 6 | /** 7 | * @author haiTao.Wang on 2025/8/21. 8 | */ 9 | public class Tools1 { 10 | 11 | @ToolMapping(description = "查询天气预报") 12 | public String getWeather(@Param(description = "城市位置") String location) { 13 | return location + "天气晴"; 14 | } 15 | 16 | @ToolMapping(description = "查询温度") 17 | public String getTemperature(@Param(description = "城市位置") String location) { 18 | return location + "温度14度"; 19 | } 20 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/a2a/demo/tool_only/Tools2.java: -------------------------------------------------------------------------------- 1 | package lab.ai.a2a.demo.tool_only; 2 | 3 | import org.noear.snack.core.utils.StringUtil; 4 | import org.noear.solon.ai.annotation.ToolMapping; 5 | import org.noear.solon.annotation.Param; 6 | 7 | /** 8 | * @author haiTao.Wang on 2025/8/21. 9 | */ 10 | public class Tools2 { 11 | 12 | @ToolMapping(description = "根据天气推荐旅游景点") 13 | public String recommendTourist(@Param(description = "天气") String weather) { 14 | if (StringUtil.isEmpty(weather)) { 15 | return "请输入天气"; 16 | } 17 | 18 | if (weather.contains("晴")) { 19 | return "公园、爬山等室外运动"; 20 | } 21 | return "海洋馆、科技馆等室内运动"; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/client/DashscopeTest.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.client; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.chat.tool.FunctionTool; 5 | import org.noear.solon.ai.mcp.McpChannel; 6 | import org.noear.solon.ai.mcp.client.McpClientProvider; 7 | 8 | import java.util.Collection; 9 | 10 | /** 11 | * 12 | * @author noear 2025/9/3 created 13 | * 14 | */ 15 | public class DashscopeTest { 16 | @Test 17 | public void case1() { 18 | McpClientProvider clientProvider = McpClientProvider.builder() 19 | .channel(McpChannel.SSE) 20 | .apiUrl("https://dashscope.aliyuncs.com/api/v1/mcps/amap-maps/sse") 21 | .apiKey("xxx") 22 | .build(); 23 | 24 | Collection tools = clientProvider.getTools(); 25 | for (FunctionTool tool : tools) { 26 | System.out.println(tool.inputSchema()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpServerTool.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.client; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 6 | import org.noear.solon.annotation.Param; 7 | 8 | @McpServerEndpoint(channel = McpChannel.STDIO) //表示使用 stdio 9 | public class McpServerTool { 10 | @ToolMapping(description = "查询天气预报") 11 | public String get_weather(@Param(description = "城市位置") String location) { 12 | return "晴,14度"; 13 | } 14 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpSseClientCloseTest.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.client; 2 | 3 | import org.noear.solon.ai.mcp.client.McpClientProvider; 4 | 5 | import java.util.Collections; 6 | 7 | /** 8 | * @author noear 2025/4/22 created 9 | */ 10 | public class McpSseClientCloseTest { 11 | public static void main(String[] args) throws Exception { 12 | McpClientProvider toolProvider = McpClientProvider.builder() 13 | .apiUrl("http://localhost:8081/sse") 14 | .build(); 15 | 16 | 17 | call(toolProvider); 18 | 19 | //关闭后即退出 20 | toolProvider.close(); 21 | } 22 | 23 | private static void call(McpClientProvider toolProvider) { 24 | try { 25 | String response = toolProvider.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent(); 26 | assert response != null; 27 | System.err.println(response); 28 | } catch (Throwable e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpSseClientRetryTest.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.client; 2 | 3 | import org.noear.solon.ai.mcp.client.McpClientProvider; 4 | import org.noear.solon.core.util.RunUtil; 5 | 6 | import java.util.Collections; 7 | 8 | /** 9 | * @author noear 2025/4/22 created 10 | */ 11 | public class McpSseClientRetryTest { 12 | public static void main(String[] args) throws Exception { 13 | McpClientProvider toolProvider = McpClientProvider.builder() 14 | .apiUrl("http://localhost:8081/sse") 15 | .build(); 16 | 17 | 18 | call(toolProvider); 19 | 20 | 21 | RunUtil.delayAndRepeat(() -> { 22 | call(toolProvider); 23 | }, 1000); 24 | 25 | System.in.read(); 26 | } 27 | 28 | private static void call(McpClientProvider toolProvider) { 29 | try { 30 | String response = toolProvider.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent(); 31 | assert response != null; 32 | System.err.println(response); 33 | } catch (Throwable e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpSseClientRetryTest2.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.client; 2 | 3 | import org.noear.solon.ai.mcp.client.McpClientProvider; 4 | 5 | import java.util.Collections; 6 | 7 | /** 8 | * @author noear 2025/4/22 created 9 | */ 10 | public class McpSseClientRetryTest2 { 11 | public static void main(String[] args) throws Exception { 12 | McpClientProvider toolProvider = McpClientProvider.builder() 13 | .apiUrl("http://localhost:8081/sse") 14 | .build(); 15 | 16 | 17 | call(toolProvider); 18 | 19 | System.in.read(); 20 | } 21 | 22 | private static void call(McpClientProvider toolProvider) { 23 | try { 24 | String response = toolProvider.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent(); 25 | assert response != null; 26 | System.err.println(response); 27 | } catch (Throwable e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/client/server/McpSseToStdioServerDemo.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.client.server; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.ai.chat.tool.FunctionTool; 5 | import org.noear.solon.ai.chat.tool.ToolProvider; 6 | import org.noear.solon.ai.mcp.McpChannel; 7 | import org.noear.solon.ai.mcp.client.McpClientProvider; 8 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; 9 | 10 | import java.util.Collection; 11 | 12 | /** 13 | * 把 sse mcp-server 转为 stdio mcp-server 14 | */ 15 | @McpServerEndpoint(name = "sse-to-stdio-tool", channel = McpChannel.STDIO) 16 | public class McpSseToStdioServerDemo implements ToolProvider { 17 | McpClientProvider sseToolProvider = McpClientProvider.builder() 18 | .apiUrl("http://localhost:8081/sse") 19 | .build(); 20 | 21 | @Override 22 | public Collection getTools() { 23 | return sseToolProvider.getTools(); 24 | } 25 | 26 | public static void main(String[] args) { 27 | Solon.start(McpSseToStdioServerDemo.class, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/debug/client/McpClientLab.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.debug.client; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.noear.solon.ai.mcp.McpChannel; 5 | import org.noear.solon.ai.mcp.client.McpClientProvider; 6 | 7 | import java.util.Collections; 8 | 9 | /** 10 | * 11 | * @author noear 2025/8/22 created 12 | * 13 | */ 14 | @Slf4j 15 | public class McpClientLab { 16 | public static void main(String[] args) throws Exception { 17 | McpClientProvider mcpClient = McpClientProvider.builder() 18 | .channel(McpChannel.STREAMABLE) 19 | .apiUrl("http://localhost:8081/mcp/") 20 | .cacheSeconds(30) 21 | .build(); 22 | 23 | String response = mcpClient.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent(); 24 | 25 | log.warn("{}", response); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/java/lab/ai/mcp/debug/server/McpApp.java: -------------------------------------------------------------------------------- 1 | package lab.ai.mcp.debug.server; 2 | 3 | import org.noear.solon.Solon; 4 | import org.noear.solon.annotation.Import; 5 | 6 | @Import(profiles = "app-server.yml") 7 | public class McpApp { 8 | public static void main(String[] args) { 9 | Solon.start(McpApp.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/app-client-serverparams.yml: -------------------------------------------------------------------------------- 1 | solon.ai: 2 | mcp: 3 | client: 4 | demo1: 5 | channel: "stdio" 6 | command: "xxx" 7 | args: 8 | - "-jar" 9 | - "/Users/noear/Downloads/demo-mcp-stdio/target/demo-mcp-stdio.jar" 10 | env: 11 | a: "1" 12 | demo2: 13 | channel: "stdio" 14 | command: "xxx" 15 | args: ["-jar", "/Users/noear/Downloads/demo-mcp-stdio/target/demo-mcp-stdio.jar"] 16 | env: {a: "1"} 17 | -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/app-client.yml: -------------------------------------------------------------------------------- 1 | server.port: 8082 2 | server.idleTimeout: 120000 3 | 4 | solon.ai: 5 | chat: 6 | demo: 7 | apiUrl: "https://api.deepseek.com/v1/chat/completions" 8 | apiKey: "sk-9f4415ddc570496581897c22e3d41a54" 9 | provider: "deepseek" 10 | model: "deepseek-chat" 11 | mcp: 12 | client: 13 | demo1: 14 | channel: streamable 15 | apiUrl: "http://localhost:8081/sse" 16 | demo2: 17 | channel: streamable 18 | apiUrl: "http://localhost:8081/demo2/sse" 19 | proxy1: 20 | channel: streamable 21 | apiUrl: "http://localhost:8081/sse" 22 | httpProxy: 23 | type: "HTTP" 24 | host: "127.0.0.1" 25 | port: 7851 26 | 27 | solon.logging.logger.root.level: TRACE -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/app-server.yml: -------------------------------------------------------------------------------- 1 | server.port: 8081 2 | server.idleTimeout: 120000 3 | 4 | solon.ai.mcp.server: 5 | demo1: 6 | channel: streamable 7 | sseEndpoint: "/sse" 8 | demo2: 9 | channel: streamable 10 | sseEndpoint: "/demo2/sse" 11 | 12 | solon.logging.logger: 13 | root.level: TRACE -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/app-server2.yml: -------------------------------------------------------------------------------- 1 | server.port: 8082 2 | server.idleTimeout: 120000 3 | server.context-path: "test" 4 | 5 | solon.logging.logger: 6 | root.level: TRACE -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/app.yml: -------------------------------------------------------------------------------- 1 | solon.logging.logger: 2 | root.level: DEBUG -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/mcpServers.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "server1": { 4 | "type": "streamable", 5 | "url": "http://localhost:8081/demo2/sse", 6 | "headers": { 7 | "Token": "value" 8 | } 9 | }, 10 | "fetch": { 11 | "type": "stdio", 12 | "command": "/full/path/to/bin/mcp-proxy", 13 | "args": [ 14 | "http://localhost:8932/sse" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /solon-ai-mcp/src/test/resources/mcpServers2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "server1": { 4 | "type": "streamable", 5 | "url": "http://localhost:8081/demo2/sse", 6 | "headers": { 7 | "Token": "value" 8 | } 9 | }, 10 | "fetch": { 11 | "command": "/full/path/to/bin/mcp-proxy", 12 | "args": [ 13 | "http://localhost:8932/sse" 14 | ] 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-ddl/src/test/resources/sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-ddl/src/test/resources/sqlite.db -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-excel/src/test/java/features/ai/load/excel/ExcelLoaderTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.load.excel; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.rag.Document; 5 | import org.noear.solon.ai.rag.loader.ExcelLoader; 6 | import org.noear.solon.core.util.ResourceUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | public class ExcelLoaderTest { 13 | private static final Logger log = LoggerFactory.getLogger(ExcelLoaderTest.class); 14 | 15 | @Test 16 | public void test1() throws Exception { 17 | ExcelLoader loader = new ExcelLoader(ResourceUtil.getResource("demo.xlsx")); 18 | List docs = loader.load(); 19 | System.out.println(docs); 20 | } 21 | 22 | @Test 23 | public void test2() throws Exception { 24 | ExcelLoader loader = new ExcelLoader(ResourceUtil.getResource("demo.xls")); 25 | List docs = loader.load(); 26 | System.out.println(docs); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xls -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xlsx -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo2.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo2.xlsx -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-html/src/test/java/features/ai/load/html/HtmlTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.load.html; 2 | 3 | import java.net.URI; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.noear.solon.ai.rag.Document; 12 | import org.noear.solon.ai.rag.loader.HtmlSimpleLoader; 13 | 14 | /** 15 | * HtmlSimpleLoader 测试用例 16 | * 17 | * @author noear 2025/2/21 created 18 | */ 19 | public class HtmlTest { 20 | private static final String TEST_URL = "https://solon.noear.org/article/about"; 21 | 22 | @Test 23 | public void testSingleUrl() throws Exception { 24 | // 测试单个URL加载 25 | HtmlSimpleLoader loader = new HtmlSimpleLoader(URI.create(TEST_URL).toURL()); 26 | List docs = loader.load(); 27 | 28 | Assertions.assertFalse(docs.isEmpty(), "文档列表不应为空"); 29 | Assertions.assertEquals(1, docs.size(), "应该只有一个文档"); 30 | 31 | Document doc = docs.get(0); 32 | Assertions.assertNotNull(doc.getContent(), "文档内容不应为空"); 33 | for (Document d : docs) { 34 | System.out.println(d.getContent()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfLoaderTest.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfLoaderTest.pdf -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfWithImg.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfWithImg.pdf -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-ppt/src/test/java/features/ai/load/ppt/PptLoaderTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.load.ppt; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.rag.Document; 5 | import org.noear.solon.ai.rag.loader.PptLoader; 6 | import org.noear.solon.core.util.ResourceUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | public class PptLoaderTest { 13 | private static final Logger log = LoggerFactory.getLogger(PptLoaderTest.class); 14 | 15 | @Test 16 | public void test1() throws Exception { 17 | PptLoader loader = new PptLoader(ResourceUtil.getResource("demo.pptx")) 18 | .options(opt -> opt.loadMode(PptLoader.LoadMode.PAGE)); 19 | List docs = loader.load(); 20 | System.out.println(docs); 21 | assert docs.size() == 2; 22 | } 23 | 24 | @Test 25 | public void test2() throws Exception { 26 | PptLoader loader = new PptLoader(ResourceUtil.getResource("demo.ppt")) 27 | .options(opt -> opt.loadMode(PptLoader.LoadMode.PAGE)); 28 | List docs = loader.load(); 29 | System.out.println(docs); 30 | assert docs.size() == 2; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.ppt -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.pptx -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-word/src/test/java/features/ai/load/word/WordLoaderTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.load.word; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.rag.Document; 5 | import org.noear.solon.ai.rag.loader.WordLoader; 6 | import org.noear.solon.core.util.ResourceUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | public class WordLoaderTest { 13 | private static final Logger log = LoggerFactory.getLogger(WordLoaderTest.class); 14 | 15 | @Test 16 | public void test1() throws Exception { 17 | WordLoader loader = new WordLoader(ResourceUtil.getResource("demo.docx")) 18 | .options(opt -> opt.loadMode(WordLoader.LoadMode.PARAGRAPH)); 19 | List docs = loader.load(); 20 | System.out.println(docs); 21 | assert docs.size() > 0; 22 | } 23 | 24 | @Test 25 | public void test2() throws Exception { 26 | WordLoader loader = new WordLoader(ResourceUtil.getResource("demo.doc")) 27 | .options(opt -> opt.loadMode(WordLoader.LoadMode.PARAGRAPH)); 28 | List docs = loader.load(); 29 | System.out.println(docs); 30 | assert docs.size() > 0; 31 | } 32 | } -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.doc -------------------------------------------------------------------------------- /solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.docx -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-chroma/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | networks: 4 | net: 5 | driver: bridge 6 | services: 7 | chromadb: 8 | image: chromadb/chroma:0.6.3 9 | volumes: 10 | - ./chromadb:/chroma/chroma 11 | environment: 12 | - IS_PERSISTENT=TRUE 13 | - PERSIST_DIRECTORY=/chroma/chroma # this is the default path, change it as needed 14 | - ANONYMIZED_TELEMETRY=${ANONYMIZED_TELEMETRY:-TRUE} 15 | ports: 16 | - 8000:8000 17 | networks: 18 | - net -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-chroma/src/main/java/org/noear/solon/ai/rag/repository/chroma/ChromaResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.chroma; 2 | 3 | /** 4 | * Chroma 响应基类 5 | * 6 | * @author 小奶奶花生米 7 | * @since 3.1 8 | */ 9 | public class ChromaResponse { 10 | private String error; 11 | private String message; 12 | 13 | public String getError() { 14 | return error; 15 | } 16 | 17 | public void setError(String error) { 18 | this.error = error; 19 | } 20 | 21 | public String getMessage() { 22 | return message; 23 | } 24 | 25 | public void setMessage(String message) { 26 | this.message = message; 27 | } 28 | 29 | public boolean hasError() { 30 | return error != null && !error.isEmpty(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-chroma/src/main/java/org/noear/solon/ai/rag/repository/chroma/CollectionsResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.chroma; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * Chroma 集合列表响应 8 | * 9 | * @author 小奶奶花生米 10 | * @since 3.1 11 | */ 12 | public class CollectionsResponse extends ChromaResponse { 13 | private List> collections; 14 | 15 | /** 16 | * 获取集合列表 17 | * 18 | * @return 集合列表 19 | */ 20 | public List> getCollections() { 21 | return collections; 22 | } 23 | 24 | /** 25 | * 设置集合列表 26 | * 27 | * @param collections 集合列表 28 | */ 29 | public void setCollections(List> collections) { 30 | this.collections = collections; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/AddDocResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * AddDocResponse 7 | * 8 | * @author 小奶奶花生米 9 | */ 10 | public class AddDocResponse extends DashVectorResponse>{ 11 | 12 | } 13 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/CreateCollectionResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | /** 4 | * CreateCollectionResponse 5 | * 6 | * @author 张超 7 | */ 8 | public class CreateCollectionResponse extends DashVectorResponse { 9 | } 10 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/DeleteCollectionResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | /** 4 | * DeleteCollectionResponse 5 | * 6 | * @author 小奶奶花生米 7 | */ 8 | public class DeleteCollectionResponse extends DashVectorResponse{ 9 | } 10 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/DeleteDocResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * DeleteDocResponse 7 | * 8 | * @author 小奶奶花生米 9 | */ 10 | public class DeleteDocResponse extends DashVectorResponse>{ 11 | } 12 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/DocOpResult.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | import org.noear.snack.annotation.ONodeAttr; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * DocOpResult 9 | * 10 | * @author 小奶奶花生米 11 | */ 12 | public class DocOpResult implements Serializable { 13 | @ONodeAttr(name = "doc_op") 14 | private String docOp; 15 | private String id; 16 | private int code; 17 | private String message; 18 | 19 | public DocOpResult() { 20 | } 21 | 22 | public String getDocOp() { 23 | return docOp; 24 | } 25 | 26 | public void setDocOp(String docOp) { 27 | this.docOp = docOp; 28 | } 29 | 30 | public String getId() { 31 | return id; 32 | } 33 | 34 | public void setId(String id) { 35 | this.id = id; 36 | } 37 | 38 | public int getCode() { 39 | return code; 40 | } 41 | 42 | public void setCode(int code) { 43 | this.code = code; 44 | } 45 | 46 | public String getMessage() { 47 | return message; 48 | } 49 | 50 | public void setMessage(String message) { 51 | this.message = message; 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/FieldType.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | /** 4 | * DashVector 字段类型枚举 5 | * 6 | * @author 小奶奶花生米 7 | */ 8 | public enum FieldType { 9 | FLOAT("FLOAT"), 10 | BOOL("BOOL"), 11 | INT("INT"), 12 | STRING("STRING"); 13 | 14 | 15 | FieldType(String name) { 16 | this.name = name; 17 | } 18 | 19 | private final String name; 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/ListCollectionsResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * ListCollectionsResponse 7 | * 8 | * @author 小奶奶花生米 9 | */ 10 | public class ListCollectionsResponse extends DashVectorResponse >{ 11 | } 12 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/QueryByIdResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * QueryByIdResponse 7 | * map 中的 key 即是文档的 id 8 | * 9 | * @author 小奶奶花生米 10 | */ 11 | public class QueryByIdResponse extends DashVectorResponse>{ 12 | } 13 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-dashvector/src/main/java/org/noear/solon/ai/rag/repository/dashvector/QueryResponse.java: -------------------------------------------------------------------------------- 1 | package org.noear.solon.ai.rag.repository.dashvector; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * QueryResponse 7 | * 8 | * @author 小奶奶花生米 9 | */ 10 | public class QueryResponse extends DashVectorResponse>{ 11 | } 12 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-elasticsearch/README.md: -------------------------------------------------------------------------------- 1 | 2 | 要求 es 7.10+(矢量支持仍比较差),建议 es 8.0+ -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-milvus/src/test/java/features/ai/repo/milvus/TestUtils.java: -------------------------------------------------------------------------------- 1 | package features.ai.repo.milvus; 2 | 3 | import org.noear.solon.ai.embedding.EmbeddingModel; 4 | 5 | /** 6 | * @author noear 2025/2/19 created 7 | */ 8 | public class TestUtils { 9 | public static EmbeddingModel getEmbeddingModel() { 10 | final String apiUrl = "http://127.0.0.1:11434/api/embed"; 11 | final String provider = "ollama"; 12 | final String model = "bge-m3";// 13 | 14 | return EmbeddingModel.of(apiUrl).provider(provider).model(model).build(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-pgvector/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | postgres: 5 | image: pgvector/pgvector:pg16 6 | container_name: solon-ai-pgvector 7 | environment: 8 | POSTGRES_DB: solon_ai_test 9 | POSTGRES_USER: postgres 10 | POSTGRES_PASSWORD: password 11 | ports: 12 | - "5432:5432" 13 | volumes: 14 | - postgres_data:/var/lib/postgresql/data 15 | command: > 16 | postgres 17 | -c shared_preload_libraries=vector 18 | -c max_connections=100 19 | -c shared_buffers=256MB 20 | -c effective_cache_size=1GB 21 | -c maintenance_work_mem=64MB 22 | -c checkpoint_completion_target=0.9 23 | -c wal_buffers=16MB 24 | -c default_statistics_target=100 25 | -c random_page_cost=1.1 26 | -c effective_io_concurrency=200 27 | -c work_mem=4MB 28 | -c min_wal_size=1GB 29 | -c max_wal_size=4GB 30 | 31 | volumes: 32 | postgres_data: -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-qdrant/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | qdrant: 5 | image: qdrant/qdrant:latest 6 | restart: always 7 | container_name: qdrant 8 | ports: 9 | - 6333:6333 10 | - 6334:6334 11 | expose: 12 | - 6333 13 | - 6334 14 | - 6335 15 | volumes: 16 | - ./qdrant_data:/qdrant/storage 17 | 18 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-qdrant/src/test/java/features/ai/repo/qdrant/TestUtils.java: -------------------------------------------------------------------------------- 1 | package features.ai.repo.qdrant; 2 | 3 | 4 | import org.noear.solon.ai.embedding.EmbeddingModel; 5 | 6 | class TestUtils { 7 | public static EmbeddingModel getEmbeddingModel() { 8 | final String apiUrl = "http://127.0.0.1:11434/api/embed"; 9 | final String provider = "ollama"; 10 | final String model = "bge-m3";// 11 | 12 | return EmbeddingModel.of(apiUrl).provider(provider).model(model).build(); 13 | } 14 | } -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-qdrant/src/test/resources/app.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | solon.logging.logger.root.level: INFO -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-redis/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | redis: 5 | image: redis/redis-stack-server:latest 6 | container_name: redisearch 7 | ports: 8 | - 16379:6379 9 | volumes: 10 | - "./data:/data" 11 | - "./logs:/logs" 12 | 13 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-redis/src/test/java/features/ai/repo/redis/TestUtils.java: -------------------------------------------------------------------------------- 1 | package features.ai.repo.redis; 2 | 3 | import org.noear.solon.ai.embedding.EmbeddingModel; 4 | 5 | /** 6 | * @author noear 2025/2/19 created 7 | */ 8 | public class TestUtils { 9 | public static EmbeddingModel getEmbeddingModel() { 10 | final String apiUrl = "http://127.0.0.1:11434/api/embed"; 11 | final String provider = "ollama"; 12 | final String model = "bge-m3";// 13 | 14 | return EmbeddingModel.of(apiUrl).provider(provider).model(model).build(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solon-ai-rag-repositorys/solon-ai-repo-redis/src/test/resources/app.yml: -------------------------------------------------------------------------------- 1 | solon.logging.logger: 2 | root: 3 | level: INFO 4 | 5 | solon.ai.repo: 6 | redis: 7 | server: "127.0.0.1:16379" # 改为你的 Redis Stack 地址 8 | db: 0 9 | maxTotal: 200 10 | 11 | 12 | solon.ai.embed: 13 | bgem3: 14 | apiUrl: "http://127.0.0.1:11434/api/embed" # 使用完整地址(而不是 api_base) 15 | provider: "ollama" # 使用 ollama 服务时,需要配置 provider 16 | model: "bge-m3:latest" 17 | -------------------------------------------------------------------------------- /solon-ai-rag-searchs/solon-ai-search-baidu/README.md: -------------------------------------------------------------------------------- 1 | # Solon AI Search Baidu 2 | 3 | **solon-ai-search-baidu** 是 Solon AI 生态系统的一部分,提供对百度 AI 搜索服务的集成支持。该模块实现了 `Repository` 接口,支持基础搜索和 AI 智能搜索两种模式。 4 | 5 | ## 🔧 配置 6 | 7 | ### 1. 获取 API Key 8 | 9 | 访问 [百度智能云控制台](https://console.bce.baidu.com/iam/#/iam/apikey/list) 获取 AppBuilder API Key: 10 | 11 | 1. 登录百度智能云控制台 12 | 2. 进入"API Key管理"页面 13 | 3. 点击"创建API Key" 14 | 4. 选择服务:千帆AppBuilder 15 | 5. 确认创建,获得 API Key 16 | 17 | ### 2. API Key 格式 18 | 19 | ``` 20 | 格式:bce-v3/ALTAK-xxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx 21 | ``` 22 | 23 | ## 📖 快速开始 24 | 25 | 参考以下测试代码: 26 | 27 | - **基础使用示例**: [BaiduAiSearchExample.java](src/test/java/features/ai/example/BaiduAiSearchExample.java) 28 | - **完整测试用例**: [BaiduAiSearchTest.java](src/test/java/features/ai/BaiduAiSearchTest.java) 29 | - **测试工具配置**: [TestUtils.java](src/test/java/features/ai/TestUtils.java) 30 | 31 | 32 | 33 | **注意**: 使用本模块需要有效的百度AppBuilder API Key,请确保遵守百度的服务条款和使用限制。 -------------------------------------------------------------------------------- /solon-ai/UPDATE_USE.md: -------------------------------------------------------------------------------- 1 | 2 | 函数(Function)相关的概念改为工具概念(Tool) 3 | 4 | * ChatFunction(聊天函数) -> FunctionTool(函数工具) 5 | * ChatFunctionDecl(聊天函数申明) -> FunctionToolDesc(函数工具描述) 6 | * ChatModel:globalFunctionAdd(...) -> ChatModel:defaultToolsAdd(...) 7 | * ChatOptions:functionAdd(...) - > ChatModel:toolsAdd(...) 8 | * `@FunctionMapping` -> `@ToolMapping` 9 | * `@FunctionParam` -> `@ToolParam` 10 | * `@ToolParam` -> `@Param` -------------------------------------------------------------------------------- /solon-ai/src/test/java/demo/ai/App.java: -------------------------------------------------------------------------------- 1 | package demo.ai; 2 | 3 | import org.noear.solon.Solon; 4 | 5 | /** 6 | * @author noear 2025/2/24 created 7 | */ 8 | public class App { 9 | public static void main(String[] args) { 10 | Solon.start(App.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/demo/ai/Config.java: -------------------------------------------------------------------------------- 1 | package demo.ai; 2 | 3 | import org.noear.solon.ai.chat.ChatConfig; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.ai.embedding.EmbeddingConfig; 6 | import org.noear.solon.ai.embedding.EmbeddingModel; 7 | import org.noear.solon.annotation.Bean; 8 | import org.noear.solon.annotation.Configuration; 9 | import org.noear.solon.annotation.Inject; 10 | 11 | /** 12 | * @author noear 2025/2/24 created 13 | */ 14 | @Configuration 15 | public class Config { 16 | 17 | @Bean 18 | public ChatModel chatModel(@Inject("${solon.ai.chat.qwen}") ChatModel chatModel) { 19 | return chatModel; 20 | } 21 | 22 | @Bean 23 | public EmbeddingModel embeddingModel(@Inject("${solon.ai.embed.bge-m3}") EmbeddingModel embeddingModel) { 24 | return embeddingModel; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/LoadTest.java: -------------------------------------------------------------------------------- 1 | package features; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.chat.dialect.ChatDialectManager; 5 | import org.noear.solon.ai.embedding.dialect.EmbeddingDialectManager; 6 | import org.noear.solon.ai.image.dialect.ImageDialectManager; 7 | import org.noear.solon.ai.reranking.dialect.RerankingDialectManager; 8 | 9 | /** 10 | * @author noear 2025/5/7 created 11 | */ 12 | public class LoadTest { 13 | @Test 14 | public void case1() { 15 | ChatDialectManager.register(null); 16 | ImageDialectManager.register(null); 17 | EmbeddingDialectManager.register(null); 18 | RerankingDialectManager.register(null); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/AbsVisionTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * @author noear 2025/7/24 created 9 | */ 10 | public abstract class AbsVisionTest { 11 | private static final Logger log = LoggerFactory.getLogger(AbsVisionTest.class); 12 | 13 | protected abstract ChatModel.Builder getChatModelBuilder(); 14 | } 15 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/App.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.chat.ChatConfig; 5 | import org.noear.solon.ai.chat.ChatModel; 6 | import org.noear.solon.ai.chat.ChatResponse; 7 | import org.noear.solon.annotation.Bean; 8 | import org.noear.solon.annotation.Configuration; 9 | import org.noear.solon.annotation.Inject; 10 | import org.noear.solon.test.SolonTest; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * @author noear 2025/2/10 created 16 | */ 17 | @Configuration 18 | @SolonTest 19 | public class App { 20 | @Bean 21 | public ChatModel build(@Inject("${solon.ai.chat.llama3}") ChatModel chatModel) { 22 | return chatModel; 23 | } 24 | 25 | @Inject 26 | ChatModel chatModel; 27 | 28 | @Test 29 | public void case1() throws IOException { 30 | //一次性返回 31 | ChatResponse resp = chatModel.prompt("hello").call(); 32 | 33 | //打印消息 34 | System.out.println(resp.getChoices()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/BigmodelTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | /** 7 | * @author noear 2025/5/7 created 8 | */ 9 | @SolonTest 10 | public class BigmodelTest extends AbsChatTest{ 11 | private static final String apiUrl = "https://open.bigmodel.cn/api/paas/v4/chat/completions"; 12 | private static final String apiKey = "52755d7995a8413783bb70ff6d44f42f.zCAKSzqlo9hmJS7s"; 13 | private static final String model = "glm-4.5-flash"; 14 | 15 | protected ChatModel.Builder getChatModelBuilder() { 16 | return ChatModel.of(apiUrl) 17 | .apiKey(apiKey) 18 | .model(model); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/DashscopeOpenAiTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | /** 7 | * @author noear 2025/1/28 created 8 | */ 9 | @SolonTest 10 | public class DashscopeOpenAiTest extends AbsChatTest{ 11 | private static final String apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"; 12 | private static final String apiKey = "sk-1ffe449611a74e61ad8e71e1b35a9858"; 13 | private static final String model = "qwen-turbo-latest"; //"deepseek-r1-distill-qwen-1.5b"; // 14 | 15 | protected ChatModel.Builder getChatModelBuilder() { 16 | return ChatModel.of(apiUrl) 17 | .apiKey(apiKey) 18 | .model(model); 19 | } 20 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/DashscopeTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | /** 7 | * @author noear 2025/1/28 created 8 | */ 9 | @SolonTest 10 | public class DashscopeTest extends AbsChatTest{ 11 | private static final String apiUrl = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"; 12 | private static final String apiKey = "sk-1ffe449611a74e61ad8e71e1b35a9858"; 13 | private static final String provider = "dashscope"; 14 | private static final String model = "qwen-turbo-latest"; 15 | 16 | protected ChatModel.Builder getChatModelBuilder() { 17 | return ChatModel.of(apiUrl) 18 | .apiKey(apiKey) 19 | .provider(provider) 20 | .model(model); 21 | } 22 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/DeepSeekR1Test.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.time.Duration; 9 | 10 | /** 11 | * @author noear 2025/1/28 created 12 | */ 13 | @SolonTest 14 | public class DeepSeekR1Test extends AbsThinkTest{ 15 | //JQC6M0GTNPGSCEXZOBUGUX0HVHCOLDIMN6XOSSSA 16 | private static final Logger log = LoggerFactory.getLogger(DeepSeekR1Test.class); 17 | private static final String apiUrl = "https://api.deepseek.com/v1/chat/completions"; 18 | private static final String apiKey = "sk-cc0af00391474299b8642af78f601c88"; 19 | private static final String model = "deepseek-reasoner"; //deepseek-reasoner//deepseek-chat 20 | 21 | protected ChatModel.Builder getChatModelBuilder() { 22 | return ChatModel.of(apiUrl) 23 | .apiKey(apiKey) 24 | .model(model) 25 | .timeout(Duration.ofSeconds(160)); 26 | } 27 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/DeepSeekTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import features.ai.chat.interceptor.ChatInterceptorTest; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.test.SolonTest; 6 | 7 | /** 8 | * @author noear 2025/1/28 created 9 | */ 10 | @SolonTest 11 | public class DeepSeekTest extends AbsChatTest { 12 | private static final String apiUrl = "https://api.deepseek.com/v1/chat/completions"; 13 | private static final String apiKey = "sk-cc0af00391474299b8642af78f601c88"; 14 | private static final String model = "deepseek-chat"; //deepseek-reasoner//deepseek-chat 15 | 16 | protected ChatModel.Builder getChatModelBuilder() { 17 | return ChatModel.of(apiUrl) 18 | .apiKey(apiKey) 19 | .model(model) 20 | .defaultInterceptorAdd(new ChatInterceptorTest()); 21 | } 22 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/GiteeaiR1Test.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import features.ai.chat.interceptor.ChatInterceptorTest; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.test.SolonTest; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * @author noear 2025/1/28 created 11 | */ 12 | @SolonTest 13 | public class GiteeaiR1Test extends AbsThinkTest{ 14 | private static final Logger log = LoggerFactory.getLogger(GiteeaiR1Test.class); 15 | private static final String apiUrl = "https://ai.gitee.com/v1/chat/completions"; 16 | private static final String apiKey = "PE6JVMP7UQI81GY6AZ0J8WEWWLFHWHROG15XUP18"; 17 | private static final String model = "DeepSeek-R1";//"Qwen3-32B";//"QwQ-32B";//"DeepSeek-V3"; //deepseek-reasoner//deepseek-chat 18 | 19 | protected ChatModel.Builder getChatModelBuilder() { 20 | return ChatModel.of(apiUrl) 21 | .apiKey(apiKey) 22 | .model(model) 23 | .defaultInterceptorAdd(new ChatInterceptorTest()); 24 | } 25 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/GiteeaiTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import features.ai.chat.interceptor.ChatInterceptorTest; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.test.SolonTest; 6 | 7 | /** 8 | * @author noear 2025/1/28 created 9 | */ 10 | @SolonTest 11 | public class GiteeaiTest extends AbsChatTest{ 12 | private static final String apiUrl = "https://ai.gitee.com/v1/chat/completions"; 13 | private static final String apiKey = "PE6JVMP7UQI81GY6AZ0J8WEWWLFHWHROG15XUP18"; 14 | private static final String model = "Qwen2.5-72B-Instruct";//"Qwen3-32B";//"QwQ-32B";//"DeepSeek-V3"; //deepseek-reasoner//deepseek-chat 15 | 16 | protected ChatModel.Builder getChatModelBuilder() { 17 | return ChatModel.of(apiUrl) 18 | .apiKey(apiKey) 19 | .model(model) 20 | .defaultInterceptorAdd(new ChatInterceptorTest()); 21 | } 22 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/ModelscopeR1Test.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import features.ai.chat.interceptor.ChatInterceptorTest; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.test.SolonTest; 6 | 7 | import java.time.Duration; 8 | 9 | /** 10 | * @author noear 2025/1/28 created 11 | */ 12 | @SolonTest 13 | public class ModelscopeR1Test extends AbsThinkTest{ 14 | private static final String apiUrl = "https://api-inference.modelscope.cn/v1/chat/completions"; 15 | private static final String apiKey = "a90656bf-08b2-47c8-b791-f5be78fe15de"; 16 | private static final String model = "deepseek-ai/DeepSeek-R1-0528"; //"Qwen/Qwen3-32B"; 17 | 18 | protected ChatModel.Builder getChatModelBuilder() { 19 | return ChatModel.of(apiUrl) 20 | .apiKey(apiKey) 21 | .model(model) 22 | .timeout(Duration.ofSeconds(160)) 23 | .defaultInterceptorAdd(new ChatInterceptorTest()); 24 | } 25 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/ModelscopeTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import features.ai.chat.interceptor.ChatInterceptorTest; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.test.SolonTest; 6 | 7 | /** 8 | * @author noear 2025/1/28 created 9 | */ 10 | @SolonTest 11 | public class ModelscopeTest extends AbsChatTest{ 12 | private static final String apiUrl = "https://api-inference.modelscope.cn/v1/chat/completions"; 13 | private static final String apiKey = "ms-ab936de2-2729-480c-8e36-aee72639e5bf"; 14 | private static final String model = "deepseek-ai/DeepSeek-V3"; //"Qwen/Qwen3-32B"; //"deepseek-ai/DeepSeek-V3"; // 15 | 16 | protected ChatModel.Builder getChatModelBuilder() { 17 | return ChatModel.of(apiUrl) 18 | .apiKey(apiKey) 19 | .model(model) 20 | .defaultOptionAdd("enable_thinking", false) 21 | .defaultInterceptorAdd(new ChatInterceptorTest()); 22 | } 23 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/OllamaR1Test.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | import java.time.Duration; 7 | 8 | /** 9 | * @author noear 2025/1/28 created 10 | */ 11 | @SolonTest 12 | public class OllamaR1Test extends AbsThinkTest { 13 | private static final String apiUrl = "http://127.0.0.1:11434/api/chat"; 14 | private static final String provider = "ollama"; 15 | private static final String model = "deepseek-r1:1.5b"; //"llama3.2";//deepseek-r1:1.5b; 16 | 17 | protected ChatModel.Builder getChatModelBuilder() { 18 | return ChatModel.of(apiUrl) 19 | .provider(provider) //需要指定供应商,用于识别接口风格(也称为方言) 20 | .model(model) 21 | .timeout(Duration.ofSeconds(160)); 22 | } 23 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/OllamaTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | /** 7 | * @author noear 2025/1/28 created 8 | */ 9 | @SolonTest 10 | public class OllamaTest extends AbsChatTest{ 11 | private static final String apiUrl = "http://127.0.0.1:11434/api/chat"; 12 | private static final String provider = "ollama"; 13 | private static final String model = "qwen2.5:1.5b"; //"llama3.2";//deepseek-r1:1.5b; 14 | 15 | protected ChatModel.Builder getChatModelBuilder() { 16 | return ChatModel.of(apiUrl) 17 | .provider(provider) //需要指定供应商,用于识别接口风格(也称为方言) 18 | .model(model); 19 | } 20 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/OpenRouterR1Test.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import features.ai.chat.interceptor.ChatInterceptorTest; 4 | import org.noear.solon.ai.chat.ChatModel; 5 | import org.noear.solon.test.SolonTest; 6 | 7 | import java.time.Duration; 8 | 9 | /** 10 | * @author noear 2025/1/28 created 11 | */ 12 | @SolonTest 13 | public class OpenRouterR1Test extends AbsThinkTest{ 14 | private static final String apiUrl = "https://openrouter.ai/api/v1/chat/completions"; 15 | private static final String apiKey = "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"; 16 | private static final String model = "tngtech/deepseek-r1t2-chimera:free"; //deepseek/deepseek-r1-0528 //deepseek/deepseek-chat-v3-0324 //tngtech/deepseek-r1t2-chimera:free 17 | 18 | protected ChatModel.Builder getChatModelBuilder() { 19 | return ChatModel.of(apiUrl) 20 | .apiKey(apiKey) 21 | .model(model) 22 | .timeout(Duration.ofSeconds(160)) 23 | .defaultInterceptorAdd(new ChatInterceptorTest()); 24 | } 25 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/SiliconflowR1Test.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | /** 7 | * @author noear 2025/5/7 created 8 | */ 9 | @SolonTest 10 | public class SiliconflowR1Test extends AbsThinkTest{ 11 | private static final String apiUrl = "https://api.siliconflow.cn/v1/chat/completions"; 12 | private static final String apiKey = "sk-urlnakaoibyseycaocaaevuogdmxsiqvxmchurjhmhehdrhc"; 13 | private static final String model = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"; //"Qwen/Qwen2.5-72B-Instruct";// deepseek-ai/DeepSeek-R1-Distill-Qwen-32B 14 | 15 | protected ChatModel.Builder getChatModelBuilder() { 16 | return ChatModel.of(apiUrl) 17 | .apiKey(apiKey) 18 | .model(model); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/SiliconflowTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat; 2 | 3 | import org.noear.solon.ai.chat.ChatModel; 4 | import org.noear.solon.test.SolonTest; 5 | 6 | /** 7 | * @author noear 2025/5/7 created 8 | */ 9 | @SolonTest 10 | public class SiliconflowTest extends AbsChatTest{ 11 | private static final String apiUrl = "https://api.siliconflow.cn/v1/chat/completions"; 12 | private static final String apiKey = "sk-urlnakaoibyseycaocaaevuogdmxsiqvxmchurjhmhehdrhc"; 13 | private static final String model = "Qwen/Qwen3-8B"; //"Qwen/Qwen2.5-72B-Instruct";// deepseek-ai/DeepSeek-R1-Distill-Qwen-32B 14 | 15 | protected ChatModel.Builder getChatModelBuilder() { 16 | return ChatModel.of(apiUrl) 17 | .apiKey(apiKey) 18 | .model(model); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/interceptor/ChatInterceptorTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat.interceptor; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.noear.solon.ai.chat.ChatRequest; 5 | import org.noear.solon.ai.chat.ChatResponse; 6 | import org.noear.solon.ai.chat.interceptor.*; 7 | import org.reactivestreams.Publisher; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author noear 2025/5/30 created 13 | */ 14 | @Slf4j 15 | public class ChatInterceptorTest implements ChatInterceptor { 16 | @Override 17 | public ChatResponse interceptCall(ChatRequest req, CallChain chain) throws IOException { 18 | log.warn("ChatInterceptor-interceptCall: " + req.getConfig().getModel()); 19 | return chain.doIntercept(req); 20 | } 21 | 22 | @Override 23 | public Publisher interceptStream(ChatRequest req, StreamChain chain) { 24 | log.warn("ChatInterceptor-interceptStream: " + req.getConfig().getModel()); 25 | return chain.doIntercept(req); 26 | } 27 | 28 | @Override 29 | public String interceptTool(ToolRequest req, ToolChain chain) throws Throwable { 30 | log.warn("ChatInterceptor-interceptTool: " + req.getConfig().getModel()); 31 | 32 | return chain.doIntercept(req); 33 | } 34 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/tool/Case10Tools.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat.tool; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | 5 | /** 6 | * @author noear 2025/5/15 created 7 | */ 8 | public class Case10Tools { 9 | @ToolMapping(description = "杭州的假日景点介绍") 10 | public String spotIntro() { 11 | return "西湖,良渚遗址"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/tool/Case8Tools.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat.tool; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Param; 5 | 6 | import java.util.HashMap; 7 | 8 | /** 9 | * @author noear 2025/5/7 created 10 | */ 11 | public class Case8Tools { 12 | @ToolMapping(description = "查询设备所有参数信息、日用电量、日产液量、生产时间等") 13 | public HashMap getOilWellData( 14 | @Param(description = "设备名称") String name, 15 | @Param(description = "年") Integer year, 16 | @Param(description = "月份") Integer month, 17 | @Param(description = "日") Integer day) { 18 | 19 | return new HashMap(){{ 20 | put("设备不存在,请尝试查询设备列表", 0); 21 | }}; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/tool/Case9Tools.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat.tool; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Param; 5 | 6 | /** 7 | * @author noear 2025/5/10 created 8 | */ 9 | public class Case9Tools { 10 | @ToolMapping(description = "将两个数字相加") 11 | public int add(@Param int a, @Param int b) { 12 | return a + b; 13 | } 14 | 15 | @ToolMapping(description = "从第一个数中减去第二个数") 16 | public int subtract(@Param int a, @Param int b) { 17 | return a - b; 18 | } 19 | 20 | @ToolMapping(description = "将两个数相乘") 21 | public int multiply(@Param int a, @Param int b) { 22 | return a * b; 23 | } 24 | 25 | @ToolMapping(description = "将第一个数除以第二个数") 26 | public float divide(@Param float a, @Param float b) { 27 | return a / b; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/chat/tool/EntityTools.java: -------------------------------------------------------------------------------- 1 | package features.ai.chat.tool; 2 | 3 | import org.noear.solon.ai.annotation.ToolMapping; 4 | import org.noear.solon.annotation.Param; 5 | 6 | public class EntityTools { 7 | @ToolMapping(description = "提交用户数据", returnDirect = true) 8 | public String post_user(@Param(description = "用户数据") User user) { 9 | System.out.println("--------: " + user); 10 | return "成功"; 11 | } 12 | 13 | public static class User { 14 | @Param(description = "用户id") 15 | private int id; 16 | 17 | @Param(description = "用户名字") 18 | private String name; 19 | 20 | public int getId() { 21 | return id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setId(int id) { 29 | this.id = id; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "User{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | '}'; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/config/ConfigTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.config; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.Solon; 5 | import org.noear.solon.ai.chat.ChatConfig; 6 | import org.noear.solon.test.SolonTest; 7 | 8 | /** 9 | * @author noear 2025/5/16 created 10 | */ 11 | @SolonTest 12 | public class ConfigTest { 13 | @Test 14 | public void proxyConfigTest() { 15 | ChatConfig config = Solon.cfg().toBean("solon.ai.chat.proxy1", ChatConfig.class); 16 | assert config != null; 17 | assert config.getProxy() != null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/embedding/OllamaTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.embedding; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.embedding.EmbeddingModel; 5 | import org.noear.solon.ai.embedding.EmbeddingResponse; 6 | import org.noear.solon.test.SolonTest; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author noear 2025/1/28 created 14 | */ 15 | @SolonTest 16 | public class OllamaTest { 17 | private static final Logger log = LoggerFactory.getLogger(OllamaTest.class); 18 | private static final String apiUrl = "http://127.0.0.1:11434/api/embed"; 19 | private static final String provider = "ollama"; 20 | private static final String model = "bge-m3";// 21 | 22 | @Test 23 | public void case1() throws IOException { 24 | EmbeddingModel embeddingModel = EmbeddingModel.of(apiUrl) 25 | .provider(provider) //需要指定供应商,用于识别接口风格(也称为方言) 26 | .model(model) 27 | .build(); 28 | 29 | //一次性返回 30 | EmbeddingResponse resp = embeddingModel 31 | .input("比较原始的风格", "能表达内在的大概过程", "太阳升起来了") 32 | .call(); 33 | 34 | //打印消息 35 | log.warn("{}", resp.getData()); 36 | } 37 | } -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/generate/OllamaTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.generate; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.Utils; 5 | import org.noear.solon.ai.generate.GenerateModel; 6 | import org.noear.solon.ai.generate.GenerateResponse; 7 | import org.noear.solon.test.SolonTest; 8 | 9 | /** 10 | * @author noear 2025/8/27 created 11 | */ 12 | @SolonTest 13 | public class OllamaTest { 14 | static final String provider = "ollama"; 15 | 16 | @Test 17 | public void case1_text() throws Exception { 18 | String apiUrl = "http://127.0.0.1:11434/api/generate"; 19 | String model = "qwen2.5:1.5b"; //"llama3.2";//deepseek-r1:1.5b; 20 | 21 | GenerateModel generateModel = GenerateModel.of(apiUrl) 22 | .model(model) 23 | .provider(provider) 24 | .build(); 25 | 26 | GenerateResponse resp = generateModel.prompt("hello") 27 | .options(o -> o.optionAdd("stream", false)) 28 | .call(); 29 | 30 | System.out.println(resp.getData()); 31 | assert resp.hasData(); 32 | assert Utils.isNotEmpty(resp.getContent().getText()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/ai/rag/TextLoaderTest.java: -------------------------------------------------------------------------------- 1 | package features.ai.rag; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.ai.rag.Document; 5 | import org.noear.solon.ai.rag.loader.TextLoader; 6 | import org.noear.solon.core.util.ResourceUtil; 7 | 8 | import java.io.File; 9 | import java.util.List; 10 | 11 | /** 12 | * 13 | * @author noear 2025/8/14 created 14 | * 15 | */ 16 | public class TextLoaderTest { 17 | @Test 18 | public void case1() throws Throwable { 19 | TextLoader textLoader = new TextLoader(ResourceUtil.getResource("app.yml")); 20 | textLoader.options(o -> o.charset("utf-8")); 21 | 22 | List documentList = textLoader.load(); 23 | 24 | assert documentList != null; 25 | assert documentList.size() >= 1; 26 | 27 | System.out.println(documentList.toString()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solon-ai/src/test/java/features/expr/Query2Test.java: -------------------------------------------------------------------------------- 1 | package features.expr; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.noear.solon.expression.Expression; 5 | import org.noear.solon.expression.snel.ConditionBuilder; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author noear 2025/3/11 created 12 | */ 13 | public class Query2Test { 14 | @Test 15 | public void case1() { 16 | // 设置查询上下文 17 | Map context = new HashMap<>(); 18 | context.put("age", 25); 19 | context.put("salary", 4000); 20 | context.put("isMarried", true); 21 | 22 | // 构建条件查询表达式树: (age > 18 AND salary < 5000) OR (NOT isMarried) 23 | ConditionBuilder cb = new ConditionBuilder(); 24 | 25 | Expression conditionNode = cb.or( 26 | cb.and(cb.gt("age", 18), cb.lt("salary", 5000)), 27 | cb.eq("isMarried", "false") 28 | ); 29 | 30 | // 计算条件查询表达式的值 31 | boolean result = conditionNode.eval(context::get); 32 | System.out.println("Result: " + result); // 输出: Result: true 33 | assert result; 34 | 35 | PrintUtil.printTree2(conditionNode); 36 | } 37 | } -------------------------------------------------------------------------------- /solon-ai/src/test/resources/app.yml: -------------------------------------------------------------------------------- 1 | 2 | server.port: 8080 3 | 4 | solon.ai.chat: 5 | llama3: 6 | apiUrl: "http://127.0.0.1:11434/api/chat" # 使用完整地址(而不是 api_base) 7 | provider: "ollama" # 使用 ollama 服务时,需要配置 provider 8 | model: "llama3.2" 9 | proxy1: 10 | apiUrl: "http://127.0.0.1:11434/api/chat" # 使用完整地址(而不是 api_base) 11 | provider: "ollama" # 使用 ollama 服务时,需要配置 provider 12 | model: "llama3.2" 13 | proxy: 14 | type: "HTTP" 15 | host: "127.0.0.1" 16 | port: 9817 17 | 18 | 19 | 20 | solon.ai.embed: 21 | bge-m3: 22 | apiUrl: "http://127.0.0.1:11434/api/embed" # 使用完整地址(而不是 api_base) 23 | provider: "ollama" # 使用 ollama 服务时,需要配置 provider 24 | model: "bge-m3:latest" -------------------------------------------------------------------------------- /solon-ai/src/test/resources/flow/case4.yml: -------------------------------------------------------------------------------- 1 | id: "ai-1" 2 | layout: 3 | - task: | 4 | let resp = chatModel.prompt("写个 solon helloworld 项目").call(); 5 | 6 | for (ChatMessage msg : resp.getMessages()) { 7 | System.out.println(msg.getContent()); 8 | } 9 | 10 | # @Test 11 | # public void case4() throws Throwable { 12 | # ChatModel chatModel = ChatModel.of("http://localhost:8080").build(); 13 | # 14 | # FlowEngine flowEngine = FlowEngine.newInstance(); 15 | # flowEngine.load(Chain.parseByUri("classpath:flow/case4.yml")); 16 | # 17 | # ChainContext ctx = new ChainContext(); 18 | # ctx.put("chatModel", chatModel); 19 | # 20 | # flowEngine.eval("ai-1"); 21 | # } -------------------------------------------------------------------------------- /solon-ai/src/test/resources/flow/case5.yml: -------------------------------------------------------------------------------- 1 | id: "ai-2" 2 | layout: 3 | - title: init 4 | task: | 5 | import org.noear.solon.ai.chat.ChatModel; 6 | 7 | ChatModel chatModel = ChatModel.of("http://localhost:8080").build(); 8 | context.put("chatModel", chatModel); //context 是内置对象(存入上下文后,后面节点就可以用了) 9 | - task: | 10 | let resp = chatModel.prompt("写个 solon helloworld 项目").call(); 11 | 12 | for (ChatMessage msg : resp.getMessages()) { 13 | System.out.println(msg.getContent()); 14 | } 15 | 16 | # @Test 17 | # public void case5() throws Throwable { 18 | # FlowEngine flowEngine = FlowEngine.newInstance(); 19 | # flowEngine.load(Chain.parseByUri("classpath:flow/case4.yml")); 20 | # 21 | # flowEngine.eval("ai-2"); 22 | # } -------------------------------------------------------------------------------- /solon_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon_icon.png --------------------------------------------------------------------------------