From a3e4d5696c3aa7cbf23716f2896332eeb4031740 Mon Sep 17 00:00:00 2001 From: Grey_D Date: Sun, 19 Mar 2023 19:13:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20API=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for ChatGPT API (for pro only) --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 4 +- README.md | 2 + example_chatgpt_api.py | 51 +- example_chatgpt_playwright.py | 21 + requirements.txt | 5 +- tasks/NLP_RESTAPI_Generation.py | 13 + tasks/NLP_RESTAPI_Repairment.py | 5 - .../dotCMS/container-api.html | 988 ++++++++++++++++++ tasks/crawler.py | 40 + example_sqlmap.py => tasks/example_sqlmap.py | 0 .../test_os_execution.py | 0 utils/chatgpt.py | 178 ++++ 13 files changed, 1268 insertions(+), 39 deletions(-) create mode 100644 example_chatgpt_playwright.py create mode 100644 tasks/NLP_RESTAPI_Generation.py delete mode 100644 tasks/NLP_RESTAPI_Repairment.py create mode 100644 tasks/crawl_page_sources/dotCMS/container-api.html create mode 100644 tasks/crawler.py rename example_sqlmap.py => tasks/example_sqlmap.py (100%) rename test_os_execution.py => tasks/test_os_execution.py (100%) create mode 100644 utils/chatgpt.py diff --git a/.DS_Store b/.DS_Store index 50fadda11b5859914e9daf7f88381ae39e6e4749..e55b67976ed4f20229d0e42362706a51d28f8e8a 100644 GIT binary patch literal 6148 zcmeHK&2G~`5S~rb)@g*4L#en_vcxqC0Re5rB@OAJ2PD)WH~(SK{&sfPOGKhM@@^AVh{!@>UED%;fp9;k zB`N5ZYtTaZ2&j7;%2PL#?vk}>Fbo(5{x=4A?{3g3Ns8z-<@YZi3WrFwZ%WkEx5=ls zZ0?T0c-oFMXt3J`ccOkO+-B2P}5auj*7 z!nV^6t7Mfrm7VEy?{3xJJD64N>A{_P)!x5aMC#o`;mO7cp-rkPzf`d zmG`LpF>B-YFbZYV1&00YtYo_j{+aVbdQ8VuLv4X^@d5bk%I*Hv7q6&8=QP3kAri2j z$%USG7d&qP7a}4!0vyDV$B~g=fAMtzQ=$_(!t7i02)YKemq!$sgY5h(n3Yd&XoR_O zsgD$4hB43yVlv6&RpfrmL~a>Y4y@09HBS|%S=9PZRGjxOn1!t6HY|M={~hdr7^Cn` z@v%Qf=n2#gTeVJEKSdg=FB>n6#|&oeP`#w+h_%X>^kd+L0mH!6V}Q>G8;Ny8>v^ix z(Sb}o0icU$R)R8L_l&W%ZfHGEHF_W-g+ixLp`REcg`?fpb`7oPsZQY}^y5S5k%fMt z2sJwV+sd3oBh@B13>XIT46LQq3g7=ntKa{5kf|943B)qu~2NHo}wrd0|Nsi1A_nqLjgk$Ln=c&Lo!3g#=_-{lMO^zHaD}V qF>V&&5N4U!uzxc<2R{c;<7PpQ@640=MI1SRT0weQHb;o8VFmzRloH_p diff --git a/.gitignore b/.gitignore index 6769e21..340dae0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ __pycache__/ *.py[cod] *$py.class +config/ +outputs/ # C extensions *.so @@ -157,4 +159,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ diff --git a/README.md b/README.md index b6775d9..3e1e1ed 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ## General The goal is to build a user-friendly bridge to automate ChatGPT and other GPT modules to automatically control terminal-based applications. This allows an automation of many human-intensive works, such as penetration testing with tools. +There are two modes of running. One is based on the project `chatgpt-wrapper`, which uses playwright browser to extend the ChatGPT API. It is recommended for free users because other API libraries cannot bypass the Cloudflare protection. The other mode is API-based, which utilizes `utils/chatgpt.py`. ChatGPT plus user can provide necessary key information to use this mode. + ## Installation 1. Install `requirements.txt` with `pip install -r requirements.txt` 2. Install `chatgpt-wrapper`: `pip install git+https://github.com/mmabrouk/chatgpt-wrapper`. More details at: https://github.com/mmabrouk/chatgpt-wrapper. diff --git a/example_chatgpt_api.py b/example_chatgpt_api.py index dd4e64a..83613f7 100644 --- a/example_chatgpt_api.py +++ b/example_chatgpt_api.py @@ -1,35 +1,22 @@ -from chatgpt_wrapper import ChatGPT -from llm_handle.parser import extract_cmd -from task_handle.cmd_execution import execute_cmd -import os +import loguru +from config.chatgpt_config import ChatGPTConfig +from utils.chatgpt import ChatGPT -def __main__(): - bot = ChatGPT() - response = bot.ask( - "Can you give me a sample command in Mac terminal for checking the user names? Please give me the code directly." - ) - sample_response = """ - Certainly! To list all user names on a Mac using the terminal, you can use the `dscl` command with the `list` option for the `/Users` node. Here's the command: - ``` - dscl . list /Users | grep -v '^_' - ``` +logger = loguru.logger - This will output a list of all user accounts on the system, excluding any system accounts that start with an underscore. - """ - # print("The response is:", response) - command = extract_cmd(str(response)) - print("The command is:", command) - - # execute the command in the terminal - output = execute_cmd(command) - print("The output is:\n", output) - # Ideally, the output should be: - """ - daemon - uname - nobody - root""" - - # delete the session in the end - bot.delete_conversation() +if __name__ == "__main__": + chatGPTAgent = ChatGPT(ChatGPTConfig()) + # the title of this conversation will be new-chat. We can delete it later. + text, conversation_id = chatGPTAgent.send_new_message("Hello, world!") + print(text, conversation_id) + # get history id + history = chatGPTAgent.get_conversation_history() + print(history) + for uuid in history: + print(uuid) + if history[uuid].lower() == "new chat": + result = chatGPTAgent.delete_conversation(uuid) + print(result) + history = chatGPTAgent.get_conversation_history() + print(history) diff --git a/example_chatgpt_playwright.py b/example_chatgpt_playwright.py new file mode 100644 index 0000000..4d5c613 --- /dev/null +++ b/example_chatgpt_playwright.py @@ -0,0 +1,21 @@ +from chatgpt_wrapper import ChatGPT + +import os + + +if __name__ == "__main__": + bot = ChatGPT() + conversations = bot.get_history() + # structure of conversation: + # {conversation_id (str): {'id': conversation_id, 'title': conversation_title, 'create_time': conversation_create_time'}} + + ## select a past conversation + selected_id = list(conversations.keys())[0] + result = bot.get_conversation(selected_id) + ## Get the conversation history + # print(result) + + ## Try to ask a question in this conversation + question = "What is the meaning of life?" + success, response, message = bot.ask("Hello, world!") + print(response) diff --git a/requirements.txt b/requirements.txt index e6eb24d..956437e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,7 @@ requests pyyaml playwright==1.28.0 sqlmap -black \ No newline at end of file +black +requests +loguru +beautifulsoup4~=4.11.2 \ No newline at end of file diff --git a/tasks/NLP_RESTAPI_Generation.py b/tasks/NLP_RESTAPI_Generation.py new file mode 100644 index 0000000..3833c97 --- /dev/null +++ b/tasks/NLP_RESTAPI_Generation.py @@ -0,0 +1,13 @@ +from utils.chatgpt import ChatGPT +from config.chatgpt_config import ChatGPTConfig + +import loguru + +logger = loguru.logger + +# format: {name: {description: str, sample_curl: str, sample_response: str}} +API_description = {} + + +if __name__ == "__main__": + chatGPTAgent = ChatGPT() diff --git a/tasks/NLP_RESTAPI_Repairment.py b/tasks/NLP_RESTAPI_Repairment.py deleted file mode 100644 index e7bb131..0000000 --- a/tasks/NLP_RESTAPI_Repairment.py +++ /dev/null @@ -1,5 +0,0 @@ -# This handle helps to translate RESTful API documentation in natural language to OpenAPI Specification 3.0 - - -##### Functions ##### -# Get the natural language description of the RESTful API. diff --git a/tasks/crawl_page_sources/dotCMS/container-api.html b/tasks/crawl_page_sources/dotCMS/container-api.html new file mode 100644 index 0000000..93a7a10 --- /dev/null +++ b/tasks/crawl_page_sources/dotCMS/container-api.html @@ -0,0 +1,988 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Container API | dotCMS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+
+ + + + + + + + +
+ + + + + + + +
+
+
+
+

Container API

+
+ Last Updated: + Sep 9, 2022 +
+ + documentation for the dotCMS Content Management System + + +
+
+ + + + + + + + + + + +
+

dotCMS templates are built out of containers, which define display behaviors for different Content Types in the context of different page layouts. This document details the endpoints of a REST API for manipulating containers with create, read, update, and delete (CRUD) operations, utilizing and returning JSON objects.

+

Containers can exist either as database entities or as file system entities — directories containing VTL files. The latter case offers advantages such as easy storage on remote repositories, synchronization through WebDav or command-line interface, or other similar conveniences. By the same measure, there are also some Container API calls that can only be performed on the database-entity type of Container.

+

All examples use the dotCMS demo site as their target. Used on another host, the Authentication header must change accordingly: For Basic authorization, use base64 encoding of a username:password string; for more secure Bearer authorization, use an API token.

+

Retrieving a Container

+

The endpoint offers several methods to retrieve containers, individually or collectively. Please note:

+
    +
  • Containers located in the database must be requested by identifier.
  • +
  • Containers located in the file system must be referenced by path.
  • +
+

To retrieve the working version of a container, use /working:

+
curl --location --request GET 'https://demo.dotcms.com/api/v1/containers/working?containerId=REPLACE_THIS_UUID' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+

To retrieve the live version of a container, call /live:

+
curl --location --request GET 'https://demo.dotcms.com/api/v1/containers/live?containerId=/application/containers/system' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg==' \
+
+

Finally, to retrieve all containers:

+
curl --location --request GET 'https://demo.dotcms.com/api/v1/containers/' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+

To adjust the displayed results, the Container API uses standard pagination parameters such as per_page, filter, etc.

+

A successful call returns containers as JSON objects, which may look, in part, like the following data:

+
{
+    "entity": {
+        "archived": false,
+        "categoryId": "27d80ebe-c9f1-4dd9-8cae-f15e644df708",
+        "deleted": false,
+        "friendlyName": "TestContainer description",
+        "iDate": 1647630014297,
+        "idate": 1647630014297,
+        "identifier": "567416cee048a876d4c60172421832ba",
+        "inode": "27d80ebe-c9f1-4dd9-8cae-f15e644df708",
+        "live": false,
+        "locked": false,
+        "map": {
+            "deleted": false,
+            "friendlyName": "TestContainer description",
+            "hasLiveVersion": false,
+            "iDate": 1647630014297,
+            "identifier": "567416cee048a876d4c60172421832ba",
+            "inode": "27d80ebe-c9f1-4dd9-8cae-f15e644df708",
+            "live": false,
+            "locked": false,
+            "modDate": 1647630014309,
+            "modUser": "dotcms.org.1",
+            "modUserName": "Admin User",
+            "showOnMenu": false,
+            "sortOrder": 0,
+            "title": "TestContainer",
+            "type": "containers",
+            "working": true
+        }
+       ...
+    }
+}
+
+

Adding a Container

+

Adding a container by API call is only possible with a database-style container. A container that exists in the file system must instead be created by adding a folder containing the necessary VTL files within the application/containers folder, either manually or by one of the methods detailed at the top of the page.

+

To add a container, make a POST call with the Container as a JSON payload:

+
curl --location --request POST 'https://demo.dotcms.com/api/v1/containers' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg==' \
+--data-raw '{
+   "title":"TestContainer",
+   "friendlyName":"TestContainer description",
+   "maxContentlets":1,
+   "notes":"Notes",
+   "preLoop":"<h1>Some Title</h1>",
+   "postLoop":"<span>Some Footer</span>",
+   "containerStructures":[
+        {
+            "structureId":"webPageContent",
+            "code":"<div> $!{body} </div>"
+        },
+        {
+            "structureId":"DotAsset",
+            "code":" <img src ='\''./contentAsset/image/${ContentIdentifier}/asset'\'' />"
+        }
+    ]
+}'
+
+

Updating a Container

+

The call to update a container is similar to creating one; it only functioning with database containers, and it has a very similar structure. However, there are two key differences:

+
    +
  • It uses the PUT method instead of POST.
  • +
  • It includes the container's identifier in the payload data.
  • +
+
curl --location --request PUT 'https://demo.dotcms.com/api/v1/containers' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg==' \
+--data-raw '{
+    "identifier":"567416cee048a876d4c60172421832ba",
+    "title":"TestContainer",
+    "friendlyName":"TestContainer description",
+    "maxContentlets":1,
+    "notes":"Notes 1",
+    "preLoop":"preLoop xxxx",
+    "postLoop":"postLoop xxxx",
+    "containerStructures":[
+        {
+            "structureId":"webPageContent",
+            "code":" code xxxx"
+        },
+        {
+            "structureId":"DotAsset",
+            "code":" tags: $!{tags}"
+        }
+    ]
+}'
+
+

Publishing or Unpublishing a Container

+

Publishing or unpublishing a container are similar PUT operations, distinguished by the use of /_publish or /_unpublish. Both operations take either a path or an identifier.

+

Publishing:

+
curl --location --request PUT 'https://demo.dotcms.com/api/v1/containers/_publish?containerId=567416cee048a876d4c60172421832ba' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+

Unpublishing:

+
curl --location --request PUT 'https://demo.dotcms.com/api/v1/containers/_unpublish?containerId=567416cee048a876d4c60172421832ba' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+

Archiving or Unarchiving a Container

+

Archiving and unarchiving works similarly to publishing or unpublishing. Note: Before archiving, containers should be unpublished.

+

To archive:

+
curl --location --request PUT 'https://demo.dotcms.com/api/v1/containers/_archive?containerId=/application/containers/system' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+

To unarchive an archived container:

+
curl --location --request PUT 'https://demo.dotcms.com/api/v1/containers/_unarchive?containerId=/application/containers/system' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+

Deleting a Container

+

Finally, you can use the call below to delete a container. Note: A container should be archived (see above) before deletion.

+
curl --location --request DELETE 'https://demo.dotcms.com/api/v1/containers?containerId=567416cee048a876d4c60172421832ba' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg=='
+
+ +
+ + +
+ +
+
+ +
+ +
+
+

On this page

+
+ +
+
+
+
+ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tasks/crawler.py b/tasks/crawler.py new file mode 100644 index 0000000..0962877 --- /dev/null +++ b/tasks/crawler.py @@ -0,0 +1,40 @@ +import requests +from bs4 import BeautifulSoup +import json + +def parse_markdown_page(url): + page = requests.get(url) + soup = BeautifulSoup(page.content, 'html.parser') + + # Extract the title of the page + title = soup.find('h1').get_text() + + # Extract the subtitles and their descriptions and code chunks + subtitles = soup.find_all('h2') + parsed_subtitles = [] + for subtitle in subtitles: + subtitle_title = subtitle.get_text() + subtitle_contents = subtitle.find_next_siblings(['p', 'pre']) + subtitle_parsed_contents = [] + for i in range(0, len(subtitle_contents), 2): + description = subtitle_contents[i].get_text() + if len(subtitle_contents) > i+1: + code = subtitle_contents[i+1].get_text() + else: + code = '' + subtitle_parsed_contents.append([description, code]) + parsed_subtitles.append([subtitle_title, subtitle_parsed_contents]) + + # Save the results as a structured JSON object + output = {'title': title} + for i in range(len(parsed_subtitles)): + output[f'subtitle{i+1}'] = parsed_subtitles[i][1] + + with open('output.json', 'w') as outfile: + json.dump(output, outfile, indent=4) + + return output + +url = 'https://www.dotcms.com/docs/latest/container-api' +output = parse_markdown_page(url) +print(output) \ No newline at end of file diff --git a/example_sqlmap.py b/tasks/example_sqlmap.py similarity index 100% rename from example_sqlmap.py rename to tasks/example_sqlmap.py diff --git a/test_os_execution.py b/tasks/test_os_execution.py similarity index 100% rename from test_os_execution.py rename to tasks/test_os_execution.py diff --git a/utils/chatgpt.py b/utils/chatgpt.py new file mode 100644 index 0000000..d3daf1e --- /dev/null +++ b/utils/chatgpt.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- + +import json +import re +import time +from uuid import uuid1 +import datetime + +import loguru +import requests + +from config.chatgpt_config import ChatGPTConfig + +logger = loguru.logger + + +class ChatGPT: + def __init__(self, config: ChatGPTConfig): + self.config = config + self.model = config.model + self.proxies = {"https": ""} + self._puid = config._puid + self.cf_clearance = config.cf_clearance + self.session_token = config.session_token + # conversation_id: message_id + self.latest_message_id_dict = {} + self.headers = dict( + { + "cookie": f"cf_clearance={self.cf_clearance}; _puid={self._puid}; " + f"__Secure-next-auth.session-token={self.session_token}", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + # 'Content-Type': 'text/event-stream; charset=utf-8', + } + ) + self.headers["authorization"] = self.get_authorization() + + def get_authorization(self): + url = "https://chat.openai.com/api/auth/session" + r = requests.get(url, headers=self.headers) + authorization = r.json()["accessToken"] + return "Bearer " + authorization + + def get_latest_message_id(self, conversation_id): + # 获取会话窗口最新消息id,连续对话必须 + url = f"https://chat.openai.com/backend-api/conversation/{conversation_id}" + r = requests.get(url, headers=self.headers, proxies=self.proxies) + return r.json()["current_node"] + + def send_new_message(self, message): + # 发送新会话窗口消息,返回会话id + logger.info(f"send_new_message") + url = "https://chat.openai.com/backend-api/conversation" + message_id = str(uuid1()) + data = { + "action": "next", + "messages": [ + { + "id": message_id, + "role": "user", + "content": {"content_type": "text", "parts": [message]}, + } + ], + "parent_message_id": str(uuid1()), + "model": self.model, + } + + r = requests.post(url, headers=self.headers, json=data, proxies=self.proxies) + if r.status_code != 200: + # 发送消息阻塞时等待20秒从新发送 + logger.error(r.json()["detail"]) + time.sleep(self.config.error_wait_time) + return self.send_new_message(message) + + last_line = None + + for line in r.iter_lines(): + if line: + decoded_line = line.decode("utf-8") + if len(decoded_line) == 12: + break + last_line = decoded_line + + result = json.loads(last_line[5:]) + text = "\n".join(result["message"]["content"]["parts"]) + conversation_id = result["conversation_id"] + response_message_id = result["message"]["id"] + self.latest_message_id_dict[conversation_id] = response_message_id + return text, conversation_id + + def send_message(self, message, conversation_id): + # 指定会话窗口发送连续对话消息 + logger.info(f"send_message") + url = "https://chat.openai.com/backend-api/conversation" + # 获取会话窗口最新消息id + if conversation_id not in self.latest_message_id_dict: + logger.info(f"conversation_id: {conversation_id}") + message_id = self.get_latest_message_id(conversation_id) + logger.info(f"message_id: {message_id}") + else: + message_id = self.latest_message_id_dict[conversation_id] + + data = { + "action": "next", + "messages": [ + { + "id": str(uuid1()), + "role": "user", + "content": {"content_type": "text", "parts": [message]}, + } + ], + "conversation_id": conversation_id, + "parent_message_id": message_id, + "model": self.model, + } + r = requests.post(url, headers=self.headers, json=data, proxies=self.proxies) + if r.status_code != 200: + # 发送消息阻塞时等待20秒从新发送 + logger.warning(r.json()["detail"]) + time.sleep(self.config.error_wait_time) + return self.send_message(message, conversation_id) + response_result = json.loads(r.text.split("data: ")[-2]) + new_message_id = response_result["message"]["id"] + self.latest_message_id_dict[conversation_id] = new_message_id + text = "\n".join(response_result["message"]["content"]["parts"]) + return text + + def extract_code_fragments(self, text): + code_fragments = re.findall(r"```(.*?)```", text, re.DOTALL) + return code_fragments + + def get_conversation_history(self, limit=20, offset=0): + # Get the conversation id in the history + url = "https://chat.openai.com/backend-api/conversations" + query_params = { + "limit": limit, + "offset": offset, + } + r = requests.get( + url, headers=self.headers, params=query_params, proxies=self.proxies + ) + if r.status_code == 200: + json_data = r.json() + conversations = {} + for item in json_data["items"]: + conversations[item["id"]] = item["title"] + return conversations + else: + logger.error("Failed to retrieve history") + return None + + def delete_conversation(self, conversation_id=None): + # delete conversation with its uuid + if not conversation_id: + return + url = f"https://chat.openai.com/backend-api/conversation/{conversation_id}" + data = { + "is_visible": False, + } + r = requests.patch(url, headers=self.headers, json=data, proxies=self.proxies) + if r.status_code == 200: + return True + else: + logger.error("Failed to delete conversation") + return False + + +if __name__ == "__main__": + chatgpt_config = ChatGPTConfig() + chatgpt = ChatGPT(chatgpt_config) + text, conversation_id = chatgpt.send_new_message( + "I am a new tester for RESTful APIs." + ) + result = chatgpt.send_message( + "generate: {'post': {'tags': ['pet'], 'summary': 'uploads an image', 'description': '', 'operationId': 'uploadFile', 'consumes': ['multipart/form-data'], 'produces': ['application/json'], 'parameters': [{'name': 'petId', 'in': 'path', 'description': 'ID of pet to update', 'required': True, 'type': 'integer', 'format': 'int64'}, {'name': 'additionalMetadata', 'in': 'formData', 'description': 'Additional data to pass to server', 'required': False, 'type': 'string'}, {'name': 'file', 'in': 'formData', 'description': 'file to upload', 'required': False, 'type': 'file'}], 'responses': {'200': {'description': 'successful operation', 'schema': {'type': 'object', 'properties': {'code': {'type': 'integer', 'format': 'int32'}, 'type': {'type': 'string'}, 'message': {'type': 'string'}}}}}, 'security': [{'petstore_auth': ['write:pets', 'read:pets']}]}}", + conversation_id, + ) + logger.info(chatgpt.extract_code_fragments(result))