網(wǎng)站開發(fā)能用react嗎長沙百度推廣公司
鶴壁市浩天電氣有限公司
2026/01/24 08:44:30
網(wǎng)站開發(fā)能用react嗎,長沙百度推廣公司,哪里有網(wǎng)站開發(fā)培訓(xùn),網(wǎng)站改版 報價為了助于大家學(xué)習(xí)理解#xff0c;本文會通過旅游規(guī)劃應(yīng)用案例演示工具調(diào)用的實際應(yīng)用開發(fā)#xff1a;工具調(diào)用#xff08;入門#xff09;什么是工具調(diào)用呢#xff1f;工具調(diào)用是Tool Calling也叫Function Calling#xff0c;讓AI大模型借用外部工具來完成它自己做不到的…為了助于大家學(xué)習(xí)理解本文會通過旅游規(guī)劃應(yīng)用案例演示工具調(diào)用的實際應(yīng)用開發(fā)工具調(diào)用入門什么是工具調(diào)用呢工具調(diào)用是Tool Calling也叫Function Calling讓AI大模型借用外部工具來完成它自己做不到的事情工具調(diào)用的工作原理整個過程是由我們應(yīng)用程序來控制的AI拿到用戶的問題發(fā)現(xiàn)回答不了這時候它需要使用工具來回答用戶的問題后端程序判斷出AI需要哪個工具然后我們讓AI使用這個工具。其實就是說AI只是決定什么時候用工具需要傳遞什么參數(shù)真正執(zhí)行工具的是我們的程序。那為什么不直接把工具丟給AI而要通過后端來執(zhí)行判斷把工具給AI關(guān)鍵是安全性如果就把工具丟給AI讓AI自己決定那肯定是不行的要是AI想用就用想干嘛就干嘛那還得了而且這樣也給AI服務(wù)器本身增加壓力。所以呢所有操作必須由我們程序控制執(zhí)行不讓AI接觸到你的API和系統(tǒng)資源。工具調(diào)用的流程1、工具定義我們程序告訴AI你可以使用哪些工具并描述工具的功能和所需參數(shù)2、工具選擇AI根據(jù)用戶問題判斷要使用哪個工具并準(zhǔn)備好相應(yīng)參數(shù)3、返回意圖AI給程序返回“我想要使用這個工具...參數(shù)是...”4、工具執(zhí)行程序接收到請求執(zhí)行相應(yīng)的工具操作5、結(jié)果返回程序?qū)⒐ぞ邎?zhí)行的結(jié)果返回給AI6、繼續(xù)對話AI根據(jù)根據(jù)返回的結(jié)果生成最終回答給用戶要實現(xiàn)這些我們當(dāng)然可以自主開發(fā)不過還是更推薦使用Spring AI、LangChain等開發(fā)框架Spring AI工具開發(fā)1、定義工具在Spring AI中主要有兩種模式1、推薦基于Methods方法 2、Functions函數(shù)式編程。第二種了解即可咱只要用第一種基于Methods方法來定義工具就可以因為它更容易編寫、理解、支持的參數(shù)和返回類型更多。Methods模式通過Tool注解定義工具通過tools方法綁定工具使用ToolParam注解提供額外的描述信息Functions模式通過Bean注解定義工具通過functions方法綁定工具2、使用工具Spring AI提供了多種靈活的方式將工具提供給ChatClient讓AI能在需要時調(diào)用工具。1、按需使用 2、全局使用 3、更底層使用方式 4、動態(tài)解析主流工具開發(fā)如果在github社區(qū)沒有找到能滿足我們需求的工具就要自己開發(fā)一個工具那就讓我們來開始開發(fā)吧文件操作工具---首先在dogaiagent根目錄下新建一個tools包在tmp目錄下新建一個file目錄。那為什么要在tmp下建一個file呢這是因為文件操作提供了兩大功能--往我們電腦保存文件和讀取文件那就會涉及到一定風(fēng)險如果AI腦抽了不斷往我們電腦保存垃圾文件那就完蛋了所以我們要規(guī)定跟AI操作文件有關(guān)的限制在tmp臨時目錄下的file中。那我們繼續(xù)在dogaiagent下再新建一個constant包再在包下新建一個FileConstant接口來放文件操作要用的一些常量代碼如下public interface FileConstant { /** * 文件保存目錄 */ String FILE_SAVE_DIR System.getProperty(user.dir) /tmp; }在tools包下新建一個FileOperationTool類來寫文件操作工具讀寫功能代碼如下/** * 文件操作工具類提供文件讀寫功能 */ public class FileOperationTool { private final String FILE_DIR FileConstant.FILE_SAVE_DIR /file; Tool(description Read content from a file) public String readFile(ToolParam(description Name of the file to read) String fileName) { String filePath FILE_DIR / fileName; try { return FileUtil.readUtf8String(filePath); } catch (Exception e) { return Error reading file: e.getMessage(); } } Tool(description Write content to a file) public String writeFile( ToolParam(description Name of the file to write) String fileName, ToolParam(description Content to write to the file) String content) { String filePath FILE_DIR / fileName; try { // 創(chuàng)建目錄 FileUtil.mkdir(FILE_DIR); FileUtil.writeUtf8String(content, filePath); return File written successfully to: filePath; } catch (Exception e) { return Error writing to file: e.getMessage(); } } }然后編寫對應(yīng)單元測試代碼如下SpringBootTest class FileOperationToolTest { Test void readFile() { FileOperationTool fileOperationTool new FileOperationTool(); String fileName 旅游規(guī)劃.txt; String result fileOperationTool.readFile(fileName); Assertions.assertNotNull(result); } Test void writeFile() { FileOperationTool fileOperationTool new FileOperationTool(); String fileName 旅游規(guī)劃.txt; String content TheChosenOne---歡迎來到GitHub; String result fileOperationTool.writeFile(fileName, content); Assertions.assertNotNull(result); } }注意測試時要先寫再讀也就是先運行writeFile()再運行readFile()。聯(lián)網(wǎng)搜索工具---使用專業(yè)的網(wǎng)頁搜索API如Search API實現(xiàn)從多個網(wǎng)站搜索內(nèi)容不過要計費但是呢新用戶免費送100次請求那讓我們來開發(fā)一個聯(lián)網(wǎng)搜索工具吧。在tools包下新建一個WebSearchTool類然后呢很簡單找到Search API的baidu將這里面文檔復(fù)制給AI讓AI給我們生成Java代碼然后將代碼復(fù)制粘貼到WebSearchTool類中代碼如下/** * Web 搜索工具類提供百度搜索功能 */ public class WebSearchTool { // SearchAPI 的搜索接口地址 private static final String SEARCH_API_URL https://www.searchapi.io/api/v1/search; private final String apiKey; public WebSearchTool(String apiKey) { this.apiKey apiKey; } Tool(description Search for information from Baidu Search Engine) public String searchWeb( ToolParam(description Search query keyword) String query) { MapString, Object paramMap new HashMap(); paramMap.put(q, query); paramMap.put(api_key, apiKey); paramMap.put(engine, baidu); try { String response HttpUtil.get(SEARCH_API_URL, paramMap); // 取出返回結(jié)果的前 5 條 JSONObject jsonObject JSONUtil.parseObj(response); // 提取 organic_results 部分 JSONArray organicResults jsonObject.getJSONArray(organic_results); ListObject objects organicResults.subList(0, 5); // 拼接搜索結(jié)果為字符串 String result objects.stream().map(obj - { JSONObject tmpJSONObject (JSONObject) obj; return tmpJSONObject.toString(); }).collect(Collectors.joining(,)); return result; } catch (Exception e) { return Error searching Baidu: e.getMessage(); } } }然后在application.yml和application-local.yml中配置API Key# searchApi search-api: api-key: 你的 API Key然后編寫對應(yīng)的單元測試代碼如下SpringBootTest class WebSearchToolTest { Value(${search-api.api-key}) private String searchApiKey; Test void searchWeb() { WebSearchTool webSearchTool new WebSearchTool(searchApiKey); String query 如何學(xué)習(xí)JavaAI; String result webSearchTool.searchWeb(query); Assertions.assertNotNull(result); } }注意如果測試出來的result為error不用擔(dān)心因為AI是有可能失敗的多Debug測試幾次會成功的。網(wǎng)頁抓取工具---網(wǎng)頁抓取工具的作用是根據(jù)網(wǎng)址解析到網(wǎng)頁的內(nèi)容使用jsoup庫來實現(xiàn)網(wǎng)頁內(nèi)容抓取和解析讓我們來開發(fā)吧首先引入jsoup的依賴dependency groupIdorg.jsoup/groupId artifactIdjsoup/artifactId version1.21.2/version /dependency在tools包下新建一個WebScrapingTool類實現(xiàn)網(wǎng)頁抓取工具代碼如下/** * Web 抓取工具類提供網(wǎng)頁抓取功能 */ public class WebScrapingTool { Tool(description Scrape the content of a web page) public String scrapeWebPage(ToolParam(description URL of the web page to scrape) String url) { try { Document doc Jsoup.connect(url).get(); return doc.html(); } catch (IOException e) { return Error scraping web page: e.getMessage(); } } }然后生成對應(yīng)單元測試代碼如下class WebScrapingToolTest { Test void scrapeWebPage() { WebScrapingTool webScrapingTool new WebScrapingTool(); String url https://www.baidu.com/; String result webScrapingTool.scrapeWebPage(url); Assertions.assertNotNull(result); } }注意Debug測試的result可能抓取不了網(wǎng)頁這是因為網(wǎng)頁是異步的沒有加載時間所以看不到抓取的信息可以使用Sublime Text粘貼進(jìn)去看。終端操作工具---指在終端執(zhí)行命令比如執(zhí)行python命令來運行腳本在tools包下新建一個TerminalOperationTool類AI生成就好了不用自己寫實現(xiàn)終端操作工具代碼如下注意這個的代碼Windows和其他系統(tǒng)不一樣其他系統(tǒng)代碼如下public class TerminalOperationTool { Tool(description Execute a command in the terminal) public String executeTerminalCommand(ToolParam(description Command to execute in the terminal) String command) { StringBuilder output new StringBuilder(); try { Process process Runtime.getRuntime().exec(command); try (BufferedReader reader new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line reader.readLine()) ! null) { output.append(line).append(
); } } int exitCode process.waitFor(); if (exitCode ! 0) { output.append(Command execution failed with exit code: ).append(exitCode); } } catch (IOException | InterruptedException e) { output.append(Error executing command: ).append(e.getMessage()); } return output.toString(); } }Windows的代碼如下public class TerminalOperationTool { Tool(description Execute a command in the terminal) public String executeTerminalCommand(ToolParam(description Command to execute in the terminal) String command) { StringBuilder output new StringBuilder(); try { ProcessBuilder builder new ProcessBuilder(cmd.exe, /c, command); // Process process Runtime.getRuntime().exec(command); Process process builder.start(); try (BufferedReader reader new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line reader.readLine()) ! null) { output.append(line).append(
); } } int exitCode process.waitFor(); if (exitCode ! 0) { output.append(Command execution failed with exit code: ).append(exitCode); } } catch (IOException | InterruptedException e) { output.append(Error executing command: ).append(e.getMessage()); } return output.toString(); } }然后生成對應(yīng)單元測試代碼如下SpringBootTest class TerminalOperationToolTest { Test void executeTerminalCommand() { TerminalOperationTool tool new TerminalOperationTool(); String command dir; String result tool.executeTerminalCommand(command); Assertions.assertNotNull(result); } }資源下載工具---使用Hutool的HttpUtil.downloadFile方法實現(xiàn)資源下載在tools包下新建一個ResourceDownloadTool類實現(xiàn)代碼如下public class ResourceDownloadTool { Tool(description Download a resource from a given URL) public String downloadResource(ToolParam(description URL of the resource to download) String url, ToolParam(description Name of the file to save the downloaded resource) String fileName) { String fileDir FileConstant.FILE_SAVE_DIR /download; String filePath fileDir / fileName; try { // 創(chuàng)建目錄 FileUtil.mkdir(fileDir); // 使用 Hutool 的 downloadFile 方法下載資源 HttpUtil.downloadFile(url, new File(filePath)); return Resource downloaded successfully to: filePath; } catch (Exception e) { return Error downloading resource: e.getMessage(); } } }然后生成對應(yīng)單元測試測試成功會在download目錄下生成logo.png測試代碼如下SpringBootTest class ResourceDownloadToolTest { Test void downloadResource() { ResourceDownloadTool tool new ResourceDownloadTool(); String url https://www.baidu.com/img/bd_logo1.png; String fileName logo.png; String result tool.downloadResource(url, fileName); assertNotNull(result); } }PDF生成工具---作用是根據(jù)文件名和內(nèi)容生成PDF文檔并且保存使用itext庫實現(xiàn)PDF生成工具不過itext對中文字體不太友好需要我們額外配置中文字體要做好建議自行下載需要的字體使用我們這邊直接就使用itext內(nèi)置的中文字體首先引入依賴dependency groupIdcom.itextpdf/groupId artifactIditext-core/artifactId version9.1.0/version typepom/type /dependency dependency groupIdcom.itextpdf/groupId artifactIdfont-asian/artifactId version9.1.0/version scopetest/scope /dependency在tools包下新建一個PDFGenerationTool類實現(xiàn)以下代碼public class PDFGenerationTool { Tool(description Generate a PDF file with given content) public String generatePDF( ToolParam(description Name of the file to save the generated PDF) String fileName, ToolParam(description Content to be included in the PDF) String content) { String fileDir FileConstant.FILE_SAVE_DIR /pdf; String filePath fileDir / fileName; try { // 創(chuàng)建目錄 FileUtil.mkdir(fileDir); // 創(chuàng)建 PdfWriter 和 PdfDocument 對象 try (PdfWriter writer new PdfWriter(filePath); PdfDocument pdf new PdfDocument(writer); Document document new Document(pdf)) { // 自定義字體需要人工下載字體文件到特定目錄 // String fontPath Paths.get(src/main/resources/static/fonts/simsun.ttf) // .toAbsolutePath().toString(); // PdfFont font PdfFontFactory.createFont(fontPath, // PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); // 使用內(nèi)置中文字體 PdfFont font PdfFontFactory.createFont(STSongStd-Light, UniGB-UCS2-H); document.setFont(font); // 創(chuàng)建段落 Paragraph paragraph new Paragraph(content); // 添加段落并關(guān)閉文檔 document.add(paragraph); } return PDF generated successfully to: filePath; } catch (IOException e) { return Error generating PDF: e.getMessage(); } } }然后生成對應(yīng)單元測試進(jìn)行測試一下代碼如下SpringBootTest class PDFGenerationToolTest { Test void generatePDF() { PDFGenerationTool tool new PDFGenerationTool(); String fileName 百度.pdf; String content 百度網(wǎng)址 https://www.baidu.com/; String result tool.generatePDF(fileName, content); assertNotNull(result); } }開發(fā)了這么多工具為了方便我們統(tǒng)一管理和綁定所有工具可以給AI一次性提供所有工具讓AI決定什么時候使用我們需要創(chuàng)建一個集中工具注冊類。在tools包下新建一個ToolRegistration類編寫以下代碼/** * 集中的工具注冊 */ Configuration public class ToolRegistration { Value(${search-api.api-key}) private String searchApiKey; Bean public ToolCallback[] allTools() { FileOperationTool fileOperationTool new FileOperationTool(); WebSearchTool webSearchTool new WebSearchTool(searchApiKey); WebScrapingTool webScrapingTool new WebScrapingTool(); ResourceDownloadTool resourceDownloadTool new ResourceDownloadTool(); TerminalOperationTool terminalOperationTool new TerminalOperationTool(); PDFGenerationTool pdfGenerationTool new PDFGenerationTool(); return ToolCallbacks.from( fileOperationTool, webSearchTool, webScrapingTool, resourceDownloadTool, terminalOperationTool, pdfGenerationTool ); } }注意Value引的是import org.springframework.beans.factory.annotation.Value;補(bǔ)充它暗含了好幾種設(shè)計模式工廠模式allTools() 方法作為一個工廠方法負(fù)責(zé)創(chuàng)建和配置多個工具實例然后將它們包裝成統(tǒng)一的數(shù)組返回。這符合工廠模式的核心思想 - 集中創(chuàng)建對象并隱藏創(chuàng)建細(xì)節(jié)。依賴注入模式通過Value注解注入配置值以及將創(chuàng)建好的工具通過 Spring 容器注入到需要它們的組件中。注冊模式該類作為一個中央注冊點集中管理和注冊所有可用的工具使它們能夠被系統(tǒng)其他部分統(tǒng)一訪問。適配器模式的應(yīng)用ToolCallbacks.from 方法可以看作是一種適配器它將各種不同的工具類轉(zhuǎn)換為統(tǒng)一的 ToolCallback 數(shù)組使系統(tǒng)能夠以一致的方式處理它們。有了這個注?冊類如果需要添加或移除工具只需修改這一個類即可更利于維護(hù)。然后我們來給我們的項目使用這些工具在TravelApp中編寫以下代碼//旅游規(guī)劃大師調(diào)用工具能力 Resource private ToolCallback[] allTools; public String doChatWithTools(String message, String chatId) { ChatResponse response chatClient .prompt() .user(message) .advisors(spec - spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) // 開啟日志便于觀察效果 .advisors(new MyLoggerAdvisor()) .tools(allTools) .call() .chatResponse(); String content response.getResult().getOutput().getText(); log.info(content: {}, content); return content; }生成對應(yīng)的單元測試代碼如下Test void doChatWithTools() { // 測試聯(lián)網(wǎng)搜索問題的答案 testMessage(國慶打算和女朋友在福建約會推薦幾個治愈的小眾打卡地); // 測試網(wǎng)頁抓取旅游規(guī)劃案例分析 testMessage(最近學(xué)習(xí)壓力太大了怎么通過旅游來放松); // 測試資源下載圖片下載 testMessage(下載一張廈門鼓浪嶼的圖片); // 測試終端操作執(zhí)行代碼 testMessage(執(zhí)行 Python3 腳本來生成數(shù)據(jù)分析報告); // 測試文件操作保存用戶檔案 testMessage(保存我的旅游規(guī)劃檔案為文件); // 測試 PDF 生成 testMessage(生成一份‘國慶旅游計劃清單’PDF包含餐廳預(yù)訂、活動流程和景點打卡); } private void testMessage(String message) { String chatId UUID.randomUUID().toString(); String answer travelApp.doChatWithTools(message, chatId); Assertions.assertNotNull(answer); }給工具?類的代碼打斷點在 Debug 模式下觀察工具的調(diào) 用過程和結(jié)果。不過這邊報錯了是因為itext的字體原因無法處理那我們把注釋的自定義字體給它開啟把內(nèi)置的字體關(guān)掉就可以了。工具調(diào)用進(jìn)階知識了解即可工具底層數(shù)據(jù)結(jié)構(gòu)Spring AI工具調(diào)用的核心在ToolCallback接口它是所有工具實現(xiàn)的基礎(chǔ)。工具上下文實際開發(fā)應(yīng)用中工具執(zhí)行可能需要額外的上下文信息Spring AI通過ToolContext提供這一功能。立即返回工具執(zhí)行的結(jié)果不需要再經(jīng)過AI模型處理而是希望直接返回給用戶Spring AI通過returnDirect屬性支持這一功能。工具底層執(zhí)行原理Spring AI提供了兩種工具執(zhí)行模式1、框架控制的工具執(zhí)行 2、用戶控制的工具執(zhí)行它們都與ToolCallingManager這個管理AI調(diào)用全過程的核心組件有關(guān)。工具解析可觀測性