Skip to content
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
41 changes: 41 additions & 0 deletions docassemble/ALDashboard/interview_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ def log(*pargs: Any, **kwargs: Any) -> None: # type: ignore
except Exception:
user_info = None # type: ignore

try:
from docassemble.base.util import get_config
except Exception:

def get_config(key: str, default: Any = None) -> Any: # type: ignore
return default


def _resolve_current_user_id() -> Optional[int]:
try:
Expand Down Expand Up @@ -585,13 +592,47 @@ def _dayaml_runtime_options(
) -> Any:
if _DAYamlRuntimeOptions is None:
return None
llm_settings = _style_llm_settings() if include_style_llm else {}
return _DAYamlRuntimeOptions(
accessibility_error_on_widgets=frozenset({"combobox"}),
style_enabled=include_style or include_style_llm,
style_include_llm=include_style_llm,
style_openai_api_key=llm_settings.get("style_openai_api_key"),
style_openai_base_url=llm_settings.get("style_openai_base_url"),
style_openai_model=llm_settings.get("style_openai_model"),
)


def _style_llm_settings() -> Dict[str, Optional[str]]:
"""Read the OpenAI-compatible credentials from the docassemble Configuration.

dayamlchecker only looks at its own flags and the OPENAI_* environment
variables, so a server configured the docassemble way (an ``open ai:``
block, or the older top-level ``openai api key``) would otherwise report a
missing API key. The precedence matches docassemble.ALToolbox.llms: an
OPENAI_API_KEY in the environment wins, then ``open ai: key``, then
``openai api key``.
"""
open_ai_config = get_config("open ai", {}) or {}
if not isinstance(open_ai_config, Mapping):
open_ai_config = {}
api_key = None
if not os.getenv("OPENAI_API_KEY"):
api_key = open_ai_config.get("key") or get_config("openai api key")
base_url = open_ai_config.get("base url") or get_config("openai base url")
model = (
open_ai_config.get("default small model")
or get_config("openai default small model")
or open_ai_config.get("default model")
or get_config("openai default model")
)
return {
"style_openai_api_key": _stringify(api_key) or None,
"style_openai_base_url": _stringify(base_url) or None,
"style_openai_model": _stringify(model) or None,
}


def _collect_dayamlchecker_findings(
content: str,
*,
Expand Down
36 changes: 36 additions & 0 deletions docassemble/ALDashboard/test/test_interview_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import tempfile
import os

from docassemble.ALDashboard import interview_linter
from docassemble.ALDashboard.interview_linter import (
get_all_text,
get_misspelled_words,
Expand Down Expand Up @@ -181,6 +182,41 @@ def _findings(self, yaml_content, lint_mode="full"):


class TestInterviewLinterStyleDelegation(unittest.TestCase):
def test_style_llm_credentials_come_from_open_ai_config(self):
config = {
"open ai": {
"key": "sk-config",
"base url": "https://llm.example.com/v1",
"default model": "small-model",
}
}
with (
patch(
"docassemble.ALDashboard.interview_linter.get_config",
side_effect=lambda key, default=None: config.get(key, default),
),
patch.dict("os.environ", {}, clear=False),
):
os.environ.pop("OPENAI_API_KEY", None)
options = interview_linter._dayaml_runtime_options(include_style_llm=True)
self.assertEqual(options.style_openai_api_key, "sk-config")
self.assertEqual(options.style_openai_base_url, "https://llm.example.com/v1")
self.assertEqual(options.style_openai_model, "small-model")

def test_style_llm_credentials_accept_legacy_openai_api_key(self):
config = {"openai api key": "sk-legacy"}
with (
patch(
"docassemble.ALDashboard.interview_linter.get_config",
side_effect=lambda key, default=None: config.get(key, default),
),
patch.dict("os.environ", {}, clear=False),
):
os.environ.pop("OPENAI_API_KEY", None)
options = interview_linter._dayaml_runtime_options(include_style_llm=True)
self.assertEqual(options.style_openai_api_key, "sk-legacy")
self.assertIsNone(options.style_openai_base_url)

@patch("docassemble.ALDashboard.interview_linter._collect_dayamlchecker_findings")
def test_ai_style_checks_are_delegated_to_dayamlchecker(self, mock_collect):
mock_collect.side_effect = [[FakeDAYamlFinding()], []]
Expand Down
Loading