ai icon indicating copy to clipboard operation
ai copied to clipboard

LangChainAdapter DataStreamProtocol changes not parsing to Messages properly

Open sicter opened this issue 8 months ago • 8 comments

Description

Using vercel-ai-sdk with a custom backend api/chat endpoint implemented in python. I am successful streaming responses back to the Next.js implementation that is using useChat(…) to call the backend. The backend is using LangGraph agents and I have AIMessageChunks responses that when passed through LangChainAdapter.toDataStreamResponse look like:

2:[{"content":""}]
2:[{"content":"Sure"}]
2:[{"content":","}]
2:[{"content":" I"}]
2:[{"content":" can"}]
2:[{"content":" help"}]
2:[{"content":" you"}]
2:[{"content":" manage"}]
2:[{"content":" the"}]
2:[{"content":" repository"}]
2:[{"content":"."}]

However, when trying to render them in pages.tsx using the messages from useChat, they are being received as "{..., content: "2:[...]"}". When I manually send other types of data stream responses (3:{...},e:{...}, 9:{...} , they properly populate the messages with content or parts, but this Data Part 2:[...] does not.

Code example

page.tsx

const {
    messages,
    ...
  } = useChat({
    body: {
      chatId,
    },
    onError: (error) => {...},
  });
  return (
        ...
        {messages.map((message, index) => (
          <PreviewMessage
            key={message.id}
            chatId={chatId}
            message={message} // THIS MESSAGE DOES NOT HAVE THE CONTENT
            isLoading={isLoading && messages.length - 1 === index}
          />
        ))}
        ...
    )

route.tsx (api/chat)

export async function POST(req: Request) {
  const { messages, chatId } = await req.json();
  const lastMessage = messages[messages.length - 1];
  try {
    const stream = createDataStream({
      execute: async (dataStream) => {
        await stream_text(chatId, lastMessage.content, dataStream);
      },
      onError: error => `Custom error: ${error.message}`,
    });
    return LangChainAdapter.toDataStreamResponse(stream);
  } catch (error) {...}
}

AI provider

ChatOpenAI

Additional context

No response

sicter avatar Apr 10 '25 06:04 sicter

@sicter are you using streamProtocol: "data", in your useChat?

Here is my code that works:

  const chat = useChat({
    api: props.endpoint,
    onResponse(response) {
      const sourcesHeader = response.headers.get("x-sources");
      const sources = sourcesHeader
        ? JSON.parse(Buffer.from(sourcesHeader, "base64").toString("utf8"))
        : [];

      const messageIndexHeader = response.headers.get("x-message-index");
      if (sources.length && messageIndexHeader !== null) {
        setSourcesForMessages({
          ...sourcesForMessages,
          [messageIndexHeader]: sources,
        });
      }
    },
    streamProtocol: "data",
    onFinish: () => {
      chat.setData([]);
    },

    onError: (e) => console.log(e),
  });

jayadehart avatar Apr 10 '25 19:04 jayadehart

Hey @jayadehart , thanks for the suggestion. I wasnt originally, but both before and after adding it I'm still getting the same result: other data stream protocols work if I send them manually, but what seems to be happening is:

  1. LangChainAdapter seems to be sending everything as 2:[{...}]
  2. the Messages content is a string that just contains the output2:[{...} in content

e.x

{messages.map((message, index) => (
  <div>{JSON.stringify(message)}</div> 
))}

results in my chat showing:

{"id":"qSuSpgfoqPOJD7Qa","createdAt":"2025-04-10T21:40:06.213Z","role":"assistant","content":"2:[[]]\n2:[[{\"id\":\"toolu_01QGhDAfs4RjQTiwfGRm8U3y\",\"input\":{},\"name\":\"code\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\"\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\"{\\\"prefix\\\"\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\": \\\"To create\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\" a cha\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\"t model\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\" in\",\"type\":\"tool_use\",\"index\":0}]]\n2:[[{\"partial_json\":\" L\",\"type\":\"tool_use\",\"index\":0}]]\n2:

If there's more detail I can provide that would help to debug, I'd be happy to share more

sicter avatar Apr 10 '25 21:04 sicter

@sicter I'm not an expert at all the underlying processing that is happening with useChat and such, but if you want to look at a working example of converting langchain stream output to a data stream you can check out my repo:

https://github.com/jayadehart/js-melee-rag/blob/main/app/api/chat/retrieval/route.ts

jayadehart avatar Apr 10 '25 21:04 jayadehart

@jayadehart Thanks so much, a reference would be super helpful. But I don't have access to your repo, can you share it with me?

sicter avatar Apr 10 '25 22:04 sicter

@sicter my bad, didn't realize I had made it private. It should be public now!

jayadehart avatar Apr 10 '25 22:04 jayadehart

@jayadehart maybe the link is wrong? I'm still getting a 404

sicter avatar Apr 10 '25 22:04 sicter

@sicter try now

jayadehart avatar Apr 10 '25 22:04 jayadehart

@jayadehart got it- thanks!

sicter avatar Apr 10 '25 22:04 sicter