mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
core[minor], langchain[patch], openai[minor], anthropic[minor], fireworks[minor], groq[minor], mistralai[minor]
```python
class ToolCall(TypedDict):
name: str
args: Dict[str, Any]
id: Optional[str]
class InvalidToolCall(TypedDict):
name: Optional[str]
args: Optional[str]
id: Optional[str]
error: Optional[str]
class ToolCallChunk(TypedDict):
name: Optional[str]
args: Optional[str]
id: Optional[str]
index: Optional[int]
class AIMessage(BaseMessage):
...
tool_calls: List[ToolCall] = []
invalid_tool_calls: List[InvalidToolCall] = []
...
class AIMessageChunk(AIMessage, BaseMessageChunk):
...
tool_call_chunks: Optional[List[ToolCallChunk]] = None
...
```
Important considerations:
- Parsing logic occurs within different providers;
- ~Changing output type is a breaking change for anyone doing explicit
type checking;~
- ~Langsmith rendering will need to be updated:
https://github.com/langchain-ai/langchainplus/pull/3561~
- ~Langserve will need to be updated~
- Adding chunks:
- ~AIMessage + ToolCallsMessage = ToolCallsMessage if either has
non-null .tool_calls.~
- Tool call chunks are appended, merging when having equal values of
`index`.
- additional_kwargs accumulate the normal way.
- During streaming:
- ~Messages can change types (e.g., from AIMessageChunk to
AIToolCallsMessageChunk)~
- Output parsers parse additional_kwargs (during .invoke they read off
tool calls).
Packages outside of `partners/`:
- https://github.com/langchain-ai/langchain-cohere/pull/7
- https://github.com/langchain-ai/langchain-google/pull/123/files
---------
Co-authored-by: Chester Curme <chester.curme@gmail.com>
218 lines
6.6 KiB
Python
218 lines
6.6 KiB
Python
"""Test ChatMistral chat model."""
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from langchain_core.messages import (
|
|
AIMessage,
|
|
AIMessageChunk,
|
|
HumanMessage,
|
|
ToolCall,
|
|
ToolCallChunk,
|
|
)
|
|
from langchain_core.pydantic_v1 import BaseModel
|
|
|
|
from langchain_mistralai.chat_models import ChatMistralAI
|
|
|
|
|
|
def test_stream() -> None:
|
|
"""Test streaming tokens from ChatMistralAI."""
|
|
llm = ChatMistralAI()
|
|
|
|
for token in llm.stream("I'm Pickle Rick"):
|
|
assert isinstance(token.content, str)
|
|
|
|
|
|
async def test_astream() -> None:
|
|
"""Test streaming tokens from ChatMistralAI."""
|
|
llm = ChatMistralAI()
|
|
|
|
async for token in llm.astream("I'm Pickle Rick"):
|
|
assert isinstance(token.content, str)
|
|
|
|
|
|
async def test_abatch() -> None:
|
|
"""Test streaming tokens from ChatMistralAI"""
|
|
llm = ChatMistralAI()
|
|
|
|
result = await llm.abatch(["I'm Pickle Rick", "I'm not Pickle Rick"])
|
|
for token in result:
|
|
assert isinstance(token.content, str)
|
|
|
|
|
|
async def test_abatch_tags() -> None:
|
|
"""Test batch tokens from ChatMistralAI"""
|
|
llm = ChatMistralAI()
|
|
|
|
result = await llm.abatch(
|
|
["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]}
|
|
)
|
|
for token in result:
|
|
assert isinstance(token.content, str)
|
|
|
|
|
|
def test_batch() -> None:
|
|
"""Test batch tokens from ChatMistralAI"""
|
|
llm = ChatMistralAI()
|
|
|
|
result = llm.batch(["I'm Pickle Rick", "I'm not Pickle Rick"])
|
|
for token in result:
|
|
assert isinstance(token.content, str)
|
|
|
|
|
|
async def test_ainvoke() -> None:
|
|
"""Test invoke tokens from ChatMistralAI"""
|
|
llm = ChatMistralAI()
|
|
|
|
result = await llm.ainvoke("I'm Pickle Rick", config={"tags": ["foo"]})
|
|
assert isinstance(result.content, str)
|
|
|
|
|
|
def test_invoke() -> None:
|
|
"""Test invoke tokens from ChatMistralAI"""
|
|
llm = ChatMistralAI()
|
|
|
|
result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"]))
|
|
assert isinstance(result.content, str)
|
|
|
|
|
|
def test_chat_mistralai_llm_output_contains_model_name() -> None:
|
|
"""Test llm_output contains model_name."""
|
|
chat = ChatMistralAI(max_tokens=10)
|
|
message = HumanMessage(content="Hello")
|
|
llm_result = chat.generate([[message]])
|
|
assert llm_result.llm_output is not None
|
|
assert llm_result.llm_output["model_name"] == chat.model
|
|
|
|
|
|
def test_chat_mistralai_streaming_llm_output_contains_model_name() -> None:
|
|
"""Test llm_output contains model_name."""
|
|
chat = ChatMistralAI(max_tokens=10, streaming=True)
|
|
message = HumanMessage(content="Hello")
|
|
llm_result = chat.generate([[message]])
|
|
assert llm_result.llm_output is not None
|
|
assert llm_result.llm_output["model_name"] == chat.model
|
|
|
|
|
|
def test_chat_mistralai_llm_output_contains_token_usage() -> None:
|
|
"""Test llm_output contains model_name."""
|
|
chat = ChatMistralAI(max_tokens=10)
|
|
message = HumanMessage(content="Hello")
|
|
llm_result = chat.generate([[message]])
|
|
assert llm_result.llm_output is not None
|
|
assert "token_usage" in llm_result.llm_output
|
|
token_usage = llm_result.llm_output["token_usage"]
|
|
assert "prompt_tokens" in token_usage
|
|
assert "completion_tokens" in token_usage
|
|
assert "total_tokens" in token_usage
|
|
|
|
|
|
def test_chat_mistralai_streaming_llm_output_not_contain_token_usage() -> None:
|
|
"""Mistral currently doesn't return token usage when streaming."""
|
|
chat = ChatMistralAI(max_tokens=10, streaming=True)
|
|
message = HumanMessage(content="Hello")
|
|
llm_result = chat.generate([[message]])
|
|
assert llm_result.llm_output is not None
|
|
assert "token_usage" in llm_result.llm_output
|
|
token_usage = llm_result.llm_output["token_usage"]
|
|
assert not token_usage
|
|
|
|
|
|
def test_structured_output() -> None:
|
|
llm = ChatMistralAI(model="mistral-large-latest", temperature=0)
|
|
schema = {
|
|
"title": "AnswerWithJustification",
|
|
"description": (
|
|
"An answer to the user question along with justification for the answer."
|
|
),
|
|
"type": "object",
|
|
"properties": {
|
|
"answer": {"title": "Answer", "type": "string"},
|
|
"justification": {"title": "Justification", "type": "string"},
|
|
},
|
|
"required": ["answer", "justification"],
|
|
}
|
|
structured_llm = llm.with_structured_output(schema)
|
|
result = structured_llm.invoke(
|
|
"What weighs more a pound of bricks or a pound of feathers"
|
|
)
|
|
assert isinstance(result, dict)
|
|
|
|
|
|
def test_streaming_structured_output() -> None:
|
|
llm = ChatMistralAI(model="mistral-large", temperature=0)
|
|
|
|
class Person(BaseModel):
|
|
name: str
|
|
age: int
|
|
|
|
structured_llm = llm.with_structured_output(Person)
|
|
strm = structured_llm.stream("Erick, 27 years old")
|
|
chunk_num = 0
|
|
for chunk in strm:
|
|
assert chunk_num == 0, "should only have one chunk with model"
|
|
assert isinstance(chunk, Person)
|
|
assert chunk.name == "Erick"
|
|
assert chunk.age == 27
|
|
chunk_num += 1
|
|
|
|
|
|
def test_tool_call() -> None:
|
|
llm = ChatMistralAI(model="mistral-large", temperature=0)
|
|
|
|
class Person(BaseModel):
|
|
name: str
|
|
age: int
|
|
|
|
tool_llm = llm.bind_tools([Person])
|
|
|
|
result = tool_llm.invoke("Erick, 27 years old")
|
|
assert isinstance(result, AIMessage)
|
|
assert result.tool_calls == [
|
|
ToolCall(name="Person", args={"name": "Erick", "age": 27}, id=None)
|
|
]
|
|
|
|
|
|
def test_streaming_tool_call() -> None:
|
|
llm = ChatMistralAI(model="mistral-large", temperature=0)
|
|
|
|
class Person(BaseModel):
|
|
name: str
|
|
age: int
|
|
|
|
tool_llm = llm.bind_tools([Person])
|
|
|
|
# where it calls the tool
|
|
strm = tool_llm.stream("Erick, 27 years old")
|
|
|
|
additional_kwargs = None
|
|
for chunk in strm:
|
|
assert isinstance(chunk, AIMessageChunk)
|
|
assert chunk.content == ""
|
|
additional_kwargs = chunk.additional_kwargs
|
|
|
|
assert additional_kwargs is not None
|
|
assert "tool_calls" in additional_kwargs
|
|
assert len(additional_kwargs["tool_calls"]) == 1
|
|
assert additional_kwargs["tool_calls"][0]["function"]["name"] == "Person"
|
|
assert json.loads(additional_kwargs["tool_calls"][0]["function"]["arguments"]) == {
|
|
"name": "Erick",
|
|
"age": 27,
|
|
}
|
|
|
|
assert isinstance(chunk, AIMessageChunk)
|
|
assert chunk.tool_call_chunks == [
|
|
ToolCallChunk(
|
|
name="Person", args='{"name": "Erick", "age": 27}', id=None, index=None
|
|
)
|
|
]
|
|
|
|
# where it doesn't call the tool
|
|
strm = tool_llm.stream("What is 2+2?")
|
|
acc: Any = None
|
|
for chunk in strm:
|
|
assert isinstance(chunk, AIMessageChunk)
|
|
acc = chunk if acc is None else acc + chunk
|
|
assert acc.content != ""
|
|
assert "tool_calls" not in acc.additional_kwargs
|