rag-core-lib 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. rag_core_lib-3.2.0/PKG-INFO +20 -0
  2. rag_core_lib-3.2.0/pyproject.toml +111 -0
  3. rag_core_lib-3.2.0/src/rag_core_lib/impl/__init__.py +0 -0
  4. rag_core_lib-3.2.0/src/rag_core_lib/impl/data_types/__init__.py +0 -0
  5. rag_core_lib-3.2.0/src/rag_core_lib/impl/data_types/content_type.py +13 -0
  6. rag_core_lib-3.2.0/src/rag_core_lib/impl/data_types/search_request.py +21 -0
  7. rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/__init__.py +13 -0
  8. rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/embedder.py +11 -0
  9. rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/embedder_type.py +12 -0
  10. rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/langchain_community_embedder.py +34 -0
  11. rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/stackit_embedder.py +80 -0
  12. rag_core_lib-3.2.0/src/rag_core_lib/impl/langfuse_manager/__init__.py +0 -0
  13. rag_core_lib-3.2.0/src/rag_core_lib/impl/langfuse_manager/langfuse_manager.py +208 -0
  14. rag_core_lib-3.2.0/src/rag_core_lib/impl/llms/__init__.py +0 -0
  15. rag_core_lib-3.2.0/src/rag_core_lib/impl/llms/llm_factory.py +88 -0
  16. rag_core_lib-3.2.0/src/rag_core_lib/impl/llms/llm_type.py +12 -0
  17. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/__init__.py +0 -0
  18. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/embedder_class_type_settings.py +18 -0
  19. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/fake_embedder_settings.py +16 -0
  20. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/langfuse_settings.py +29 -0
  21. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/logging_settings.py +23 -0
  22. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/ollama_embedder_settings.py +17 -0
  23. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/ollama_llm_settings.py +43 -0
  24. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/rag_class_types_settings.py +30 -0
  25. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/retry_decorator_settings.py +78 -0
  26. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/stackit_embedder_settings.py +36 -0
  27. rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/stackit_vllm_settings.py +36 -0
  28. rag_core_lib-3.2.0/src/rag_core_lib/impl/tracers/__init__.py +0 -0
  29. rag_core_lib-3.2.0/src/rag_core_lib/impl/tracers/langfuse_traced_runnable.py +49 -0
  30. rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/__init__.py +0 -0
  31. rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/async_threadsafe_semaphore.py +71 -0
  32. rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/retry_decorator.py +211 -0
  33. rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/utils.py +71 -0
  34. rag_core_lib-3.2.0/src/rag_core_lib/runnables/__init__.py +0 -0
  35. rag_core_lib-3.2.0/src/rag_core_lib/runnables/async_runnable.py +60 -0
  36. rag_core_lib-3.2.0/src/rag_core_lib/secret_provider/__init__.py +0 -0
  37. rag_core_lib-3.2.0/src/rag_core_lib/secret_provider/secret_provider.py +30 -0
  38. rag_core_lib-3.2.0/src/rag_core_lib/tracers/__init__.py +0 -0
  39. rag_core_lib-3.2.0/src/rag_core_lib/tracers/traced_runnable.py +79 -0
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.3
2
+ Name: rag-core-lib
3
+ Version: 3.2.0
4
+ Summary: The perfect rag template
5
+ Author: STACKIT GmbH & Co. KG
6
+ Requires-Python: >=3.13,<4.0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Requires-Dist: deprecated (>=1.2.18,<2.0.0)
10
+ Requires-Dist: flashrank (>=0.2.10,<0.3.0)
11
+ Requires-Dist: langchain (>=0.3.25,<0.4.0)
12
+ Requires-Dist: langchain-community (==0.3.30)
13
+ Requires-Dist: langchain-core (==0.3.77)
14
+ Requires-Dist: langchain-openai (>=0.3.27,<0.4.0)
15
+ Requires-Dist: langfuse (==3.6.1)
16
+ Requires-Dist: oauthlib (>=3.2.2,<4.0.0)
17
+ Requires-Dist: openai (>=1.77.0,<2.0.0)
18
+ Requires-Dist: pydantic (>=2.11.4,<3.0.0)
19
+ Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
20
+ Requires-Dist: requests-oauthlib (>=2.0.0,<3.0.0)
@@ -0,0 +1,111 @@
1
+ [build-system]
2
+ requires = ["poetry-core"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [tool.poetry]
6
+ name = "rag-core-lib"
7
+ version = "v3.2.0"
8
+ description = "The perfect rag template"
9
+ authors = ["STACKIT GmbH & Co. KG"]
10
+ packages = [{ include = "rag_core_lib", from = "src" }]
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.13"
14
+ langchain = "^0.3.25"
15
+ langchain-community = "0.3.30"
16
+ flashrank = "^0.2.10"
17
+ pydantic-settings = "^2.2.1"
18
+ pydantic = "^2.11.4"
19
+ oauthlib = "^3.2.2"
20
+ requests-oauthlib = "^2.0.0"
21
+ langfuse = "3.6.1"
22
+ deprecated = "^1.2.18"
23
+ openai = "^1.77.0"
24
+ langchain-core = "0.3.77"
25
+ langchain-openai = "^0.3.27"
26
+
27
+
28
+ [tool.poetry.group.dev.dependencies]
29
+ debugpy = "^1.8.14"
30
+ pytest = "^8.3.5"
31
+ pytest-asyncio = "^0.26.0"
32
+ coverage = "^7.5.4"
33
+ flake8 = "^7.2.0"
34
+ flake8-black = "^0.4.0"
35
+ flake8-pyproject = "^1.2.3"
36
+ flake8-quotes = "^3.4.0"
37
+ flake8-return = "^1.2.0"
38
+ flake8-annotations-complexity = "^0.1.0"
39
+ flake8-bandit = "^4.1.1"
40
+ flake8-bugbear = "^24.12.12"
41
+ flake8-builtins = "^2.5.0"
42
+ flake8-comprehensions = "^3.15.0"
43
+ flake8-eradicate = "^1.5.0"
44
+ flake8-expression-complexity = "^0.0.11"
45
+ flake8-pytest-style = "^2.1.0"
46
+ pep8-naming = "^0.15.1"
47
+ flake8-eol = "^0.0.8"
48
+ flake8-exceptions = "^0.0.1a0"
49
+ flake8-simplify = "^0.22.0"
50
+ flake8-wot = "^0.2.0"
51
+ flake8-function-order = "^0.0.5"
52
+ flake8-tidy-imports = "^4.10.0"
53
+ black = "^25.1.0"
54
+ # flake8-logging-format = "^2024.24.12"
55
+ # flake8-docstrings = "^1.7.0"
56
+
57
+
58
+ [tool.flake8]
59
+ exclude= [".eggs", ".git", ".hg", ".mypy_cache", ".tox", ".venv", ".devcontainer", "venv", "_build", "buck-out", "build", "dist", "**/__init__.py"]
60
+ statistics = true
61
+ show-source = false
62
+ max-complexity = 8
63
+ max-annotations-complexity = 3
64
+ docstring-convention = 'numpy'
65
+ max-line-length = 120
66
+ ignore = ["E203", "W503", "E704"]
67
+ inline-quotes = '"'
68
+ docstring-quotes = '"""'
69
+ multiline-quotes = '"""'
70
+ dictionaries = ["en_US", "python", "technical", "pandas"]
71
+ ban-relative-imports = true
72
+ per-file-ignores = """
73
+ ./src/rag_core/impl/prompt_templates/answer_generation_prompt.py: E501,
74
+ ./tests/*: S101,E402,
75
+ ./src/rag_core_lib/impl/llms/llm_factory.py: B009
76
+ """
77
+
78
+ [tool.black]
79
+ line-length = 120
80
+ exclude = """
81
+ /(
82
+ .eggs
83
+ | .git
84
+ | .hg
85
+ | .mypy_cache
86
+ | .nox
87
+ | .pants.d
88
+ | .tox
89
+ | .venv
90
+ | _build
91
+ | buck-out
92
+ | build
93
+ | dist
94
+ | node_modules
95
+ | venv
96
+ )/
97
+ """
98
+
99
+ [tool.isort]
100
+ profile = "black"
101
+ skip = ['.eggs', '.git', '.hg', '.mypy_cache', '.nox', '.pants.d', '.tox', '.venv', '_build', 'buck-out', 'build', 'dist', 'node_modules', 'venv']
102
+ skip_gitignore = true
103
+
104
+ [tool.pylint]
105
+ max-line-length = 120
106
+
107
+ [tool.pytest.ini_options]
108
+ log_cli = 1
109
+ log_cli_level = "DEBUG"
110
+ pythonpath = "src"
111
+ testpaths = "src/tests"
File without changes
@@ -0,0 +1,13 @@
1
+ """Enum describing the type of information extracted from a document."""
2
+
3
+ from enum import StrEnum, unique
4
+
5
+
6
+ @unique
7
+ class ContentType(StrEnum):
8
+ """Enum describing the type of information extracted from a document."""
9
+
10
+ TEXT = "TEXT"
11
+ TABLE = "TABLE"
12
+ SUMMARY = "SUMMARY"
13
+ IMAGE = "IMAGE"
@@ -0,0 +1,21 @@
1
+ """Module for the SearchRequest data type."""
2
+
3
+ from typing import Optional
4
+
5
+ from pydantic import BaseModel, StrictStr
6
+
7
+
8
+ class SearchRequest(BaseModel):
9
+ """
10
+ Represents a search request.
11
+
12
+ Parameters
13
+ ----------
14
+ search_term : str
15
+ The search term to be used.
16
+ metadata : Optional[dict[str, str]], optional
17
+ Additional metadata for the search request (default None)
18
+ """
19
+
20
+ search_term: StrictStr
21
+ metadata: Optional[dict[str, str]] = None
@@ -0,0 +1,13 @@
1
+ """Embeddings helpers shared across STACKIT libraries."""
2
+
3
+ from rag_core_lib.impl.embeddings.embedder import Embedder
4
+ from rag_core_lib.impl.embeddings.embedder_type import EmbedderType
5
+ from rag_core_lib.impl.embeddings.langchain_community_embedder import LangchainCommunityEmbedder
6
+ from rag_core_lib.impl.embeddings.stackit_embedder import StackitEmbedder
7
+
8
+ __all__ = [
9
+ "Embedder",
10
+ "EmbedderType",
11
+ "LangchainCommunityEmbedder",
12
+ "StackitEmbedder",
13
+ ]
@@ -0,0 +1,11 @@
1
+ """Module containing the Embedder abstract base class."""
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class Embedder(ABC):
7
+ """Abstract base class for an embedder."""
8
+
9
+ @abstractmethod
10
+ def get_embedder(self) -> "Embedder":
11
+ """Return an instance of the embedder itself."""
@@ -0,0 +1,12 @@
1
+ """Module containing the EmbedderType enumeration."""
2
+
3
+ from enum import StrEnum, unique
4
+
5
+
6
+ @unique
7
+ class EmbedderType(StrEnum):
8
+ """An enumeration representing different types of embedders."""
9
+
10
+ OLLAMA = "ollama"
11
+ STACKIT = "stackit"
12
+ FAKE = "fake"
@@ -0,0 +1,34 @@
1
+ """Module that contains the LangchainCommunityEmbedder class."""
2
+
3
+ from langchain_core.embeddings import Embeddings
4
+
5
+ from rag_core_lib.impl.embeddings.embedder import Embedder
6
+
7
+
8
+ class LangchainCommunityEmbedder(Embedder, Embeddings):
9
+ """A wrapper around any LangChain-provided embedder."""
10
+
11
+ def __init__(self, embedder: Embeddings):
12
+ """Initialise the wrapper with the provided embedder.
13
+
14
+ Parameters
15
+ ----------
16
+ embedder : Embeddings
17
+ The embedder instance to delegate to.
18
+ """
19
+ self._embedder = embedder
20
+
21
+ def get_embedder(self) -> "LangchainCommunityEmbedder":
22
+ """Return the embedder instance."""
23
+
24
+ return self
25
+
26
+ def embed_documents(self, texts: list[str]) -> list[list[float]]:
27
+ """Embed a list of documents using the wrapped embedder."""
28
+
29
+ return self._embedder.embed_documents(texts)
30
+
31
+ def embed_query(self, text: str) -> list[float]:
32
+ """Embed a single query using the wrapped embedder."""
33
+
34
+ return self._embedder.embed_query(text)
@@ -0,0 +1,80 @@
1
+ """Module that contains the StackitEmbedder class."""
2
+
3
+ import logging
4
+
5
+ from langchain_core.embeddings import Embeddings
6
+ from openai import APIConnectionError, APIError, APITimeoutError, OpenAI, RateLimitError
7
+
8
+ from rag_core_lib.impl.embeddings.embedder import Embedder
9
+ from rag_core_lib.impl.settings.retry_decorator_settings import RetryDecoratorSettings
10
+ from rag_core_lib.impl.settings.stackit_embedder_settings import StackitEmbedderSettings
11
+ from rag_core_lib.impl.utils.retry_decorator import (
12
+ create_retry_decorator_settings,
13
+ retry_with_backoff,
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class StackitEmbedder(Embedder, Embeddings):
20
+ """LangChain-compatible embedder for STACKIT's OpenAI-compatible service."""
21
+
22
+ def __init__(
23
+ self,
24
+ stackit_embedder_settings: StackitEmbedderSettings,
25
+ retry_decorator_settings: RetryDecoratorSettings,
26
+ ) -> None:
27
+ """Initialise the Stackit embedder.
28
+
29
+ Parameters
30
+ ----------
31
+ stackit_embedder_settings : StackitEmbedderSettings
32
+ Configuration for the STACKIT embeddings endpoint.
33
+ retry_decorator_settings : RetryDecoratorSettings
34
+ Fallback retry configuration when the STACKIT-specific overrides are missing.
35
+ """
36
+ self._client = OpenAI(
37
+ api_key=stackit_embedder_settings.api_key,
38
+ base_url=stackit_embedder_settings.base_url,
39
+ )
40
+ self._settings = stackit_embedder_settings
41
+ self._retry_decorator_settings = create_retry_decorator_settings(
42
+ stackit_embedder_settings,
43
+ retry_decorator_settings,
44
+ )
45
+
46
+ def get_embedder(self) -> "StackitEmbedder":
47
+ """Return the embedder instance."""
48
+
49
+ return self
50
+
51
+ def embed_documents(self, texts: list[str]) -> list[list[float]]:
52
+ """Embed multiple documents using the STACKIT embeddings endpoint."""
53
+
54
+ @self._retry_with_backoff_wrapper()
55
+ def _call(documents: list[str]) -> list[list[float]]:
56
+ responses = self._client.embeddings.create(
57
+ input=documents,
58
+ model=self._settings.model,
59
+ )
60
+ return [data.embedding for data in responses.data]
61
+
62
+ return _call(texts)
63
+
64
+ def embed_query(self, text: str) -> list[float]:
65
+ """Embed a single query using the STACKIT embeddings endpoint."""
66
+
67
+ embeddings_list = self.embed_documents([text])
68
+ if embeddings_list:
69
+ embeddings = embeddings_list[0]
70
+ return embeddings if embeddings else []
71
+ logger.warning("No embeddings found for query: %s", text)
72
+ return embeddings_list
73
+
74
+ def _retry_with_backoff_wrapper(self):
75
+ return retry_with_backoff(
76
+ settings=self._retry_decorator_settings,
77
+ exceptions=(APIError, RateLimitError, APITimeoutError, APIConnectionError),
78
+ rate_limit_exceptions=(RateLimitError,),
79
+ logger=logger,
80
+ )
@@ -0,0 +1,208 @@
1
+ """Module for managing Langfuse prompts and Langfuse Language Models (LLMs)."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ from langchain.prompts import ChatPromptTemplate
7
+ from langchain_core.language_models.llms import LLM
8
+ from langfuse import Langfuse
9
+ from langfuse.api.resources.commons.errors.not_found_error import NotFoundError
10
+ from langfuse.model import TextPromptClient
11
+ from langchain_core.prompts import SystemMessagePromptTemplate, HumanMessagePromptTemplate
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class LangfuseManager:
17
+ """Manage prompts using Langfuse and a Large Language Model (LLM).
18
+
19
+ Attributes
20
+ ----------
21
+ API_KEY_FILTER : str
22
+ A filter string used to exclude the API key from configurations.
23
+ """
24
+
25
+ API_KEY_FILTER: str = "api_key"
26
+
27
+ def __init__(
28
+ self,
29
+ langfuse: Langfuse,
30
+ managed_prompts: dict[str, ChatPromptTemplate],
31
+ llm: LLM,
32
+ ):
33
+ """
34
+ Initialize the LangfuseManager.
35
+
36
+ Parameters
37
+ ----------
38
+ langfuse : Langfuse
39
+ An instance of the Langfuse class.
40
+ managed_prompts : dict of ChatPromptTemplate
41
+ A dictionary where keys are strings and values are ChatPromptTemplate instances representing
42
+ managed prompts.
43
+ llm : LLM
44
+ An instance of the LLM class.
45
+ """
46
+ self._langfuse = langfuse
47
+ self._llm = llm
48
+ self._managed_prompts = managed_prompts
49
+
50
+ def init_prompts(self) -> None:
51
+ """
52
+ Initialize the prompts managed by the LangfuseManager.
53
+
54
+ This method iterates over the keys of the managed prompts and retrieves
55
+ each prompt using the `get_langfuse_prompt` method.
56
+
57
+ Returns
58
+ -------
59
+ None
60
+ """
61
+ for key in list(self._managed_prompts.keys()):
62
+ self.get_langfuse_prompt(key)
63
+
64
+ def get_langfuse_prompt(self, base_prompt_name: str) -> Optional[TextPromptClient]:
65
+ """
66
+ Retrieve the prompt from Langfuse Prompt Management.
67
+
68
+ This method tries to fetch the prompt from Langfuse. If not found, it creates
69
+ a new chat prompt from the local ChatPromptTemplate.
70
+
71
+ Parameters
72
+ ----------
73
+ base_prompt_name : str
74
+ The name of the base prompt to retrieve.
75
+
76
+ Returns
77
+ -------
78
+ Optional[TextPromptClient]
79
+ The Langfuse prompt client if found, None otherwise.
80
+ """
81
+ try:
82
+ langfuse_prompt = self._langfuse.get_prompt(base_prompt_name, type="chat")
83
+ return langfuse_prompt
84
+ except NotFoundError:
85
+ logger.info("Prompt '%s' not found in Langfuse. Creating new chat prompt.", base_prompt_name)
86
+
87
+ local_prompt = self._managed_prompts[base_prompt_name]
88
+ chat_messages = self._convert_chat_prompt_to_langfuse_format(local_prompt)
89
+
90
+ # Get LLM config (excluding API keys)
91
+ llm_configurable_configs = {
92
+ config.id: config.default for config in self._llm.config_specs if self.API_KEY_FILTER not in config.id
93
+ }
94
+
95
+ self._langfuse.create_prompt(
96
+ name=base_prompt_name,
97
+ type="chat",
98
+ prompt=chat_messages,
99
+ config=llm_configurable_configs,
100
+ labels=["production"],
101
+ )
102
+
103
+ langfuse_prompt = self._langfuse.get_prompt(base_prompt_name, type="chat")
104
+ return langfuse_prompt
105
+
106
+ except Exception:
107
+ logger.exception("Error occurred while getting prompt template from langfuse")
108
+ return None
109
+
110
+ def get_base_llm(self, name: str) -> LLM:
111
+ """
112
+ Get the Langfuse prompt, the configuration as well as Large Language Model (LLM).
113
+
114
+ Parameters
115
+ ----------
116
+ name : str
117
+ The name of the Langfuse prompt to retrieve the configuration for.
118
+
119
+ Returns
120
+ -------
121
+ LLM
122
+ The base Large Language Model. If the Langfuse prompt is not found,
123
+ returns the LLM with a fallback configuration.
124
+ """
125
+ langfuse_prompt = self.get_langfuse_prompt(name)
126
+ if not langfuse_prompt:
127
+ logger.error("Using fallback for llm")
128
+ return self._llm
129
+
130
+ return self._llm.with_config({"configurable": langfuse_prompt.config})
131
+
132
+ def get_base_prompt(self, name: str) -> ChatPromptTemplate:
133
+ """
134
+ Retrieve the base prompt from managed prompts, with optional Langfuse integration.
135
+
136
+ This method attempts to fetch the prompt from Langfuse. If found, it creates a
137
+ ChatPromptTemplate with Langfuse metadata for proper tracing integration.
138
+ If not found, it returns the fallback ChatPromptTemplate.
139
+
140
+ Parameters
141
+ ----------
142
+ name : str
143
+ The name of the prompt to retrieve.
144
+
145
+ Returns
146
+ -------
147
+ ChatPromptTemplate
148
+ The ChatPromptTemplate for the requested prompt, optionally with Langfuse metadata.
149
+ """
150
+ langfuse_prompt = self.get_langfuse_prompt(name)
151
+
152
+ if langfuse_prompt:
153
+ # For chat prompts, get_langchain_prompt() returns a list of messages
154
+ # We need to convert this back to ChatPromptTemplate
155
+ chat_messages = langfuse_prompt.get_langchain_prompt()
156
+
157
+ langchain_messages = []
158
+ for message in chat_messages:
159
+ if isinstance(message, dict):
160
+ role = message.get("role")
161
+ content = message.get("content", "")
162
+ elif isinstance(message, (list, tuple)) and len(message) >= 2:
163
+ role = message[0]
164
+ content = message[1] if len(message) > 1 else ""
165
+ else:
166
+ logger.warning("Unexpected message format: %s", message)
167
+ continue
168
+
169
+ if role == "system":
170
+ langchain_messages.append(SystemMessagePromptTemplate.from_template(content))
171
+ elif role == "user":
172
+ langchain_messages.append(HumanMessagePromptTemplate.from_template(content))
173
+
174
+ chat_prompt_template = ChatPromptTemplate.from_messages(langchain_messages)
175
+
176
+ chat_prompt_template.metadata = {"langfuse_prompt": langfuse_prompt}
177
+
178
+ return chat_prompt_template
179
+ logger.error("Could not retrieve prompt template from langfuse. Using fallback value.")
180
+ return self._managed_prompts[name]
181
+
182
+ def _convert_chat_prompt_to_langfuse_format(self, chat_prompt: ChatPromptTemplate) -> list[dict]:
183
+ """
184
+ Convert a ChatPromptTemplate to Langfuse chat format.
185
+
186
+ Parameters
187
+ ----------
188
+ chat_prompt : ChatPromptTemplate
189
+ The ChatPromptTemplate to convert.
190
+
191
+ Returns
192
+ -------
193
+ list[dict]
194
+ A list of message dictionaries in Langfuse format.
195
+ """
196
+ chat_messages = []
197
+
198
+ for message in chat_prompt.messages:
199
+ # Convert SystemMessagePromptTemplate
200
+ if hasattr(message, "prompt") and message.prompt.template:
201
+ if message.__class__.__name__ == "SystemMessagePromptTemplate":
202
+ chat_messages.append({"role": "system", "content": message.prompt.template})
203
+ elif message.__class__.__name__ == "HumanMessagePromptTemplate":
204
+ chat_messages.append({"role": "user", "content": message.prompt.template})
205
+ elif message.__class__.__name__ == "AIMessagePromptTemplate":
206
+ chat_messages.append({"role": "assistant", "content": message.prompt.template})
207
+
208
+ return chat_messages
@@ -0,0 +1,88 @@
1
+ from pydantic_settings import BaseSettings
2
+ from langchain.chat_models import init_chat_model
3
+ from langchain.chat_models.base import _SUPPORTED_PROVIDERS
4
+ from langchain_core.language_models.base import BaseLanguageModel
5
+ from langchain_core.runnables import ConfigurableField
6
+
7
+
8
+ def extract_configurable_fields(settings: BaseSettings) -> dict[str, ConfigurableField]:
9
+ """
10
+ Extract configurable fields from the given settings.
11
+
12
+ Parameters
13
+ ----------
14
+ settings : BaseSettings
15
+ Pydantic settings instance containing model field metadata.
16
+
17
+ Returns
18
+ -------
19
+ dict[str, ConfigurableField]
20
+ Mapping from field name to ConfigurableField for fields with a title.
21
+
22
+ Notes
23
+ -----
24
+ Uses getattr() to access the class attribute 'model_fields' so as to avoid
25
+ instance-level deprecation warnings in Pydantic v2+ and static analysis tools.
26
+ """
27
+ fields: dict[str, ConfigurableField] = {}
28
+ cls = settings.__class__
29
+ fields_meta = getattr(cls, "model_fields")
30
+ for name, meta in fields_meta.items():
31
+ if meta.title:
32
+ fields[name] = ConfigurableField(id=name, name=meta.title)
33
+ return fields
34
+
35
+
36
+ # Mapping of generic names to provider-specific kwarg keys
37
+ _PROVIDER_KEY_MAP: dict[str, dict[str, str]] = {
38
+ "openai": {"api_key": "openai_api_key", "base_url": "openai_api_base"},
39
+ }
40
+
41
+
42
+ def chat_model_provider(
43
+ settings: BaseSettings,
44
+ provider: str = "openai",
45
+ ) -> BaseLanguageModel:
46
+ """
47
+ Initialize a LangChain chat model with unified settings mapping and configurable fields.
48
+
49
+ Parameters
50
+ ----------
51
+ settings : BaseSettings
52
+ Pydantic settings subclass containing at least 'model'.
53
+ provider : str, optional
54
+ Name of the chat model provider (default 'openai').
55
+
56
+ Returns
57
+ -------
58
+ BaseLanguageModel
59
+ Initialized chat model instance.
60
+
61
+ Raises
62
+ ------
63
+ ValueError
64
+ If 'model' is not defined in settings or if the provider is unsupported.
65
+ """
66
+ data = settings.model_dump(exclude_none=True)
67
+ model_name = data.pop("model", None)
68
+ if not model_name:
69
+ raise ValueError("'model' must be defined in settings")
70
+
71
+ key_map = _PROVIDER_KEY_MAP.get(provider, {})
72
+ for generic_key, specific_key in key_map.items():
73
+ if generic_key in data:
74
+ data[specific_key] = data.pop(generic_key)
75
+
76
+ if provider not in _SUPPORTED_PROVIDERS:
77
+ raise ValueError(f"Unsupported provider '{provider}'. Supported: {_SUPPORTED_PROVIDERS}")
78
+
79
+ chat = init_chat_model(
80
+ model=model_name,
81
+ model_provider=provider,
82
+ **data,
83
+ )
84
+ config_fields = extract_configurable_fields(settings)
85
+ if config_fields:
86
+ return chat.configurable_fields(**config_fields)
87
+
88
+ return chat
@@ -0,0 +1,12 @@
1
+ """Module containing the Large Language Model (LLM) type enum class."""
2
+
3
+ from enum import StrEnum, unique
4
+
5
+
6
+ @unique
7
+ class LLMType(StrEnum):
8
+ """Enum class representing different types of Large Language Models (LLMs)."""
9
+
10
+ OLLAMA = "ollama"
11
+ STACKIT = "stackit"
12
+ FAKE = "fake"
@@ -0,0 +1,18 @@
1
+ """Settings for selecting the embedder implementation."""
2
+
3
+ from pydantic import Field
4
+ from pydantic_settings import BaseSettings
5
+
6
+ from rag_core_lib.impl.embeddings.embedder_type import EmbedderType
7
+
8
+
9
+ class EmbedderClassTypeSettings(BaseSettings):
10
+ """Settings controlling which embedder implementation is used."""
11
+
12
+ class Config:
13
+ """Configure environment integration for the settings."""
14
+
15
+ env_prefix = "EMBEDDER_CLASS_TYPE_"
16
+ case_sensitive = False
17
+
18
+ embedder_type: EmbedderType = Field(default=EmbedderType.STACKIT)
@@ -0,0 +1,16 @@
1
+ """Settings for the deterministic fake embedder used in tests."""
2
+
3
+ from pydantic import Field, PositiveInt
4
+ from pydantic_settings import BaseSettings
5
+
6
+
7
+ class FakeEmbedderSettings(BaseSettings):
8
+ """Configuration for the fake embedder implementation."""
9
+
10
+ class Config:
11
+ """Configure environment integration for the settings."""
12
+
13
+ env_prefix = "FAKE_EMBEDDER_"
14
+ case_sensitive = False
15
+
16
+ size: PositiveInt = Field(default=768)