/
dictator
/
ragflow_sync
Обзор
Документация
Войти
/
dictator
/
ragflow_sync
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
ragflow_chat/chat.py
240 строк
9 KB
Developer
fix: don't add eliza. prefix to llm_id (only to rerank_id)
24 июл 2026, 15:13
24 июл 2026, 15:13
0063c6c
Код
Авторство
О чём код?
""" RAGFlow Chat client. Provides RAGFlowChat class for managing RAGFlow chats. """ import logging from typing import Any from ragflow_uploader.client import RAGFlowClient from .config import ChatConfig, _qualify_model_id, _unqualify_model_id logger = logging.getLogger(__name__) class RAGFlowChat: """Client for RAGFlow chat management.""" def __init__(self, client: RAGFlowClient): """Initialize RAGFlow chat client. Args: client: RAGFlow HTTP client """ self._client = client def create_chat(self, config: ChatConfig) -> dict[str, Any]: """Create a new chat in RAGFlow. Args: config: Chat configuration Returns: Created chat information including ID """ logger.info(f"Creating chat: {config.name}") # Single POST with REST API format (prompt_config, llm_id, etc.). # Avoid follow-up PUTs — a PUT without dataset_ids detaches all # knowledge bases (kb_ids is reset to []), and a raw request # bypassing _request would swallow server-side errors. return self._client._request("POST", "/chats", json=config.to_payload()) def list_chats( self, dataset_id: str | None = None, page: int = 1, page_size: int = 100, ) -> list[dict[str, Any]]: """List chats in RAGFlow. Args: dataset_id: Filter by dataset ID page: Page number (1-indexed) page_size: Number of items per page Returns: List of chat information """ params: dict[str, Any] = {"page": page, "page_size": page_size} if dataset_id: params["dataset_id"] = dataset_id logger.debug(f"Listing chats (page={page}, page_size={page_size})") data = self._client._request("GET", "/chats", params=params) # New RAGFlow returns {"chats": [...], "total": N}; # older versions returned {"data": [...]} if isinstance(data, dict): return data.get("data", data.get("chats", [])) return data def get_chat(self, chat_id: str) -> dict[str, Any]: """Get chat information. Uses the direct GET /chats/<id> endpoint when available, falling back to iterating the list endpoint for older RAGFlow versions. Args: chat_id: Chat ID Returns: Chat information """ logger.debug(f"Getting chat: {chat_id}") try: return self._client._request("GET", f"/chats/{chat_id}") except Exception: # Fallback: iterate list endpoint (older RAGFlow versions) data = self._client._request("GET", "/chats") if isinstance(data, dict): chats = data.get("data", data.get("chats", [])) else: chats = data if not isinstance(chats, list): return {} for chat in chats: if chat.get("id") == chat_id: return chat logger.warning(f"Chat not found: {chat_id}") return {} def update_chat( self, chat_id: str, name: str | None = None, description: str | None = None, llm_id: str | None = None, reranker: str | None = None, prompt: str | None = None, dataset_ids: list[str] | None = None, ) -> dict[str, Any]: """Update chat configuration. Args: chat_id: Chat ID name: New chat name description: New chat description llm_id: New chat model reranker: New reranker model prompt: New system prompt dataset_ids: New list of dataset IDs Returns: Updated chat information """ # Fetch current chat to preserve all existing fields (PUT is full overwrite) existing = self.get_chat(chat_id) # Build payload matching RAGFlow API schema exactly # Ref: https://github.com/infiniflow/ragflow/blob/main/docs/references/http_api_reference.md # # Note: new RAGFlow returns REST format (prompt_config at top level); # older versions return SDK format (prompt, llm). Support both. existing_prompt = existing.get("prompt_config") or existing.get("prompt", {}) existing_llm = existing.get("llm_setting") or existing.get("llm", {}) api_payload: dict[str, Any] = {} # Top-level writable fields per RAGFlow API if name is not None: api_payload["name"] = name if description is not None: api_payload["description"] = description if dataset_ids is not None: api_payload["dataset_ids"] = list(dataset_ids) # LLM: llm_id (top-level string) + llm_setting (top-level object) if llm_id is not None: # Server stores llm_id WITHOUT eliza. prefix (e.g. Qwen/Qwen3.6-27B___OpenAI-API@...) # but rejects it with eliza. prefix. Only add factory suffix if missing. if "___" not in llm_id and "@" not in llm_id: llm_id = f"{llm_id}___OpenAI-API@OpenAI-API-Compatible" api_payload["llm_id"] = llm_id api_payload["llm_setting"] = { "temperature": existing_llm.get("temperature", 0.1), "top_p": existing_llm.get("top_p", 0.3), "presence_penalty": existing_llm.get("presence_penalty", 0.4), "frequency_penalty": existing_llm.get("frequency_penalty", 0.7), "max_tokens": existing_llm.get("max_tokens", 512), } # Reranker: send fully qualified model ID. # The RAGFlow server resolves it to tenant_rerank_id only when # the full format "eliza.<name>___Factory@Suffix" is used. if reranker is not None: api_payload["rerank_id"] = ( _qualify_model_id(reranker) if reranker else "" ) # Prompt config: convert existing prompt to REST API format. # New RAGFlow returns prompt_config (REST keys: system, prologue, parameters); # older versions return prompt (SDK keys: prompt, opener, variables). if prompt is not None or reranker is not None: # Detect format and extract values sys_prompt = existing_prompt.get("system") or existing_prompt.get("prompt", "") prologue = existing_prompt.get("prologue") or existing_prompt.get("opener", "") params = existing_prompt.get("parameters") or existing_prompt.get("variables", []) empty_resp = existing_prompt.get("empty_response", "") quote = existing_prompt.get("quote") if "quote" in existing_prompt else existing_prompt.get("show_quote", True) keyword = existing_prompt.get("keyword", False) tts_val = existing_prompt.get("tts", False) refine = existing_prompt.get("refine_multiturn", False) use_kg_val = existing_prompt.get("use_kg", False) reasoning_val = existing_prompt.get("reasoning", False) toc_val = existing_prompt.get("toc_enhance", False) prompt_config: dict[str, Any] = { "system": sys_prompt, "prologue": prologue, "parameters": params, "empty_response": empty_resp, "quote": quote, "keyword": keyword, "tts": tts_val, "refine_multiturn": refine, "use_kg": use_kg_val, "reasoning": reasoning_val, "toc_enhance": toc_val, } if prompt is not None: prompt_config["system"] = prompt api_payload["prompt_config"] = prompt_config # Retrieval params - read from existing prompt. New format: top-level; # older format: inside prompt/prompt_config. api_payload["similarity_threshold"] = existing.get("similarity_threshold") or existing_prompt.get("similarity_threshold", 0.3) api_payload["vector_similarity_weight"] = existing.get("vector_similarity_weight") or existing_prompt.get( "keywords_similarity_weight", 0.3 ) api_payload["top_n"] = existing.get("top_n") or existing_prompt.get("top_n", 24) api_payload["top_k"] = existing.get("top_k", 1024) # Preserve dataset_ids from existing if not overridden if "dataset_ids" not in api_payload and existing.get("datasets"): api_payload["dataset_ids"] = [d["id"] for d in existing["datasets"]] logger.info(f"Updating chat: {chat_id}") self._client._request("PUT", f"/chats/{chat_id}", json=api_payload) return self.get_chat(chat_id) def delete_chat(self, chat_id: str) -> bool: """Delete a chat by its ID. Uses the official RAGFlow API: DELETE /api/v1/chats with JSON body {"ids": [chat_id]}. This is safer than path-based deletion, which can behave inconsistently across RAGFlow versions. Args: chat_id: Chat ID to delete Returns: True if successful Raises: RAGFlowError: If the API call fails """ logger.info(f"Deleting chat: {chat_id}") self._client._request("DELETE", "/chats", json={"ids": [chat_id]}) return True