b7f52be4c9
CI / Run CI (push) Has been cancelled
CI / check-backend (push) Has been cancelled
CI / check-frontend (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / e2e-tests (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
169 lines
5.1 KiB
Python
169 lines
5.1 KiB
Python
"""Tests for async LangChain callback handlers."""
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from langchain_core.outputs import GenerationChunk
|
|
|
|
from chainlit.langchain.callbacks import LangchainTracer
|
|
from chainlit.step import Step
|
|
|
|
|
|
def create_mock_run(**kwargs):
|
|
"""Helper to create a properly mocked Run object with all required attributes."""
|
|
run = Mock()
|
|
run.id = kwargs.get("id", uuid4())
|
|
run.parent_run_id = kwargs.get("parent_run_id", None)
|
|
run.name = kwargs.get("name", "test_run")
|
|
run.run_type = kwargs.get("run_type", "llm")
|
|
run.tags = kwargs.get("tags", [])
|
|
run.inputs = kwargs.get("inputs", {})
|
|
run.outputs = kwargs.get("outputs", None)
|
|
run.serialized = kwargs.get("serialized", {})
|
|
run.extra = kwargs.get("extra", {})
|
|
run.start_time = kwargs.get("start_time", datetime.now())
|
|
run.end_time = kwargs.get("end_time", None)
|
|
run.error = kwargs.get("error", None)
|
|
return run
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_run():
|
|
"""Create a mock LangChain Run object."""
|
|
return create_mock_run(
|
|
name="test_run",
|
|
run_type="llm",
|
|
inputs={"input": "test input"},
|
|
serialized={"name": "test_llm"},
|
|
)
|
|
|
|
|
|
async def test_tracer_initialization(mock_chainlit_context):
|
|
"""Test LangchainTracer initialization."""
|
|
async with mock_chainlit_context:
|
|
tracer = LangchainTracer()
|
|
|
|
assert tracer.steps == {}
|
|
assert tracer.parent_id_map == {}
|
|
assert tracer.ignored_runs == set()
|
|
assert tracer.stream_final_answer is False
|
|
assert tracer.answer_reached is False
|
|
|
|
|
|
async def test_on_llm_start(mock_chainlit_context):
|
|
"""Test on_llm_start callback."""
|
|
async with mock_chainlit_context:
|
|
tracer = LangchainTracer()
|
|
run_id = uuid4()
|
|
prompts = ["Test prompt"]
|
|
|
|
await tracer.on_llm_start(
|
|
serialized={"name": "test_llm"},
|
|
prompts=prompts,
|
|
run_id=run_id,
|
|
)
|
|
|
|
assert str(run_id) in tracer.completion_generations
|
|
completion_gen = tracer.completion_generations[str(run_id)]
|
|
assert completion_gen["prompt"] == "Test prompt"
|
|
assert completion_gen["token_count"] == 0
|
|
assert completion_gen["tt_first_token"] is None
|
|
assert "start" in completion_gen
|
|
|
|
|
|
async def test_on_llm_new_token(mock_chainlit_context):
|
|
"""Test on_llm_new_token with token streaming."""
|
|
async with mock_chainlit_context:
|
|
tracer = LangchainTracer()
|
|
run_id = uuid4()
|
|
|
|
await tracer.on_llm_start(
|
|
serialized={"name": "test_llm"},
|
|
prompts=["Test prompt"],
|
|
run_id=run_id,
|
|
)
|
|
|
|
chunk = GenerationChunk(text="Hello")
|
|
await tracer.on_llm_new_token(
|
|
token="Hello",
|
|
chunk=chunk,
|
|
run_id=run_id,
|
|
)
|
|
|
|
completion_gen = tracer.completion_generations[str(run_id)]
|
|
assert completion_gen["token_count"] == 1
|
|
assert completion_gen["tt_first_token"] is not None
|
|
|
|
|
|
async def test_start_trace(mock_chainlit_context):
|
|
"""Test _start_trace creates steps correctly."""
|
|
async with mock_chainlit_context:
|
|
tracer = LangchainTracer()
|
|
|
|
# Test LLM run
|
|
llm_run = create_mock_run(
|
|
id=uuid4(),
|
|
name="test_llm",
|
|
run_type="llm",
|
|
inputs={"input": "test"},
|
|
)
|
|
|
|
with patch.object(Step, "send", new_callable=AsyncMock):
|
|
await tracer._start_trace(llm_run)
|
|
|
|
assert str(llm_run.id) in tracer.steps
|
|
assert tracer.steps[str(llm_run.id)].type == "llm"
|
|
|
|
# Test ignored run
|
|
ignored_run = create_mock_run(
|
|
id=uuid4(),
|
|
name="RunnableSequence",
|
|
run_type="chain",
|
|
)
|
|
tracer.to_ignore = ["RunnableSequence"]
|
|
await tracer._start_trace(ignored_run)
|
|
|
|
assert str(ignored_run.id) in tracer.ignored_runs
|
|
|
|
|
|
async def test_on_run_update(mock_chainlit_context):
|
|
"""Test _on_run_update updates steps."""
|
|
async with mock_chainlit_context:
|
|
tracer = LangchainTracer()
|
|
run_id = uuid4()
|
|
|
|
step = Step(id=str(run_id), name="test_tool", type="tool")
|
|
tracer.steps[str(run_id)] = step
|
|
|
|
run = create_mock_run(
|
|
id=run_id,
|
|
name="test_tool",
|
|
run_type="tool",
|
|
outputs={"output": "result"},
|
|
)
|
|
|
|
with patch.object(step, "update", new_callable=AsyncMock):
|
|
await tracer._on_run_update(run)
|
|
|
|
assert step.output is not None
|
|
|
|
|
|
async def test_error_handling(mock_chainlit_context):
|
|
"""Test error handling in callbacks."""
|
|
async with mock_chainlit_context:
|
|
tracer = LangchainTracer()
|
|
run_id = uuid4()
|
|
|
|
step = Step(id=str(run_id), name="test_step", type="llm")
|
|
tracer.steps[str(run_id)] = step
|
|
|
|
error = ValueError("Test error")
|
|
|
|
with patch.object(step, "update", new_callable=AsyncMock):
|
|
await tracer._on_error(error, run_id=run_id)
|
|
|
|
assert step.is_error is True
|
|
assert step.output == "Test error"
|