diff --git a/memgpt/agent.py b/memgpt/agent.py index 357b6f96..f3690334 100644 --- a/memgpt/agent.py +++ b/memgpt/agent.py @@ -34,6 +34,7 @@ from memgpt.constants import ( CORE_MEMORY_PERSONA_CHAR_LIMIT, LLM_MAX_TOKENS, CLI_WARNING_PREFIX, + JSON_ENSURE_ASCII, ) from .errors import LLMError from .functions.functions import USER_FUNCTIONS_DIR, load_all_function_sets @@ -66,11 +67,13 @@ def link_functions(function_schemas): f"Function '{f_name}' was specified in agent.state.functions, but is not in function library:\n{available_functions.keys()}" ) # Once we find a matching function, make sure the schema is identical - if json.dumps(f_schema) != json.dumps(linked_function["json_schema"]): + if json.dumps(f_schema, ensure_ascii=JSON_ENSURE_ASCII) != json.dumps( + linked_function["json_schema"], ensure_ascii=JSON_ENSURE_ASCII + ): # error_message = ( # f"Found matching function '{f_name}' from agent.state.functions inside function library, but schemas are different." - # + f"\n>>>agent.state.functions\n{json.dumps(f_schema, indent=2)}" - # + f"\n>>>function library\n{json.dumps(linked_function['json_schema'], indent=2)}" + # + f"\n>>>agent.state.functions\n{json.dumps(f_schema, indent=2, ensure_ascii=JSON_ENSURE_ASCII)}" + # + f"\n>>>function library\n{json.dumps(linked_function['json_schema'], indent=2, ensure_ascii=JSON_ENSURE_ASCII)}" # ) schema_diff = get_schema_diff(f_schema, linked_function["json_schema"]) error_message = ( @@ -441,7 +444,7 @@ class Agent(object): if "name" in user_message_json: packed_user_message["name"] = user_message_json["name"] user_message_json.pop("name", None) - packed_user_message["content"] = json.dumps(user_message_json) + packed_user_message["content"] = json.dumps(user_message_json, ensure_ascii=JSON_ENSURE_ASCII) except Exception as e: print(f"{CLI_WARNING_PREFIX}handling of 'name' field failed with: {e}") input_message_sequence = self.messages + [packed_user_message] diff --git a/memgpt/cli/cli.py b/memgpt/cli/cli.py index 23cce665..02ef2b22 100644 --- a/memgpt/cli/cli.py +++ b/memgpt/cli/cli.py @@ -22,7 +22,7 @@ import memgpt.presets.presets as presets import memgpt.utils as utils from memgpt.utils import printd, open_folder_in_explorer, suppress_stdout from memgpt.config import MemGPTConfig -from memgpt.constants import MEMGPT_DIR, CLI_WARNING_PREFIX +from memgpt.constants import MEMGPT_DIR, CLI_WARNING_PREFIX, JSON_ENSURE_ASCII from memgpt.agent import Agent from memgpt.embeddings import embedding_model from memgpt.server.constants import WS_DEFAULT_PORT, REST_DEFAULT_PORT @@ -546,8 +546,8 @@ def run( typer.secho(f"🎉 Created new agent '{agent_state.name}'", fg=typer.colors.GREEN) # pretty print agent config - # printd(json.dumps(vars(agent_config), indent=4, sort_keys=True)) - # printd(json.dumps(agent_init_state), indent=4, sort_keys=True)) + # printd(json.dumps(vars(agent_config), indent=4, sort_keys=True, ensure_ascii=JSON_ENSURE_ASCII)) + # printd(json.dumps(agent_init_state), indent=4, sort_keys=True, ensure_ascii=JSON_ENSURE_ASCII)) # configure llama index original_stdout = sys.stdout # unfortunate hack required to suppress confusing print statements from llama index diff --git a/memgpt/constants.py b/memgpt/constants.py index 58778c71..f313b105 100644 --- a/memgpt/constants.py +++ b/memgpt/constants.py @@ -102,3 +102,6 @@ FUNCTION_PARAM_TYPE_REQ_HEARTBEAT = "boolean" FUNCTION_PARAM_DESCRIPTION_REQ_HEARTBEAT = "Request an immediate heartbeat after function execution. Set to 'true' if you want to send a follow-up message or run a follow-up function." RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE = 5 + +# GLOBAL SETTINGS FOR `json.dumps()` +JSON_ENSURE_ASCII = False diff --git a/memgpt/functions/function_sets/base.py b/memgpt/functions/function_sets/base.py index 3ec2d379..95b1c13a 100644 --- a/memgpt/functions/function_sets/base.py +++ b/memgpt/functions/function_sets/base.py @@ -4,7 +4,7 @@ import os import json import math -from memgpt.constants import MAX_PAUSE_HEARTBEATS, RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE +from memgpt.constants import MAX_PAUSE_HEARTBEATS, RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE, JSON_ENSURE_ASCII ### Functions / tools the agent can use # All functions should return a response string (or None) @@ -109,7 +109,7 @@ def conversation_search(self, query: str, page: Optional[int] = 0) -> Optional[s else: results_pref = f"Showing {len(results)} of {total} results (page {page}/{num_pages}):" results_formatted = [f"timestamp: {d['timestamp']}, {d['message']['role']} - {d['message']['content']}" for d in results] - results_str = f"{results_pref} {json.dumps(results_formatted)}" + results_str = f"{results_pref} {json.dumps(results_formatted, ensure_ascii=JSON_ENSURE_ASCII)}" return results_str @@ -139,7 +139,7 @@ def conversation_search_date(self, start_date: str, end_date: str, page: Optiona else: results_pref = f"Showing {len(results)} of {total} results (page {page}/{num_pages}):" results_formatted = [f"timestamp: {d['timestamp']}, {d['message']['role']} - {d['message']['content']}" for d in results] - results_str = f"{results_pref} {json.dumps(results_formatted)}" + results_str = f"{results_pref} {json.dumps(results_formatted, ensure_ascii=JSON_ENSURE_ASCII)}" return results_str @@ -182,5 +182,5 @@ def archival_memory_search(self, query: str, page: Optional[int] = 0) -> Optiona else: results_pref = f"Showing {len(results)} of {total} results (page {page}/{num_pages}):" results_formatted = [f"timestamp: {d['timestamp']}, memory: {d['content']}" for d in results] - results_str = f"{results_pref} {json.dumps(results_formatted)}" + results_str = f"{results_pref} {json.dumps(results_formatted, ensure_ascii=JSON_ENSURE_ASCII)}" return results_str diff --git a/memgpt/functions/function_sets/extras.py b/memgpt/functions/function_sets/extras.py index 21ef96d5..ca8de3a2 100644 --- a/memgpt/functions/function_sets/extras.py +++ b/memgpt/functions/function_sets/extras.py @@ -4,7 +4,12 @@ import json import requests -from memgpt.constants import MESSAGE_CHATGPT_FUNCTION_MODEL, MESSAGE_CHATGPT_FUNCTION_SYSTEM_MESSAGE, MAX_PAUSE_HEARTBEATS +from memgpt.constants import ( + MESSAGE_CHATGPT_FUNCTION_MODEL, + MESSAGE_CHATGPT_FUNCTION_SYSTEM_MESSAGE, + MAX_PAUSE_HEARTBEATS, + JSON_ENSURE_ASCII, +) from memgpt.openai_tools import create @@ -118,7 +123,7 @@ def http_request(self, method: str, url: str, payload_json: Optional[str] = None payload = json.loads(payload_json) else: payload = {} - print(f"[HTTP] launching {method} request to {url}, payload=\n{json.dumps(payload, indent=2)}") + print(f"[HTTP] launching {method} request to {url}, payload=\n{json.dumps(payload, indent=2, ensure_ascii=JSON_ENSURE_ASCII)}") response = requests.request(method, url, json=payload, headers=headers) return {"status_code": response.status_code, "headers": dict(response.headers), "body": response.text} diff --git a/memgpt/local_llm/chat_completion_proxy.py b/memgpt/local_llm/chat_completion_proxy.py index d3d3739b..83f5b4dc 100644 --- a/memgpt/local_llm/chat_completion_proxy.py +++ b/memgpt/local_llm/chat_completion_proxy.py @@ -19,7 +19,7 @@ from memgpt.local_llm.utils import get_available_wrappers, count_tokens from memgpt.local_llm.function_parser import patch_function from memgpt.prompts.gpt_summarize import SYSTEM as SUMMARIZE_SYSTEM_MESSAGE from memgpt.errors import LocalLLMConnectionError, LocalLLMError -from memgpt.constants import CLI_WARNING_PREFIX +from memgpt.constants import CLI_WARNING_PREFIX, JSON_ENSURE_ASCII has_shown_warning = False @@ -126,7 +126,7 @@ def get_chat_completion( chat_completion_result = llm_wrapper.output_to_chat_completion_response(result, first_message=first_message) else: chat_completion_result = llm_wrapper.output_to_chat_completion_response(result) - printd(json.dumps(chat_completion_result, indent=2)) + printd(json.dumps(chat_completion_result, indent=2, ensure_ascii=JSON_ENSURE_ASCII)) except Exception as e: raise LocalLLMError(f"Failed to parse JSON from local LLM response - error: {str(e)}") @@ -143,13 +143,13 @@ def get_chat_completion( usage["prompt_tokens"] = count_tokens(prompt) # NOTE: we should compute on-the-fly anyways since we might have to correct for errors during JSON parsing - usage["completion_tokens"] = count_tokens(json.dumps(chat_completion_result)) + usage["completion_tokens"] = count_tokens(json.dumps(chat_completion_result, ensure_ascii=JSON_ENSURE_ASCII)) """ if usage["completion_tokens"] is None: printd(f"usage dict was missing completion_tokens, computing on-the-fly...") # chat_completion_result is dict with 'role' and 'content' # token counter wants a string - usage["completion_tokens"] = count_tokens(json.dumps(chat_completion_result)) + usage["completion_tokens"] = count_tokens(json.dumps(chat_completion_result, ensure_ascii=JSON_ENSURE_ASCII)) """ # NOTE: this is the token count that matters most diff --git a/memgpt/local_llm/function_parser.py b/memgpt/local_llm/function_parser.py index f3041603..9bb4e9b6 100644 --- a/memgpt/local_llm/function_parser.py +++ b/memgpt/local_llm/function_parser.py @@ -1,6 +1,7 @@ import copy import json +from memgpt.constants import JSON_ENSURE_ASCII NO_HEARTBEAT_FUNCS = ["send_message", "pause_heartbeats"] @@ -14,14 +15,14 @@ def insert_heartbeat(message): params = message_copy.get("function_call").get("arguments") params = json.loads(params) params["request_heartbeat"] = True - message_copy["function_call"]["arguments"] = json.dumps(params) + message_copy["function_call"]["arguments"] = json.dumps(params, ensure_ascii=JSON_ENSURE_ASCII) elif message_copy.get("tool_call"): # function_name = message.get("tool_calls")[0].get("function").get("name") params = message_copy.get("tool_calls")[0].get("function").get("arguments") params = json.loads(params) params["request_heartbeat"] = True - message_copy["tools_calls"][0]["function"]["arguments"] = json.dumps(params) + message_copy["tools_calls"][0]["function"]["arguments"] = json.dumps(params, ensure_ascii=JSON_ENSURE_ASCII) return message_copy diff --git a/memgpt/local_llm/llm_chat_completion_wrappers/airoboros.py b/memgpt/local_llm/llm_chat_completion_wrappers/airoboros.py index bda61e04..5fa9f63a 100644 --- a/memgpt/local_llm/llm_chat_completion_wrappers/airoboros.py +++ b/memgpt/local_llm/llm_chat_completion_wrappers/airoboros.py @@ -2,6 +2,7 @@ import json from .wrapper_base import LLMChatCompletionWrapper from ..json_parser import clean_json +from ...constants import JSON_ENSURE_ASCII from ...errors import LLMJSONParsingError @@ -112,7 +113,7 @@ class Airoboros21Wrapper(LLMChatCompletionWrapper): "function": function_call["name"], "params": json.loads(function_call["arguments"]), } - return json.dumps(airo_func_call, indent=2) + return json.dumps(airo_func_call, indent=2, ensure_ascii=JSON_ENSURE_ASCII) # Add a sep for the conversation if self.include_section_separators: @@ -202,7 +203,7 @@ class Airoboros21Wrapper(LLMChatCompletionWrapper): "content": None, "function_call": { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), }, } return message @@ -321,7 +322,7 @@ class Airoboros21InnerMonologueWrapper(Airoboros21Wrapper): **json.loads(function_call["arguments"]), }, } - return json.dumps(airo_func_call, indent=2) + return json.dumps(airo_func_call, indent=2, ensure_ascii=JSON_ENSURE_ASCII) # Add a sep for the conversation if self.include_section_separators: @@ -440,7 +441,7 @@ class Airoboros21InnerMonologueWrapper(Airoboros21Wrapper): "content": inner_thoughts, "function_call": { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), }, } return message diff --git a/memgpt/local_llm/llm_chat_completion_wrappers/chatml.py b/memgpt/local_llm/llm_chat_completion_wrappers/chatml.py index 49e2c837..fdacde9b 100644 --- a/memgpt/local_llm/llm_chat_completion_wrappers/chatml.py +++ b/memgpt/local_llm/llm_chat_completion_wrappers/chatml.py @@ -2,6 +2,7 @@ import json from .wrapper_base import LLMChatCompletionWrapper from ..json_parser import clean_json +from ...constants import JSON_ENSURE_ASCII from ...errors import LLMJSONParsingError @@ -128,7 +129,7 @@ class ChatMLInnerMonologueWrapper(LLMChatCompletionWrapper): **json.loads(function_call["arguments"]), }, } - return json.dumps(airo_func_call, indent=self.json_indent) + return json.dumps(airo_func_call, indent=self.json_indent, ensure_ascii=JSON_ENSURE_ASCII) # NOTE: BOS/EOS chatml tokens are NOT inserted here def _compile_assistant_message(self, message) -> str: @@ -160,7 +161,7 @@ class ChatMLInnerMonologueWrapper(LLMChatCompletionWrapper): # Otherwise just dump the full json try: user_msg_json = json.loads(message["content"]) - user_msg_str = json.dumps(user_msg_json, indent=self.json_indent) + user_msg_str = json.dumps(user_msg_json, indent=self.json_indent, ensure_ascii=JSON_ENSURE_ASCII) except: user_msg_str = message["content"] @@ -175,7 +176,7 @@ class ChatMLInnerMonologueWrapper(LLMChatCompletionWrapper): try: # indent the function replies function_return_dict = json.loads(message["content"]) - function_return_str = json.dumps(function_return_dict, indent=self.json_indent) + function_return_str = json.dumps(function_return_dict, indent=self.json_indent, ensure_ascii=JSON_ENSURE_ASCII) except: function_return_str = message["content"] @@ -312,7 +313,7 @@ class ChatMLInnerMonologueWrapper(LLMChatCompletionWrapper): "content": inner_thoughts, "function_call": { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), }, } return message @@ -380,7 +381,7 @@ class ChatMLOuterInnerMonologueWrapper(ChatMLInnerMonologueWrapper): **json.loads(function_call["arguments"]), }, } - return json.dumps(airo_func_call, indent=self.json_indent) + return json.dumps(airo_func_call, indent=self.json_indent, ensure_ascii=JSON_ENSURE_ASCII) def output_to_chat_completion_response(self, raw_llm_output, first_message=False): """NOTE: Modified to expect "inner_thoughts" outside the function @@ -441,7 +442,7 @@ class ChatMLOuterInnerMonologueWrapper(ChatMLInnerMonologueWrapper): "content": inner_thoughts, # "function_call": { # "name": function_name, - # "arguments": json.dumps(function_parameters), + # "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), # }, } @@ -449,7 +450,7 @@ class ChatMLOuterInnerMonologueWrapper(ChatMLInnerMonologueWrapper): if function_name is not None: message["function_call"] = { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), } return message diff --git a/memgpt/local_llm/llm_chat_completion_wrappers/dolphin.py b/memgpt/local_llm/llm_chat_completion_wrappers/dolphin.py index fd8dabcd..aa4e8f0b 100644 --- a/memgpt/local_llm/llm_chat_completion_wrappers/dolphin.py +++ b/memgpt/local_llm/llm_chat_completion_wrappers/dolphin.py @@ -2,6 +2,7 @@ import json from .wrapper_base import LLMChatCompletionWrapper from ..json_parser import clean_json +from ...constants import JSON_ENSURE_ASCII from ...errors import LLMJSONParsingError @@ -125,7 +126,7 @@ class Dolphin21MistralWrapper(LLMChatCompletionWrapper): "function": function_call["name"], "params": json.loads(function_call["arguments"]), } - return json.dumps(airo_func_call, indent=2) + return json.dumps(airo_func_call, indent=2, ensure_ascii=JSON_ENSURE_ASCII) # option (1): from HF README: # <|im_start|>user @@ -237,7 +238,7 @@ class Dolphin21MistralWrapper(LLMChatCompletionWrapper): "content": None, "function_call": { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), }, } return message diff --git a/memgpt/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py b/memgpt/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py index fa027a16..b7de8714 100644 --- a/memgpt/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +++ b/memgpt/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py @@ -1,5 +1,6 @@ import json from .wrapper_base import LLMChatCompletionWrapper +from ...constants import JSON_ENSURE_ASCII class SimpleSummaryWrapper(LLMChatCompletionWrapper): @@ -85,7 +86,7 @@ class SimpleSummaryWrapper(LLMChatCompletionWrapper): "function": function_call["name"], "params": json.loads(function_call["arguments"]), } - return json.dumps(airo_func_call, indent=2) + return json.dumps(airo_func_call, indent=2, ensure_ascii=JSON_ENSURE_ASCII) # Add a sep for the conversation if self.include_section_separators: @@ -147,7 +148,7 @@ class SimpleSummaryWrapper(LLMChatCompletionWrapper): "content": raw_llm_output, # "function_call": { # "name": function_name, - # "arguments": json.dumps(function_parameters), + # "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), # }, } return message diff --git a/memgpt/local_llm/llm_chat_completion_wrappers/zephyr.py b/memgpt/local_llm/llm_chat_completion_wrappers/zephyr.py index 7b635c1f..e8aaf64f 100644 --- a/memgpt/local_llm/llm_chat_completion_wrappers/zephyr.py +++ b/memgpt/local_llm/llm_chat_completion_wrappers/zephyr.py @@ -2,6 +2,7 @@ import json from .wrapper_base import LLMChatCompletionWrapper from ..json_parser import clean_json +from ...constants import JSON_ENSURE_ASCII from ...errors import LLMJSONParsingError @@ -75,7 +76,7 @@ class ZephyrMistralWrapper(LLMChatCompletionWrapper): "function": function_call["name"], "params": json.loads(function_call["arguments"]), } - return json.dumps(airo_func_call, indent=2) + return json.dumps(airo_func_call, indent=2, ensure_ascii=JSON_ENSURE_ASCII) for message in messages[1:]: assert message["role"] in ["user", "assistant", "function"], message @@ -168,7 +169,7 @@ class ZephyrMistralWrapper(LLMChatCompletionWrapper): "content": None, "function_call": { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), }, } return message @@ -237,7 +238,7 @@ class ZephyrMistralInnerMonologueWrapper(ZephyrMistralWrapper): **json.loads(function_call["arguments"]), }, } - return json.dumps(airo_func_call, indent=2) + return json.dumps(airo_func_call, indent=2, ensure_ascii=JSON_ENSURE_ASCII) # Add a sep for the conversation if self.include_section_separators: @@ -335,7 +336,7 @@ class ZephyrMistralInnerMonologueWrapper(ZephyrMistralWrapper): "content": inner_thoughts, "function_call": { "name": function_name, - "arguments": json.dumps(function_parameters), + "arguments": json.dumps(function_parameters, ensure_ascii=JSON_ENSURE_ASCII), }, } return message diff --git a/memgpt/local_llm/settings/settings.py b/memgpt/local_llm/settings/settings.py index dc6ba497..e83d15e9 100644 --- a/memgpt/local_llm/settings/settings.py +++ b/memgpt/local_llm/settings/settings.py @@ -1,7 +1,7 @@ import json import os -from memgpt.constants import MEMGPT_DIR +from memgpt.constants import MEMGPT_DIR, JSON_ENSURE_ASCII from memgpt.local_llm.settings.simple import settings as simple_settings from memgpt.local_llm.settings.deterministic_mirostat import settings as det_miro_settings @@ -47,7 +47,9 @@ def get_completions_settings(defaults="simple") -> dict: with open(settings_file, "r") as file: user_settings = json.load(file) if len(user_settings) > 0: - printd(f"Updating base settings with the following user settings:\n{json.dumps(user_settings,indent=2)}") + printd( + f"Updating base settings with the following user settings:\n{json.dumps(user_settings,indent=2, ensure_ascii=JSON_ENSURE_ASCII)}" + ) settings.update(user_settings) else: printd(f"'{settings_file}' was empty, ignoring...") diff --git a/memgpt/main.py b/memgpt/main.py index 5f74f5ea..207c60a7 100644 --- a/memgpt/main.py +++ b/memgpt/main.py @@ -203,7 +203,9 @@ def run_agent_loop(memgpt_agent, config: MemGPTConfig, first, no_verify=False, c text = user_input[len("/rewrite ") :].strip() args = json.loads(memgpt_agent.messages[x].get("function_call").get("arguments")) args["message"] = text - memgpt_agent.messages[x].get("function_call").update({"arguments": json.dumps(args)}) + memgpt_agent.messages[x].get("function_call").update( + {"arguments": json.dumps(args, ensure_ascii=constants.JSON_ENSURE_ASCII)} + ) break continue diff --git a/memgpt/openai_backcompat/openai_object.py b/memgpt/openai_backcompat/openai_object.py index 4bfa7905..38e24bcf 100644 --- a/memgpt/openai_backcompat/openai_object.py +++ b/memgpt/openai_backcompat/openai_object.py @@ -8,6 +8,8 @@ from enum import Enum import openai +from memgpt.constants import JSON_ENSURE_ASCII + # from openai import api_requestor, util # from openai.openai_response import OpenAIResponse # from openai.util import ApiType @@ -339,7 +341,7 @@ class OpenAIObject(dict): def __str__(self): obj = self.to_dict_recursive() - return json.dumps(obj, sort_keys=True, indent=2) + return json.dumps(obj, sort_keys=True, indent=2, ensure_ascii=JSON_ENSURE_ASCII) def to_dict(self): return dict(self) diff --git a/memgpt/server/rest_api/server.py b/memgpt/server/rest_api/server.py index 9fe2f9fd..3a3a53d5 100644 --- a/memgpt/server/rest_api/server.py +++ b/memgpt/server/rest_api/server.py @@ -7,6 +7,7 @@ from fastapi import FastAPI, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel +from memgpt.constants import JSON_ENSURE_ASCII from memgpt.server.server import SyncServer from memgpt.server.rest_api.interface import QueuingInterface import memgpt.utils as utils @@ -130,7 +131,7 @@ async def user_message(body: UserMessage): async def formatted_message_generator(): async for message in interface.message_generator(): - formatted_message = f"data: {json.dumps(message)}\n\n" + formatted_message = f"data: {json.dumps(message, ensure_ascii=JSON_ENSURE_ASCII)}\n\n" yield formatted_message await asyncio.sleep(1) diff --git a/memgpt/server/server.py b/memgpt/server/server.py index bbceea58..2976364e 100644 --- a/memgpt/server/server.py +++ b/memgpt/server/server.py @@ -371,7 +371,9 @@ class SyncServer(LockingServer): text = command[len("rewrite ") :].strip() args = json.loads(memgpt_agent.messages[x].get("function_call").get("arguments")) args["message"] = text - memgpt_agent.messages[x].get("function_call").update({"arguments": json.dumps(args)}) + memgpt_agent.messages[x].get("function_call").update( + {"arguments": json.dumps(args, ensure_ascii=constants.JSON_ENSURE_ASCII)} + ) break # No skip options diff --git a/memgpt/server/ws_api/example_client.py b/memgpt/server/ws_api/example_client.py index e49d226b..9fa547d8 100644 --- a/memgpt/server/ws_api/example_client.py +++ b/memgpt/server/ws_api/example_client.py @@ -4,6 +4,7 @@ import json import websockets import memgpt.server.ws_api.protocol as protocol +from memgpt.constants import JSON_ENSURE_ASCII from memgpt.server.constants import WS_DEFAULT_PORT, WS_CLIENT_TIMEOUT from memgpt.server.utils import condition_to_stop_receiving, print_server_response @@ -32,7 +33,7 @@ async def send_message_and_print_replies(websocket, user_message, agent_id): if CLEAN_RESPONSES: print_server_response(response) else: - print(f"Server response:\n{json.dumps(response, indent=2)}") + print(f"Server response:\n{json.dumps(response, indent=2, ensure_ascii=JSON_ENSURE_ASCII)}") # Check for a specific condition to break the loop if condition_to_stop_receiving(response): @@ -63,7 +64,7 @@ async def basic_cli_client(): # Wait for the response response = await websocket.recv() response = json.loads(response) - print(f"Server response:\n{json.dumps(response, indent=2)}") + print(f"Server response:\n{json.dumps(response, indent=2, ensure_ascii=JSON_ENSURE_ASCII)}") await asyncio.sleep(1) diff --git a/memgpt/server/ws_api/protocol.py b/memgpt/server/ws_api/protocol.py index 93a0a100..70ad4095 100644 --- a/memgpt/server/ws_api/protocol.py +++ b/memgpt/server/ws_api/protocol.py @@ -1,5 +1,8 @@ import json +from memgpt.constants import JSON_ENSURE_ASCII + + # Server -> client @@ -9,7 +12,8 @@ def server_error(msg): { "type": "server_error", "message": msg, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -18,7 +22,8 @@ def server_command_response(status): { "type": "command_response", "status": status, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -27,7 +32,8 @@ def server_agent_response_error(msg): { "type": "agent_response_error", "message": msg, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -35,7 +41,8 @@ def server_agent_response_start(): return json.dumps( { "type": "agent_response_start", - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -43,7 +50,8 @@ def server_agent_response_end(): return json.dumps( { "type": "agent_response_end", - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -53,7 +61,8 @@ def server_agent_internal_monologue(msg): "type": "agent_response", "message_type": "internal_monologue", "message": msg, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -63,7 +72,8 @@ def server_agent_assistant_message(msg): "type": "agent_response", "message_type": "assistant_message", "message": msg, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -73,7 +83,8 @@ def server_agent_function_message(msg): "type": "agent_response", "message_type": "function_message", "message": msg, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -86,7 +97,8 @@ def client_user_message(msg, agent_id=None): "type": "user_message", "message": msg, "agent_id": agent_id, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) @@ -96,5 +108,6 @@ def client_command_create(config): "type": "command", "command": "create_agent", "config": config, - } + }, + ensure_ascii=JSON_ENSURE_ASCII, ) diff --git a/memgpt/system.py b/memgpt/system.py index 22e20598..e68ee6f2 100644 --- a/memgpt/system.py +++ b/memgpt/system.py @@ -6,6 +6,7 @@ from .constants import ( INITIAL_BOOT_MESSAGE_SEND_MESSAGE_THOUGHT, INITIAL_BOOT_MESSAGE_SEND_MESSAGE_FIRST_MSG, MESSAGE_SUMMARY_WARNING_STR, + JSON_ENSURE_ASCII, ) @@ -61,7 +62,7 @@ def get_heartbeat(reason="Automated timer", include_location=False, location_nam if include_location: packaged_message["location"] = location_name - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) def get_login_event(last_login="Never (first login)", include_location=False, location_name="San Francisco, CA, USA"): @@ -76,7 +77,7 @@ def get_login_event(last_login="Never (first login)", include_location=False, lo if include_location: packaged_message["location"] = location_name - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) def package_user_message(user_message, time=None, include_location=False, location_name="San Francisco, CA, USA", name=None): @@ -94,7 +95,7 @@ def package_user_message(user_message, time=None, include_location=False, locati if name: packaged_message["name"] = name - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) def package_function_response(was_success, response_string, timestamp=None): @@ -105,7 +106,7 @@ def package_function_response(was_success, response_string, timestamp=None): "time": formatted_time, } - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) def package_summarize_message(summary, summary_length, hidden_message_count, total_message_count, timestamp=None): @@ -121,7 +122,7 @@ def package_summarize_message(summary, summary_length, hidden_message_count, tot "time": formatted_time, } - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) def package_summarize_message_no_summary(hidden_message_count, timestamp=None, message=None): @@ -140,7 +141,7 @@ def package_summarize_message_no_summary(hidden_message_count, timestamp=None, m "time": formatted_time, } - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) def get_token_limit_warning(): @@ -151,4 +152,4 @@ def get_token_limit_warning(): "time": formatted_time, } - return json.dumps(packaged_message) + return json.dumps(packaged_message, ensure_ascii=JSON_ENSURE_ASCII) diff --git a/memgpt/utils.py b/memgpt/utils.py index 1b659029..9d23be02 100644 --- a/memgpt/utils.py +++ b/memgpt/utils.py @@ -26,6 +26,7 @@ from memgpt.constants import ( CLI_WARNING_PREFIX, CORE_MEMORY_HUMAN_CHAR_LIMIT, CORE_MEMORY_PERSONA_CHAR_LIMIT, + JSON_ENSURE_ASCII, ) from memgpt.openai_backcompat.openai_object import OpenAIObject @@ -756,7 +757,7 @@ def validate_function_response(function_response_string: any, strict: bool = Fal # Allow dict through since it will be cast to json.dumps() try: # TODO find a better way to do this that won't result in double escapes - function_response_string = json.dumps(function_response_string) + function_response_string = json.dumps(function_response_string, ensure_ascii=JSON_ENSURE_ASCII) except: raise ValueError(function_response_string) @@ -864,8 +865,8 @@ def get_human_text(name: str): def get_schema_diff(schema_a, schema_b): # Assuming f_schema and linked_function['json_schema'] are your JSON schemas - f_schema_json = json.dumps(schema_a, indent=2) - linked_function_json = json.dumps(schema_b, indent=2) + f_schema_json = json.dumps(schema_a, indent=2, ensure_ascii=JSON_ENSURE_ASCII) + linked_function_json = json.dumps(schema_b, indent=2, ensure_ascii=JSON_ENSURE_ASCII) # Compute the difference using difflib difference = list(difflib.ndiff(f_schema_json.splitlines(keepends=True), linked_function_json.splitlines(keepends=True))) diff --git a/tests/test_function_parser.py b/tests/test_function_parser.py index 57153009..a720715f 100644 --- a/tests/test_function_parser.py +++ b/tests/test_function_parser.py @@ -1,5 +1,6 @@ import json +from memgpt.constants import JSON_ENSURE_ASCII from memgpt.local_llm.function_parser import patch_function import memgpt.system as system @@ -32,7 +33,7 @@ EXAMPLE_FUNCTION_CALL_CORE_MEMORY_APPEND_MISSING = { "content": "I'll append to memory.", "function_call": { "name": "core_memory_append", - "arguments": json.dumps({"content": "new_stuff"}), + "arguments": json.dumps({"content": "new_stuff"}, ensure_ascii=JSON_ENSURE_ASCII), }, }, } diff --git a/tests/test_websocket_server.py b/tests/test_websocket_server.py index 58423e79..7b25a03c 100644 --- a/tests/test_websocket_server.py +++ b/tests/test_websocket_server.py @@ -4,6 +4,7 @@ import json import websockets import pytest +from memgpt.constants import JSON_ENSURE_ASCII from memgpt.server.constants import WS_DEFAULT_PORT from memgpt.server.ws_api.server import WebSocketServer from memgpt.config import AgentConfig @@ -36,7 +37,7 @@ async def test_websocket_server(): async with websockets.connect(uri) as websocket: # Initialize the server with a test config print("Sending config to server...") - await websocket.send(json.dumps({"type": "initialize", "config": test_config})) + await websocket.send(json.dumps({"type": "initialize", "config": test_config}, ensure_ascii=JSON_ENSURE_ASCII)) # Wait for the response response = await websocket.recv() print(f"Response from the agent: {response}") @@ -45,7 +46,7 @@ async def test_websocket_server(): # Send a message to the agent print("Sending message to server...") - await websocket.send(json.dumps({"type": "message", "content": "Hello, Agent!"})) + await websocket.send(json.dumps({"type": "message", "content": "Hello, Agent!"}, ensure_ascii=JSON_ENSURE_ASCII)) # Wait for the response # NOTE: we should be waiting for multiple responses response = await websocket.recv()