Skip to content

Commit 8ce90c3

Browse files
authored
Merge pull request #90 from buerokratt/wip
Sync wip branches
2 parents eb0e3b5 + 1d25d90 commit 8ce90c3

File tree

7 files changed

+323
-43
lines changed

7 files changed

+323
-43
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ datasets
99
logs/
1010
data_sets
1111
vault/agent-out
12+
13+
# Snyk Security Extension - AI Rules (auto-generated)
14+
.github/instructions/snyk_rules.instructions.md

CONTRIBUTING.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,202 @@ git commit -m "added package-name dependency"
178178
#### 5. Open a PR
179179
CI will validate that the lockfile and environment are consistent. If you forgot to update the lockfile, the PR will fail with a clear error.
180180

181+
---
182+
183+
## Type Safety Practices
184+
185+
Python is a dynamically typed language. This flexibility makes Python productive and expressive, but it also increases the risk of subtle bugs caused by incorrect function calls, unexpected None values, or inconsistent data structures.To balance flexibility with long-term maintainability we use [Pyright](https://microsoft.github.io/pyright) for CI level type-checking.
186+
187+
We run Pyright in `standard` mode. This mode provides strong type correctness guarantees without requiring the full strictness and annotation overhead of `strict` mode.
188+
189+
You can check the exact type checking constraints enforced in `standard` mode here in the `Diagnostic Defaults` section of the [Pyright documentation](https://microsoft.github.io/pyright/#/configuration?id=diagnostic-settings-defaults).
190+
191+
`standard` mode in Pyright is chosen because it enforces the following principles:
192+
193+
- **Catch real bugs early** - It prevents incorrect function calls, invalid attribute access, misuse of Optional values, inconsistent overloads, and a wide range of type errors that would otherwise only appear at runtime.
194+
195+
- **Maintain clarity without excessive annotation burden** - Developers are not expected to annotate every variable or build fully typed signatures for every function. Pyright uses inference aggressively, and `standard` mode focuses on correctness where types are known or inferred.
196+
197+
- **Work seamlessly with third-party libraries** - Many Python libraries ship without type stubs. In `standard` mode, these imports are treated as Any, allowing us to use them without blocking type checks while still preserving type safety inside our own code.
198+
199+
### Runtime Type Safety at System Boundaries
200+
201+
While Pyright provides excellent static type checking during development, **system boundaries** require additional runtime validation. These are points where our Python code interfaces with external systems, user input, or network requests where data types cannot be guaranteed at compile time.
202+
203+
In this project, we use **Pydantic** for rigorous runtime type checking at these critical handover points:
204+
205+
#### FastAPI Endpoints
206+
All FastAPI route handlers use Pydantic models for request/response validation:
207+
- Request bodies are validated against Pydantic schemas
208+
- Query parameters and path parameters are type-checked at runtime
209+
- Response models ensure consistent API contract enforcement
210+
```python
211+
# Example: API endpoint with Pydantic validation
212+
from pydantic import BaseModel
213+
from fastapi import FastAPI
214+
215+
class UserRequest(BaseModel):
216+
name: str
217+
age: int
218+
219+
@app.post("/users")
220+
async def create_user(user: UserRequest):
221+
# Pydantic validates name is string, age is int
222+
# Invalid data raises 422 before reaching this code
223+
return {"id": 1, "name": user.name}
224+
```
225+
226+
This dual approach of **static type checking with Pyright** + **runtime validation with Pydantic** ensures both development-time correctness and production-time reliability at system boundaries where type safety cannot be statically guaranteed.
227+
228+
**Note: Type checks are only run on core source code and not on test-cases**
229+
230+
## Linter Rules
231+
232+
Consistent linting is essential for maintaining a reliable and scalable code-base. By adhering to a well-defined linter configuration, we ensure the code remains readable, secure, and predictable even as the project evolves.
233+
234+
The following set of rules are enabled in this repository. Linter rules are enforced automatically through the CI pipeline and must pass before merging changes into the `wip`, `dev`, or `main` branches.
235+
.
236+
237+
Each category is summarized with a description and a link to the Ruff documentation explaining these rules.
238+
239+
### Selected Linter Rule Categories
240+
241+
#### E4, E7, E9 — Pycodestyle Error Rules
242+
243+
These check for fundamental correctness issues such as import formatting, indentation, and syntax problems that would otherwise cause runtime failures.
244+
245+
- **E4**: Import formatting and blank-line rules
246+
(https://docs.astral.sh/ruff/rules/#pycodestyle-e4)
247+
248+
- **E7**: Indentation and tab-related issues
249+
(https://docs.astral.sh/ruff/rules/#pycodestyle-e7)
250+
251+
- **E9**: Syntax errors and runtime error patterns (e.g., undefined names in certain contexts)
252+
(https://docs.astral.sh/ruff/rules/#pycodestyle-e9)
253+
254+
#### F — Pyflakes
255+
256+
Static analysis rules that detect real bug patterns such as unused variables, unused imports, undefined names, duplicate definitions, and logical mistakes that can cause bugs.
257+
258+
(https://docs.astral.sh/ruff/rules/#pyflakes-f)
259+
260+
#### B — Flake8-Bugbear
261+
262+
A set of high-value checks for common Python pitfalls: mutable default arguments, improper exception handling, unsafe patterns, redundant checks, and subtle bugs that impact correctness and security.
263+
264+
(https://docs.astral.sh/ruff/rules/#flake8-bugbear-b)
265+
266+
#### T20 — Flake8-Print
267+
268+
Flags any usage of `print()` or `pprint()` in production code to prevent leaking sensitive information, mixing debug output into logs, or introducing uncontrolled console output.
269+
270+
(https://docs.astral.sh/ruff/rules/#flake8-print-t20)
271+
272+
#### N — PEP8-Naming
273+
274+
Ensures consistent and conventional naming across classes, functions, variables, and modules. This helps maintain readability across the engineering team and reinforces clarity in code reviews.
275+
276+
(https://docs.astral.sh/ruff/rules/#pep8-naming-n)
277+
278+
#### ANN — Flake8-Annotations
279+
280+
Enforces type annotation discipline across functions, methods, and class structures. With Pyright used for type checking, these rules ensure that type information remains explicit and complete.
281+
282+
(https://docs.astral.sh/ruff/rules/#flake8-annotations-ann)
283+
284+
#### ERA — Eradicate
285+
286+
Removes or flags commented-out code fragments. Commented code tends to accumulate over time and reduces clarity. The goal is to keep the repository clean and avoid keeping dead code in version control.
287+
288+
(https://docs.astral.sh/ruff/rules/#eradicate-era)
289+
290+
#### PERF — Perflint
291+
292+
Performance-oriented rules that highlight inefficient constructs, slow loops, unnecessary list or dict operations, and patterns that degrade runtime efficiency.
293+
294+
(https://docs.astral.sh/ruff/rules/#perflint-perf)
295+
296+
### Fixing Linting Issues
297+
298+
Linting issues should always be resolved manually.
299+
We **strongly discourage** relying on autofixes using `ruff check --fix` for this repository.
300+
301+
Unlike `ruff format`, which performs safe and predictable code formatting, the linter's autofix mode can alter control flow, refactor logic, or rewrite expressions in ways that introduce unintended bugs.
302+
303+
All linter errors will have **rule-code** like `ANN204` for example.
304+
You can use the command line command
305+
```bash
306+
ruff rule <rule-code> #for example: ANN204
307+
```
308+
309+
to get an explanation on the rule code, why it's a problem and how you can fix it.
310+
311+
Human oversight is essential to ensure that any corrective changes maintain the intended behavior of the application. Contributors should review each reported linting issue, understand why it is flagged, and apply the appropriate fix by hand.
312+
313+
---
314+
315+
## Formatting Rules
316+
317+
This repository uses the **Ruff Formatter** for code formatting. Its behavior is deterministic, safe, and aligned with the [Black Code Style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html).
318+
319+
Formatting is enforced automatically through the CI pipeline and must pass before merging changes into the `wip`, `dev`, or `main` branches.
320+
321+
### Selected Formatting Behaviors
322+
323+
#### String Quote Style
324+
325+
All string literals are formatted using **double quotes**.
326+
This preserves consistency across the codebase and avoids unnecessary formatting churn.
327+
328+
(https://docs.astral.sh/ruff/formatter/#quote-style)
329+
330+
#### Indentation Style
331+
332+
Indentation always uses **spaces, not tabs**.
333+
This mirrors the formatting style adopted by Black and avoids ambiguity across editors and environments.
334+
335+
(https://docs.astral.sh/ruff/formatter/#indent-style)
336+
337+
#### Magic Trailing Commas
338+
339+
The formatter respects magic trailing commas, meaning:
340+
341+
- **Adding a trailing comma** in lists, dicts, tuples, or function calls will trigger multi-line formatting.
342+
- **Removing a trailing comma** results in a more compact single-line layout where appropriate.
343+
344+
This produces stable diffs and predictable wrapping behavior.
345+
346+
(https://docs.astral.sh/ruff/formatter/#skip-magic-trailing-comma)
347+
348+
#### Automatic Line Ending Detection
349+
350+
Ruff automatically detects and preserves the correct line-ending style (LF or CRLF) based on the existing file.
351+
This prevents accidental line-ending changes when multiple developers work on different systems.
352+
353+
(https://docs.astral.sh/ruff/formatter/#line-ending)
354+
355+
#### Docstring Code Blocks
356+
357+
The formatter **does not reformat** code blocks inside docstrings.
358+
This ensures that examples, snippets, API usage patterns, and documentation content remain exactly as written, preventing unintended modifications to teaching material or markdown-style fenced blocks.
359+
360+
(https://docs.astral.sh/ruff/formatter/#docstring-code-format)
361+
362+
### Applying Formatting
363+
364+
Unlike lint autofixes, **formatting changes are safe by design**.
365+
The formatter never changes logical behavior, control flow, or semantics. It only standardizes layout.
366+
367+
You can run formatting locally using:
368+
369+
```bash
370+
uv run ruff format
371+
```
372+
373+
All formatting issues must be resolved before creating a pull request or merging into protected branches.
374+
375+
376+
181377
---
182378

183379
### Important Notes

pyproject.toml

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dependencies = [
1111
"openai>=1.106.1",
1212
"numpy>=2.3.2",
1313
"pre-commit>=4.3.0",
14-
"pyright>=1.1.404",
14+
"pyright>=1.1.407",
1515
"pytest>=8.4.1",
1616
"pyyaml>=6.0.2",
1717
"ruff>=0.12.12",
@@ -37,41 +37,88 @@ dependencies = [
3737
"langfuse>=3.8.1",
3838
]
3939

40+
[tool.ruff]
41+
# Exclude a variety of commonly ignored directories.
42+
exclude = [
43+
".bzr",
44+
".direnv",
45+
".eggs",
46+
".git",
47+
".git-rewrite",
48+
".hg",
49+
".ipynb_checkpoints",
50+
".mypy_cache",
51+
".nox",
52+
".pants.d",
53+
".pyenv",
54+
".pytest_cache",
55+
".pytype",
56+
".ruff_cache",
57+
".svn",
58+
".tox",
59+
".venv",
60+
".vscode",
61+
"__pypackages__",
62+
"_build",
63+
"buck-out",
64+
"build",
65+
"dist",
66+
"node_modules",
67+
"site-packages",
68+
"venv",
69+
]
70+
71+
# Same as Black Formatter.
72+
line-length = 88
73+
indent-width = 4
74+
75+
# Set Python Version - 3.12
76+
target-version = "py312"
77+
78+
fix = false
79+
80+
81+
[tool.ruff.lint]
82+
83+
84+
select = ["E4", "E7", "E9", "F", "B", "T20", "N", "ANN", "ERA", "PERF"]
85+
ignore = []
86+
87+
# Allow fix for all enabled rules (when `--fix`) is provided.
88+
fixable = ["ALL"]
89+
unfixable = []
90+
91+
92+
[tool.ruff.format]
93+
# Like Black, use double quotes for strings.
94+
quote-style = "double"
95+
96+
# Like Black, indent with spaces, rather than tabs.
97+
indent-style = "space"
98+
99+
# Like Black, respect magic trailing commas.
100+
skip-magic-trailing-comma = false
101+
102+
# Like Black, automatically detect the appropriate line ending.
103+
line-ending = "auto"
104+
105+
docstring-code-format = false
106+
docstring-code-line-length = "dynamic"
107+
108+
109+
40110
[tool.pyright]
41111
# --- Environment & discovery ---
42112
pythonVersion = "3.12.10" # Target Python semantics (pattern matching, typing features, stdlib types).
43113
venvPath = "." # Where virtual envs live relative to repo root.
44114
venv = ".venv" # The specific env name uv manages (uv sync creates .venv).
45115

46116
# --- What to analyze ---
47-
include = ["src", "tests"] # Top-level packages & tests to check.
117+
include = ["*"] # Top-level packages & tests to check.
48118
exclude = [
49119
"**/.venv", "**/__pycache__", "build", "dist", ".git",
50-
".ruff_cache", ".mypy_cache"
120+
".ruff_cache", ".mypy_cache", "tests/", "**/tests/"
51121
]
52122

53123
# --- Global strictness ---
54-
typeCheckingMode = "strict" # Enforce full strict mode repo-wide (see notes below).
55-
useLibraryCodeForTypes = true # If a lib lacks stubs, inspect its code to infer types where possible.
56-
57-
# Make the most common "loose" mistakes fail fast in strict mode.
58-
# You can tune these individually if you need a temporary carve-out.
59-
reportMissingTypeStubs = "error" # Untyped third-party libs must have type info (stubs or inline).
60-
reportUnknownVariableType = "error" # Vars with unknown/implicit Any are not allowed.
61-
reportUnknownMemberType = "error" # Members on unknowns are not allowed.
62-
reportUnknownArgumentType = "error" # Call arguments can't be unknown.
63-
reportUnknownLambdaType = "error" # Lambda params must be typed in strict contexts.
64-
reportImplicitOptional = "error" # T | None must be explicit; no silent Optional.
65-
reportMissingTypeArgument = "error" # Generic types must specify their parameters.
66-
reportIncompatibleVariableOverride = "error" # Subclass fields must type-refine correctly.
67-
reportInvalidTypeVarUse = "error" # Catch misuse of TypeVar/variance.
68-
reportUntypedFunctionDecorator = "error" # Decorators must be typed (prevents Any leakage).
69-
reportUnusedVariable = "error" # Ditto; promote to "error" if you want hard hygiene.
70-
reportUnusedImport = "warning" # Hygiene: warn, but don’t fail builds.
71-
72-
73-
# Tests often deserialize lots of data and patch frameworks; keep them strict,
74-
# but relax "missing stubs" so untyped test-only libs don’t block you.
75-
[[tool.pyright.overrides]]
76-
module = "tests/**"
77-
reportMissingTypeStubs = "warning"
124+
typeCheckingMode = "standard" # Standard typechecking mode

src/llm_orchestration_service.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from prompt_refine_manager.prompt_refiner import PromptRefinerAgent
2424
from src.response_generator.response_generate import ResponseGeneratorAgent
2525
from src.response_generator.response_generate import stream_response_native
26+
from src.vector_indexer.constants import ResponseGenerationConstants
2627
from src.llm_orchestrator_config.llm_ochestrator_constants import (
2728
OUT_OF_SCOPE_MESSAGE,
2829
TECHNICAL_ISSUE_MESSAGE,
@@ -343,7 +344,7 @@ async def stream_orchestration_response(
343344
].check_scope_quick(
344345
question=refined_output.original_question,
345346
chunks=relevant_chunks,
346-
max_blocks=10,
347+
max_blocks=ResponseGenerationConstants.DEFAULT_MAX_BLOCKS,
347348
)
348349
timing_dict["scope_check"] = time.time() - start_time
349350

@@ -382,7 +383,7 @@ async def bot_response_generator() -> AsyncIterator[str]:
382383
agent=components["response_generator"],
383384
question=refined_output.original_question,
384385
chunks=relevant_chunks,
385-
max_blocks=10,
386+
max_blocks=ResponseGenerationConstants.DEFAULT_MAX_BLOCKS,
386387
):
387388
yield token
388389

@@ -1619,13 +1620,17 @@ def _format_chunks_for_test_response(
16191620
relevant_chunks: List of retrieved chunks with metadata
16201621
16211622
Returns:
1622-
List of ChunkInfo objects with rank and content, or None if no chunks
1623+
List of ChunkInfo objects with rank and content (limited to top 5), or None if no chunks
16231624
"""
16241625
if not relevant_chunks:
16251626
return None
16261627

1628+
# Limit to top-k chunks that are actually used in response generation
1629+
max_blocks = ResponseGenerationConstants.DEFAULT_MAX_BLOCKS
1630+
limited_chunks = relevant_chunks[:max_blocks]
1631+
16271632
formatted_chunks = []
1628-
for rank, chunk in enumerate(relevant_chunks, start=1):
1633+
for rank, chunk in enumerate(limited_chunks, start=1):
16291634
# Extract text content - prefer "text" key, fallback to "content"
16301635
chunk_text = chunk.get("text", chunk.get("content", ""))
16311636
if isinstance(chunk_text, str) and chunk_text.strip():
@@ -1682,7 +1687,7 @@ def _generate_rag_response(
16821687
generator_result = response_generator.forward(
16831688
question=refined_output.original_question,
16841689
chunks=relevant_chunks or [],
1685-
max_blocks=10,
1690+
max_blocks=ResponseGenerationConstants.DEFAULT_MAX_BLOCKS,
16861691
)
16871692

16881693
answer = (generator_result.get("answer") or "").strip()

0 commit comments

Comments
 (0)