Revert "Refa: Chats /chat API to RESTFul (#13871)" (#13877)

### What problem does this PR solve?

This reverts commit 1a608ac411.

### Type of change

- [x] Other (please describe):
This commit is contained in:
Liu An
2026-04-01 11:05:29 +08:00
committed by GitHub
parent 1a608ac411
commit b1d28b5898
52 changed files with 3584 additions and 2044 deletions

View File

@@ -2756,11 +2756,10 @@ Creates a chat assistant.
- `'Authorization: Bearer <YOUR_API_KEY>'`
- Body:
- `"name"`: `string`
- `"icon"`: `string`
- `"avatar"`: `string`
- `"dataset_ids"`: `list[string]`
- `"llm_id"`: `string`
- `"llm_setting"`: `object`
- `"prompt_config"`: `object`
- `"llm"`: `object`
- `"prompt"`: `object`
##### Request example
@@ -2779,16 +2778,27 @@ curl --request POST \
- `"name"`: (*Body parameter*), `string`, *Required*
The name of the chat assistant.
- `"icon"`: (*Body parameter*), `string`
- `"avatar"`: (*Body parameter*), `string`
Base64 encoding of the avatar.
- `"dataset_ids"`: (*Body parameter*), `list[string]`
The IDs of the associated datasets. If omitted or set to `[]`, an empty chat assistant is created and datasets can be attached later.
- `"llm_id"`: (*Body parameter*), `string`
The chat model name. If not set, the user's default chat model will be used.
- `"llm_setting"`: (*Body parameter*), `object`
The LLM settings for the chat assistant to create. An `llm_setting` object may contain the following attributes:
- `"dataset_ids"`: (*Body parameter*), `list[string]`
The IDs of the associated datasets.
- `"llm"`: (*Body parameter*), `object`
The LLM settings for the chat assistant to create. If it is not explicitly set, a JSON object with the following values will be generated as the default. An `llm` JSON object contains the following attributes:
- `"model_name"`, `string`
The chat model name. If not set, the user's default chat model will be used.
:::caution WARNING
`model_type` is an *internal* parameter, serving solely as a temporary workaround for the current model-configuration design limitations.
Its main purpose is to let *multimodal* models (stored in the database as `"image2text"`) pass backend validation/dispatching. Be mindful that:
- Do *not* treat it as a stable public API.
- It is subject to change or removal in future releases.
:::
- `"model_type"`: `string`
A model type specifier. Only `"chat"` and `"image2text"` are recognized; any other inputs, or when omitted, are treated as `"chat"`.
- `"model_name"`, `string`
- `"temperature"`: `float`
Controls the randomness of the model's predictions. A lower temperature results in more conservative responses, while a higher temperature yields more creative and diverse responses. Defaults to `0.1`.
- `"top_p"`: `float`
@@ -2797,27 +2807,21 @@ curl --request POST \
This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation. Defaults to `0.4`.
- `"frequency penalty"`: `float`
Similar to the presence penalty, this reduces the models tendency to repeat the same words frequently. Defaults to `0.7`.
- `"prompt_config"`: (*Body parameter*), `object`
Instructions for the LLM to follow. A `prompt_config` object may contain the following attributes:
- `"system"`: `string` The prompt content.
- `"prologue"`: `string` The opening greeting for the user.
- `"parameters"`: `object[]` This argument lists the variables to use in the system prompt. Note that:
- `"prompt"`: (*Body parameter*), `object`
Instructions for the LLM to follow. If it is not explicitly set, a JSON object with the following values will be generated as the default. A `prompt` JSON object contains the following attributes:
- `"similarity_threshold"`: `float` RAGFlow employs either a combination of weighted keyword similarity and weighted vector cosine similarity, or a combination of weighted keyword similarity and weighted reranking score during retrieval. This argument sets the threshold for similarities between the user query and chunks. If a similarity score falls below this threshold, the corresponding chunk will be excluded from the results. The default value is `0.2`.
- `"keywords_similarity_weight"`: `float` This argument sets the weight of keyword similarity in the hybrid similarity score with vector cosine similarity or reranking model similarity. By adjusting this weight, you can control the influence of keyword similarity in relation to other similarity measures. The default value is `0.7`.
- `"top_n"`: `int` This argument specifies the number of top chunks with similarity scores above the `similarity_threshold` that are fed to the LLM. The LLM will *only* access these 'top N' chunks. The default value is `6`.
- `"variables"`: `object[]` This argument lists the variables to use in the 'System' field of **Chat Configurations**. Note that:
- `"knowledge"` is a reserved variable, which represents the retrieved chunks.
- All the variables in `"system"` should be curly bracketed.
- All the variables in 'System' should be curly bracketed.
- The default value is `[{"key": "knowledge", "optional": true}]`.
- `"rerank_model"`: `string` If it is not specified, vector cosine similarity will be used; otherwise, reranking score will be used.
- `top_k`: `int` Refers to the process of reordering or selecting the top-k items from a list or set based on a specific ranking criterion. Default to 1024.
- `"empty_response"`: `string` If nothing is retrieved in the dataset for the user's question, this will be used as the response. To allow the LLM to improvise when nothing is found, leave this blank.
- `"quote"`: `boolean` Indicates whether the source of text should be displayed. Defaults to `true`.
- `"tts"`: `boolean`
- `"refine_multiturn"`: `boolean`
- `"use_kg"`: `boolean`
- `"reasoning"`: `boolean`
- `"cross_languages"`: `list[string]`
- `"tavily_api_key"`: `string`
- `"toc_enhance"`: `boolean`
- `"similarity_threshold"`: (*Body parameter*), `float`
- `"vector_similarity_weight"`: (*Body parameter*), `float`
- `"top_n"`: (*Body parameter*), `int`
- `"top_k"`: (*Body parameter*), `int`
- `"rerank_id"`: (*Body parameter*), `string`
- `"opener"`: `string` The opening greeting for the user. Defaults to `"Hi! I am your assistant, can I help you?"`.
- `"show_quote`: `boolean` Indicates whether the source of text should be displayed. Defaults to `true`.
- `"prompt"`: `string` The prompt content.
#### Response
@@ -2827,42 +2831,39 @@ Success:
{
"code": 0,
"data": {
"icon": "",
"avatar": "",
"create_date": "Thu, 24 Oct 2024 11:18:29 GMT",
"create_time": 1729768709023,
"dataset_ids": [
"527fa74891e811ef9c650242ac120006"
],
"kb_names": [
"dataset_1"
],
"description": "A helpful Assistant",
"do_refer": "1",
"id": "b1f2f15691f911ef81180242ac120003",
"language": "English",
"llm_id": "qwen-plus@Tongyi-Qianwen",
"llm_setting": {
"llm": {
"frequency_penalty": 0.7,
"model_name": "qwen-plus@Tongyi-Qianwen",
"presence_penalty": 0.4,
"temperature": 0.1,
"top_p": 0.3
},
"name": "12234",
"prompt_config": {
"prompt": {
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
"prologue": "Hi! I'm your assistant. What can I do for you?",
"quote": true,
"system": "You are an intelligent assistant...",
"parameters": [
"keywords_similarity_weight": 0.3,
"opener": "Hi! I'm your assistant. What can I do for you?",
"prompt": "You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence \"The answer you are looking for is not found in the knowledge base!\" Answers need to consider chat history.\n ",
"rerank_model": "",
"similarity_threshold": 0.2,
"top_n": 6,
"variables": [
{
"key": "knowledge",
"optional": false
}
]
},
"rerank_id": "",
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"top_n": 6,
"prompt_type": "simple",
"status": "1",
"tenant_id": "69736c5e723611efb51b0242ac120007",
@@ -2878,7 +2879,7 @@ Failure:
```json
{
"code": 102,
"message": "Duplicated chat name."
"message": "Duplicated chat name in creating dataset."
}
```
@@ -2888,9 +2889,7 @@ Failure:
**PUT** `/api/v1/chats/{chat_id}`
Replaces the persisted configuration of a specified chat assistant.
Use this endpoint only when you intend to send the full configuration to keep. Omitted fields are reset to server defaults. For partial updates, use `PATCH /api/v1/chats/{chat_id}` instead.
Updates configurations for a specified chat assistant.
#### Request
@@ -2901,11 +2900,10 @@ Use this endpoint only when you intend to send the full configuration to keep. O
- `'Authorization: Bearer <YOUR_API_KEY>'`
- Body:
- `"name"`: `string`
- `"icon"`: `string`
- `"avatar"`: `string`
- `"dataset_ids"`: `list[string]`
- `"llm_id"`: `string`
- `"llm_setting"`: `object`
- `"prompt_config"`: `object`
- `"llm"`: `object`
- `"prompt"`: `object`
##### Request example
@@ -2916,23 +2914,7 @@ curl --request PUT \
--header 'Authorization: Bearer <YOUR_API_KEY>' \
--data '
{
"name":"Test",
"icon":"",
"dataset_ids":["0b2cbc8c877f11ef89070242ac120005"],
"llm_id":"qwen-plus@Tongyi-Qianwen",
"llm_setting":{"temperature":0.1,"top_p":0.3,"presence_penalty":0.4,"frequency_penalty":0.7},
"prompt_config":{
"system":"You are an intelligent assistant...",
"prologue":"Hi! I'\''m your assistant. What can I do for you?",
"parameters":[{"key":"knowledge","optional":false}],
"empty_response":"Sorry! No relevant content was found in the knowledge base!",
"quote":true
},
"similarity_threshold":0.2,
"vector_similarity_weight":0.3,
"top_n":6,
"top_k":1024,
"rerank_id":""
"name":"Test"
}'
```
@@ -2942,110 +2924,36 @@ curl --request PUT \
The ID of the chat assistant to update.
- `"name"`: (*Body parameter*), `string`, *Required*
The revised name of the chat assistant.
- `"icon"`: (*Body parameter*), `string`
- `"avatar"`: (*Body parameter*), `string`
Base64 encoding of the avatar.
- `"dataset_ids"`: (*Body parameter*), `list[string]`
- `"dataset_ids"`: (*Body parameter*), `list[string]`
The IDs of the associated datasets.
- `"llm_id"`: (*Body parameter*), `string`
The chat model name. If not set, the user's default chat model will be used.
- `"llm_setting"`: (*Body parameter*), `object`
The LLM settings for the chat assistant. An `llm_setting` object contains the following attributes:
- `"model_type"`: `string`
A model type specifier. Only `"chat"` and `"image2text"` are recognized; any other inputs, or when omitted, are treated as `"chat"`.
- `"llm"`: (*Body parameter*), `object`
The LLM settings for the chat assistant to create. If it is not explicitly set, a dictionary with the following values will be generated as the default. An `llm` object contains the following attributes:
- `"model_name"`, `string`
The chat model name. If not set, the user's default chat model will be used.
- `"temperature"`: `float`
Controls the randomness of the model's predictions. A lower temperature results in more conservative responses, while a higher temperature yields more creative and diverse responses. Defaults to `0.1`.
- `"top_p"`: `float`
Also known as “nucleus sampling”, this parameter sets a threshold to select a smaller set of words to sample from. It focuses on the most likely words, cutting off the less probable ones. Defaults to `0.3`
- `"presence_penalty"`: `float`
This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation. Defaults to `0.4`.
This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation. Defaults to `0.2`.
- `"frequency penalty"`: `float`
Similar to the presence penalty, this reduces the models tendency to repeat the same words frequently. Defaults to `0.7`.
- `"prompt_config"`: (*Body parameter*), `object`
- `"similarity_threshold"`: (*Body parameter*), `float`
- `"vector_similarity_weight"`: (*Body parameter*), `float`
- `"top_n"`: (*Body parameter*), `int`
- `"top_k"`: (*Body parameter*), `int`
- `"rerank_id"`: (*Body parameter*), `string`
Any field omitted from the request body is reset to the server-side default value for `PUT`.
#### Response
Success: returns the full updated chat assistant object.
```json
{
"code": 0,
"data": {
"id": "04d0d8e28d1911efa3630242ac120006",
"name": "Test",
"description": "A helpful Assistant",
"icon": "",
"dataset_ids": ["527fa74891e811ef9c650242ac120006"],
"kb_names": ["dataset_1"],
"llm_id": "qwen-plus@Tongyi-Qianwen",
"llm_setting": {
"frequency_penalty": 0.7,
"presence_penalty": 0.4,
"temperature": 0.1,
"top_p": 0.3
},
"prompt_config": {
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
"prologue": "Hi! I'm your assistant. What can I do for you?",
"quote": true,
"system": "You are an intelligent assistant...",
"parameters": [{"key": "knowledge", "optional": false}]
},
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"top_n": 6,
"top_k": 1024,
"rerank_id": "",
"status": "1",
"tenant_id": "69736c5e723611efb51b0242ac120007",
"create_time": 1729232406637,
"update_time": 1729232406638
}
}
```
Failure:
```json
{
"code": 102,
"message": "Duplicated chat name."
}
```
---
### Get chat assistant
**GET** `/api/v1/chats/{chat_id}`
Retrieves a specified chat assistant.
#### Request
- Method: GET
- URL: `/api/v1/chats/{chat_id}`
- Headers:
- `'Authorization: Bearer <YOUR_API_KEY>'`
##### Request example
```bash
curl --request GET \
--url http://{address}/api/v1/chats/{chat_id} \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
##### Request parameters
- `chat_id`: (*Path parameter*)
The ID of the chat assistant to retrieve.
- `"prompt"`: (*Body parameter*), `object`
Instructions for the LLM to follow. A `prompt` object contains the following attributes:
- `"similarity_threshold"`: `float` RAGFlow employs either a combination of weighted keyword similarity and weighted vector cosine similarity, or a combination of weighted keyword similarity and weighted rerank score during retrieval. This argument sets the threshold for similarities between the user query and chunks. If a similarity score falls below this threshold, the corresponding chunk will be excluded from the results. The default value is `0.2`.
- `"keywords_similarity_weight"`: `float` This argument sets the weight of keyword similarity in the hybrid similarity score with vector cosine similarity or reranking model similarity. By adjusting this weight, you can control the influence of keyword similarity in relation to other similarity measures. The default value is `0.7`.
- `"top_n"`: `int` This argument specifies the number of top chunks with similarity scores above the `similarity_threshold` that are fed to the LLM. The LLM will *only* access these 'top N' chunks. The default value is `8`.
- `"variables"`: `object[]` This argument lists the variables to use in the 'System' field of **Chat Configurations**. Note that:
- `"knowledge"` is a reserved variable, which represents the retrieved chunks.
- All the variables in 'System' should be curly bracketed.
- The default value is `[{"key": "knowledge", "optional": true}]`
- `"rerank_model"`: `string` If it is not specified, vector cosine similarity will be used; otherwise, reranking score will be used.
- `"empty_response"`: `string` If nothing is retrieved in the dataset for the user's question, this will be used as the response. To allow the LLM to improvise when nothing is found, leave this blank.
- `"opener"`: `string` The opening greeting for the user. Defaults to `"Hi! I am your assistant, can I help you?"`.
- `"show_quote`: `boolean` Indicates whether the source of text should be displayed. Defaults to `true`.
- `"prompt"`: `string` The prompt content.
#### Response
@@ -3053,38 +2961,7 @@ Success:
```json
{
"code": 0,
"data": {
"icon": "",
"create_date": "Fri, 18 Oct 2024 06:20:06 GMT",
"create_time": 1729232406637,
"description": "A helpful Assistant",
"id": "04d0d8e28d1911efa3630242ac120006",
"dataset_ids": ["527fa74891e811ef9c650242ac120006"],
"kb_names": ["dataset_1"],
"language": "English",
"llm_id": "qwen-plus@Tongyi-Qianwen",
"llm_setting": {
"temperature": 0.1,
"top_p": 0.3
},
"name": "my_chat",
"prompt_config": {
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
"prologue": "Hi! I'm your assistant. What can I do for you?",
"quote": true,
"system": "You are an intelligent assistant...",
"parameters": [{"key": "knowledge", "optional": false}]
},
"rerank_id": "",
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"top_n": 6,
"status": "1",
"tenant_id": "69736c5e723611efb51b0242ac120007",
"update_date": "Fri, 18 Oct 2024 06:20:06 GMT",
"update_time": 1729232406638
}
"code": 0
}
```
@@ -3093,112 +2970,7 @@ Failure:
```json
{
"code": 102,
"message": "No authorization."
}
```
---
### Partially update chat assistant
**PATCH** `/api/v1/chats/{chat_id}`
Partially updates a specified chat assistant.
This endpoint preserves unspecified fields. Nested `llm_setting` and `prompt_config` objects are deep-merged with the existing configuration, so it is the recommended endpoint for renaming a chat assistant or updating only a subset of settings.
#### Request
- Method: PATCH
- URL: `/api/v1/chats/{chat_id}`
- Headers:
- `'content-Type: application/json'`
- `'Authorization: Bearer <YOUR_API_KEY>'`
- Body: any subset of the fields accepted by `PUT /api/v1/chats/{chat_id}`
##### Request example
```bash
curl --request PATCH \
--url http://{address}/api/v1/chats/{chat_id} \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <YOUR_API_KEY>' \
--data '{
"llm_id": "gpt-4o",
"llm_setting": {"temperature": 0.5}
}'
```
#### Response
Success: returns the full updated chat assistant object (same structure as `PUT /api/v1/chats/{chat_id}`).
```json
{
"code": 0,
"data": {
"id": "04d0d8e28d1911efa3630242ac120006",
"name": "Renamed assistant",
"llm_id": "qwen-plus@Tongyi-Qianwen",
"..." : "..."
}
}
```
Failure:
```json
{
"code": 102,
"message": "No authorization."
}
```
---
### Delete chat assistant
**DELETE** `/api/v1/chats/{chat_id}`
Deletes a single chat assistant by ID.
#### Request
- Method: DELETE
- URL: `/api/v1/chats/{chat_id}`
- Headers:
- `'Authorization: Bearer <YOUR_API_KEY>'`
##### Request example
```bash
curl --request DELETE \
--url http://{address}/api/v1/chats/{chat_id} \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
##### Request parameters
- `chat_id`: (*Path parameter*)
The ID of the chat assistant to delete.
#### Response
Success:
```json
{
"code": 0,
"data": true
}
```
Failure:
```json
{
"code": 102,
"message": "No authorization."
"message": "Duplicated chat name in updating dataset."
}
```
@@ -3276,14 +3048,14 @@ Failure:
### List chat assistants
**GET** `/api/v1/chats?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&keywords={keywords}&owner_ids={owner_id}&name={chat_name}&id={chat_id}`
**GET** `/api/v1/chats?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={chat_name}&id={chat_id}`
Lists chat assistants.
#### Request
- Method: GET
- URL: `/api/v1/chats?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&keywords={keywords}&owner_ids={owner_id}&name={chat_name}&id={chat_id}`
- URL: `/api/v1/chats?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={chat_name}&id={chat_id}`
- Headers:
- `'Authorization: Bearer <YOUR_API_KEY>'`
@@ -3291,32 +3063,26 @@ Lists chat assistants.
```bash
curl --request GET \
--url http://{address}/api/v1/chats?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&keywords={keywords}&owner_ids={owner_id}&name={chat_name}&id={chat_id} \
--url http://{address}/api/v1/chats?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={chat_name}&id={chat_id} \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
##### Request parameters
- `page`: (*Filter parameter*), `integer`
- `page`: (*Filter parameter*), `integer`
Specifies the page on which the chat assistants will be displayed. Defaults to `1`.
- `page_size`: (*Filter parameter*), `integer`
- `page_size`: (*Filter parameter*), `integer`
The number of chat assistants on each page. Defaults to `30`.
- `orderby`: (*Filter parameter*), `string`
- `orderby`: (*Filter parameter*), `string`
The attribute by which the results are sorted. Available options:
- `create_time` (default)
- `update_time`
- `desc`: (*Filter parameter*), `boolean`
- `desc`: (*Filter parameter*), `boolean`
Indicates whether the retrieved chat assistants should be sorted in descending order. Defaults to `true`.
- `keywords`: (*Filter parameter*), `string`
Case-insensitive fuzzy match against chat assistant names.
- `owner_ids`: (*Filter parameter*), `string` (repeatable)
Filter by owner tenant IDs. Can be specified multiple times: `?owner_ids=id1&owner_ids=id2`.
- `id`: (*Filter parameter*), `string`
The ID of the chat assistant to retrieve with exact match.
- `name`: (*Filter parameter*), `string`
The name of the chat assistant to retrieve with exact match.
When `id` or `name` is provided, exact filtering takes precedence over `keywords`.
- `id`: (*Filter parameter*), `string`
The ID of the chat assistant to retrieve.
- `name`: (*Filter parameter*), `string`
The name of the chat assistant to retrieve.
#### Response
@@ -3325,50 +3091,47 @@ Success:
```json
{
"code": 0,
"data": {
"chats": [
{
"icon": "",
"create_date": "Fri, 18 Oct 2024 06:20:06 GMT",
"create_time": 1729232406637,
"description": "A helpful Assistant",
"id": "04d0d8e28d1911efa3630242ac120006",
"dataset_ids": ["527fa74891e811ef9c650242ac120006"],
"kb_names": ["dataset_1"],
"language": "English",
"llm_id": "qwen-plus@Tongyi-Qianwen",
"llm_setting": {
"frequency_penalty": 0.7,
"presence_penalty": 0.4,
"temperature": 0.1,
"top_p": 0.3
},
"name": "13243",
"prompt_config": {
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
"prologue": "Hi! I'm your assistant. What can I do for you?",
"quote": true,
"system": "You are an intelligent assistant...",
"parameters": [
{
"key": "knowledge",
"optional": false
}
]
},
"rerank_id": "",
"data": [
{
"avatar": "",
"create_date": "Fri, 18 Oct 2024 06:20:06 GMT",
"create_time": 1729232406637,
"description": "A helpful Assistant",
"do_refer": "1",
"id": "04d0d8e28d1911efa3630242ac120006",
"dataset_ids": ["527fa74891e811ef9c650242ac120006"],
"language": "English",
"llm": {
"frequency_penalty": 0.7,
"model_name": "qwen-plus@Tongyi-Qianwen",
"presence_penalty": 0.4,
"temperature": 0.1,
"top_p": 0.3
},
"name": "13243",
"prompt": {
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
"keywords_similarity_weight": 0.3,
"opener": "Hi! I'm your assistant. What can I do for you?",
"prompt": "You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence \"The answer you are looking for is not found in the knowledge base!\" Answers need to consider chat history.\n",
"rerank_model": "",
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"top_n": 6,
"prompt_type": "simple",
"status": "1",
"tenant_id": "69736c5e723611efb51b0242ac120007",
"update_date": "Fri, 18 Oct 2024 06:20:06 GMT",
"update_time": 1729232406638
}
],
"total": 1
}
"variables": [
{
"key": "knowledge",
"optional": false
}
]
},
"prompt_type": "simple",
"status": "1",
"tenant_id": "69736c5e723611efb51b0242ac120007",
"top_k": 1024,
"update_date": "Fri, 18 Oct 2024 06:20:06 GMT",
"update_time": 1729232406638
}
]
}
```