Skip to content
This repository was archived by the owner on May 5, 2026. It is now read-only.

Commit 1428ac0

Browse files
authored
Add support for older chat input schemas to chat playground (#526)
CC @eyurtsev @efriis
1 parent 4bf7b0f commit 1428ac0

10 files changed

Lines changed: 178 additions & 57 deletions

File tree

CONTRIBUTING.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,32 @@ To run linting for this project:
4949
```sh
5050
make lint
5151
```
52+
53+
## Frontend Playground Development
54+
55+
Here are a few tips to keep in mind when developing the LangServe playgrounds:
56+
57+
### Setup
58+
59+
Switch directories to `langserve/playground` or `langserve/chat_playground`, then run `yarn` to install required
60+
dependencies. `yarn dev` will start the playground at `http://localhost:5173/____LANGSERVE_BASE_URL/` in dev mode.
61+
62+
You can run one of the chains in the `examples/` repo using `poetry run python path/to/file.py`.
63+
64+
### Setting CORS
65+
66+
You may need to add the following to an example route when developing the playground in dev mode to handle CORS:
67+
68+
```python
69+
from fastapi.middleware.cors import CORSMiddleware
70+
71+
# Set all CORS enabled origins
72+
app.add_middleware(
73+
CORSMiddleware,
74+
allow_origins=["*"],
75+
allow_credentials=True,
76+
allow_methods=["*"],
77+
allow_headers=["*"],
78+
expose_headers=["*"],
79+
)
80+
```

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,14 @@ runnable and share a link with the configuration:
334334

335335
LangServe also supports a chat-focused playground that opt into and use under `/my_runnable/playground/`.
336336
Unlike the general playground, only certain types of runnables are supported - the runnable's input schema must
337-
be a `dict` with a single key, and that key's value must be a list of chat messages. The runnable
338-
can return either an `AIMessage` or a string.
337+
be a `dict` with either:
338+
339+
- a single key, and that key's value must be a list of chat messages.
340+
- two keys, one whose value is a list of messages, and the other representing the most recent message.
341+
342+
We recommend you use the first format.
343+
344+
The runnable must also return either an `AIMessage` or a string.
339345

340346
To enable it, you must set `playground_type="chat",` when adding your route. Here's an example:
341347

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python
2+
"""Example of a simple chatbot that just passes current conversation
3+
state back and forth between server and client.
4+
"""
5+
from typing import List, Union
6+
7+
from fastapi import FastAPI
8+
from langchain.chat_models import ChatAnthropic
9+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
10+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
11+
12+
from langserve import add_routes
13+
from langserve.pydantic_v1 import BaseModel, Field
14+
15+
app = FastAPI(
16+
title="LangChain Server",
17+
version="1.0",
18+
description="Spin up a simple api server using Langchain's Runnable interfaces",
19+
)
20+
21+
22+
# Declare a chain
23+
prompt = ChatPromptTemplate.from_messages(
24+
[
25+
("system", "You are a helpful, professional assistant named Cob."),
26+
MessagesPlaceholder(variable_name="messages"),
27+
("human", "{input}"),
28+
]
29+
)
30+
31+
chain = prompt | ChatAnthropic(model="claude-2")
32+
33+
34+
class InputChat(BaseModel):
35+
"""Input for the chat endpoint."""
36+
37+
messages: List[Union[HumanMessage, AIMessage, SystemMessage]] = Field(
38+
...,
39+
description="The chat messages representing the current conversation.",
40+
)
41+
42+
input: str
43+
44+
45+
add_routes(
46+
app,
47+
chain.with_types(input_type=InputChat),
48+
enable_feedback_endpoint=True,
49+
enable_public_trace_link_endpoint=True,
50+
playground_type="chat",
51+
)
52+
53+
if __name__ == "__main__":
54+
import uvicorn
55+
56+
uvicorn.run(app, host="localhost", port=8000)

examples/chat_playground/server.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import List, Union
66

77
from fastapi import FastAPI
8-
from fastapi.middleware.cors import CORSMiddleware
98
from langchain.chat_models import ChatAnthropic
109
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
1110
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
@@ -20,17 +19,6 @@
2019
)
2120

2221

23-
# Set all CORS enabled origins
24-
app.add_middleware(
25-
CORSMiddleware,
26-
allow_origins=["*"],
27-
allow_credentials=True,
28-
allow_methods=["*"],
29-
allow_headers=["*"],
30-
expose_headers=["*"],
31-
)
32-
33-
3422
# Declare a chain
3523
prompt = ChatPromptTemplate.from_messages(
3624
[

langserve/chat_playground/dist/assets/index-434ff580.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

langserve/chat_playground/dist/assets/index-b47ed17e.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

langserve/chat_playground/dist/assets/index-c5f8c3dc.js renamed to langserve/chat_playground/dist/assets/index-d9089d96.js

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

langserve/chat_playground/dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<link rel="icon" href="/____LANGSERVE_BASE_URL/favicon.ico" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Chat Playground</title>
8-
<script type="module" crossorigin src="/____LANGSERVE_BASE_URL/assets/index-c5f8c3dc.js"></script>
9-
<link rel="stylesheet" href="/____LANGSERVE_BASE_URL/assets/index-b47ed17e.css">
8+
<script type="module" crossorigin src="/____LANGSERVE_BASE_URL/assets/index-d9089d96.js"></script>
9+
<link rel="stylesheet" href="/____LANGSERVE_BASE_URL/assets/index-434ff580.css">
1010
</head>
1111
<body>
1212
<div id="root"></div>

langserve/chat_playground/src/App.tsx

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,54 @@ export function App() {
1414
const outputDataSchema = outputSchema?.data?.schema;
1515
const isLoading = inputProps === undefined || outputDataSchema === undefined;
1616
const inputKeys = Object.keys(inputProps ?? {});
17-
const isSupported = isLoading || (
17+
const inputSchemaSupported = (
1818
inputKeys.length === 1 &&
19-
inputProps[inputKeys[0]].type === "array" &&
20-
(
21-
outputDataSchema.anyOf?.find((option) => option.properties?.type?.enum?.includes("ai")) ||
22-
outputDataSchema.type === "string"
19+
inputProps?.[inputKeys[0]].type === "array"
20+
) || (
21+
inputKeys.length === 2 && (
22+
(
23+
inputProps?.[inputKeys[0]].type === "array" ||
24+
inputProps?.[inputKeys[1]].type === "string"
25+
) || (
26+
inputProps?.[inputKeys[0]].type === "string" ||
27+
inputProps?.[inputKeys[1]].type === "array"
28+
)
2329
)
2430
);
31+
const outputSchemaSupported = (
32+
outputDataSchema?.anyOf?.find((option) => option.properties?.type?.enum?.includes("ai")) ||
33+
outputDataSchema?.type === "string"
34+
);
35+
const isSupported = isLoading || (inputSchemaSupported && outputSchemaSupported);
2536
return (
2637
<div className="flex items-center flex-col text-ls-black bg-background">
2738
<AppCallbackContext.Provider value={context}>
2839
{isSupported
2940
? <ChatWindow
3041
startStream={startStream}
3142
stopStream={stopStream}
32-
inputKey={inputKeys[0]}
43+
messagesInputKey={inputProps?.[inputKeys[0]].type === "array" ? inputKeys[0] : inputKeys[1]}
44+
inputKey={inputProps?.[inputKeys[0]].type === "string" ? inputKeys[0] : inputKeys[1]}
3345
></ChatWindow>
34-
: <div className="h-[100vh] w-[100vw] flex justify-center items-center text-xl">
35-
<span className="text-center">
36-
The chat playground is only supported for chains that take a single array of messages as input
37-
<br/>
38-
and return either an AIMessage or a string.
46+
: <div className="h-[100vh] w-[100vw] flex justify-center items-center text-xl p-16">
47+
<span>
48+
The chat playground is only supported for chains that take one of the following as input:
49+
<ul className="mt-8 list-disc ml-6">
50+
<li>
51+
a dict with a single key containing a list of messages
52+
</li>
53+
<li>
54+
a dict with two keys: one a string input, one an list of messages
55+
</li>
56+
</ul>
57+
<br />
58+
and which return either an <code>AIMessage</code> or a string.
59+
<br />
60+
<br />
61+
You can test this chain in the default LangServe playground instead.
3962
<br />
4063
<br />
41-
You can test this chain in the default LangServe playground instead. Please set <code>playground_type="default"</code>.
64+
To use the default playground, set <code>playground_type="default"</code> when adding the route in your backend.
4265
</span>
4366
</div>}
4467
</AppCallbackContext.Provider>

langserve/chat_playground/src/components/ChatWindow.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ export function isAIMessage(x: unknown): x is AIMessage {
3333
export function ChatWindow(props: {
3434
startStream: (input: unknown, config: unknown) => Promise<void>;
3535
stopStream: (() => void) | undefined;
36-
inputKey: string;
36+
messagesInputKey: string;
37+
inputKey?: string;
3738
}) {
38-
const { startStream, inputKey } = props;
39+
const { startStream, messagesInputKey, inputKey } = props;
3940

4041
const [currentInputValue, setCurrentInputValue] = useState("");
4142
const [isLoading, setIsLoading] = useState(false);
@@ -58,7 +59,18 @@ export function ChatWindow(props: {
5859
setMessages(newMessages);
5960
setCurrentInputValue("");
6061
// TODO: Add config schema support
61-
startStream({ [inputKey]: newMessages }, {});
62+
if (inputKey === undefined) {
63+
startStream({ [messagesInputKey]: newMessages }, {});
64+
} else {
65+
console.log({
66+
[messagesInputKey]: newMessages.slice(0, -1),
67+
[inputKey]: newMessages[newMessages.length - 1].content
68+
})
69+
startStream({
70+
[messagesInputKey]: newMessages.slice(0, -1),
71+
[inputKey]: newMessages[newMessages.length - 1].content
72+
}, {});
73+
}
6274
};
6375

6476
const regenerateMessages = () => {
@@ -67,7 +79,14 @@ export function ChatWindow(props: {
6779
}
6880
setIsLoading(true);
6981
// TODO: Add config schema support
70-
startStream({ [inputKey]: messages }, {});
82+
if (inputKey === undefined) {
83+
startStream({ [messagesInputKey]: messages }, {});
84+
} else {
85+
startStream({
86+
[messagesInputKey]: messages.slice(0, -1),
87+
[inputKey]: messages[messages.length - 1]
88+
}, {});
89+
}
7190
};
7291

7392
useStreamCallback("onStart", () => {

0 commit comments

Comments
 (0)