mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-27 19:02:03 +08:00
feat: Langfuse session grouping for multi-turn chat traces (#15679)
## Summary This PR passes `session_id` into Langfuse trace observations so multi-turn chat messages can be grouped under the same session in Langfuse. Changes include: - Propagate `session_id` from chat/session APIs into `dialog_service.async_chat`. - Pass `session_id` into Langfuse `start_observation(...)`. - Share Langfuse `trace_context` with chat, embedding, rerank, and TTS model bundles where applicable. - Add unit coverage to verify Langfuse observations receive `session_id`. - Update affected test stubs for the new optional Langfuse context arguments. ## Related Issue Closes: #15636 ## Change Type - [x] Feature - [x] Bug fix - [x] Test - [ ] Refactor - [ ] Documentation - [ ] Breaking change ## Real Behavior Proof Before this change: - Langfuse observations were created without `session_id`. - Multi-turn chat traces could not be grouped by session in Langfuse. After this change: - Chat/session flows pass `session_id` into `async_chat`. - Langfuse observations include `session_id`. - Related model bundles receive shared trace context and session metadata. Validation result: ```bash uv run python -m py_compile \ api/db/services/tenant_llm_service.py \ api/db/services/llm_service.py \ api/db/services/dialog_service.py \ api/db/services/conversation_service.py \ api/apps/restful_apis/chat_api.py \ test/unit_test/api/db/services/test_dialog_service_final_answer.py \ test/unit_test/api/db/services/test_dialog_service_use_sql_source_columns.py ``` Passed. ```bash uv run pytest \ test/unit_test/api/db/services/test_dialog_service_final_answer.py \ test/unit_test/api/db/services/test_dialog_service_use_sql_source_columns.py -q ``` Result: ```text 11 passed in 16.89s ``` ```bash git diff --check ``` Passed. ## Checklist - [x] Analyzed the issue requirement. - [x] Checked existing Langfuse trace integration. - [x] Implemented only the requested session grouping behavior. - [x] Added/updated unit tests. - [x] Ran focused tests successfully. - [x] Ran Python compile validation. - [x] Ran whitespace diff validation.
This commit is contained in:
@@ -406,7 +406,7 @@ def test_async_chat_final_event_carries_decorated_answer(monkeypatch):
|
||||
# get_models returns (kbs, embd_mdl, rerank_mdl, chat_mdl, tts_mdl)
|
||||
monkeypatch.setattr(
|
||||
dialog_service, "get_models",
|
||||
lambda _dialog: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
lambda _dialog, **_kwargs: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.KnowledgebaseService, "get_field_map", lambda _kb_ids: {}
|
||||
@@ -473,7 +473,7 @@ def test_async_chat_langfuse_uses_start_observation(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_models",
|
||||
lambda _dialog: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
lambda _dialog, **_kwargs: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.KnowledgebaseService, "get_field_map", lambda _kb_ids: {}
|
||||
@@ -511,6 +511,105 @@ def test_async_chat_langfuse_uses_start_observation(monkeypatch):
|
||||
assert langfuse.observation.ended is True
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_async_chat_langfuse_observation_includes_session_id(monkeypatch):
|
||||
_FakeLangfuseClient.instances = []
|
||||
monkeypatch.setattr(_FakeLangfuseClient, "fail_start_observation", False)
|
||||
chat_mdl = _StreamingChatModel("Session traces should be grouped.")
|
||||
retriever = _StubRetriever()
|
||||
|
||||
monkeypatch.setattr(
|
||||
dialog_service, "get_model_type_by_name",
|
||||
lambda _tid, _llm_id: ["chat"]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_model_config_from_provider_instance",
|
||||
lambda _tid, _type, _llm_id: _LLM_CONFIG,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLangfuseService, "filter_by_tenant",
|
||||
lambda tenant_id: SimpleNamespace(
|
||||
public_key="public",
|
||||
secret_key="secret",
|
||||
host="http://langfuse.local",
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(dialog_service, "Langfuse", _FakeLangfuseClient)
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_models",
|
||||
lambda _dialog, **_kwargs: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.KnowledgebaseService, "get_field_map", lambda _kb_ids: {}
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.KnowledgebaseService, "get_by_ids", lambda _ids: [_KB]
|
||||
)
|
||||
monkeypatch.setattr(dialog_service.settings, "retriever", retriever, raising=False)
|
||||
monkeypatch.setattr(dialog_service, "label_question", lambda _q, _kbs: "")
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"kb_prompt",
|
||||
lambda _kbinfos, _max_tokens, **_kw: ["RAGFlow is a RAG engine."],
|
||||
)
|
||||
|
||||
dialog = _make_dialog(chat_mdl)
|
||||
messages = [{"role": "user", "content": "What is RAGFlow?"}]
|
||||
|
||||
events = _collect(dialog_service.async_chat(dialog, messages, stream=True, quote=True, session_id="session-1"))
|
||||
|
||||
assert any(e.get("final") is True for e in events)
|
||||
langfuse = _FakeLangfuseClient.instances[0]
|
||||
assert langfuse.observation_kwargs["trace_context"] == {"trace_id": "trace-id"}
|
||||
assert langfuse.observation_kwargs["session_id"] == "session-1"
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_get_models_passes_langfuse_trace_context_to_llm_bundles(monkeypatch):
|
||||
captured = []
|
||||
|
||||
class _FakeBundle:
|
||||
def __init__(self, tenant_id, model_config, **kwargs):
|
||||
self.tenant_id = tenant_id
|
||||
self.model_config = model_config
|
||||
self.trace_context = kwargs.get("trace_context")
|
||||
self.langfuse_session_id = kwargs.get("langfuse_session_id")
|
||||
captured.append((tenant_id, model_config["model_type"], kwargs))
|
||||
|
||||
monkeypatch.setattr(dialog_service.KnowledgebaseService, "get_by_ids", lambda _ids: [_KB])
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_model_config_from_provider_instance",
|
||||
lambda _tenant_id, model_type, _model_id: {**_LLM_CONFIG, "model_type": model_type},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_tenant_default_model_by_type",
|
||||
lambda _tenant_id, model_type: {**_LLM_CONFIG, "model_type": model_type},
|
||||
)
|
||||
monkeypatch.setattr(dialog_service, "LLMBundle", _FakeBundle)
|
||||
|
||||
dialog = _make_dialog(None)
|
||||
dialog.rerank_id = "rerank-1"
|
||||
dialog.prompt_config["tts"] = True
|
||||
trace_context = {"trace_id": "trace-id"}
|
||||
|
||||
dialog_service.get_models(dialog, trace_context=trace_context, langfuse_session_id="session-1")
|
||||
|
||||
assert len(captured) == 4
|
||||
assert {model_type for _, model_type, _ in captured} == {
|
||||
dialog_service.LLMType.EMBEDDING,
|
||||
dialog_service.LLMType.CHAT,
|
||||
dialog_service.LLMType.RERANK,
|
||||
dialog_service.LLMType.TTS,
|
||||
}
|
||||
for _, _, kwargs in captured:
|
||||
assert kwargs["trace_context"] is trace_context
|
||||
assert kwargs["langfuse_session_id"] == "session-1"
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_async_chat_continues_when_langfuse_observation_start_fails(monkeypatch):
|
||||
"""
|
||||
@@ -543,7 +642,7 @@ def test_async_chat_continues_when_langfuse_observation_start_fails(monkeypatch)
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_models",
|
||||
lambda _dialog: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
lambda _dialog, **_kwargs: ([_KB], chat_mdl, None, chat_mdl, None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.KnowledgebaseService, "get_field_map", lambda _kb_ids: {}
|
||||
|
||||
@@ -295,7 +295,7 @@ def test_async_chat_uses_all_docs_when_no_doc_ids_selected(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_models",
|
||||
lambda _dialog: ([SimpleNamespace(tenant_id="tenant-id")], object(), None, chat_model, None),
|
||||
lambda _dialog, **_kwargs: ([SimpleNamespace(tenant_id="tenant-id")], object(), None, chat_model, None),
|
||||
)
|
||||
monkeypatch.setattr(dialog_service.KnowledgebaseService, "get_field_map", lambda _kb_ids: {})
|
||||
monkeypatch.setattr(dialog_service, "label_question", lambda _question, _kbs: None)
|
||||
|
||||
Reference in New Issue
Block a user