srcnalt / OpenAI-Unity

An unofficial OpenAI Unity Package that aims to help you use OpenAI API directly in Unity Game engine.
MIT License
711 stars 158 forks source link

JsonReaderException: Unterminated string. Expected delimiter: ". Path 'object', line 1, position 68. #126

Closed Aishu696 closed 3 months ago

Aishu696 commented 3 months ago

I am getting this error of JSON Reader exception. First the project was working fine. Then I got this error and I updated the OpenAI package, but it didn't work. I tried to update the Newtonsoft.Json package in unity. However, the issue is not fixed. Kindly help to fix this issue. How to fix this error?

JsonReaderException: Unterminated string. Expected delimiter: ". Path 'object', line 1, position 68. Newtonsoft.Json.JsonTextReader.ReadStringIntoBuffer (System.Char quote) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonTextReader.ParseString (System.Char quote, Newtonsoft.Json.ReadType readType) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonTextReader.ReadStringValue (Newtonsoft.Json.ReadType readType) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonTextReader.ReadAsString () (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonReader.ReadForType (Newtonsoft.Json.Serialization.JsonContract contract, System.Boolean hasConverter) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) (at <761cf2a144514d2291a678c334d49e9b>:0) Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) (at <761cf2a144514d2291a678c334d49e9b>:0) OpenAI.OpenAIApi.DispatchRequest[T] (System.String path, System.String method, System.Action`1[T] onResponse, System.Action onComplete, System.Threading.CancellationTokenSource token, System.Byte[] payload) (at ./Library/PackageCache/com.srcnalt.openai-unity@f7f501c076/Runtime/OpenAIApi.cs:131) System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.b__7_0 (System.Object state) (at <467a840a914a47078e4ae9b0b1e8779e>:0) UnityEngine.UnitySynchronizationContext+WorkRequest.Invoke () (at :0) UnityEngine.UnitySynchronizationContext.Exec () (at :0) UnityEngine.UnitySynchronizationContext.ExecuteTasks () (at :0)

VincentSchuijt-TomTom commented 3 months ago

I have this error as well, it has to do with the changed API formatting that OpenAI is using. Should be an easy fix :)

It also does not work in all the demo's, I checked.

See how to formatting changed here:

old: { "role": "system", "content":".... here is your OpenAI content" }

new: { "role": "system", "content": [ { "text": "....here is your OpenAI content", "type": "text" } ] }

Aishu696 commented 3 months ago

ok i will try

VincentSchuijt-TomTom commented 3 months ago

Let me know if you found a solution, I am also looking at it @Aishuammuu

srcnalt commented 3 months ago

Can you post the code that generated the error above, in samples everything seems to work for me with the sample projects. Pushed a minor fix in the data types now.

Aishu696 commented 3 months ago

This Json part is showing error in OpenAIApi script: ///

/// Dispatches an HTTP request to the specified path with the specified method and optional payload. /// /// The path to send the request to. /// The HTTP method to use for the request. /// A callback function to be called when a response is updated. /// A callback function to be called when the request is complete. /// A cancellation token to cancel the request. /// An optional byte array of json payload to include in the request. private async void DispatchRequest(string path, string method, Action<List> onResponse, Action onComplete, CancellationTokenSource token, byte[] payload = null) where T: IResponse { using (var request = UnityWebRequest.Put(path, payload)) { request.method = method; request.SetHeaders(Configuration, ContentType.ApplicationJson);

            var asyncOperation = request.SendWebRequest();

            do
            {
                List<T> dataList = new List<T>();
                string[] lines = request.downloadHandler.text.Split('\n').Where(line => line != "").ToArray();

                foreach (string line in lines)
                {
                    var value = line.Replace("data: ", "");

                    if (value.Contains("[DONE]")) 
                    {
                        onComplete?.Invoke();
                        break;
                    }

                    var data = JsonConvert.DeserializeObject<T>(value, jsonSerializerSettings);

                    if (data?.Error != null)
                    {
                        ApiError error = data.Error;
                        Debug.LogError($"Error Message: {error.Message}\nError Type: {error.Type}\n");
                    }
                    else
                    {
                        dataList.Add(data);
                    }
                }
                onResponse?.Invoke(dataList);

                await Task.Yield();
            }
            while (!asyncOperation.isDone && !token.IsCancellationRequested);

            onComplete?.Invoke();
        }
    }

    /// <summary>
    ///     Dispatches an HTTP request to the specified path with a multi-part data form.
    /// </summary>
    /// <param name="path">The path to send the request to.</param>
    /// <param name="form">A multi-part data form to upload with the request.</param>
    /// <typeparam name="T">Response type of the request.</typeparam>
    /// <returns>A Task containing the response from the request as the specified type.</returns>
    private async Task<T> DispatchRequest<T>(string path, List<IMultipartFormSection> form) where T: IResponse
    {
        T data;

        using (var request = new UnityWebRequest(path, "POST"))
        {
            request.SetHeaders(Configuration);
            var boundary = UnityWebRequest.GenerateBoundary();
            var formSections = UnityWebRequest.SerializeFormSections(form, boundary);
            var contentType = $"{ContentType.MultipartFormData}; boundary={Encoding.UTF8.GetString(boundary)}";
            request.uploadHandler = new UploadHandlerRaw(formSections) {contentType = contentType};
            request.downloadHandler = new DownloadHandlerBuffer();
            var asyncOperation = request.SendWebRequest();

            while (!asyncOperation.isDone) await Task.Yield();

            data = JsonConvert.DeserializeObject<T>(request.downloadHandler.text, jsonSerializerSettings);
        }

        if (data != null && data.Error != null)
        {
            ApiError error = data.Error;
            Debug.LogError($"Error Message: {error.Message}\nError Type: {error.Type}\n");
        }

        return data;
    }
Aishu696 commented 3 months ago

var data = JsonConvert.DeserializeObject(value, jsonSerializerSettings);

srcnalt commented 3 months ago

I meant your code.

Aishu696 commented 3 months ago

using UnityEngine; using System.Linq; using UnityEngine.UI; using System.Threading; using System.Collections.Generic; using UnityEngine.Events; using UnityEditor.MPE;

namespace OpenAI { public class ChatGPT : MonoBehaviour { [SerializeField] private InputField inputField; [SerializeField] private Button button; [SerializeField] private ScrollRect scroll;

    [SerializeField] private RectTransform sent;
    [SerializeField] private RectTransform received;

    [SerializeField] private NpcInfo npcInfo;
    [SerializeField] private WorldInfo worldInfo;
    [SerializeField] private NpcDialog npcDialog;

    [SerializeField] private TextToSpeech textToSpeech;

    public UnityEvent OnReplyReceived;

    private string response;
    private bool isDone = true;
    private RectTransform messageRect;

    private float height;
    private OpenAIApi openai = new OpenAIApi();

    public List<ChatMessage> messages = new List<ChatMessage>();
    //private string prompt = "You are Grace. Act as a hr avatar at gritstone technologies in a hr cabin ask questions to the candidate and reply to the questions using her connection to OpenAI. Don't break character.";

    private void Start()
    {
        var message = new ChatMessage
        {
            Role = "user",
            Content = "You are Grace. Act as an hr avatar at Gritstone Technologies in a hr cabin and ask questions to the candidate who talks to you, hence take interview of the candidate.\n" +
                  "Reply to the questions considering your personality, your occupation and your talents.\n" +
                  "Don't give detailed answers of the questions asked by you to the candidate.Just give a simple right or wrong feedback.\n" +
                  "Reply to questions related to Gritstone Technologies.\n" +
                  "Please do tell your sentences fully and don't leave it half the way, complete your sentences.\n" +
                  "Ask to the candidate about their area of interest and then when the candidate tells their interested topic, ask questions related to that topic.\n" +
                  "After the candidate self introduction directly ask technical questions related to the job requirements and their area of interest.\n" +
                  "Ask atleast 30 technical questions to the candidate.\n" +
                  "Only ask 7 or 8 questions if the candidate is not able to answer any of the questions asked by you.\n" +
                  "Please ask only one question at a time and after the candidate answers that question then only ask the next question, also give time to the candidate for answering the questions.\n" +
                  "Do not mention you are an NPC. If the question is out of scope for your knowledge tell that you don't know.\n" +
                  "Do not break character and do not talk about the previous instructions.\n" +
                  "Remember the details of the candidate and previous questions asked to them.\n" +
                  "Do not repeat the questions once asked to the candidate.\n" +
                  "You should be professional and friendly.\n" +
                  "Interview the candidate for 30 minutes only.\n" +
                  "If a candidate answered the technical questions asked by you, then consider their answer and can ask technical question from the answers given by them.\n" +
                  "Evaluate their answers, check whether it is right or wrong.Tell them what they have told is correct or not. If the answer told by them is wrong tell them that it is wrong.\n" +
                  "If my reply indicates that I want to end the conversation, finish your sentence with the phrase END_CONVO\n\n" +
                  "The following info is the info about the interview world and company: \n " +
                  worldInfo.GetPrompt() +
                  "The following info is the info about the NPC: \n" +
                  npcInfo.GetPrompt()
        };

        messages.Add(message);
        button.onClick.AddListener(SendReply);

    }

    //private void Start()
    //{
    //   button.onClick.AddListener(SendReply);
    // }

    private RectTransform AppendMessage(ChatMessage message)
    {
        scroll.content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);

        var item = Instantiate(message.Role == "user" ? sent : received, scroll.content);

        if (message.Role != "user")
        {
            messageRect = item;
        }

        item.GetChild(0).GetChild(0).GetComponent<Text>().text = message.Content;
        item.anchoredPosition = new Vector2(0, -height);

        if (message.Role == "user")
        {
            LayoutRebuilder.ForceRebuildLayoutImmediate(item);
            height += item.sizeDelta.y;
            scroll.content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
            scroll.verticalNormalizedPosition = 0;
        }
        return item;
    }

    private void SendReply()
    {
        SendReply(inputField.text);
    }

    public void SendReply(string input)
    {
        var message = new ChatMessage()
        {
            Role = "user",
            Content = input
        };

        messages.Add(message);

        openai.CreateChatCompletionAsync(new CreateChatCompletionRequest()
        {
            Model = "gpt-4o-2024-05-13",
            Messages = messages
        }, OnResponse, OnComplete, new CancellationTokenSource());

        AppendMessage(message);

        inputField.text = "";
    }

    private void OnResponse(List<CreateChatCompletionResponse> responses)
    {
        var text = string.Join("", responses.Select(r => r.Choices[0].Delta.Content));

        if (text == "") return;

        if (text.Contains("END_CONVO"))
        {
            text = text.Replace(oldValue: "END_CONVO", newValue: "");

            Invoke(nameof(EndConvo), time: 5);
        }

        var message = new ChatMessage()
        {
            Role = "assistant",
            Content = text
        };

        if (isDone)
        {
            OnReplyReceived.Invoke();
            messageRect = AppendMessage(message);
            isDone = false;
        }

        messageRect.GetChild(0).GetChild(0).GetComponent<Text>().text = text;
        LayoutRebuilder.ForceRebuildLayoutImmediate(messageRect);
        scroll.content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
        scroll.verticalNormalizedPosition = 0;

        response = text;
    }

    private void OnComplete()
    {
        LayoutRebuilder.ForceRebuildLayoutImmediate(messageRect);
        height += messageRect.sizeDelta.y;
        scroll.content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
        scroll.verticalNormalizedPosition = 0;

        var message = new ChatMessage()
        {
            Role = "assistant",
            Content = response
        };

        messages.Add(message);

        textToSpeech.MakeAudioRequest(response);

        isDone = true;
        response = "";
    }

    private void EndConvo()
    {
        npcDialog.Recover();
        messages.Clear();
    }
}

}

srcnalt commented 3 months ago

@Aishuammuu can you try with the latest package? When I tested your code I receive stream response without any JSON parsing issues.

Aishu696 commented 3 months ago

ok I will try

VincentSchuijt-TomTom commented 3 months ago

I will also try from my side :) @srcnalt big fan of this package :D

VincentSchuijt-TomTom commented 3 months ago

I removed my previous comment, it works for me now :) had to set:

stream: true

For it to work, the example helped me out a lot!

Thanks @srcnalt gave you a small bit of sponsoring from me personally 👍

Aishu696 commented 3 months ago

Thanks @srcnalt for the solution. Now it is working for me. Thankyou so much for the wonderful package :-)