openai / openai-node

Official JavaScript / TypeScript library for the OpenAI API
https://www.npmjs.com/package/openai
Apache License 2.0
7.94k stars 863 forks source link

ChatCompletionUserMessageParam.content should be string | null #462

Closed transitive-bullshit closed 1 year ago

transitive-bullshit commented 1 year ago

Confirm this is a Node library issue and not an underlying OpenAI API issue

Describe the bug

According to the OpenAI docs, ChatCompletionMessageParam.content should always be either a string or null.

But currently, this typing is broken due to:

export interface ChatCompletionUserMessageParam {
    /**
     * The contents of the user message.
     */
    content: string | Array<ChatCompletionContentPart> | null;
    /**
     * The role of the messages author, in this case `user`.
     */
    role: 'user';
}

What is ChatCompletionContentPart? It doesn't seem to be referenced anywhere in the OpenAI docs, and it breaks the very common TS usage of message.content being a string if truthy.

If this change is intended, then it doesn't seem to be consistent w/ the OpenAI docs as far as I can tell (source), and it makes for a much more awkward DX.

To Reproduce

import type { OpenAI } from 'openai'

let msg: OpenAI.ChatCompletionMessageParam = /* ... */
if (msg.content) {
  // msg.content should be type `string` here, but it's currently `string` | Array<ChatCompletionContentPart>
}

Code snippets

No response

OS

macOS

Node version

any node

Library version

latest

transitive-bullshit commented 1 year ago

It seems like maybe this is from the beta message object, which has a bit more indirect content access. I don't think this should be affecting the non-beta normal chat completion types, though.

rattrayalex commented 1 year ago

Hey Travis, sorry for the confusion here and I agree this can be a suboptimal typing experience. This is the team's intended behavior at this time.

What's going on here is that ChatCompletionUserMessageParam is something you can send to the API, but not something you can receive from the API (hence Param in the type name). The type is set to be multimodal (allow content: string | Array<{type: text, text: string} | {type: image_url, image_url: string}> | null) for all messages, even though it applies only to the gpt-4-vision-preview model right now.

it doesn't seem to be consistent w/ the OpenAI docs as far as I can tell (source)

This is a message that is returned from the API, and right now the chat completions API only responds with text, not images, so there's no array in that type.

I agree this lends some new annoyances to working with the types. If you have suggestions for how to make this better in the SDK, I'm all ears!

transitive-bullshit commented 1 year ago

Thanks for the thorough response, Alex 🙏

rattrayalex commented 1 year ago

You bet! If this continues to cause headaches, sharing code samples and feedback would be welcome!

amreetmeher commented 2 months ago

I am getting reponse of undefined when i use 'key={String(message.content)}' Please help me fix my code.

Screenshot_12-8-2024_215513_localhost

"use client";

import { useState } from "react";
import axios from "axios";
import { z } from "zod";
import { cn } from "@/lib/utils";
import Heading from "@/components/heading";
import { MessageSquare } from "lucide-react";
import { useForm } from "react-hook-form";
import { formSchema } from "./constants";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { Empty } from "@/components/empty";
import Loader from "@/components/loader";
import { UserAvatar } from "@/components/user-avatar";
import { BotAvatar } from "@/components/bot-avatar";
import { ChatCompletionMessageParam } from "openai/resources/chat/completions.mjs";

const ConversationPage = () => {
  const router = useRouter();
  const [messages, setmessages] = useState<ChatCompletionMessageParam[]>([]);
  //solved this issue from github repo:https://github.com/openai/openai-node/discussions/217
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      prompt: "",
    },
  });

  const isLoading = form.formState.isSubmitting;

  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      const userMessage: ChatCompletionMessageParam = {
        role: "user",
        content: values.prompt,
      };
      const newMessages = [...messages, userMessage];
      const response = await axios.post("/api/conversation", {
        messages: newMessages,
      });
      setmessages((current) => [...current, userMessage, response.data]);
      form.reset();
    } catch (error: any) {
      console.log(error);
    } finally {
      router.refresh();
    }
  };

  return (
    <div>
      <Heading
        title="Conversation"
        description="Seamless Messaging, Endless Possibilities"
        icon={MessageSquare}
        iconColor="text-violet-500"
        bgColor="bg-violet-500/10"
      />
      <div className="px-4 lg:px-8 ">
        <div>
          <Form {...form}>
            <form
              onSubmit={form.handleSubmit(onSubmit)}
              className="rounded-lg border w-full p-4 px-3 md:px-6 focus-within:shadow-sm grid grid-cols-12 gap-2"
            >
              <FormField
                name="prompt"
                render={({ field }) => (
                  <FormItem className="col-span-12 lg:col-span-10">
                    <FormControl className="m-0 p-0">
                      <Input
                        className="border-0 outline-none focus-visible:ring-0 focus-visible:ring-transparent"
                        disabled={isLoading}
                        placeholder="How do I calculate radius of a circle ?"
                        {...field}
                      />
                    </FormControl>
                  </FormItem>
                )}
              />
              <Button
                className="col-span-12 lg:col-span-2 w-full"
                disabled={isLoading}
              >
                Generate
              </Button>
            </form>
          </Form>
        </div>
        <div className="space-y-4 mt-4">
          {isLoading && (
            <div className="p-8 rounded-lg w-full flex items-center justify-center bg-muted">
              <Loader />
            </div>
          )}
          {messages.length === 0 && !isLoading && (
            <Empty label="No convo started" />
          )}
          <div className="flex flex-col-reverse gap-y-4">
            {messages.map((message) => (
              <div
                key={String(message.content)}
                className={cn(
                  "p-8 w-full flex items-start gap-x-8 rounded-full",
                  message.role == "user"
                    ? "bg-white border border-black/10"
                    : "bg-muted"
                )}
              >
                {message.role === "user" ? <UserAvatar /> : <BotAvatar />}
                <p className="text-sm">{String(message.content)}</p>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

export default ConversationPage;
RobertCraigie commented 2 months ago

Please ask for help debugging your code on the community forum or the OpenAI discord.