From cc4c519ce0b6a5b7b7b0013713025efe1b610351 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 06:06:57 +0000 Subject: [PATCH 01/15] update --- logicnet/protocol.py | 31 +++++++++++++++++++++++++++- logicnet/utils/config.py | 2 +- neurons/validator/validator_proxy.py | 20 ++++++++++-------- tmp.py | 29 ++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 tmp.py diff --git a/logicnet/protocol.py b/logicnet/protocol.py index aeae615e..d4494bce 100644 --- a/logicnet/protocol.py +++ b/logicnet/protocol.py @@ -60,4 +60,33 @@ def deserialize_response(self): return { "logic_answer": self.logic_answer, "logic_reasoning": self.logic_reasoning, - } \ No newline at end of file + } + + +class LogicRequest(pydantic.BaseModel): + """ + Logic Synapse for the LogicNet protocol + """ + + # MINER NEED TO FILL THIS INFORMATION + logic_question: str = pydantic.Field( + "", + description="Logic question to be answered by miner. It can be noised question from the raw logic question from synthetic loop.", + ) + logic_answer: Union[str, object] = pydantic.Field( + "", description="Short logic answer as a summary of the logic reasoning." + ) + logic_reasoning: str = pydantic.Field( + "", + description="Reasoning when answering the logic question", + ) + + # SYNAPSE INFORMATION + category: str = pydantic.Field( + "", + description="One of the categories in the Validator main.", + ) + timeout: int = pydantic.Field( + 64, + description="Timeout for the miner to answer the logic question.", + ) diff --git a/logicnet/utils/config.py b/logicnet/utils/config.py index a8e195e2..8fbaad11 100644 --- a/logicnet/utils/config.py +++ b/logicnet/utils/config.py @@ -119,7 +119,7 @@ def add_args(cls, parser): "--proxy.proxy_client_url", type=str, help="The url initialize credentials for proxy.", - default="http://logicapi.aitprotocol.ai/proxy_client", + default="http://logicnet.aitprotocol.ai/proxy_client", ) parser.add_argument( diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 96988871..19fe4233 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -1,6 +1,5 @@ from fastapi import FastAPI, HTTPException, Depends from pydantic import BaseModel -from typing import Optional from concurrent.futures import ThreadPoolExecutor import uvicorn from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey @@ -14,11 +13,13 @@ import httpx import threading + class OrganicRequest(BaseModel): authorization: str - payload: Optional[logicnet.protocol.LogicSynapse] = None + synapse_request: logicnet.protocol.LogicRequest = None re_check: bool = False + class ValidatorProxy: """ TODO. NOT IMPLEMENTED FOR LOGICNET @@ -90,9 +91,7 @@ def authenticate_token(self, public_key_bytes): status_code=401, detail="Error getting authentication token" ) - def organic_reward( - self, synapse, response, uid, rewarder, timeout - ): + def organic_reward(self, synapse, response, uid, rewarder, timeout): if callable(rewarder): uids, rewards = rewarder([uid], [response], synapse) else: @@ -115,7 +114,7 @@ def organic_reward( async def forward(self, data: OrganicRequest): self.authenticate_token(data.authorization) - synapse = data.payload + synapse = logicnet.protocol.LogicSynapse(**data.synapse_request.dict()) if data.re_check: bt.logging.info("Rechecking validators") self.get_credentials() @@ -135,8 +134,11 @@ async def forward(self, data: OrganicRequest): output = None for uid, should_reward in self.validator.query_queue.get_query_for_proxy( category - ): - should_reward = random.random() < self.validator.config.proxy.checking_probability or should_reward + ): + should_reward = ( + random.random() < self.validator.config.proxy.checking_probability + or should_reward + ) bt.logging.info( f"Forwarding request to miner {uid} with recent scores: {self.validator.miner_manager.all_uids_info[uid].scores}" ) @@ -168,4 +170,4 @@ async def forward(self, data: OrganicRequest): return HTTPException(status_code=500, detail="No valid response received") async def get_self(self): - return self \ No newline at end of file + return self diff --git a/tmp.py b/tmp.py new file mode 100644 index 00000000..31d5da97 --- /dev/null +++ b/tmp.py @@ -0,0 +1,29 @@ +from openai import OpenAI + +client = OpenAI( + base_url="http://localhost:8000/v1", + api_key="", +) + +completion = client.chat.completions.create( + model="Qwen/Qwen2-7B-Instruct", + messages=[{"role": "user", "content": "Hello!"}], +) + +print(completion.choices[0].message) + +a = { + "id": "cmpl-fffbffa3648f4d92b0e838bf097b7551", + "object": "text_completion", + "created": 1721745088, + "model": "casperhansen/llama-3-70b-instruct-awq", + "choices": [ + { + "index": 0, + "text": "What a fascinating topic! Here's a story about cryptography that I'd love to share with you:\n\n**The Story of Mary, Queen of Scots, and the Cipher that Changed History**\n\nIn the 16th century, Mary, Queen of Scots, was a powerful figure in European politics. She was a Catholic, and her claim to the English throne was a threat to the Protestant Queen Elizabeth I of England. Mary was eventually forced to flee Scotland and seek refuge in England, where she was placed under house arrest.\n\nDespite her captivity, Mary continued to plot against Elizabeth, seeking to overthrow her and take the throne for herself. To communicate with her co-conspirators, Mary turned to cryptography. She used a complex cipher, known as the \"Babington Cipher,\" to encode her messages.\n\nThe Babington Cipher was a sophisticated system, using a combination of substitution and transposition techniques to conceal the meaning of the messages. Mary and her co-conspirators believed it was unbreakable.\n\nHowever, Elizabeth's spymaster, Sir Francis Walsingham, had other plans. He had a team of cryptanalysts, led by a brilliant mathematician named Thomas Phelippes, who were tasked with cracking the cipher.\n\nAfter months of work, Phelippes finally succeeded in breaking the Babington Cipher. He discovered a message from Mary to her co-conspirators, detailing a plot to assassinate Elizabeth and take the throne.\n\nThe consequences were severe. Mary was put on trial, and the decoded messages were used as evidence against her. In 1587, she was found guilty of treason and executed.\n\nThis event marked a turning point in the history of cryptography. It showed that even the most sophisticated ciphers could be broken, and that cryptography was not a foolproof means of secure communication.\n\nThe story of Mary, Queen of Scots, and the Babington Cipher serves as a reminder of the cat-and-mouse game that has been played between cryptographers and cryptanalysts throughout history. It's a game that continues to this day, with new encryption techniques and methods of attack being developed all the time.\n\nI hope you enjoyed this story! Do you have any questions about cryptography or would you like to hear more stories like this?", + "finish_reason": "stop", + "stop_reason": 128009, + } + ], + "usage": {"prompt_tokens": 47, "total_tokens": 500, "completion_tokens": 453}, +} From 26dc1e01e74189500831f73155918863aba01f15 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 06:14:39 +0000 Subject: [PATCH 02/15] update --- neurons/validator/validator_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 19fe4233..285566c8 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -114,12 +114,12 @@ def organic_reward(self, synapse, response, uid, rewarder, timeout): async def forward(self, data: OrganicRequest): self.authenticate_token(data.authorization) - synapse = logicnet.protocol.LogicSynapse(**data.synapse_request.dict()) if data.re_check: bt.logging.info("Rechecking validators") self.get_credentials() return {"message": "done"} bt.logging.info("Received an organic request!") + synapse = logicnet.protocol.LogicSynapse(**data.synapse_request.dict()) category = synapse.category category_config = self.validator.categories[category] From 38f015a59f3df3172a60c48d1b7239b3409b9957 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 06:25:36 +0000 Subject: [PATCH 03/15] update --- neurons/validator/validator_proxy.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 285566c8..adcff6cb 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -16,8 +16,12 @@ class OrganicRequest(BaseModel): authorization: str - synapse_request: logicnet.protocol.LogicRequest = None - re_check: bool = False + synapse_request: logicnet.protocol.LogicRequest + + +class Recheck(BaseModel): + authorization: str + re_check: bool class ValidatorProxy: @@ -40,6 +44,12 @@ def __init__( methods=["POST"], dependencies=[Depends(self.get_self)], ) + self.app.add_api_route( + "/recheck", + self.re_check, + methods=["POST"], + dependencies=[Depends(self.get_self)], + ) self.loop = asyncio.get_event_loop() if self.validator.config.proxy.port: self.start_server() @@ -91,6 +101,12 @@ def authenticate_token(self, public_key_bytes): status_code=401, detail="Error getting authentication token" ) + def re_check(self, data: Recheck): + self.authenticate_token(data.authorization) + bt.logging.info("Rechecking validators") + self.get_credentials() + return {"message": "done"} + def organic_reward(self, synapse, response, uid, rewarder, timeout): if callable(rewarder): uids, rewards = rewarder([uid], [response], synapse) @@ -114,10 +130,6 @@ def organic_reward(self, synapse, response, uid, rewarder, timeout): async def forward(self, data: OrganicRequest): self.authenticate_token(data.authorization) - if data.re_check: - bt.logging.info("Rechecking validators") - self.get_credentials() - return {"message": "done"} bt.logging.info("Received an organic request!") synapse = logicnet.protocol.LogicSynapse(**data.synapse_request.dict()) From 7222fe73ad561d5227c6aeed47f978908699799f Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 06:30:26 +0000 Subject: [PATCH 04/15] update --- neurons/validator/validator_proxy.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index adcff6cb..2353cf4f 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -59,11 +59,7 @@ def get_credentials(self): response = client.post( f"{self.validator.config.proxy.proxy_client_url}/get_credentials", json={ - "postfix": ( - f":{self.validator.config.proxy.port}/validator_proxy" - if self.validator.config.proxy.port - else "" - ), + "port": self.validator.proxy.port, "uid": self.validator.uid, }, ) From 4a44a64118fc4b3a6fdb82e6eef974fcd8d60a45 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 06:33:43 +0000 Subject: [PATCH 05/15] update --- neurons/validator/validator_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 2353cf4f..0e3ea40a 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -59,7 +59,7 @@ def get_credentials(self): response = client.post( f"{self.validator.config.proxy.proxy_client_url}/get_credentials", json={ - "port": self.validator.proxy.port, + "port": self.validator.config.proxy.port, "uid": self.validator.uid, }, ) From c869b3b6f1842f3a96d18cdfa840caa4401b49b5 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 06:41:41 +0000 Subject: [PATCH 06/15] update --- logicnet/validator/miner_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/logicnet/validator/miner_manager.py b/logicnet/validator/miner_manager.py index a1e211ed..457cb4e8 100644 --- a/logicnet/validator/miner_manager.py +++ b/logicnet/validator/miner_manager.py @@ -41,6 +41,8 @@ def __repr__(self): return str(self.to_dict()) + "\n" def to_dict(self): + # Round score to 4 decimal places + self.scores = [round(score, 3) for score in self.scores] return { "category": self.category, "scores": self.scores, From 1f7363bdd60928419876e6dcb7c3adc14a013dd1 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 07:25:28 +0000 Subject: [PATCH 07/15] update --- logicnet/utils/config.py | 9 ++++++++- neurons/validator/validator.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/logicnet/utils/config.py b/logicnet/utils/config.py index 8fbaad11..877d9418 100644 --- a/logicnet/utils/config.py +++ b/logicnet/utils/config.py @@ -119,7 +119,14 @@ def add_args(cls, parser): "--proxy.proxy_client_url", type=str, help="The url initialize credentials for proxy.", - default="http://logicnet.aitprotocol.ai/proxy_client", + default="https://logicnet.aitprotocol.ai/proxy_client", + ) + + parser.add_arugment( + "--storage.storage_url", + type=str, + help="The url initialize to store miner's information.", + default="https://logicnet.aitprotocol.ai/storage", ) parser.add_argument( diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index 0251e70b..126e1599 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -9,6 +9,8 @@ import traceback import threading from neurons.validator.core.serving_queue import QueryQueue +import requests + def init_category(config=None): category = { @@ -30,6 +32,7 @@ def init_category(config=None): } return category + class Validator(BaseValidatorNeuron): def __init__(self, config=None): """ @@ -99,6 +102,21 @@ def forward(self): str(self.miner_manager.all_uids_info).replace("},", "},\n"), ) + miner_informations = self.miner_manager.all_uids_info + + def _post_miner_informations(miner_informations): + requests.post( + self.config.storage.storage_url, + { + "miner_informations": miner_informations, + }, + ) + + thread = threading.Thread( + target=_post_miner_informations, + args=(miner_informations,), + ) + actual_time_taken = time.time() - loop_start if actual_time_taken < loop_base_time: @@ -243,6 +261,7 @@ def load_state(self): self.step = 0 bt.logging.info("Could not find previously saved state.", e) + # The main function parses the configuration and runs the validator. if __name__ == "__main__": with Validator() as validator: From dc7bd1aacfad97dcd27de6913ab4647bcdbe043e Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 08:33:37 +0000 Subject: [PATCH 08/15] update --- logicnet/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logicnet/utils/config.py b/logicnet/utils/config.py index 877d9418..cf3f2423 100644 --- a/logicnet/utils/config.py +++ b/logicnet/utils/config.py @@ -122,7 +122,7 @@ def add_args(cls, parser): default="https://logicnet.aitprotocol.ai/proxy_client", ) - parser.add_arugment( + parser.add_argument( "--storage.storage_url", type=str, help="The url initialize to store miner's information.", From deef6c02c9493c80a70b2d247295d03c3eb4a36d Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 08:39:07 +0000 Subject: [PATCH 09/15] update --- logicnet/validator/miner_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logicnet/validator/miner_manager.py b/logicnet/validator/miner_manager.py index 457cb4e8..050503db 100644 --- a/logicnet/validator/miner_manager.py +++ b/logicnet/validator/miner_manager.py @@ -42,7 +42,7 @@ def __repr__(self): def to_dict(self): # Round score to 4 decimal places - self.scores = [round(score, 3) for score in self.scores] + self.scores = [round(score, 3) for score in self.scores][-NO_OF_RECENT_SCORES:] return { "category": self.category, "scores": self.scores, From e650e0f9538e10009824367ab6955a6ac5e83519 Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 08:45:23 +0000 Subject: [PATCH 10/15] update --- neurons/validator/validator_proxy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 0e3ea40a..dcc2112f 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -21,7 +21,6 @@ class OrganicRequest(BaseModel): class Recheck(BaseModel): authorization: str - re_check: bool class ValidatorProxy: @@ -85,8 +84,8 @@ def start_server(self): ) def authenticate_token(self, public_key_bytes): - public_key_bytes = base64.b64decode(public_key_bytes) try: + public_key_bytes = base64.b64decode(public_key_bytes) self.verify_credentials(public_key_bytes) bt.logging.info("Successfully authenticated token") return public_key_bytes From 5b2f042fe2fc3b1ff3d842f95b4d502cde160fde Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 08:57:01 +0000 Subject: [PATCH 11/15] update --- neurons/validator/validator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index 126e1599..0e476866 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -106,9 +106,10 @@ def forward(self): def _post_miner_informations(miner_informations): requests.post( - self.config.storage.storage_url, - { - "miner_informations": miner_informations, + url=self.config.storage.storage_url, + json={ + "miner_information": miner_informations, + "validator_uid": int(self.uid), }, ) From 33e230fb9a443e36b5a5aa298809d0d86bb210fd Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 09:00:46 +0000 Subject: [PATCH 12/15] update --- neurons/validator/validator.py | 37 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index 0e476866..d169ba42 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -65,7 +65,7 @@ def forward(self): Query miners by batched from the serving queue then process challenge-generating -> querying -> rewarding in background by threads DEFAULT: 16 miners per batch, 600 seconds per loop. """ - + self.store_miner_infomation() bt.logging.info("Updating available models & uids") async_batch_size = self.config.async_batch_size loop_base_time = self.config.loop_base_time # default is 600 seconds @@ -101,22 +101,7 @@ def forward(self): "Loop completed, uids info:\n", str(self.miner_manager.all_uids_info).replace("},", "},\n"), ) - - miner_informations = self.miner_manager.all_uids_info - - def _post_miner_informations(miner_informations): - requests.post( - url=self.config.storage.storage_url, - json={ - "miner_information": miner_informations, - "validator_uid": int(self.uid), - }, - ) - - thread = threading.Thread( - target=_post_miner_informations, - args=(miner_informations,), - ) + self.store_miner_infomation() actual_time_taken = time.time() - loop_start @@ -262,6 +247,24 @@ def load_state(self): self.step = 0 bt.logging.info("Could not find previously saved state.", e) + def store_miner_infomation(self): + miner_informations = self.miner_manager.all_uids_info + + def _post_miner_informations(miner_informations): + requests.post( + url=self.config.storage.storage_url, + json={ + "miner_information": miner_informations, + "validator_uid": int(self.uid), + }, + ) + + thread = threading.Thread( + target=_post_miner_informations, + args=(miner_informations,), + ) + thread.start() + # The main function parses the configuration and runs the validator. if __name__ == "__main__": From 6b7bc761abd880bb01c6c0e1fde8ab5edf7b9bfd Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 09:17:41 +0000 Subject: [PATCH 13/15] update --- logicnet/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logicnet/utils/config.py b/logicnet/utils/config.py index cf3f2423..af1dd1dc 100644 --- a/logicnet/utils/config.py +++ b/logicnet/utils/config.py @@ -126,7 +126,7 @@ def add_args(cls, parser): "--storage.storage_url", type=str, help="The url initialize to store miner's information.", - default="https://logicnet.aitprotocol.ai/storage", + default="https://logicnet.aitprotocol.ai/proxy_client/store_miner_information", ) parser.add_argument( From 89c230ee8e70698d9aeac7852eec4ebf4f81010e Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 09:20:42 +0000 Subject: [PATCH 14/15] update --- logicnet/validator/miner_manager.py | 3 +++ neurons/validator/validator.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/logicnet/validator/miner_manager.py b/logicnet/validator/miner_manager.py index 050503db..9e941887 100644 --- a/logicnet/validator/miner_manager.py +++ b/logicnet/validator/miner_manager.py @@ -58,6 +58,9 @@ def __init__(self, validator): self.all_uids = [int(uid.item()) for uid in self.validator.metagraph.uids] self.all_uids_info = {uid: MinerInfo() for uid in self.all_uids} + def to_dict(self): + return {uid: info.to_dict() for uid, info in self.all_uids_info.items()} + def get_miner_info(self): """ QUERY MINER's INFORMATION SYNAPSE diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index d169ba42..adea7e52 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -248,7 +248,7 @@ def load_state(self): bt.logging.info("Could not find previously saved state.", e) def store_miner_infomation(self): - miner_informations = self.miner_manager.all_uids_info + miner_informations = self.miner_manager.to_dict() def _post_miner_informations(miner_informations): requests.post( From 5dc48bb5f1f4308d448f38e8b633e4da44dc7f8b Mon Sep 17 00:00:00 2001 From: toilaluan Date: Mon, 29 Jul 2024 11:15:12 +0000 Subject: [PATCH 15/15] del --- tmp.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 tmp.py diff --git a/tmp.py b/tmp.py deleted file mode 100644 index 31d5da97..00000000 --- a/tmp.py +++ /dev/null @@ -1,29 +0,0 @@ -from openai import OpenAI - -client = OpenAI( - base_url="http://localhost:8000/v1", - api_key="", -) - -completion = client.chat.completions.create( - model="Qwen/Qwen2-7B-Instruct", - messages=[{"role": "user", "content": "Hello!"}], -) - -print(completion.choices[0].message) - -a = { - "id": "cmpl-fffbffa3648f4d92b0e838bf097b7551", - "object": "text_completion", - "created": 1721745088, - "model": "casperhansen/llama-3-70b-instruct-awq", - "choices": [ - { - "index": 0, - "text": "What a fascinating topic! Here's a story about cryptography that I'd love to share with you:\n\n**The Story of Mary, Queen of Scots, and the Cipher that Changed History**\n\nIn the 16th century, Mary, Queen of Scots, was a powerful figure in European politics. She was a Catholic, and her claim to the English throne was a threat to the Protestant Queen Elizabeth I of England. Mary was eventually forced to flee Scotland and seek refuge in England, where she was placed under house arrest.\n\nDespite her captivity, Mary continued to plot against Elizabeth, seeking to overthrow her and take the throne for herself. To communicate with her co-conspirators, Mary turned to cryptography. She used a complex cipher, known as the \"Babington Cipher,\" to encode her messages.\n\nThe Babington Cipher was a sophisticated system, using a combination of substitution and transposition techniques to conceal the meaning of the messages. Mary and her co-conspirators believed it was unbreakable.\n\nHowever, Elizabeth's spymaster, Sir Francis Walsingham, had other plans. He had a team of cryptanalysts, led by a brilliant mathematician named Thomas Phelippes, who were tasked with cracking the cipher.\n\nAfter months of work, Phelippes finally succeeded in breaking the Babington Cipher. He discovered a message from Mary to her co-conspirators, detailing a plot to assassinate Elizabeth and take the throne.\n\nThe consequences were severe. Mary was put on trial, and the decoded messages were used as evidence against her. In 1587, she was found guilty of treason and executed.\n\nThis event marked a turning point in the history of cryptography. It showed that even the most sophisticated ciphers could be broken, and that cryptography was not a foolproof means of secure communication.\n\nThe story of Mary, Queen of Scots, and the Babington Cipher serves as a reminder of the cat-and-mouse game that has been played between cryptographers and cryptanalysts throughout history. It's a game that continues to this day, with new encryption techniques and methods of attack being developed all the time.\n\nI hope you enjoyed this story! Do you have any questions about cryptography or would you like to hear more stories like this?", - "finish_reason": "stop", - "stop_reason": 128009, - } - ], - "usage": {"prompt_tokens": 47, "total_tokens": 500, "completion_tokens": 453}, -}