Fix attachments not take effect in agentic chat (#17895)

This commit is contained in:
Wang Qi
2026-08-06 11:12:22 +08:00
committed by GitHub
parent 4c6f575913
commit e35956bcc4
5 changed files with 100 additions and 37 deletions

View File

@@ -290,10 +290,6 @@ class DialogService(CommonService):
async def async_chat_solo(dialog, messages, stream=True, session_id=None):
attachments = ""
image_attachments = []
image_files = []
if dialog.llm_id:
if dialog.tenant_llm_id:
try:
@@ -319,12 +315,8 @@ async def async_chat_solo(dialog, messages, stream=True, session_id=None):
chat_mdl = LLMBundle(dialog.tenant_id, model_config, langfuse_session_id=session_id)
factory = model_config.get("llm_factory", "") if model_config else ""
if "files" in messages[-1]:
if model_config["model_type"] == "chat":
text_attachments, image_attachments = split_file_attachments(messages[-1]["files"])
else:
text_attachments, image_files = split_file_attachments(messages[-1]["files"], raw=True)
attachments = "\n\n".join(text_attachments)
text_attachments_content, image_attachments, image_files = get_files_content(messages[-1], model_config["model_type"])
prompt_config = dialog.prompt_config
tts_mdl = None
@@ -332,8 +324,8 @@ async def async_chat_solo(dialog, messages, stream=True, session_id=None):
default_tts_model = get_tenant_default_model_by_type(dialog.tenant_id, LLMType.TTS)
tts_mdl = LLMBundle(dialog.tenant_id, default_tts_model, trace_context=chat_mdl.trace_context, langfuse_session_id=session_id)
msg = [{"role": m["role"], "content": re.sub(r"##\d+\$\$", "", m["content"])} for m in messages if m["role"] != "system"]
if attachments and msg:
msg[-1]["content"] += attachments
if text_attachments_content and msg:
msg[-1]["content"] += text_attachments_content
if model_config["model_type"] == "chat" and image_attachments:
convert_last_user_msg_to_multimodal(msg, image_attachments, factory)
sys_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
@@ -402,6 +394,19 @@ def get_models(dialog, trace_context=None, langfuse_session_id=None):
return kbs, embd_mdl, rerank_mdl, chat_mdl, tts_mdl
def get_files_content(last_message, model_type):
text_attachments_content = ""
image_attachments = []
image_files = []
if "files" in last_message:
if model_type == "chat":
text_attachments, image_attachments = split_file_attachments(last_message["files"])
else:
text_attachments, image_files = split_file_attachments(last_message["files"], raw=True)
text_attachments_content = "\n\n".join(text_attachments)
return text_attachments_content, image_attachments, image_files
def split_file_attachments(files: list[dict] | None, raw: bool = False) -> tuple[list[str], list[str] | list[dict]]:
if not files:
return [], []
@@ -636,40 +641,35 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
retriever = settings.retriever
questions = [m["content"] for m in messages if m["role"] == "user"][-3:]
attachments = None
if "doc_ids" in kwargs:
attachments = [doc_id for doc_id in kwargs["doc_ids"].split(",") if doc_id]
attachments_ = ""
image_attachments = []
image_files = []
if "doc_ids" in messages[-1]:
attachments = [doc_id for doc_id in messages[-1]["doc_ids"] if doc_id]
if "files" in messages[-1]:
if llm_model_config["model_type"] == "chat":
text_attachments, image_attachments = split_file_attachments(messages[-1]["files"])
else:
text_attachments, image_files = split_file_attachments(messages[-1]["files"], raw=True)
attachments_ = "\n\n".join(text_attachments)
prompt_config = dialog.prompt_config
# Get scoped_doc_ids
scoped_doc_ids = None
if "doc_ids" in kwargs:
scoped_doc_ids = [doc_id for doc_id in kwargs["doc_ids"].split(",") if doc_id]
if "doc_ids" in messages[-1]:
scoped_doc_ids = [doc_id for doc_id in messages[-1]["doc_ids"] if doc_id]
if dialog.meta_data_filter:
attachments = await apply_meta_data_filter(
scoped_doc_ids = await apply_meta_data_filter(
dialog.meta_data_filter,
None,
questions[-1],
chat_mdl,
attachments,
scoped_doc_ids,
kb_ids=dialog.kb_ids,
metas_loader=lambda: DocMetadataService.get_flatted_meta_by_kbs(dialog.kb_ids),
)
# Get chat attachments
text_attachments_content, image_attachments, image_files = get_files_content(messages[-1], llm_model_config["model_type"])
prompt_config = dialog.prompt_config
include_reference_metadata, metadata_fields = _resolve_reference_metadata(prompt_config, request_payload=kwargs)
field_map = KnowledgebaseService.get_field_map(dialog.kb_ids)
logging.debug(f"field_map retrieved: {field_map}")
# try to use sql if field mapping is good to go
if field_map:
logging.debug("Use SQL to retrieval:{}".format(questions[-1]))
ans = await use_sql(questions[-1], field_map, dialog.tenant_id, chat_mdl, prompt_config.get("quote", True), dialog.kb_ids, doc_ids=attachments)
ans = await use_sql(questions[-1], field_map, dialog.tenant_id, chat_mdl, prompt_config.get("quote", True), dialog.kb_ids, doc_ids=scoped_doc_ids)
# For aggregate queries (COUNT, SUM, etc.), chunks may be empty but answer is still valid
if ans and (ans.get("reference", {}).get("chunks") or ans.get("answer")):
if include_reference_metadata and ans.get("reference", {}).get("chunks"):
@@ -689,7 +689,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
logging.warning("prompt_config['parameters'] is missing 'knowledge' entry despite kb_ids being set; auto-fixing.")
prompt_config.setdefault("parameters", []).append({"key": "knowledge", "optional": False})
param_keys.append("knowledge")
logging.debug(f"attachments={attachments}, param_keys={param_keys}, embd_mdl={embd_mdl}")
logging.debug(f"scoped_doc_ids={scoped_doc_ids}, param_keys={param_keys}, embd_mdl={embd_mdl}")
sys_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
kwargs["date"] = sys_date
@@ -735,7 +735,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
page_size=dialog.top_n,
similarity_threshold=0.2,
vector_similarity_weight=0.3,
doc_ids=attachments,
doc_ids=scoped_doc_ids,
),
internet_enabled=use_web_search,
)
@@ -770,7 +770,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
dialog.top_n,
dialog.similarity_threshold,
dialog.vector_similarity_weight,
doc_ids=attachments,
doc_ids=scoped_doc_ids,
top=dialog.top_k,
aggs=True,
rerank_mdl=rerank_mdl,
@@ -828,7 +828,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
kwargs.setdefault("knowledge", "")
gen_conf = dialog.llm_setting
system_content = prompt_config["system"].format(**kwargs) + attachments_
system_content = prompt_config["system"].format(**kwargs) + text_attachments_content
# If knowledge was retrieved but the template has no {knowledge}
# placeholder, auto-append it so the LLM still sees the context.
if knowledges and "{knowledge}" not in prompt_config.get("system", ""):
@@ -1878,6 +1878,12 @@ async def rag_agent(dialog, messages, stream=True, **kwargs):
yield ans
return
kbs, embd_mdl, rerank_mdl, chat_mdl, tts_mdl = get_models(dialog)
model_type = chat_mdl.model_config["model_type"]
factory = chat_mdl.model_config.get("llm_factory", "") if chat_mdl.model_config else ""
text_attachments_content, image_attachments, image_files = get_files_content(messages[-1], model_type)
agent_messages = deepcopy(messages)
if model_type == "chat" and image_attachments:
convert_last_user_msg_to_multimodal(agent_messages, image_attachments, factory)
use_web_search = _should_use_web_search(prompt_config, kwargs.get("internet"))
logging.debug("web_search kb=%s configured=%s internet=%r enabled=%s", bool(dialog.kb_ids), has_web_search_provider(prompt_config), kwargs.get("internet"), use_web_search)
tenant_ids = list(set([kb.tenant_id for kb in kbs]))
@@ -1923,6 +1929,7 @@ async def rag_agent(dialog, messages, stream=True, **kwargs):
empty_response=prompt_config.get("empty_response", ""),
do_refer=False,
thinking_mode=thinking_mode,
text_attachments_content=text_attachments_content,
)
async def decorate_answer(answer):
@@ -2011,7 +2018,10 @@ async def rag_agent(dialog, messages, stream=True, **kwargs):
async def _drive_stream():
try:
stream_iter = chat_mdl.async_chat_streamly_delta(rag_tools.sys_prompt(), messages, gen_conf)
if model_type == "chat":
stream_iter = chat_mdl.async_chat_streamly_delta(rag_tools.sys_prompt(), agent_messages, gen_conf)
else:
stream_iter = chat_mdl.async_chat_streamly_delta(rag_tools.sys_prompt(), agent_messages, gen_conf, images=image_files)
async for kind, value, state in _stream_with_think_delta(stream_iter):
event_queue.put_nowait(("stream", kind, value, state))
except Exception:
@@ -2111,8 +2121,11 @@ async def rag_agent(dialog, messages, stream=True, **kwargs):
final["audio_binary"] = None
yield final
else:
answer = await chat_mdl.async_chat(rag_tools.sys_prompt(), messages, gen_conf)
user_content = messages[-1].get("content", "[content not available]")
if model_type == "chat":
answer = await chat_mdl.async_chat(rag_tools.sys_prompt(), agent_messages, gen_conf)
else:
answer = await chat_mdl.async_chat(rag_tools.sys_prompt(), agent_messages, gen_conf, images=image_files)
user_content = agent_messages[-1].get("content", "[content not available]")
logging.debug("User: {}|Assistant: {}".format(user_content, answer))
res = await decorate_answer(answer)
res["audio_binary"] = tts(tts_mdl, answer)