Spring AI Alibaba Graph基于 ReAct Agent 的天氣預報查詢系統

1、在本示例中,我們僅為 Agent 綁定了一個天氣查詢服務,接收到用戶的天氣查詢服務后,流程會在 AgentNode 和 ToolNode 之間循環執行,直到完成用戶指令。示例中判斷指令完成的條件(即 ReAct 結束條件)也很簡單,模型 AssistantMessage 無 tool_call 指令則結束(采用默認行為)。

2、pom文件

<properties><gson.version>2.10.1</gson.version></properties><dependencies><dependency><groupId>net.sourceforge.plantuml</groupId><artifactId>plantuml-mit</artifactId><version>1.2024.4</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><!--		<dependency>-->
<!--			<groupId>com.alibaba.cloud.ai</groupId>-->
<!--			<artifactId>spring-ai-alibaba-starter</artifactId>-->
<!--			<version>${project.parent.version}</version>-->
<!--		</dependency>--><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-graph-core</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-studio</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>document-parser-tika</artifactId><version>1.0.0-M6.1</version></dependency><dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.1</version><scope>compile</scope></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-extra</artifactId><version>5.8.20</version><scope>compile</scope></dependency><!-- HttpClient 核心庫 --><dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId><version>5.2.3</version></dependency><!-- 如果需要使用 HttpClient 連接池管理 --><dependency><groupId>org.apache.httpcomponents.core5</groupId><artifactId>httpcore5</artifactId><version>5.2.3</version></dependency><!-- This dependency automatically matches the appropriate version of Chrome Driver, --><!-- causing it to be slower on first boot --><dependency><groupId>io.github.bonigarcia</groupId><artifactId>webdrivermanager</artifactId><version>5.7.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>4.25.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-chrome-driver</artifactId><version>4.25.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-api</artifactId><version>4.25.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-remote-driver</artifactId><version>4.25.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-manager</artifactId><version>4.25.0</version></dependency><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-http</artifactId><version>4.25.0</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.15</version></dependency></dependencies>

3、配置文件

#
# Copyright 2024-2025 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#server:port: 18080spring:application:name: spring-ai-alibaba-helloworldai:alibaba:toolcalling:weather:enabled: trueapi-key: aaaaaopenai:base-url: https://dashscope.aliyuncs.com/compatible-modeapi-key: sk-xxxxoooooyyyyyyxxxxooooochat:options:model: qwen-max-latest

4、天氣接口 tool

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.alibaba.cloud.ai.example.graph.react.tool.weather.function;import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;/*** @author yingzi* @since 2025/3/27:11:07*/
public class FreeWeatherService implements Function<FreeWeatherService.Request, FreeWeatherService.Response> {private static final Logger logger = LoggerFactory.getLogger(FreeWeatherService.class);private static final String WEATHER_API_URL = "http://t.weather.sojson.com/api/weather/city/";private final WebClient webClient;private final ObjectMapper objectMapper = new ObjectMapper();private final static Map<String, String> CITY_NAME_CODE_MAP = Map.of("杭州", "101210101","上海", "101020100", "南京", "101190101", "合肥", "101220101");public FreeWeatherService() {this.webClient = WebClient.builder().defaultHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded").build();}public static Response fromJson(Map<String, Object> json) {Map<String, Object> location = (Map<String, Object>) json.get("cityInfo");Map<String, Object> data = (Map<String, Object>) json.get("data");List<Map<String, Object>> forecastDays = (List<Map<String, Object>>) data.get("forecast");Map<String, Object> current = forecastDays.get(0);String city = (String) location.get("city");return new Response(city, current, forecastDays.subList(1, forecastDays.size()));}@Overridepublic Response apply(Request request) {if (request == null || !StringUtils.hasText(request.city())) {logger.error("Invalid request: city is required.");return null;}try {return doGetWeatherMock(request);} catch (Exception e) {logger.error("Failed to fetch weather data: {}", e.getMessage());return null;}}@NotNullprivate Response doGetWeatherMock(Request request) throws JsonProcessingException {return doGetWeather(WEATHER_API_URL, request);}@NotNullprivate Response doGetWeather(String url, Request request) throws JsonProcessingException {String city = request.city();String cityCode = CITY_NAME_CODE_MAP.get(city);if (org.apache.commons.lang3.StringUtils.isBlank(cityCode)) {return null;}Mono<String> responseMono = webClient.get().uri(url + cityCode).retrieve().bodyToMono(String.class);String jsonResponse = responseMono.block();assert jsonResponse != null;Response response = fromJson(objectMapper.readValue(jsonResponse, new TypeReference<Map<String, Object>>() {}));logger.info("Weather data fetched successfully for city: {}", response.city());return response;}@JsonInclude(JsonInclude.Include.NON_NULL)@JsonClassDescription("Weather Service API request")public record Request(@JsonProperty(required = true, value = "city") @JsonPropertyDescription("city name") String city,@JsonProperty(required = true,value = "days") @JsonPropertyDescription("Number of days of weather forecast. Value ranges from 1 to 14") int days) {}@JsonClassDescription("Weather Service API response")public record Response(@JsonProperty(required = true, value = "city") @JsonPropertyDescription("city name") String city,@JsonProperty(required = true,value = "current") @JsonPropertyDescription("Current weather info") Map<String, Object> current,@JsonProperty(required = true,value = "forecastDays") @JsonPropertyDescription("Forecast weather info") List<Map<String, Object>> forecastDays) {}}
/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.alibaba.cloud.ai.example.graph.react.tool.weather.function;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;@Configuration
@ConditionalOnClass(FreeWeatherService.class)
@ConditionalOnProperty(prefix = "spring.ai.alibaba.toolcalling.weather", name = "enabled", havingValue = "true")
public class FreeWeatherAutoConfiguration {@Bean(name = "getWeatherFunction")@ConditionalOnMissingBean@Description("Use api.weather to get weather information.")public FreeWeatherService getWeatherFunction() {return new FreeWeatherService();}}

5、核心代碼

/** Licensed to the Apache Software Foundation (ASF) under one or more* contributor license agreements.  See the NOTICE file distributed with* this work for additional information regarding copyright ownership.* The ASF licenses this file to You under the Apache License, Version 2.0* (the "License"); you may not use this file except in compliance with* the License.  You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.example.graph.react;import java.util.concurrent.TimeUnit;import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.GraphRepresentation;
import com.alibaba.cloud.ai.graph.GraphStateException;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.util.Timeout;import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.tool.resolution.ToolCallbackResolver;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestClient;@Configuration
public class ReactAutoconfiguration {@Beanpublic ReactAgent normalReactAgent(ChatModel chatModel, ToolCallbackResolver resolver) throws GraphStateException {ChatClient chatClient = ChatClient.builder(chatModel).defaultTools("getWeatherFunction").defaultAdvisors(new SimpleLoggerAdvisor()).defaultOptions(OpenAiChatOptions.builder().internalToolExecutionEnabled(false).build()).build();return ReactAgent.builder().name("React Agent Demo").chatClient(chatClient).resolver(resolver).maxIterations(10).build();}@Beanpublic CompiledGraph reactAgentGraph(@Qualifier("normalReactAgent") ReactAgent reactAgent)throws GraphStateException {GraphRepresentation graphRepresentation = reactAgent.getStateGraph().getGraph(GraphRepresentation.Type.PLANTUML);System.out.println("\n\n");System.out.println(graphRepresentation.content());System.out.println("\n\n");return reactAgent.getAndCompileGraph();}@Beanpublic RestClient.Builder createRestClient() {// 2. 創建 RequestConfig 并設置超時RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(Timeout.of(10, TimeUnit.MINUTES)) // 設置連接超時.setResponseTimeout(Timeout.of(10, TimeUnit.MINUTES)).setConnectionRequestTimeout(Timeout.of(10, TimeUnit.MINUTES)).build();// 3. 創建 CloseableHttpClient 并應用配置HttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).build();// 4. 使用 HttpComponentsClientHttpRequestFactory 包裝 HttpClientHttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);// 5. 創建 RestClient 并設置請求工廠return RestClient.builder().requestFactory(requestFactory);}}
/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.example.graph.react;import java.util.List;
import java.util.Map;
import java.util.Optional;import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.OverAllState;import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/react")
public class ReactController {private final CompiledGraph compiledGraph;ReactController(@Qualifier("reactAgentGraph") CompiledGraph compiledGraph) {this.compiledGraph = compiledGraph;}@GetMapping("/chat")public String simpleChat(String query) {Optional<OverAllState> result = compiledGraph.invoke(Map.of("messages", new UserMessage(query)));List<Message> messages = (List<Message>) result.get().value("messages").get();AssistantMessage assistantMessage = (AssistantMessage) messages.get(messages.size() - 1);return assistantMessage.getText();}}

測試地址?

http://localhost:18080/react/chat?query=%E5%88%86%E5%88%AB%E5%B8%AE%E6%88%91%E6%9F%A5%E8%AF%A2%E6%9D%AD%E5%B7%9E%E3%80%81%E4%B8%8A%E6%B5%B7%E5%92%8C%E5%8D%97%E4%BA%AC%E7%9A%84%E5%A4%A9%E6%B0%94

測試結果如下

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77891.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77891.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77891.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HCIP(綜合實驗2)

1.實驗拓補圖 2.實驗要求 1.根據提供材料劃分VLAN以及IP地址&#xff0c;PC1/PC2屬于生產一部員工劃分VLAN10,PC3屬于生產二部劃分VLAN20 2.HJ-1HJ-2交換機需要配置鏈路聚合以保證業務數據訪問的高帶寬需求 3.VLAN的放通遵循最小VLAN透傳原則 4.配置MSTP生成樹解決二層環路問題…

使用 rebase 輕松管理主干分支

前言 最近遇到一個技術團隊的 dev 環境分支錯亂&#xff0c;因為是多人合作大家各自提交信息&#xff0c;導致出現很多交叉合并記錄&#xff0c;讓對應 log 看起來非常混亂&#xff0c;難以閱讀。 舉例說明 假設我們有一個項目&#xff0c;最初develop分支有 3 個提交記錄&a…

使用openssl為localhost創建自簽名

文章目錄 自簽名生成命令安裝安裝證書瀏覽器證書管理器 自簽名 生成命令 使用openssl生成私鑰和證書。 openssl req -x509 -newkey rsa:4096 -nodes -days 365 -subj "/CNlocalhost" -addext "subjectAltNameDNS:localhost" -keyout cert.key -out cer…

AI編程助手Cline之快速介紹

Cline 是一款深度集成在 Visual Studio Code&#xff08;VSCode&#xff09; 中的開源 AI 編程助手插件&#xff0c;旨在通過結合大語言模型&#xff08;如 Claude 3.5 Sonnet、DeepSeek V3、Google Gemini 等&#xff09;和工具鏈&#xff0c;為開發者提供自動化任務執行、智能…

1.微服務拆分與通信模式

目錄 一、微服務拆分原則與策略 業務驅動拆分方法論 ? DDD&#xff08;領域驅動設計&#xff09;中的限界上下文劃分 ? 業務功能正交性評估&#xff08;高內聚、低耦合&#xff09; 技術架構拆分策略 ? 數據層拆分&#xff08;垂直分庫 vs 水平分表&#xff09; ? 服務粒…

Element Plus表格組件深度解析:構建高性能企業級數據視圖

一、架構設計與核心能力 Element Plus的表格組件&#xff08;el-table&#xff09;基于Vue 3的響應式系統構建&#xff0c;通過聲明式配置實現復雜數據渲染。其核心設計理念體現在三個層級&#xff1a; 數據驅動&#xff1a;通過data屬性綁定數據源&#xff0c;支持動態更新與…

07前端項目----面包屑

面包屑 效果實現代碼全局事件總線-$bus 效果 實現代碼 上節searchParams中參數categoryName是表示一二三級分類所點擊的列表名 <!--bread面包屑--> <div class"bread"><ul class"fl sui-breadcrumb"><li><a href"#"…

kafka jdbc connector適配kadb數據實時同步

測試結論 源端增量獲取方式包括&#xff1a;bulk、incrementing、timestamp、incrementingtimestamp&#xff08;混合&#xff09;&#xff0c;各種方式說明如下&#xff1a; bulk: 一次同步整個表的數據 incrementing: 使用嚴格的自增列標識增量數據。不支持對舊數據的更新…

基于Hadoop的音樂推薦系統(源碼+lw+部署文檔+講解),源碼可白嫖!

摘要 本畢業生數據分析與可視化系統采用B/S架構&#xff0c;數據庫是MySQL&#xff0c;網站的搭建與開發采用了先進的Java語言、爬蟲技術進行編寫&#xff0c;使用了Spring Boot框架。該系統從兩個對象&#xff1a;由管理員和用戶來對系統進行設計構建。主要功能包括&#xff…

CentOS的安裝以及網絡配置

CentOS的下載 在學習docker之前&#xff0c;我們需要知道的就是docker是運行在Linux內核之上的&#xff0c;所以我們需要Linux環境的操作系統&#xff0c;當然了你也可以選擇安裝ubuntu等操作系統&#xff0c;如果你不想在本機安裝的話還可以考慮買阿里或者華為的云服務器&…

【條形碼識別改名工具】如何批量識別圖片條形碼,并以條碼內容批量重命名,基于WPF和Zxing的開發總結

批量圖片條形碼識別與重命名系統 (WPF + ZXing)開發總結 項目適用場景 ??電商商品管理??:批量處理商品圖片,根據條形碼自動分類歸檔??圖書館系統??:掃描圖書條形碼快速建立電子檔案??醫療檔案管理??:通過藥品條形碼整理醫療圖片資料??倉儲管理??:自動化識…

RAGFlow安裝+本地知識庫+踩坑記錄

RAGFlow是一種融合了數據檢索與生成式模型的新型系統架構&#xff0c;其核心思想在于將大規模檢索系統與先進的生成式模型&#xff08;如Transformer、GPT系列&#xff09;相結合&#xff0c;從而在回答查詢時既能利用海量數據的知識庫&#xff0c;又能生成符合上下文語義的自然…

android liveData observeForever 與 observe對比

LiveData 是一個非常有用的組件,用于在數據變化時通知觀察者。LiveData 提供了兩種主要的觀察方法:observe 和 observeForever。這兩種方法在使用場景、生命周期感知以及內存管理等方面有所不同。 一、observe 方法?? ??1. 基本介紹?? ??生命周期感知??:observe…

web-ssrfme

一、題目源碼 <?php highlight_file(__file__); function curl($url){ $ch curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_HEADER, 0);echo curl_exec($ch);curl_close($ch); }if(isset($_GET[url])){$url $_GET[url];if(preg_match(/file…

企業AI應用模式解析:從本地部署到混合架構

在人工智能快速發展的今天&#xff0c;企業如何選擇合適的大模型應用方式成為了一個關鍵問題。本文將詳細介紹六種主流的企業AI應用模式&#xff0c;幫助您根據自身需求做出最優選擇。 1. 本地部署&#xff08;On-Premise Deployment&#xff09; 特點&#xff1a;將模型下載…

OpenCV 圖形API(49)顏色空間轉換-----將 NV12 格式的圖像數據轉換為 BGR 顏色空間函數NV12toBGR()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 將圖像從NV12&#xff08;YUV420p&#xff09;顏色空間轉換為BGR。 該函數將輸入圖像從NV12顏色空間轉換為RGB。Y、U和V通道值的常規范圍是0到25…

【java實現+4種變體完整例子】排序算法中【桶排序】的詳細解析,包含基礎實現、常見變體的完整代碼示例,以及各變體的對比表格

以下是桶排序的詳細解析&#xff0c;包含基礎實現、常見變體的完整代碼示例&#xff0c;以及各變體的對比表格&#xff1a; 一、桶排序基礎實現 原理 將數據分到有限數量的桶中&#xff0c;每個桶內部使用其他排序算法&#xff08;如插入排序或快速排序&#xff09;&#xf…

Linux[基本指令]

Linux[基本指令] pwd 查看當前所處的工作目錄 斜杠在Linux中作為路徑分割符 路徑存在的價值為了確定文件的唯一性 cd指令 更改路徑 cd 你要去的路徑(直接進入) cd . 當前目錄 cd . . 上級目錄(路徑回退) 最后的’/為根目錄(根節點) Linux還是window的目錄結構都是樹狀…

git -- 對遠程倉庫的操作 -- 查看,添加(與clone對比),抓取和拉取,推送(注意點,抓取更新+合并的三種方法,解決沖突,對比),移除

目錄 對遠程倉庫的操作 介紹 查看 (git remote) 介紹 查看詳細信息 添加(git remote add) 介紹 與 git clone對比 從遠程倉庫中抓取與拉取 抓取(git fetch) 拉取(git pull) 推送(git push) 介紹 注意 抓取更新合并的方法 git fetch git merge 解決沖突 git …

vue3 excel文件導入

文章目錄 前言使用在vue文件中的使用 前言 最近寫小組官網涉及到了excel文件導入的功能 場景是導入小組成員年級 班級 郵箱 組別 姓名等基本信息的excel表格用于展示各組信息 使用 先下載js庫 npm install xlsx為了提高代碼的復用性 我將它寫成了一個通用的函數 import ap…