Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bigchuidw3_spring-ai-agentx.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "bigchuidw3/spring-ai-agentx", "files": {"spring-ai-agentx-samples/src/main/java/com/agentx/ai/samples/TestConfig.java": {"74206edcf6fb207f25bbc52594e033d681f9f2d9": {"t": 1786068550.2060003, "n": 18306, "h": "cc7f7a49254e52b7", "c": "package com.agentx.ai.samples;\n\nimport com.agentx.ai.core.agent.ReactAgent;\nimport com.agentx.ai.core.chatmodels.DeepSeekV4ChatModel;\nimport com.agentx.ai.core.model.AgentStreamEvent;\nimport com.agentx.ai.core.model.PauseState;\nimport com.agentx.ai.core.model.RunnableParams;\nimport com.agentx.ai.core.model.SubAgentSource;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\nimport org.springframework.ai.document.MetadataMode;\nimport org.springframework.ai.embedding.EmbeddingModel;\nimport org.springframework.ai.openai.OpenAiChatModel;\nimport org.springframework.ai.openai.OpenAiChatOptions;\nimport org.springframework.ai.openai.OpenAiEmbeddingModel;\nimport org.springframework.ai.openai.OpenAiEmbeddingOptions;\nimport org.springframework.ai.openai.api.OpenAiApi;\nimport org.springframework.ai.zhipuai.ZhiPuAiChatModel;\nimport org.springframework.ai.zhipuai.ZhiPuAiChatOptions;\nimport org.springframework.ai.zhipuai.api.ZhiPuAiApi;\nimport org.springframework.ai.vectorstore.pgvector.PgVectorStore;\nimport org.springframework.http.client.ReactorClientHttpRequestFactory;\nimport org.springframework.jdbc.core.JdbcTemplate;\n\nimport org.springframework.http.client.reactive.ReactorClientHttpConnector;\nimport org.springframework.retry.policy.NeverRetryPolicy;\nimport org.springframework.retry.support.RetryTemplate;\nimport org.springframework.web.client.RestClient;\nimport org.springframework.web.reactive.function.client.WebClient;\nimport reactor.core.publisher.Flux;\nimport reactor.netty.http.client.HttpClient;\n\nimport javax.sql.DataSource;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.UUID;\n\n/**\n * 测试公共配置 — 所有测试类共享的工厂方法和常量。\n *\n * @author bigchui\n *\n */\npublic final class TestConfig {\n\n private static final Properties secrets = loadSecrets();\n\n // 便捷访问方法\n private static String s(String key) { return secrets.getProperty(key); }\n private static String s(String key, String defaultValue) { return secrets.getProperty(key, defaultValue); }\n private static int si(String key, int defaultValue) { return Integer.parseInt(s(key, String.valueOf(defaultValue))); }\n\n // ===== 公开常量(用于测试打印)=====\n public static final String CHAT_MODEL = s(\"dashscope.chat.model\", \"qwen-plus\");\n public static final String EMBEDDING_MODEL = s(\"embedding.model\", \"text-embedding-v3\");\n public static final String SKILLS_DIR = s(\"skills.dir\", \"\");\n\n /** HTTP 响应超时(5 分钟),思考模型非流式调用需要较长等待 */\n private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(300);\n\n private TestConfig() {\n }\n\n private static Properties loadSecrets() {\n Properties props = new Properties();\n try (InputStream is = TestConfig.class.getClassLoader().getResourceAsStream(\"secrets.properties\")) {\n if (is != null) {\n props.load(is);\n } else {\n System.err.println(\"[TestConfig] 未找到 secrets.properties,请复制 secrets.properties.example 并填入真实值\");\n }\n } catch (IOException e) {\n throw new RuntimeException(\"Failed to load secrets.properties\", e);\n }\n return props;\n }\n\n // ---------- Factory Methods ----------\n\n public static ChatModel createChatModel() {\n OpenAiChatOptions opts = new OpenAiChatOptions();\n opts.setModel(s(\"dashscope.chat.model\", \"qwen-plus\"));\n opts.setTemperature(0.7);\n Map<String,Object> map = new HashMap<>();\n Map<String,Object> submap = new HashMap<>();\n submap.put(\"enable_thinking\",true);\n map.put(\"chat_template_kwargs\",submap);\n opts.setExtraBody(map);\n\n HttpClient httpClient = HttpClient.create().responseTimeout(HTTP_TIMEOUT);\n\n return OpenAiChatModel.builder()\n .openAiApi(OpenAiApi.builder()\n .baseUrl(s(\"dashscope.base.url\", \"https://dashscope.aliyuncs.com/compatible-mode/\"))\n .apiKey(s(\"dashscope.api.key\"))\n .restClientBuilder(RestClient.builder()\n .requestFactory(new ReactorClientHttpRequestFactory(httpClient)))\n .webClientBuilder(WebClient.builder()\n .clientConnector(new ReactorClientHttpConnector(httpClient)))\n .build())\n .defaultOptions(opts)\n .build();\n }\n\n public static ChatModel createMiniMaxChatModel() {\n OpenAiChatOptions opts = new OpenAiChatOptions();\n opts.setModel(s(\"minimax.chat.model\", \"MiniMax-M2.7\"));\n opts.setTemperature(0.7);\n\n HttpClient httpClient = HttpClient.create().responseTimeout(HTTP_TIMEOUT);\n\n RetryTemplate noRetryTemplate = new RetryTemplate();\n noRetryTemplate.setRetryPolicy(new NeverRetryPolicy());\n\n return OpenAiChatModel.builder()\n .openAiApi(OpenAiApi.builder()\n .baseUrl(s(\"minimax.base.url\", \"https://api.minimaxi.com/\"))\n .apiKey(s(\"minimax.api.key\"))\n .restClientBuilder(RestClient.builder()\n .requestFactory(new ReactorClientHttpRequestFactory(httpClient)))\n .webClientBuilder(WebClient.builder()\n .clientConnector(new ReactorClientHttpConnector(httpClient)))\n .build())\n .defaultOptions(opts)\n .retryTemplate(noRetryTemplate)\n .build();\n }\n\n /**\n * 错误用法,deepseek v4不兼容openai chatmodel\n * @return\n */\n public static ChatModel createDeepSeekChatModel() {\n OpenAiChatOptions opts = new OpenAiChatOptions();\n opts.setModel(s(\"deepseek.chat.model\", \"deepseek-chat\"));\n opts.setTemperature(0.7);\n\n HttpClient httpClient = HttpClient.create().responseTimeout(HTTP_TIMEOUT);\n\n RetryTemplate noRetryTemplate = new RetryTemplate();\n noRetryTemplate.setRetryPolicy(new NeverRetryPolicy());\n\n return OpenAiChatModel.builder()\n .openAiApi(OpenAiApi.builder()\n .baseUrl(s(\"deepseek.base.url\", \"https://api.deepseek.com\"))\n .apiKey(s(\"deepseek.api.key\"))\n .restClientBuilder(RestClient.builder()\n .requestFactory(new ReactorClientHttpRequestFactory(httpClient)))\n .webClientBuilder(WebClient.builder()\n .clientConnector(new ReactorClientHttpConnector(httpClient)))\n .build())\n .defaultOptions(opts)\n .retryTemplate(noRetryTemplate)\n .build();\n }\n\n /**\n * 正确用法\n * @return\n */\n public static ChatModel createDeepSeekV4ChatModel(){\n DeepSeekApi deepSeekApi = DeepSeekApi.builder()\n .apiKey(s(\"deepseek.api.key\"))\n .baseUrl(s(\"deepseek.base.url\"))\n .build();\n DeepSeekChatOptions options = DeepSeekChatOptions.builder()\n .model(s(\"deepseek.chat.model\"))\n .temperature(0.7)\n .build();\n DeepSeekV4ChatModel chatModel = DeepSeekV4ChatModel.builder()\n .deepSeekApi(deepSeekApi)\n .defaultOptions(options)\n .build();\n return chatModel;\n }\n\n public static ChatModel createZhiPuChatModel() {\n ZhiPuAiApi api = ZhiPuAiApi.builder()\n .baseUrl(\"https://open.bigmodel.cn/api/coding/paas/\")\n .apiKey(s(\"zhipu.api.key\"))\n .build();\n\n ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()\n .model(s(\"zhipu.chat.model\", \"glm-4.7\"))\n .temperature(0.7)\n .thinking(ZhiPuAiApi.ChatCompletionRequest.Thinking.enabled())\n .build();\n\n // 禁用 Spring AI 内部重试,由 AgentX 框架统一处理重试\n RetryTemplate noRetryTemplate = new RetryTemplate();\n noRetryTemplate.setRetryPolicy(new NeverRetryPolicy());\n\n return new ZhiPuAiChatModel(api, options, noRetryTemplate);\n }\n\n /**\n * 创建 OpenAI 兼容风格的 ZhiPu ChatModel。\n * <p>\n * 通过 OpenAI 兼容接口访问 GLM 模型,支持 {@code reasoning_content} 字段提取。\n * <p>\n * 与 {@link #createZhiPuChatModel()} 的区别:\n * <ul>\n * <li>原生 ZhiPu 实现 — 不提取 reasoning_content,无法使用 REASONING_CONTENT 模式</li>\n * <li>OpenAI 兼容接口 — 正确提取 reasoning_content 到 metadata,支持 REASONING_CONTENT 模式</li>\n * </ul>\n */\n public static ChatModel createZhiPuOpenAiChatModel() {\n OpenAiChatOptions opts = new OpenAiChatOptions();\n opts.setModel(s(\"zhipu.chat.model\", \"glm-4.7\"));\n opts.setTemperature(0.7);\n\n // 思考模型非流式调用需要更长超时(模型生成完整响应后才开始发送)\n HttpClient httpClient = HttpClient.create()\n .responseTimeout(HTTP_TIMEOUT);\n\n RestClient.Builder restClientBuilder = RestClient.builder()\n .requestFactory(new ReactorClientHttpRequestFactory(httpClient));\n WebClient.Builder webClientBuilder = WebClient.builder()\n .clientConnector(new ReactorClientHttpConnector(httpClient));\n\n RetryTemplate noRetryTemplate = new RetryTemplate();\n noRetryTemplate.setRetryPolicy(new NeverRetryPolicy());\n\n return OpenAiChatModel.builder()\n .openAiApi(OpenAiApi.builder()\n .baseUrl(s(\"zhipu.openai.base.url\", \"https://open.bigmodel.cn/api/coding/paas/v4\"))\n .apiKey(s(\"zhipu.api.key\"))\n .completionsPath(\"/chat/completions\")\n .restClientBuilder(restClientBuilder)\n .webClientBuilder(webClientBuilder)\n .build())\n .defaultOptions(opts)\n .retryTemplate(noRetryTemplate)\n .build();\n }\n\n public static EmbeddingModel createEmbeddingModel() {\n OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder()\n .model(EMBEDDING_MODEL)\n .dimensions(1024)\n .build();\n\n return new OpenAiEmbeddingModel(\n OpenAiApi.builder()\n .baseUrl(s(\"dashscope.base.url\", \"https://dashscope.aliyuncs.com/compatible-mode/\"))\n .apiKey(s(\"dashscope.api.key\"))\n .build(),\n MetadataMode.EMBED,\n options\n );\n }\n\n /**\n * 创建使用错误 API Key 的 ChatModel,用于测试 ErrorEvent。\n */\n public static ChatModel createBadApiKeyChatModel() {\n DeepSeekApi deepSeekApi = DeepSeekApi.builder()\n .apiKey(\"sk-bad-api-key-for-testing-error-event\")\n .baseUrl(s(\"deepseek.base.url\", \"https://api.deepseek.com\"))\n .build();\n DeepSeekChatOptions options = DeepSeekChatOptions.builder()\n .model(s(\"deepseek.chat.model\", \"deepseek-chat\"))\n .temperature(0.7)\n .build();\n return DeepSeekV4ChatModel.builder()\n .deepSeekApi(deepSeekApi)\n .defaultOptions(options)\n .build();\n }\n\n public static DataSource createMySqlDataSource() {\n HikariDataSource ds = new HikariDataSource();\n ds.setJdbcUrl(s(\"mysql.url\", \"jdbc:mysql://localhost:3306/agent_test?useSSL=false&allowPublicKeyRetrieval=true\"));\n ds.setUsername(s(\"mysql.user\", \"root\"));\n ds.setPassword(s(\"mysql.password\", \"root\"));\n ds.setDriverClassName(\"com.mysql.cj.jdbc.Driver\");\n return ds;\n }\n\n public static DataSource createPgDataSource() {\n HikariDataSource ds = new HikariDataSource();\n ds.setJdbcUrl(String.format(\"jdbc:postgresql://%s:%d/%s\",\n s(\"pg.host\", \"192.168.11.163\"),\n si(\"pg.port\", 5433),\n s(\"pg.database\", \"vector_store\")));\n ds.setUsername(s(\"pg.user\", \"postgres\"));\n ds.setPassword(s(\"pg.password\", \"postgres\"));\n ds.setDriverClassName(\"org.postgresql.Driver\");\n return ds;\n }\n\n public static PgVectorStore createPgVectorStore(DataSource pgDataSource, EmbeddingModel embeddingModel) {\n JdbcTemplate jdbcTemplate = new JdbcTemplate(pgDataSource);\n PgVectorStore store = PgVectorStore.builder(jdbcTemplate, embeddingModel)\n .dimensions(1024)\n .distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE)\n .indexType(PgVectorStore.PgIndexType.HNSW)\n .initializeSchema(true)\n .removeExistingVectorStoreTable(false)\n .vectorTableName(s(\"pg.table.name\", \"agentx_long_term_memory\"))\n .maxDocumentBatchSize(100)\n .build();\n try {\n store.afterPropertiesSet();\n } catch (Exception e) {\n throw new RuntimeException(\"Failed to initialize PgVectorStore\", e);\n }\n return store;\n }\n\n // ---------- Helper Methods ----------\n\n public static String randomConvId() {\n return \"conv_\" + UUID.randomUUID();\n }\n\n public static String randomUserId(String prefix) {\n return prefix + \"_\" + System.currentTimeMillis();\n }\n\n public static RunnableParams buildParams(String convId, String userId) {\n return RunnableParams.builder()\n .conversationId(convId)\n .userId(userId)\n .build();\n }\n\n /**\n * 流式调用 Agent 并实时打印输出。\n */\n public static void streamAndPrint(ReactAgent agent, String query) {\n streamAndPrint(agent, query, null);\n }\n\n public static void streamAndPrint(ReactAgent agent, String query, RunnableParams params) {\n System.out.println(\"Q: \" + query);\n System.out.print(\"A: \");\n if (params != null) {\n agent.stream(query, params)\n .doOnNext(chunk -> {\n System.out.print(chunk);\n System.out.flush();\n })\n .doOnError(err -> System.err.println(\"\\nError: \" + err.getMessage()))\n .blockLast();\n } else {\n agent.stream(query)\n .doOnNext(chunk -> {\n System.out.print(chunk);\n System.out.flush();\n })\n .doOnError(err -> System.err.println(\"\\nError: \" + err.getMessage()))\n .blockLast();\n }\n System.out.println(\"\\n\");\n }\n\n public static void printTestHeader(String title) {\n System.out.println(\"\\n\" + \"=\".repeat(60));\n System.out.println(\" \" + title);\n System.out.println(\"=\".repeat(60) + \"\\n\");\n }\n\n // ---------- 流式事件工具方法 ----------\n\n /**\n * 上一个事件是否为 Thinking,用于控制标记打印\n */\n private static boolean lastEventWasThinking = false;\n\n /**\n * 当前事件来源的 Agent 名称(null = 主 Agent),用于 SubAgent 分隔\n */\n private static String currentAgentName = null;\n\n /**\n * 从事件中提取 SubAgentSource。\n */\n private static SubAgentSource extractSource(AgentStreamEvent event) {\n return switch (event) {\n case AgentStreamEvent.Text e -> e.source();\n case AgentStreamEvent.Thinking e -> e.source();\n case AgentStreamEvent.ToolStart e -> e.source();\n case AgentStreamEvent.ToolEnd e -> e.source();\n case AgentStreamEvent.Complete e -> e.source();\n default -> null;\n };\n }\n\n /**\n * 格式化打印单个流式事件。\n * <p>\n * - SubAgent 来源切换时打印 ━━━ [agent名] ━━━ 分隔线\n * - Thinking 事件只在进入思考时打印一次标记,避免每个 chunk 重复前缀\n */\n public static void printEvent(AgentStreamEvent event) {\n // SubAgent 来源切换检测\n SubAgentSource source = extractSource(event);\n String agentName = source != null ? source.agentName() : null;\n if (!java.util.Objects.equals(agentName, currentAgentName)) {\n if (currentAgentName != null) System.out.println();\n currentAgentName = agentName;\n lastEventWasThinking = false;\n String label = agentName != null ? agentName : \"main\";\n System.out.println(\"\\n━━━ [\" + label + \"] ━━━\");\n }\n\n switch (event) {\n case AgentStreamEvent.Thinking t -> {\n if (!lastEventWasThinking) {\n System.out.print(\"\\n[Thinking] \");\n }\n System.out.print(t.content());\n lastEventWasThinking = true;\n }\n case AgentStreamEvent.Text t -> {\n if (lastEventWasThinking) {\n System.out.println(\"\\n---\");\n System.out.println(\"\\n最终答案:\");\n }\n System.out.print(t.content());\n lastEventWasThinking = false;\n }\n case AgentStreamEvent.ToolStart ts ->\n System.out.println(\"\\n[ToolStart] \" + ts.toolName() + \" id=\" + ts.toolCallId());\n case AgentStreamEvent.ToolEnd te -> System.out.println(\"[ToolEnd] \" + te.toolName() + \" result=\"\n + (te.result() != null && te.result().length() > 80\n ? te.result().substring(0, 80) + \"...\"\n : te.result()));\n case AgentStreamEvent.Error e ->\n System.out.println(\"\\n[Error:\" + e.code().code() + \"] \" + e.message() + \"\\n \" + e.detail());\n case AgentStreamEvent.Complete c -> System.out.println(\"\\n[Complete]:promptTokens:\"+c.totalPromptTokens()+ \",completionTokens:\"+c.totalCompletionTokens());\n case AgentStreamEvent.Paused p -> System.out.println(\"[Paused]\");\n }\n }\n\n /**\n * 收集流式事件,实时打印所有事件,返回 PauseState(如果暂停)。\n */\n public static PauseState collectStreamEvents(Flux<AgentStreamEvent> flux) {\n PauseState[] holder = new PauseState[1];\n flux.doOnNext(event -> {\n if (event instanceof AgentStreamEvent.Paused p) {\n holder[0] = p.state();\n } else {\n printEvent(event);\n }\n }).blockLast();\n return holder[0];\n }\n}\n"}}}}