ai icon indicating copy to clipboard operation
ai copied to clipboard

fix (ai): Accept `name` on CoreMessage, pass to providers

Open AaronFriel opened this issue 1 year ago • 4 comments

Adds an optional name field to the relevant message types in the core package, provider SDK, and the OpenAI provider. A review of the current providers found no others that accept a name field, so no other providers are modified.

OpenAI's API is leading the way here, whether for multi-user or multi-agent interactions, LLM APIs are likely to accept named participants beyond 'user', 'assistant', etc.

As described in #2198, the name field may be used with OpenAI's API to provide additional context for system and assistant messages, e.g.: for guardrails and reducing the risks of prompt injection.

Fixes #2198

AaronFriel avatar Jul 08 '24 03:07 AaronFriel

@lgrammel is it possible to get this reviewed?

AaronFriel avatar Jul 22 '24 22:07 AaronFriel

Right now, this would be a very OpenAI specific feature. No other provider has implemented message names after they have been around almost a year and I have not heard any requests for this feature (outside of this PR). I want to wait and see if other providers adopt it first or if there is more demand, to prevent adding idiosyncratic functionality to the AI SDK API.

lgrammel avatar Jul 23 '24 06:07 lgrammel

@lgrammel totally understand. Would you accept a PR with any of these three styles to allow a vendor-specific option without an API contract that it will always be supported?

TypeScript definitions
// CSS Prefix Style
type CoreMessageA = {    
    [prefixedExtension: `-${string}-${string}`]: any;
}

// Nested vendor, option style:
type CoreMessageB = {
    extensions: {
        [vendor: string]: {
            [option: string]: any;
        }
    }
}

// Flat, optional keys (no vendor/provider prefix):
type CoreMessageC = {
    extensions: {
        [option: string]: any;
    }
}
// CSS Prefix Style
const foo: CoreMessageA = {
    "-openai-name": "copilot"
}

// Nested vendor, option style:
const bar: CoreMessageB = {
    extensions: {
        openai: {
            name: "copilot"
        }
    }
};

// Flat, optional keys (no vendor/provider prefix):
const baz: CoreMessageC = {
    extensions: {
        name: "copilot"
    }
};

AaronFriel avatar Aug 06 '24 21:08 AaronFriel

@AaronFriel something like this is needed for sure. type safety is an issue. in the past i used generics on the models / providers for this, but it makes the system very complex and brittle. i'm leaning towards option c even tho it compromises type safety.

lgrammel avatar Aug 07 '24 07:08 lgrammel

fyi i'm planning to add this support to the language model v2 spec

lgrammel avatar Aug 15 '24 12:08 lgrammel

we now support provider specific extensions on messages that could be used for names: https://github.com/vercel/ai/pull/2697

lgrammel avatar Aug 16 '24 17:08 lgrammel

@lgrammel I rebased the PR, and found a bug, curious how you want to proceed:

This works as expected:

        const result = await convertToLanguageModelPrompt({
          prompt: {
            type: 'messages',
            prompt: undefined,
            messages: [
              {
                role: 'user',
                content: [
                  {
                    type: 'text',
                    text: 'hello, world!',
                    experimental_providerMetadata: {
                      [providerName]: {
                        name: 'foo',
                        [key]: value,
                      },
                    },
                  },
                ],
              },
            ],
          },
          modelSupportsImageUrls: undefined,
        });

However, this does not, but it does type check:

        const result = await convertToLanguageModelPrompt({
          prompt: {
            type: 'messages',
            prompt: undefined,
            messages: [
              {
                role: 'user',
                content: [
                  {
                    type: 'text',
                    text: 'hello, world!',
                  },
                ],
                experimental_providerMetadata: {
                  [providerName]: {
                    name: 'foo',
                    [key]: value,
                  },
                },
              },
            ],
          },
          modelSupportsImageUrls: undefined,
        });

In the second example, the provider metadata does not flow through and the result. Instead, this is the passing assertion:

        expect(result).toEqual([
          {
            role: 'user',
            content: [
              {
                type: 'text',
                text: 'hello, world!',
              },
            ],
            // No provider metadata!
          },
        ] satisfies LanguageModelV1Prompt);

It looks like the issue is that CoreUserMessage declares the field:

https://github.com/vercel/ai/blob/15791b0a208c1e38d24893303e0e3b36221d3f5e/packages/ai/core/prompt/message.ts#L48-L58

The UserContent type can be an array of TextPart or ImagePart:

https://github.com/vercel/ai/blob/15791b0a208c1e38d24893303e0e3b36221d3f5e/packages/ai/core/prompt/message.ts#L77-L77

Which can then declare its own experimental provider metadata:

https://github.com/vercel/ai/blob/15791b0a208c1e38d24893303e0e3b36221d3f5e/packages/ai/core/prompt/content-part.ts#L11-L25

Should:

  • Only one of these declare experimental_providerMetadata?
  • Both, but there is a merge or overwriting that occurs on a part-by-part basis?

AaronFriel avatar Aug 22 '24 00:08 AaronFriel

Maybe I'm missing something, but for me it seems to work as expected: https://github.com/vercel/ai/pull/2776/files

The provider metadata on the message is passed through on the message (you can ignore the content part metadata for the name feature, it's meant for extensions that work on the content part level)

lgrammel avatar Aug 22 '24 15:08 lgrammel

Any Update ? Not work name for Open AI

MwSpaceLLC avatar Mar 15 '25 13:03 MwSpaceLLC