diff --git a/conf/llm_factories.json b/conf/llm_factories.json index 8f898da90..175ebf014 100644 --- a/conf/llm_factories.json +++ b/conf/llm_factories.json @@ -6267,6 +6267,14 @@ "is_tools": true } ] + }, + { + "name": "RAGcon", + "logo": "", + "tags": "LLM,TEXT EMBEDDING,TTS,TEXT RE-RANK,SPEECH2TEXT,IMAGE2TEXT", + "status": "1", + "rank": "100", + "llm": [] } ] } diff --git a/rag/llm/chat_model.py b/rag/llm/chat_model.py index e763fca53..10b2fb515 100644 --- a/rag/llm/chat_model.py +++ b/rag/llm/chat_model.py @@ -1658,3 +1658,17 @@ class LiteLLMBase(ABC): completion_args["extra_headers"] = extra_headers return completion_args +class RAGconChat(Base): + """ + RAGcon Chat Provider - routes through LiteLLM proxy + + All model types are handled through a unified LiteLLM endpoint. + Default Base URL: https://connect.ragcon.com/v1 + """ + _FACTORY_NAME = "RAGcon" + + def __init__(self, key, model_name, base_url=None, **kwargs): + if not base_url: + base_url = "https://connect.ragcon.com/v1" + + super().__init__(key, model_name, base_url, **kwargs) diff --git a/rag/llm/cv_model.py b/rag/llm/cv_model.py index 7543ca640..ff868d6bd 100644 --- a/rag/llm/cv_model.py +++ b/rag/llm/cv_model.py @@ -1252,3 +1252,26 @@ class MoonshotCV(GptV4): if not base_url: base_url = "https://api.moonshot.cn/v1" super().__init__(key, model_name, lang=lang, base_url=base_url, **kwargs) + + +class RAGconCV(GptV4): + """ + RAGcon CV Provider - routes through LiteLLM proxy + + Supports vision models through LiteLLM. + Default Base URL: https://connect.ragcon.ai/v1 + """ + _FACTORY_NAME = "RAGcon" + + def __init__(self, key, model_name, lang="Chinese", base_url="", **kwargs): + + if not base_url: + base_url = "https://connect.ragcon.com/v1" + + # Initialize client + self.client = OpenAI(api_key=key, base_url=base_url) + self.async_client = AsyncOpenAI(api_key=key, base_url=base_url) + self.model_name = model_name + self.lang = lang + + Base.__init__(self, **kwargs) \ No newline at end of file diff --git a/rag/llm/embedding_model.py b/rag/llm/embedding_model.py index 79dc96acc..f4b58619b 100644 --- a/rag/llm/embedding_model.py +++ b/rag/llm/embedding_model.py @@ -1080,3 +1080,17 @@ class JiekouAIEmbed(OpenAIEmbed): if not base_url: base_url = "https://api.jiekou.ai/openai/v1/embeddings" super().__init__(key, model_name, base_url) + +class RAGconEmbed(OpenAIEmbed): + """ + RAGcon Embedding Provider - routes through LiteLLM proxy + + Default Base URL: https://connect.ragcon.ai/v1 + """ + _FACTORY_NAME = "RAGcon" + + def __init__(self, key, model_name="text-embedding-3-small", base_url=None): + if not base_url: + base_url = "https://connect.ragcon.com/v1" + + super().__init__(key, model_name, base_url) \ No newline at end of file diff --git a/rag/llm/rerank_model.py b/rag/llm/rerank_model.py index b8fd19dac..5002fe765 100644 --- a/rag/llm/rerank_model.py +++ b/rag/llm/rerank_model.py @@ -506,3 +506,47 @@ class JiekouAIRerank(JinaRerank): if not base_url: base_url = "https://api.jiekou.ai/openai/v1/rerank" super().__init__(key, model_name, base_url) + +class RAGconRerank(Base): + """ + RAGcon Rerank Provider - routes through LiteLLM proxy + + Assumes LiteLLM proxy supports /rerank endpoint. + Default Base URL: https://connect.ragcon.ai/v1 + """ + _FACTORY_NAME = "RAGcon" + + def __init__(self, key, model_name, base_url=None, **kwargs): + if not base_url: + base_url = "https://connect.ragcon.com/v1" + + self._api_key = key + self._base_url = base_url + + self.headers = {"Content-Type": "application/json", "Authorization": f"Bearer {key}"} + self.model_name = model_name + + + def similarity(self, query: str, texts: list): + # noway to config Ragflow , use fix setting + texts = [truncate(t, 500) for t in texts] + data = { + "model": self.model_name, + "query": query, + "documents": texts, + "top_n": len(texts), + } + token_count = 0 + for t in texts: + token_count += num_tokens_from_string(t) + res = requests.post(self._base_url + "/rerank", headers=self.headers, json=data).json() + rank = np.zeros(len(texts), dtype=float) + try: + for d in res["results"]: + rank[d["index"]] = d["relevance_score"] + except Exception as _e: + log_exception(_e, res) + + rank = Base._normalize_rank(rank) + + return rank, token_count \ No newline at end of file diff --git a/rag/llm/sequence2txt_model.py b/rag/llm/sequence2txt_model.py index abbdb4de3..0191482cf 100644 --- a/rag/llm/sequence2txt_model.py +++ b/rag/llm/sequence2txt_model.py @@ -376,3 +376,48 @@ class ZhipuSeq2txt(Base): return f"**ERROR**: code: {error['code']}, message: {error['message']}", 0 except Exception as e: return "**ERROR**: " + str(e), 0 + + +class RAGconSeq2txt(Base): + """ + RAGcon Sequence2Text Provider - routes through LiteLLM proxy + + Speech-to-text models routed through LiteLLM. + Default Base URL: https://connect.ragcon.com/v1 + """ + _FACTORY_NAME = "RAGcon" + + def __init__(self, key, model_name, base_url=None, lang="English", **kwargs): + # Use provided base_url or fallback to default + if not base_url: + base_url = "https://connect.ragcon.com/v1" + + self.base_url = base_url + self.model_name = model_name + self.key = key + self.lang = lang + + self.client = OpenAI(api_key=key, base_url=self.base_url) + + def transcription(self, audio_path, **kwargs): + """ + Transcribe audio file using RAGcon's OpenAI-compatible API. + Uses Whisper's automatic language detection for German and English audio. + + Args: + audio_path: Path to the audio file + **kwargs: Additional parameters (currently unused but maintained for compatibility) + + Returns: + tuple: (transcribed_text, token_count) + """ + with open(audio_path, "rb") as audio_file: + # Call RAGcon API - Whisper will auto-detect language + transcription = self.client.audio.transcriptions.create( + model=self.model_name, + file=audio_file + ) + + # Return text and token count + text = transcription.text.strip() + return text, num_tokens_from_string(text) diff --git a/rag/llm/tts_model.py b/rag/llm/tts_model.py index 602ea165a..b39b6a8c7 100644 --- a/rag/llm/tts_model.py +++ b/rag/llm/tts_model.py @@ -482,3 +482,51 @@ class StepFunTTS(OpenAITTS): yield chunk yield num_tokens_from_string(text) + + +class RAGconTTS(Base): + """ + RAGcon TTS Provider - routes through LiteLLM proxy + + Text-to-speech models routed through LiteLLM. + Default Base URL: https://connect.ragcon.ai/v1 + """ + _FACTORY_NAME = "RAGcon" + + def __init__(self, key, model_name, base_url=None, **kwargs): + if not base_url: + base_url = "https://connect.ragcon.com/v1" + + self.base_url = base_url + self.api_key = key + self.model_name = model_name + self.headers = { + "accept": "application/json", + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}" + } + + def tts(self, text, voice="English Female", stream=True): + """ + Uses LiteLLM's /v1/audio/speech endpoint + """ + + payload = { + "model": self.model_name, + "input": text, + "voice": voice + } + + response = requests.post( + f"{self.base_url}/audio/speech", + headers=self.headers, + json=payload, + stream=stream + ) + + if response.status_code != 200: + raise Exception(f"**Error**: {response.status_code}, {response.text}") + + for chunk in response.iter_content(chunk_size=1024): + if chunk: + yield chunk diff --git a/web/src/assets/svg/llm/ragcon.svg b/web/src/assets/svg/llm/ragcon.svg new file mode 100644 index 000000000..11acb8776 --- /dev/null +++ b/web/src/assets/svg/llm/ragcon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/web/src/components/svg-icon.tsx b/web/src/components/svg-icon.tsx index 6a14fc64b..87cf46bf4 100644 --- a/web/src/components/svg-icon.tsx +++ b/web/src/components/svg-icon.tsx @@ -85,6 +85,7 @@ const svgIcons = [ LLMFactory.N1n, // LLMFactory.DeerAPI, LLMFactory.Avian, + LLMFactory.RAGcon, ]; export const LlmIcon = ({ diff --git a/web/src/constants/llm.ts b/web/src/constants/llm.ts index 6cde18fcb..e3f1dffba 100644 --- a/web/src/constants/llm.ts +++ b/web/src/constants/llm.ts @@ -64,6 +64,7 @@ export enum LLMFactory { PaddleOCR = 'PaddleOCR', N1n = 'n1n', Avian = 'Avian', + RAGcon = 'RAGcon', } // Please lowercase the file name @@ -133,6 +134,7 @@ export const IconMap = { [LLMFactory.PaddleOCR]: 'paddleocr', [LLMFactory.N1n]: 'n1n', [LLMFactory.Avian]: 'avian', + [LLMFactory.RAGcon]: 'ragcon', }; export const APIMapUrl = { diff --git a/web/src/pages/user-setting/constants.tsx b/web/src/pages/user-setting/constants.tsx index 22fadbab1..1e32df753 100644 --- a/web/src/pages/user-setting/constants.tsx +++ b/web/src/pages/user-setting/constants.tsx @@ -39,6 +39,7 @@ export const LocalLlmFactories = [ LLMFactory.GPUStack, LLMFactory.ModelScope, LLMFactory.VLLM, + LLMFactory.RAGcon, ]; export enum TenantRole { diff --git a/web/src/pages/user-setting/setting-model/modal/ollama-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/ollama-modal/index.tsx index bfe7807b0..a1c00e5aa 100644 --- a/web/src/pages/user-setting/setting-model/modal/ollama-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/ollama-modal/index.tsx @@ -27,6 +27,7 @@ const llmFactoryToUrlMap: Partial> = { [LLMFactory.LMStudio]: 'https://lmstudio.ai/docs/basics', [LLMFactory.OpenAiAPICompatible]: 'https://platform.openai.com/docs/models/gpt-4', + [LLMFactory.RAGcon]: 'https://www.ragcon.ai/erste-schritte-mit-ragflow/', [LLMFactory.TogetherAI]: 'https://docs.together.ai/docs/deployment-options', [LLMFactory.Replicate]: 'https://replicate.com/docs/topics/deployments', [LLMFactory.OpenRouter]: 'https://openrouter.ai/docs', @@ -81,6 +82,14 @@ const OllamaModal = ({ 'speech2text', 'tts', ]), + [LLMFactory.RAGcon]: buildModelTypeOptions([ + 'chat', + 'embedding', + 'rerank', + 'image2text', + 'speech2text', + 'tts', + ]), [LLMFactory.ModelScope]: buildModelTypeOptions(['chat']), [LLMFactory.GPUStack]: buildModelTypeOptions([ 'chat',