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.
- rag_core_lib-3.2.0/PKG-INFO +20 -0
- rag_core_lib-3.2.0/pyproject.toml +111 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/data_types/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/data_types/content_type.py +13 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/data_types/search_request.py +21 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/__init__.py +13 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/embedder.py +11 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/embedder_type.py +12 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/langchain_community_embedder.py +34 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/embeddings/stackit_embedder.py +80 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/langfuse_manager/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/langfuse_manager/langfuse_manager.py +208 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/llms/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/llms/llm_factory.py +88 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/llms/llm_type.py +12 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/embedder_class_type_settings.py +18 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/fake_embedder_settings.py +16 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/langfuse_settings.py +29 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/logging_settings.py +23 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/ollama_embedder_settings.py +17 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/ollama_llm_settings.py +43 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/rag_class_types_settings.py +30 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/retry_decorator_settings.py +78 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/stackit_embedder_settings.py +36 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/settings/stackit_vllm_settings.py +36 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/tracers/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/tracers/langfuse_traced_runnable.py +49 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/async_threadsafe_semaphore.py +71 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/retry_decorator.py +211 -0
- rag_core_lib-3.2.0/src/rag_core_lib/impl/utils/utils.py +71 -0
- rag_core_lib-3.2.0/src/rag_core_lib/runnables/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/runnables/async_runnable.py +60 -0
- rag_core_lib-3.2.0/src/rag_core_lib/secret_provider/__init__.py +0 -0
- rag_core_lib-3.2.0/src/rag_core_lib/secret_provider/secret_provider.py +30 -0
- rag_core_lib-3.2.0/src/rag_core_lib/tracers/__init__.py +0 -0
- 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
|
|
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
|
+
)
|
|
File without changes
|
|
@@ -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
|
|
File without changes
|
|
@@ -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"
|
|
File without changes
|
|
@@ -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)
|