learning-commons-evaluators 0.1.0__py3-none-any.whl

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 (35) hide show
  1. learning_commons_evaluators/__init__.py +154 -0
  2. learning_commons_evaluators/config.py +27 -0
  3. learning_commons_evaluators/errors.py +31 -0
  4. learning_commons_evaluators/evaluators/__init__.py +29 -0
  5. learning_commons_evaluators/evaluators/base.py +434 -0
  6. learning_commons_evaluators/evaluators/conventionality.py +117 -0
  7. learning_commons_evaluators/evaluators/vocabulary.py +195 -0
  8. learning_commons_evaluators/logger.py +99 -0
  9. learning_commons_evaluators/providers/__init__.py +15 -0
  10. learning_commons_evaluators/providers/langchain.py +108 -0
  11. learning_commons_evaluators/py.typed +0 -0
  12. learning_commons_evaluators/schemas/__init__.py +75 -0
  13. learning_commons_evaluators/schemas/common_inputs.py +101 -0
  14. learning_commons_evaluators/schemas/config.py +166 -0
  15. learning_commons_evaluators/schemas/conventionality.py +32 -0
  16. learning_commons_evaluators/schemas/errors.py +592 -0
  17. learning_commons_evaluators/schemas/evaluator.py +220 -0
  18. learning_commons_evaluators/schemas/input_specs.py +131 -0
  19. learning_commons_evaluators/schemas/metadata.py +132 -0
  20. learning_commons_evaluators/schemas/text_complexity.py +48 -0
  21. learning_commons_evaluators/schemas/vocabulary.py +68 -0
  22. learning_commons_evaluators/settings/__init__.py +9 -0
  23. learning_commons_evaluators/settings/_generated_conventionality_settings.py +101 -0
  24. learning_commons_evaluators/settings/_generated_vocabulary_settings.py +404 -0
  25. learning_commons_evaluators/settings/conventionality/__init__.py +0 -0
  26. learning_commons_evaluators/settings/conventionality/contracts.toml +130 -0
  27. learning_commons_evaluators/settings/load_settings.py +196 -0
  28. learning_commons_evaluators/settings/vocabulary/__init__.py +0 -0
  29. learning_commons_evaluators/settings/vocabulary/contracts.toml +641 -0
  30. learning_commons_evaluators/version.py +10 -0
  31. learning_commons_evaluators-0.1.0.dist-info/METADATA +93 -0
  32. learning_commons_evaluators-0.1.0.dist-info/RECORD +35 -0
  33. learning_commons_evaluators-0.1.0.dist-info/WHEEL +5 -0
  34. learning_commons_evaluators-0.1.0.dist-info/licenses/LICENSE +21 -0
  35. learning_commons_evaluators-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,117 @@
1
+ """Conventionality evaluator: evaluates text for conventionality of language."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ import textstat # type: ignore[import-untyped]
8
+ from langchain_core.output_parsers import JsonOutputParser
9
+ from langchain_core.prompts import ChatPromptTemplate
10
+ from pydantic import Field
11
+
12
+ from learning_commons_evaluators.evaluators.base import BaseEvaluator
13
+ from learning_commons_evaluators.schemas.common_inputs import (
14
+ GradeInputField,
15
+ TextInputField,
16
+ )
17
+ from learning_commons_evaluators.schemas.conventionality import (
18
+ ConventionalityEvaluationSettings,
19
+ ConventionalityOutput,
20
+ )
21
+ from learning_commons_evaluators.schemas.evaluator import (
22
+ EvaluationExplanation,
23
+ EvaluationInput,
24
+ )
25
+ from learning_commons_evaluators.schemas.metadata import (
26
+ EvaluationMetadata,
27
+ EvaluatorMetadata,
28
+ )
29
+ from learning_commons_evaluators.schemas.text_complexity import (
30
+ TextComplexityAnswer,
31
+ TextComplexityResult,
32
+ )
33
+ from learning_commons_evaluators.settings._generated_conventionality_settings import (
34
+ CONFIG as _CONVENTIONALITY_CONFIG,
35
+ )
36
+
37
+ _INPUT_SETTINGS = _CONVENTIONALITY_CONFIG.evaluator_metadata.inputs
38
+
39
+
40
+ class ConventionalityEvaluationInput(EvaluationInput):
41
+ """Input for a conventionality evaluation.
42
+
43
+ Constraints (min/max text length, allowed grades) are sourced from
44
+ ``[[evaluator_metadata.inputs]]`` in evaluator settings and applied
45
+ automatically — callers supply raw values, not field objects.
46
+
47
+ Example::
48
+
49
+ inp = ConventionalityEvaluationInput(text="The quick brown fox...", grade=5)
50
+ """
51
+
52
+ _input_settings: ClassVar[dict] = _INPUT_SETTINGS
53
+
54
+ text: TextInputField = Field(description="The text to evaluate.")
55
+ grade: GradeInputField = Field(description="The grade level of the text.")
56
+
57
+ def __init__(self, *, text: str, grade: int, **kwargs):
58
+ super().__init__(text=text, grade=grade, **kwargs)
59
+
60
+
61
+ class ConventionalityEvaluator(
62
+ BaseEvaluator[
63
+ ConventionalityEvaluationInput,
64
+ TextComplexityResult,
65
+ ConventionalityEvaluationSettings,
66
+ ]
67
+ ):
68
+ """Evaluates text for conventionality (idioms, metaphors, implied meaning) relative to grade."""
69
+
70
+ metadata: EvaluatorMetadata = _CONVENTIONALITY_CONFIG.evaluator_metadata
71
+ default_evaluation_settings: ConventionalityEvaluationSettings = (
72
+ _CONVENTIONALITY_CONFIG.evaluation_settings
73
+ )
74
+
75
+ async def evaluate_impl(
76
+ self,
77
+ input: ConventionalityEvaluationInput,
78
+ evaluation_settings: ConventionalityEvaluationSettings,
79
+ evaluation_metadata: EvaluationMetadata,
80
+ ) -> TextComplexityResult:
81
+ """Run conventionality evaluation. Returns TextComplexityResult with answer, explanation, metadata."""
82
+ step_prompt_settings = evaluation_settings.prompt_settings_step_conventionality_evaluation
83
+
84
+ fk_score = round(textstat.flesch_kincaid_grade(input.text.value), 2)
85
+ prompt_inputs = input.input_values()
86
+ prompt_inputs["fk_score"] = fk_score
87
+
88
+ parser = JsonOutputParser(pydantic_object=ConventionalityOutput)
89
+ prompts = _CONVENTIONALITY_CONFIG.prompts
90
+ template = ChatPromptTemplate.from_messages(
91
+ [
92
+ ("system", prompts["system_prompt"]),
93
+ ("human", prompts["human_prompt"]),
94
+ ]
95
+ ).partial(format_instructions=parser.get_format_instructions())
96
+ conventionality_output = await self.execute_prompt_chain_step(
97
+ step_name="conventionality_evaluation",
98
+ prompt_settings=step_prompt_settings,
99
+ evaluation_metadata=evaluation_metadata,
100
+ template=template,
101
+ chain_inputs=prompt_inputs,
102
+ parser_output_type=ConventionalityOutput,
103
+ )
104
+
105
+ answer = TextComplexityAnswer.from_score(conventionality_output.complexity_score)
106
+ return TextComplexityResult(
107
+ answer=answer,
108
+ explanation=EvaluationExplanation(
109
+ summary=conventionality_output.reasoning,
110
+ details={
111
+ "conventionality_features": conventionality_output.conventionality_features,
112
+ "grade_context": conventionality_output.grade_context,
113
+ "instructional_insights": conventionality_output.instructional_insights,
114
+ },
115
+ ),
116
+ metadata=evaluation_metadata,
117
+ )
@@ -0,0 +1,195 @@
1
+ """Vocabulary evaluator: evaluates text for vocabulary complexity relative to grade level."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, ClassVar
6
+
7
+ import textstat # type: ignore[import-untyped]
8
+ from langchain_core.output_parsers import JsonOutputParser
9
+ from langchain_core.prompts import ChatPromptTemplate
10
+ from pydantic import Field
11
+
12
+ from learning_commons_evaluators.evaluators.base import BaseEvaluator
13
+ from learning_commons_evaluators.schemas.common_inputs import (
14
+ GradeInputField,
15
+ TextInputField,
16
+ )
17
+ from learning_commons_evaluators.schemas.config import PromptSettings
18
+ from learning_commons_evaluators.schemas.evaluator import (
19
+ EvaluationExplanation,
20
+ EvaluationInput,
21
+ )
22
+ from learning_commons_evaluators.schemas.metadata import (
23
+ EvaluationMetadata,
24
+ EvaluatorMetadata,
25
+ )
26
+ from learning_commons_evaluators.schemas.text_complexity import (
27
+ TextComplexityAnswer,
28
+ TextComplexityResult,
29
+ )
30
+ from learning_commons_evaluators.schemas.vocabulary import (
31
+ VocabularyComplexityOutput,
32
+ VocabularyEvaluationSettings,
33
+ normalize_complexity_output,
34
+ )
35
+ from learning_commons_evaluators.settings._generated_vocabulary_settings import (
36
+ CONFIG as _VOCABULARY_CONFIG,
37
+ )
38
+
39
+ _INPUT_SETTINGS = _VOCABULARY_CONFIG.evaluator_metadata.inputs
40
+
41
+ # Grades 3–4 use the Gemini-based rubric prompt; grades 5–12 use the GPT-based annotation guide prompt.
42
+ _GRADES_3_4 = frozenset({3, 4})
43
+
44
+
45
+ class VocabularyEvaluationInput(EvaluationInput):
46
+ """Input for a vocabulary evaluation.
47
+
48
+ Constraints (allowed grade levels) are sourced from
49
+ ``[[evaluator_metadata.inputs]]`` in evaluator settings and applied
50
+ automatically — callers supply raw values, not field objects.
51
+
52
+ Example::
53
+
54
+ inp = VocabularyEvaluationInput(text="The quick brown fox...", grade=5)
55
+ """
56
+
57
+ _input_settings: ClassVar[dict] = _INPUT_SETTINGS
58
+
59
+ text: TextInputField = Field(description="The text to evaluate.")
60
+ grade: GradeInputField = Field(description="The grade level of the text (3–12).")
61
+
62
+ def __init__(self, *, text: str, grade: int, **kwargs):
63
+ super().__init__(text=text, grade=grade, **kwargs)
64
+
65
+
66
+ class VocabularyEvaluator(
67
+ BaseEvaluator[VocabularyEvaluationInput, TextComplexityResult, VocabularyEvaluationSettings]
68
+ ):
69
+ """Evaluates text for vocabulary complexity relative to the target grade level.
70
+
71
+ The evaluation runs in two steps:
72
+ 1. **Background knowledge** – the model generates a short assumption about what
73
+ students at the target grade already know about the text's topic. This
74
+ provides context that keeps the complexity rating from penalising familiar
75
+ domain words.
76
+ 2. **Vocabulary complexity** – a grade-specific prompt + model produces JSON
77
+ matching the notebook ``Output`` shape. Raw JSON is passed through
78
+ :func:`~learning_commons_evaluators.schemas.vocabulary.normalize_complexity_output`
79
+ (same behaviour as the notebook), then validated. Grades 3–4 use Gemini;
80
+ grades 5–12 use GPT. Scores are normalised to :class:`TextComplexityAnswer`.
81
+
82
+ Supported grades: 3–12.
83
+ """
84
+
85
+ metadata: EvaluatorMetadata = _VOCABULARY_CONFIG.evaluator_metadata
86
+ default_evaluation_settings: VocabularyEvaluationSettings = (
87
+ _VOCABULARY_CONFIG.evaluation_settings
88
+ )
89
+
90
+ async def evaluate_impl(
91
+ self,
92
+ input: VocabularyEvaluationInput,
93
+ evaluation_settings: VocabularyEvaluationSettings,
94
+ evaluation_metadata: EvaluationMetadata,
95
+ ) -> TextComplexityResult:
96
+ """Run the two-step vocabulary evaluation and return a TextComplexityResult.
97
+
98
+ Grade validation is handled by the framework before this method is called:
99
+ ``VocabularyEvaluationInput`` automatically constrains ``grade`` to the
100
+ evaluator's ``allowed_grades`` from settings (3–12), so
101
+ ``BaseEvaluator.evaluate`` / ``evaluate_sync`` raise before reaching here for unsupported grades.
102
+ """
103
+ ps_bk = evaluation_settings.prompt_settings_step_background_knowledge
104
+ ps_34 = evaluation_settings.prompt_settings_step_vocab_grades_3_4
105
+ ps_og = evaluation_settings.prompt_settings_step_vocab_other_grades
106
+
107
+ grade = input.grade.value
108
+ text = input.text.value
109
+ fk_score = round(textstat.flesch_kincaid_grade(text), 2)
110
+ prompts = _VOCABULARY_CONFIG.prompts
111
+
112
+ # ── Step 1: background knowledge ──────────────────────────────────────
113
+ bk_template = ChatPromptTemplate.from_messages(
114
+ [("human", prompts["background_knowledge_prompt"])]
115
+ )
116
+ background_knowledge: str = await self.execute_prompt_chain_step(
117
+ step_name="background_knowledge",
118
+ prompt_settings=ps_bk,
119
+ evaluation_metadata=evaluation_metadata,
120
+ template=bk_template,
121
+ chain_inputs={"text": text, "grade": grade},
122
+ parser_output_type=None,
123
+ )
124
+
125
+ # ── Step 2: vocabulary complexity (grade-specific prompts, shared Output shape)
126
+ chain_inputs: dict[str, Any] = {
127
+ "text": input.text.value,
128
+ "student_grade_level": grade,
129
+ "student_background_knowledge": background_knowledge,
130
+ }
131
+ if grade in _GRADES_3_4:
132
+ chain_inputs["fk_level"] = fk_score
133
+ answer, explanation = await self._run_vocab_complexity_chain(
134
+ chain_inputs=chain_inputs,
135
+ evaluation_metadata=evaluation_metadata,
136
+ prompt_settings_vocab=ps_34,
137
+ system_prompt=prompts["vocab_grades_3_4_system_prompt"],
138
+ user_prompt_template=prompts["vocab_grades_3_4_user_prompt"],
139
+ )
140
+ else:
141
+ answer, explanation = await self._run_vocab_complexity_chain(
142
+ chain_inputs=chain_inputs,
143
+ evaluation_metadata=evaluation_metadata,
144
+ prompt_settings_vocab=ps_og,
145
+ system_prompt=prompts["vocab_other_grades_system_prompt"],
146
+ user_prompt_template=prompts["vocab_other_grades_user_prompt"],
147
+ )
148
+
149
+ return TextComplexityResult(
150
+ answer=answer,
151
+ explanation=explanation,
152
+ metadata=evaluation_metadata,
153
+ )
154
+
155
+ async def _run_vocab_complexity_chain(
156
+ self,
157
+ *,
158
+ chain_inputs: dict[str, Any],
159
+ evaluation_metadata: EvaluationMetadata,
160
+ prompt_settings_vocab: PromptSettings,
161
+ system_prompt: str,
162
+ user_prompt_template: str,
163
+ ) -> tuple[TextComplexityAnswer, EvaluationExplanation]:
164
+ parser = JsonOutputParser(pydantic_object=VocabularyComplexityOutput)
165
+ template = ChatPromptTemplate.from_messages(
166
+ [
167
+ ("system", system_prompt),
168
+ ("human", user_prompt_template),
169
+ ]
170
+ ).partial(format_instructions=parser.get_format_instructions())
171
+
172
+ output = await self.execute_prompt_chain_step(
173
+ step_name="vocab_complexity",
174
+ prompt_settings=prompt_settings_vocab,
175
+ evaluation_metadata=evaluation_metadata,
176
+ template=template,
177
+ chain_inputs=chain_inputs,
178
+ parser_output_type=VocabularyComplexityOutput,
179
+ json_dict_normalizer=normalize_complexity_output,
180
+ )
181
+
182
+ # Normalise the score string: the prompt may return spaces ("very complex")
183
+ # but TextComplexityAnswer expects underscores ("very_complex").
184
+ score = output.complexity_score.lower().replace(" ", "_")
185
+ answer = TextComplexityAnswer.from_score(score)
186
+ explanation = EvaluationExplanation(
187
+ summary=output.reasoning,
188
+ details={
189
+ "tier_2_words": output.tier_2_words,
190
+ "tier_3_words": output.tier_3_words,
191
+ "archaic_words": output.archaic_words,
192
+ "other_complex_words": output.other_complex_words,
193
+ },
194
+ )
195
+ return answer, explanation
@@ -0,0 +1,99 @@
1
+ """Logging utilities for the Learning Commons Evaluators SDK.
2
+
3
+ Follows the "Configuring logging for a library" section of the Python logging HOWTO:
4
+
5
+ - A :class:`logging.NullHandler` is attached once to the logger named
6
+ ``learning_commons_evaluators`` when this module is imported, so optional log
7
+ calls never trigger "no handler" warnings if the host app does not configure
8
+ logging.
9
+ - The library does not call :func:`logging.basicConfig` or otherwise configure
10
+ the root logger.
11
+ - By default, :class:`~learning_commons_evaluators.schemas.config.EvaluatorConfig`
12
+ uses the package logger ``learning_commons_evaluators`` so messages propagate
13
+ to the root like typical libraries. Use :func:`create_silent_logger` when
14
+ passing ``logger=`` if you need to discard SDK log lines entirely.
15
+ - Applications attach handlers to the root or to ``learning_commons_evaluators``,
16
+ adjust levels (e.g. suppress the SDK while the app is at DEBUG), pass a custom
17
+ :class:`~logging.Logger` through config, or use :func:`create_logger` for a
18
+ small stream-handler helper.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+
25
+ Logger = logging.Logger
26
+
27
+ SDK_LOGGER_NAME = "learning_commons_evaluators"
28
+
29
+
30
+ def _has_non_null_handler(logger: logging.Logger) -> bool:
31
+ return any(not isinstance(h, logging.NullHandler) for h in logger.handlers)
32
+
33
+
34
+ def _install_library_null_handler() -> None:
35
+ """Attach NullHandler to the SDK root logger once (stdlib library pattern)."""
36
+ root = logging.getLogger(SDK_LOGGER_NAME)
37
+ if not any(isinstance(h, logging.NullHandler) for h in root.handlers):
38
+ root.addHandler(logging.NullHandler())
39
+
40
+
41
+ _install_library_null_handler()
42
+
43
+
44
+ def get_logger(name: str | None = None) -> logging.Logger:
45
+ """Return the SDK root logger or a child (``learning_commons_evaluators.<name>``).
46
+
47
+ Does not add handlers or change levels; configuration is left to the
48
+ application or to :func:`create_logger`.
49
+ """
50
+ if name is None:
51
+ return logging.getLogger(SDK_LOGGER_NAME)
52
+ return logging.getLogger(f"{SDK_LOGGER_NAME}.{name}")
53
+
54
+
55
+ def create_logger(
56
+ name: str | None = None,
57
+ level: int = logging.WARNING,
58
+ handler: logging.Handler | None = None,
59
+ *,
60
+ use_logger: logging.Logger | None = None,
61
+ ) -> logging.Logger:
62
+ """Return ``use_logger`` unchanged, or configure an SDK subtree logger.
63
+
64
+ When no ``use_logger`` is given, sets *level* and attaches *handler*, or a
65
+ :class:`logging.StreamHandler` if there is no non-:class:`~logging.NullHandler`
66
+ handler yet (the import-time NullHandler alone does not count).
67
+
68
+ Intended for applications and quick debugging, not for internal SDK calls.
69
+ """
70
+ if use_logger is not None:
71
+ return use_logger
72
+
73
+ logger = get_logger(name)
74
+ logger.setLevel(level)
75
+
76
+ if handler is not None:
77
+ logger.addHandler(handler)
78
+ elif not _has_non_null_handler(logger):
79
+ stream_handler = logging.StreamHandler()
80
+ stream_handler.setLevel(level)
81
+ stream_handler.setFormatter(logging.Formatter("[%(levelname)s] %(name)s: %(message)s"))
82
+ logger.addHandler(stream_handler)
83
+
84
+ return logger
85
+
86
+
87
+ def create_silent_logger() -> logging.Logger:
88
+ """Logger that discards all records (no propagation). Use for ``logger=`` opt-out."""
89
+ logger = logging.getLogger(f"{SDK_LOGGER_NAME}.silent")
90
+ for h in list(logger.handlers):
91
+ logger.removeHandler(h)
92
+ logger.addHandler(logging.NullHandler())
93
+ logger.propagate = False
94
+ return logger
95
+
96
+
97
+ def format_error(error: BaseException) -> str:
98
+ """Format an exception for logging."""
99
+ return f"{type(error).__name__}: {error}"
@@ -0,0 +1,15 @@
1
+ """LLM providers for use in chains (e.g. LangChain).
2
+
3
+ create_provider() returns a langchain_core BaseChatModel (OpenAI, Google, Anthropic).
4
+
5
+ Config types (LLMProviderConfig and provider-specific configs:
6
+ AnthropicLLMProviderConfig, GoogleLLMProviderConfig, OpenAILLMProviderConfig)
7
+ live in learning_commons_evaluators.schemas.config.
8
+ """
9
+
10
+ from learning_commons_evaluators.providers.langchain import (
11
+ create_provider,
12
+ token_usage_from_aimessage,
13
+ )
14
+
15
+ __all__ = ["create_provider", "token_usage_from_aimessage"]
@@ -0,0 +1,108 @@
1
+ """LangChain chat model factory.
2
+
3
+ create_provider(prompt_settings, evaluator_config) builds a LangChain chat model
4
+ (OpenAI, Google, Anthropic) using PromptSettings and EvaluatorConfig.
5
+ Set model and temperature when building the chain, e.g. llm.bind(model="gpt-4o", temperature=0.7).
6
+
7
+ token_usage_from_aimessage() extracts TokenUsage from a LangChain AIMessage.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from learning_commons_evaluators.errors import ConfigurationError
15
+ from learning_commons_evaluators.schemas.config import (
16
+ EvaluatorConfig,
17
+ LLMProvider,
18
+ PromptSettings,
19
+ )
20
+ from learning_commons_evaluators.schemas.metadata import TokenUsage
21
+
22
+ if TYPE_CHECKING:
23
+ from langchain_core.language_models.chat_models import BaseChatModel
24
+
25
+
26
+ def token_usage_from_aimessage(message: Any, prompt_settings: PromptSettings) -> TokenUsage:
27
+ """Extract TokenUsage from a LangChain AIMessage and prompt settings."""
28
+ usage = getattr(message, "usage_metadata", None) or getattr(
29
+ message, "response_metadata", {}
30
+ ).get("usage_metadata")
31
+ if not usage:
32
+ return TokenUsage(
33
+ provider_type=prompt_settings.provider_type,
34
+ model=prompt_settings.model,
35
+ input_tokens=0,
36
+ output_tokens=0,
37
+ )
38
+ return TokenUsage(
39
+ provider_type=prompt_settings.provider_type,
40
+ model=prompt_settings.model,
41
+ input_tokens=usage.get("input_tokens", 0),
42
+ output_tokens=usage.get("output_tokens", 0),
43
+ )
44
+
45
+
46
+ def _create_openai_llm(
47
+ prompt_settings: PromptSettings, evaluator_config: EvaluatorConfig
48
+ ) -> BaseChatModel:
49
+ from langchain_openai import ChatOpenAI
50
+
51
+ openai_config = evaluator_config.openai_llm_provider_config
52
+ if openai_config is None:
53
+ raise ConfigurationError("OpenAI provider config is not set on EvaluatorConfig")
54
+ kwargs: dict[str, Any] = {
55
+ "api_key": openai_config.api_key,
56
+ "model": prompt_settings.model,
57
+ "temperature": prompt_settings.temperature,
58
+ }
59
+ return ChatOpenAI(**kwargs)
60
+
61
+
62
+ def _create_google_llm(
63
+ prompt_settings: PromptSettings, evaluator_config: EvaluatorConfig
64
+ ) -> BaseChatModel:
65
+ from langchain_google_genai import ChatGoogleGenerativeAI
66
+
67
+ google_config = evaluator_config.google_llm_provider_config
68
+ if google_config is None:
69
+ raise ConfigurationError("Google provider config is not set on EvaluatorConfig")
70
+ return ChatGoogleGenerativeAI(
71
+ google_api_key=google_config.api_key,
72
+ model=prompt_settings.model,
73
+ temperature=prompt_settings.temperature,
74
+ )
75
+
76
+
77
+ def _create_anthropic_llm(
78
+ prompt_settings: PromptSettings, evaluator_config: EvaluatorConfig
79
+ ) -> BaseChatModel:
80
+ from langchain_anthropic import ChatAnthropic
81
+
82
+ anthropic_config = evaluator_config.anthropic_llm_provider_config
83
+ if anthropic_config is None:
84
+ raise ConfigurationError("Anthropic provider config is not set on EvaluatorConfig")
85
+ return ChatAnthropic(
86
+ api_key=anthropic_config.api_key,
87
+ model=prompt_settings.model,
88
+ temperature=prompt_settings.temperature,
89
+ )
90
+
91
+
92
+ def create_provider(
93
+ prompt_settings: PromptSettings, evaluator_config: EvaluatorConfig
94
+ ) -> BaseChatModel:
95
+ """Create a LangChain chat model from a PromptSettings for use in a chain."""
96
+ if prompt_settings.provider_type == LLMProvider.OPENAI:
97
+ return _create_openai_llm(prompt_settings, evaluator_config)
98
+ if prompt_settings.provider_type == LLMProvider.GOOGLE:
99
+ return _create_google_llm(prompt_settings, evaluator_config)
100
+ if prompt_settings.provider_type == LLMProvider.ANTHROPIC:
101
+ return _create_anthropic_llm(prompt_settings, evaluator_config)
102
+ raise ConfigurationError(
103
+ f"Unsupported LLM provider type: {prompt_settings.provider_type!r}. "
104
+ "Expected openai, google, or anthropic."
105
+ )
106
+
107
+
108
+ __all__ = ["create_provider", "token_usage_from_aimessage"]
File without changes
@@ -0,0 +1,75 @@
1
+ """Schema and config types. Import from submodules or from learning_commons_evaluators."""
2
+
3
+ from learning_commons_evaluators.schemas.common_inputs import (
4
+ GradeInputField,
5
+ TextInputField,
6
+ )
7
+ from learning_commons_evaluators.schemas.config import (
8
+ EvaluationSettings,
9
+ LLMProvider,
10
+ PromptSettings,
11
+ )
12
+ from learning_commons_evaluators.schemas.conventionality import (
13
+ ConventionalityEvaluationSettings,
14
+ ConventionalityOutput,
15
+ )
16
+ from learning_commons_evaluators.schemas.errors import InputValidationError
17
+ from learning_commons_evaluators.schemas.evaluator import (
18
+ EvaluationAnswer,
19
+ EvaluationExplanation,
20
+ EvaluationInput,
21
+ EvaluationResult,
22
+ InputField,
23
+ )
24
+ from learning_commons_evaluators.schemas.input_specs import (
25
+ AnyInputSpec,
26
+ GradeInputSpec,
27
+ InputSpec,
28
+ TextInputSpec,
29
+ )
30
+ from learning_commons_evaluators.schemas.metadata import (
31
+ PROMPT_STEP_EXTRA_PROMPT_SETTINGS,
32
+ PROMPT_STEP_EXTRA_TOKEN_USAGE,
33
+ EvaluationMetadata,
34
+ EvaluatorMaturity,
35
+ EvaluatorMetadata,
36
+ InputMetadata,
37
+ Status,
38
+ StepMetadata,
39
+ TokenUsage,
40
+ prompt_settings_to_extras_value,
41
+ )
42
+ from learning_commons_evaluators.schemas.text_complexity import (
43
+ TextComplexityEvaluationInput,
44
+ )
45
+
46
+ __all__ = [
47
+ "AnyInputSpec",
48
+ "ConventionalityEvaluationSettings",
49
+ "ConventionalityOutput",
50
+ "GradeInputSpec",
51
+ "InputSpec",
52
+ "TextInputSpec",
53
+ "EvaluationAnswer",
54
+ "EvaluationExplanation",
55
+ "EvaluationInput",
56
+ "EvaluationMetadata",
57
+ "EvaluationResult",
58
+ "EvaluationSettings",
59
+ "EvaluatorMetadata",
60
+ "EvaluatorMaturity",
61
+ "GradeInputField",
62
+ "InputField",
63
+ "InputMetadata",
64
+ "LLMProvider",
65
+ "PromptSettings",
66
+ "PROMPT_STEP_EXTRA_PROMPT_SETTINGS",
67
+ "PROMPT_STEP_EXTRA_TOKEN_USAGE",
68
+ "Status",
69
+ "StepMetadata",
70
+ "TextComplexityEvaluationInput",
71
+ "TextInputField",
72
+ "TokenUsage",
73
+ "InputValidationError",
74
+ "prompt_settings_to_extras_value",
75
+ ]