langchain-ai / langchain

🦜🔗 Build context-aware reasoning applications
https://python.langchain.com
MIT License
93.57k stars 15.08k forks source link

Inconsistent behaviour from RunnableWithMessageHistory documentation examples with runnables of different signatures #27283

Open Abd-elr4hman opened 5 days ago

Abd-elr4hman commented 5 days ago

Checked other resources

Example Code

.

Error Message and Stack Trace (if applicable)

No response

Description

Examples of runnables with different signatures:

In the RunnableWithMessageHistory documentation there are three main examples of runnables with different signatures:

  1. Takes a dict as input and returns a BaseMessage.
    
    from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
    from langchain_openai.chat_models import ChatOpenAI
    from langchain_community.chat_message_histories import ChatMessageHistory
    from langchain_core.chat_history import BaseChatMessageHistory
    from langchain_core.runnables.history import RunnableWithMessageHistory

model = ChatOpenAI(api_key=OPENAI_API_KEY) prompt = ChatPromptTemplate.from_messages( [ ( "system", "You're an assistant who's good at {ability}. Respond in 20 words or fewer", ), MessagesPlaceholder(variable_name="history"), ("human", "{input}"), ] ) runnable = prompt | model

store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory: if session_id not in store: store[session_id] = ChatMessageHistory() return store[session_id]

with_message_history = RunnableWithMessageHistory( runnable, get_session_history, input_messages_key="input", history_messages_key="history", )


2. Messages input, dict output
```python
from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableParallel

runnable = RunnableParallel({"output_message": ChatOpenAI()})

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

with_message_history = RunnableWithMessageHistory(
    runnable,
    get_session_history,
    output_messages_key="output_message",
)

with_message_history.invoke(
    [HumanMessage(content="What did Simone de Beauvoir believe about free will")],
    config={"configurable": {"session_id": "baz"}},
)
  1. Messages input, messages output
    runnable = ChatOpenAI(api_key=OPENAI_API_KEY)
    with_message_history = RunnableWithMessageHistory(
    runnable,
    get_session_history,
    )

Comparing input schema generated for each

If I run the following lines for each example:

print(runnable.get_input_jsonschema())
print(issubclass(chain.get_input_schema(), BaseModel))
print(issubclass(chain.get_input_schema(),RootModel))

I get the following outputs respectively:

  1. dict input, message output Screenshot 2024-10-11 at 8 57 41 PM

True --> input schema is subclass of pydantic BaseModel False --> input schema is not a subclass of pydantic RootModel

  1. Messages input, dict output Screenshot 2024-10-11 at 8 59 41 PM True --> input schema is subclass of pydantic BaseModel False --> input schema is not a subclass of pydantic RootModel

  2. Messages input, messages output Screenshot 2024-10-11 at 9 00 48 PM

True --> input schema is a subclass of pydantic BaseModel True --> input schema is a subclass of pydantic RootModel

Inconsistencies

Even though both the second and the third examples expect Messages input... the underlying runnable generated a completely different schema type for each...

the schema for the second example contains a required field "root" which is only used with RootModel subclass, yet the field is defined on a BaseModel subclass which I believe is unintended, and is the result of wrapping the runnable --> ChatOpenAI() that has a messages input segnature in a RunnableParallel

runnable = RunnableParallel({"output_message": ChatOpenAI()})

I would expect the second and the third example underlying runnables:

runnable = RunnableParallel({"output_message": ChatOpenAI()})

and

runnable = ChatOpenAI()

to produce similar input schema when i call

runnable.get_input_schema()

Screenshot 2024-10-11 at 9 12 49 PM

I would love to get it clear if the current RunnableParallel get_input_schema behaviour is intended.

System Info

System Information

OS: Darwin OS Version: Darwin Kernel Version 22.1.0: Sun Oct 9 20:14:54 PDT 2022; root:xnu-8792.41.9~2/RELEASE_X86_64 Python Version: 3.11.7 (v3.11.7:fa7a6f2303, Dec 4 2023, 15:22:56) [Clang 13.0.0 (clang-1300.0.29.30)]

Package Information

langchain_core: 0.3.10 langchain: 0.3.3 langchain_community: 0.3.2 langsmith: 0.1.132 langchain_cli: 0.0.24 langchain_openai: 0.2.2 langchain_text_splitters: 0.3.0 langserve: 0.2.1

Optional packages not installed

langgraph

Other Dependencies

aiohttp: 3.9.5 async-timeout: Installed. No version info available. dataclasses-json: 0.6.6 fastapi: 0.110.3 gitpython: 3.1.43 httpx: 0.27.0 jsonpatch: 1.33 langserve[all]: Installed. No version info available. libcst: 1.4.0 numpy: 1.26.4 openai: 1.51.2 orjson: 3.10.3 packaging: 23.2 pydantic: 2.9.2 pydantic-settings: 2.5.2 pyproject-toml: 0.0.10 PyYAML: 6.0.1 requests: 2.32.3 requests-toolbelt: 1.0.0 SQLAlchemy: 2.0.30 sse-starlette: 1.8.2 tenacity: 8.3.0 tiktoken: 0.7.0 tomlkit: 0.12.5 typer[all]: Installed. No version info available. typing-extensions: 4.12.0 uvicorn: 0.23.2

Abd-elr4hman commented 5 days ago

I believe solving this issue might be useful to continue progress on the pr: https://github.com/langchain-ai/langchain/pull/25756

I opened a new issue for it because it did not seem to have anything with RunnableWithMessageHistory Implementation.