Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce API doc #48

Merged
merged 9 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/mkdocs_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
restore-keys: |
mkdocs-material-
- name: Install dependencies
run: pip install mkdocs-material[imaging] mkdocstrings[python] mkdocs-api-autonav pillow cairosvg mike
run: |
pipx install uv
uv sync --only-group docs
# - run: mike set-default --push latest
- run: mike deploy --push develop
- run: uv run --only-group docs mike deploy --push develop
7 changes: 5 additions & 2 deletions .github/workflows/mkdocs_rel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ jobs:
restore-keys: |
mkdocs-material-
- name: Install dependencies
run: pip install mkdocs-material[imaging] mkdocstrings[python] mkdocs-api-autonav pillow cairosvg mike
run: |
pipx install uv
uv sync --only-group docs
# - run: uv run --only-group docs mike set-default --push latest
- name: Deploy documentation
run: |
major=$(echo "${GITHUB_REF_NAME:1}" | cut -d "." -f 1)
minor=$(echo "${GITHUB_REF_NAME:1}" | cut -d "." -f 2)
mike deploy --push --update-aliases "${major}.${minor}" latest
uv run --only-group docs mike deploy --push --update-aliases "${major}.${minor}" latest
8 changes: 4 additions & 4 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: pipx install uv
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install development dependencies with extras
run: uv sync
- name: Install uv and development dependencies with extras
run: |
pipx install uv
uv sync
- name: Run linting
run: uv run poe lint
testing:
Expand Down
6 changes: 0 additions & 6 deletions config/qa_default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ model: &qa_model
prompt_config:
text: "Du bist ein hilfreicher Assistent.\nBitte beantworte die Frage: '{question}' basierend auf dem folgenden Kontext:\n{context}\nGib nur die hilfreiche Antwort unten zurück und nichts anderes. Halte dich außerdem sehr kurz mit der Antwort und antworte nur in Stichworten."
features:
fact_checking:
enabled: false
model:
<<: *qa_model
prompt_config:
path: "prompts/qa_fact_checking.txt"
analyze:
model:
<<: *qa_model
Expand Down
2 changes: 2 additions & 0 deletions examples/chaining.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""A chaining example for the chat service."""

import logging

from gerd.config import load_gen_config
Expand Down
2 changes: 2 additions & 0 deletions examples/generate.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Example of how to use the chat service to generate text."""

import logging

from gerd.config import load_gen_config
Expand Down
5 changes: 5 additions & 0 deletions examples/grascco_instruct.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""GRASCCO dataset preparation for instruction training.

This example demonstrates how to generate training data for the Instruct model using
the Grascco dataset.
"""
# %%
# import packages

Expand Down
2 changes: 2 additions & 0 deletions examples/hello.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""A 'hello world' example for GERD."""

import logging

from gerd.config import load_gen_config
Expand Down
4 changes: 3 additions & 1 deletion examples/run_lora_instruction.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""An example of using LoRA to instruct a model to generate a certain response."""

from pathlib import Path

from gerd.config import PROJECT_DIR, load_gen_config
Expand Down Expand Up @@ -25,7 +27,7 @@
print(res.text) # noqa: T201

print("\n\nMit LoRA\n========") # noqa: T201
config.model.loras.append(lora_config.output_dir)
config.model.loras.add(lora_config.output_dir)
chat = ChatService(config)
res = chat.submit_user_message({"content": "Bitte erweitere die Zahl 42."})
print(res.text) # noqa: T201
4 changes: 3 additions & 1 deletion examples/run_lora_unstructured.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Example of using a previously trained LoRA with a chat model."""

from pathlib import Path

from gerd.config import PROJECT_DIR, load_gen_config
Expand Down Expand Up @@ -25,7 +27,7 @@
print(res.text) # noqa: T201

print("\n\nMit LoRA\n========") # noqa: T201
config.model.loras.append(lora_config.output_dir)
config.model.loras.add(lora_config.output_dir)
chat = ChatService(config)
res = chat.generate({"text": "Sehr geehrte Frau Kollegin,"})
print(res.text) # noqa: T201
6 changes: 6 additions & 0 deletions examples/train_lora_instruction.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""Example script to train a LoRA model on a simple instruction dataset.

The generated data set is very simple and should only illustrate the intended
usage of the InstructTrainingData and InstructTrainingSample classes.
"""

import logging
import time
from pathlib import Path
Expand Down
2 changes: 2 additions & 0 deletions examples/train_lora_unstructured.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Example script to train a LoRA model on unstructured data."""

import logging
import time
from pathlib import Path
Expand Down
8 changes: 7 additions & 1 deletion gerd/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
"""GERD"""
"""Generating and evaluating relevant documentation (GERD).

This package provides the GERD system for working with large language models (LLMs).
This includes means to generate texts using different backends and frontends.
The system is designed to be flexible and extensible to support different use cases.
It can also be used for Retrieval Augmented Generation (RAG) tasks or as a chatbot.
"""

from importlib.metadata import version

Expand Down
4 changes: 0 additions & 4 deletions gerd/backend.py

This file was deleted.

12 changes: 12 additions & 0 deletions gerd/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""This module contains backend implementations that manage services.

These backends can be used by frontends such as gradio.
Furthermore, the backend module contains service implementations for loading LLMs or
vector stores for Retrieval Augmented Generation.
"""

from gerd.backends.bridge import Bridge
from gerd.transport import Transport

TRANSPORTER: Transport = Bridge()
"""The default transporter that connects the backend services to the frontend."""
32 changes: 29 additions & 3 deletions gerd/backends/bridge.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""The Bridge connects backend and frontend services directly for local use."""

import logging
from typing import Dict, List, Optional

from typing_extensions import override

from gerd.config import load_gen_config, load_qa_config
from gerd.gen import GenerationService
from gerd.qa import QAService
Expand All @@ -23,58 +27,80 @@


class Bridge(Transport):
"""Direct connection between backend services and frontend.

Frontends that make use of the [`Transport`][gerd.transport.Transport] abstraction
can use `Bridge` to get accessto generation and QA services directly. This is useful
for local use cases where the frontend and backend are running in the same process.
"""

def __init__(self) -> None:
"""The services associated with the bridge are initialized lazily."""
super().__init__()
self._qa: Optional[QAService] = None
self._gen: Optional[GenerationService] = None

@property
def qa(self) -> QAService:
"""Get the QA service instance. It will be created if it does not exist."""
if self._qa is None:
self._qa = QAService(load_qa_config())
return self._qa

@property
def gen(self) -> GenerationService:
"""Get the generation service instance.

It will be created if it does not exist.
"""
if self._gen is None:
self._gen = GenerationService(load_gen_config())
return self._gen

@override
def qa_query(self, question: QAQuestion) -> QAAnswer:
return self.qa.query(question)

@override
def analyze_query(self) -> QAAnalyzeAnswer:
return self.qa.analyze_query()

@override
def analyze_mult_prompts_query(self) -> QAAnalyzeAnswer:
return self.qa.analyze_mult_prompts_query()

@override
def db_query(self, question: QAQuestion) -> List[DocumentSource]:
return self.qa.db_query(question)

@override
def db_embedding(self, question: QAQuestion) -> List[float]:
return self.qa.db_embedding(question)

@override
def add_file(self, file: QAFileUpload) -> QAAnswer:
return self.qa.add_file(file)

@override
def remove_file(self, file_name: str) -> QAAnswer:
return self.qa.remove_file(file_name)

@override
def set_gen_prompt(self, config: PromptConfig) -> PromptConfig:
return self.gen.set_prompt_config(config)

@override
def get_gen_prompt(self) -> PromptConfig:
return self.gen.get_prompt_config()

@override
def set_qa_prompt(self, config: PromptConfig, qa_mode: QAModesEnum) -> QAAnswer:
return self.qa.set_prompt_config(config, qa_mode)

@override
def get_qa_prompt(self, qa_mode: QAModesEnum) -> PromptConfig:
return self.qa.get_prompt_config(qa_mode)

@override
def generate(self, parameters: Dict[str, str]) -> GenResponse:
return self.gen.generate(parameters)

def gen_continue(self, parameters: Dict[str, str]) -> GenResponse:
return self.gen.gen_continue(parameters)
31 changes: 22 additions & 9 deletions gerd/backends/rest_client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""REST client for the GERD server."""

import logging
from typing import Dict, List

import requests
from pydantic import TypeAdapter
from typing_extensions import override

from gerd.config import CONFIG
from gerd.transport import QAQuestion
Expand All @@ -24,12 +27,20 @@


class RestClient(Transport):
"""REST client for the GERD server."""

def __init__(self) -> None:
"""The client initalizes the server URL.

It is retrieved from the global [CONFIG][gerd.config.CONFIG].
Other (timeout) settings are also set here but not configurable as of now.
"""
super().__init__()
self._url = f"http://{CONFIG.server.host}:{CONFIG.server.port}{CONFIG.server.api_prefix}"
self.timeout = 10
self.longtimeout = 10000

@override
def qa_query(self, question: QAQuestion) -> QAAnswer:
return QAAnswer.model_validate(
requests.post(
Expand All @@ -39,6 +50,7 @@ def qa_query(self, question: QAQuestion) -> QAAnswer:
).json()
)

@override
def analyze_query(self) -> QAAnalyzeAnswer:
return QAAnalyzeAnswer.model_validate(
requests.post(
Expand All @@ -47,6 +59,7 @@ def analyze_query(self) -> QAAnalyzeAnswer:
).json()
)

@override
def analyze_mult_prompts_query(self) -> QAAnalyzeAnswer:
return QAAnalyzeAnswer.model_validate(
requests.post(
Expand All @@ -55,6 +68,7 @@ def analyze_mult_prompts_query(self) -> QAAnalyzeAnswer:
).json()
)

@override
def db_query(self, question: QAQuestion) -> List[DocumentSource]:
request = question.model_dump_json()
_LOGGER.debug("db_query - request: %s", request)
Expand All @@ -66,6 +80,7 @@ def db_query(self, question: QAQuestion) -> List[DocumentSource]:
_LOGGER.debug("db_query - response: %s", response.json())
return TypeAdapter(List[DocumentSource]).validate_python(response.json())

@override
def db_embedding(self, question: QAQuestion) -> List[float]:
request = question.model_dump_json()
_LOGGER.debug("db_embedding - request: %s", request)
Expand All @@ -77,6 +92,7 @@ def db_embedding(self, question: QAQuestion) -> List[float]:
_LOGGER.debug("db_embedding - response: %s", response.json())
return TypeAdapter(List[float]).validate_python(response.json())

@override
def add_file(self, file: QAFileUpload) -> QAAnswer:
t = file.model_dump_json().encode("utf-8")
return QAAnswer.model_validate(
Expand All @@ -87,6 +103,7 @@ def add_file(self, file: QAFileUpload) -> QAAnswer:
).json()
)

@override
def remove_file(self, file_name: str) -> QAAnswer:
return QAAnswer.model_validate(
requests.delete(
Expand All @@ -96,6 +113,7 @@ def remove_file(self, file_name: str) -> QAAnswer:
).json()
)

@override
def set_gen_prompt(self, config: PromptConfig) -> PromptConfig:
return PromptConfig.model_validate(
requests.post(
Expand All @@ -105,11 +123,13 @@ def set_gen_prompt(self, config: PromptConfig) -> PromptConfig:
).json()
)

@override
def get_gen_prompt(self) -> PromptConfig:
return PromptConfig.model_validate(
requests.get(f"{self._url}/gen/prompt", timeout=self.timeout).json()
)

@override
def set_qa_prompt(self, config: PromptConfig, qa_mode: QAModesEnum) -> QAAnswer:
return QAAnswer.model_validate(
requests.post(
Expand All @@ -119,6 +139,7 @@ def set_qa_prompt(self, config: PromptConfig, qa_mode: QAModesEnum) -> QAAnswer:
).json()
)

@override
def get_qa_prompt(self, qa_mode: QAModesEnum) -> PromptConfig:
return PromptConfig.model_validate(
requests.get(
Expand All @@ -128,6 +149,7 @@ def get_qa_prompt(self, qa_mode: QAModesEnum) -> PromptConfig:
).json()
)

@override
def generate(self, parameters: Dict[str, str]) -> GenResponse:
return GenResponse.model_validate(
requests.post(
Expand All @@ -136,12 +158,3 @@ def generate(self, parameters: Dict[str, str]) -> GenResponse:
timeout=self.timeout,
).json()
)

def gen_continue(self, parameters: Dict[str, str]) -> GenResponse:
return GenResponse.model_validate(
requests.post(
f"{self._url}/gen/gen_continue",
json=parameters,
timeout=self.timeout,
).json()
)
Loading