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
66 changes: 59 additions & 7 deletions sdks/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ Evaluates vocabulary complexity using the Qual Text Complexity rubric (SAP).
**Constructor:**
```typescript
const evaluator = new VocabularyEvaluator({
googleApiKey: string; // Required - Google API key
openaiApiKey: string; // Required - OpenAI API key
googleApiKey?: string; // Google API key (required by this evaluator)
openaiApiKey?: string; // OpenAI API key (required by this evaluator)
maxRetries?: number; // Optional - Max retry attempts (default: 2)
telemetry?: boolean | TelemetryOptions; // Optional (default: true)
logger?: Logger; // Optional - Custom logger
Expand Down Expand Up @@ -80,14 +80,14 @@ await evaluator.evaluate(text: string, grade: string)

Evaluates sentence structure complexity based on grammatical features.

**Supported Grades:** K-12
**Supported Grades:** 3-12

**Uses:** OpenAI GPT-4o

**Constructor:**
```typescript
const evaluator = new SentenceStructureEvaluator({
openaiApiKey: string; // Required - OpenAI API key
openaiApiKey?: string; // OpenAI API key (required by this evaluator)
maxRetries?: number; // Optional - Max retry attempts (default: 2)
telemetry?: boolean | TelemetryOptions; // Optional (default: true)
logger?: Logger; // Optional - Custom logger
Expand Down Expand Up @@ -121,7 +121,51 @@ await evaluator.evaluate(text: string, grade: string)

---

### 3. Grade Level Appropriateness Evaluator
### 3. Text Complexity Evaluator

Composite evaluator that analyzes both vocabulary and sentence structure complexity in parallel.

**Supported Grades:** 3-12

**Uses:** Google Gemini 2.5 Pro + OpenAI GPT-4o (composite)

**Constructor:**
```typescript
const evaluator = new TextComplexityEvaluator({
googleApiKey?: string; // Google API key (required by this evaluator)
openaiApiKey?: string; // OpenAI API key (required by this evaluator)
maxRetries?: number; // Optional - Max retry attempts (default: 2)
telemetry?: boolean | TelemetryOptions; // Optional (default: true)
logger?: Logger; // Optional - Custom logger
logLevel?: LogLevel; // Optional - Logging verbosity (default: WARN)
});
```

**API:**
```typescript
await evaluator.evaluate(text: string, grade: string)
```

**Returns:**
```typescript
{
score: {
overall: string; // Overall complexity (highest of the two)
vocabulary: string; // Vocabulary complexity score
sentenceStructure: string; // Sentence structure complexity score
};
reasoning: string; // Combined reasoning from both evaluators
metadata: EvaluationMetadata;
_internal: {
vocabulary: EvaluationResult | { error: Error };
sentenceStructure: EvaluationResult | { error: Error };
};
}
```
Comment on lines +149 to +164

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different, but usable, shape than we currently have.
We need to get alignment from data, eng, design on how we use the eval outputs.


---

### 4. Grade Level Appropriateness Evaluator

Determines appropriate grade level for text.

Expand All @@ -132,7 +176,7 @@ Determines appropriate grade level for text.
**Constructor:**
```typescript
const evaluator = new GradeLevelAppropriatenessEvaluator({
googleApiKey: string; // Required - Google API key
googleApiKey?: string; // Google API key (required by this evaluator)
maxRetries?: number; // Optional - Max retry attempts (default: 2)
telemetry?: boolean | TelemetryOptions; // Optional (default: true)
logger?: Logger; // Optional - Custom logger
Expand Down Expand Up @@ -253,10 +297,12 @@ See [docs/telemetry.md](./docs/telemetry.md) for telemetry configuration and pri

## Configuration Options

All evaluators support these common options:
All evaluators use the same `BaseEvaluatorConfig` interface:

```typescript
interface BaseEvaluatorConfig {
googleApiKey?: string; // Google API key (required by some evaluators)
openaiApiKey?: string; // OpenAI API key (required by some evaluators)
maxRetries?: number; // Max API retry attempts (default: 2)
telemetry?: boolean | TelemetryOptions; // Telemetry config (default: true)
logger?: Logger; // Custom logger (optional)
Expand All @@ -265,6 +311,12 @@ interface BaseEvaluatorConfig {
}
```

**Note:** Which API keys are required depends on the evaluator. The SDK validates required keys at runtime based on the evaluator's metadata:
- **Vocabulary**: Requires both `googleApiKey` and `openaiApiKey`
- **Sentence Structure**: Requires `openaiApiKey` only
- **Text Complexity**: Requires both `googleApiKey` and `openaiApiKey`
- **Grade Level Appropriateness**: Requires `googleApiKey` only

---

## License
Expand Down
48 changes: 37 additions & 11 deletions sdks/typescript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdks/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
},
"dependencies": {
"compromise": "^14.13.0",
"p-limit": "^5.0.0",
"syllable": "^5.0.1",
"zod": "^3.22.4"
},
Expand Down
89 changes: 85 additions & 4 deletions sdks/typescript/src/evaluators/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type TelemetryMetadata,
type TokenUsage,
} from '../telemetry/index.js';
import { ValidationError } from '../errors.js';
import { ConfigurationError, ValidationError } from '../errors.js';
import { createLogger, LogLevel, type Logger } from '../logger.js';

/**
Expand Down Expand Up @@ -80,6 +80,25 @@ export interface BaseEvaluatorConfig {
logLevel?: LogLevel;
}

/**
* Evaluator metadata interface
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reviewer - Making configuration and validation shared code across evals

* Each evaluator must provide this metadata as static properties
*/
export interface EvaluatorMetadata {
/** Unique identifier for the evaluator (e.g., 'vocabulary', 'sentence-structure') */
readonly id: string;
/** Human-readable name (e.g., 'Vocabulary', 'Sentence Structure') */
readonly name: string;
/** Brief description of what the evaluator does */
readonly description: string;
/** Supported grade levels (e.g., ['3', '4', '5', ...]) */
readonly supportedGrades: readonly string[];
Comment on lines +94 to +95

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the shape new evals are taking, it looks like grade isn't a core attribute.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the previous playground, we ended up setting up an input fields framework. Each eval would define what type of inputs it used, e.g. inputs: [{name: 'grade', type:'select', required: true, options: ['1st grade', '2nd grade']}]...
I thought we wouldn't need something this elaborate for a while, but all the new evaluators have different inputs. None use grade.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address this in a different PR

/** Whether this evaluator requires a Google API key */
readonly requiresGoogleKey: boolean;
/** Whether this evaluator requires an OpenAI API key */
readonly requiresOpenAIKey: boolean;
}
Comment on lines +85 to +100

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


/**
* Abstract base class for all evaluators
*
Expand All @@ -88,6 +107,9 @@ export interface BaseEvaluatorConfig {
* - Text validation
* - Grade validation (with overridable default)
* - Metadata creation
*
* Concrete evaluators must implement:
* - static metadata: Provide evaluator metadata (see EvaluatorMetadata interface)
*/
export abstract class BaseEvaluator {
protected telemetryClient?: TelemetryClient;
Expand All @@ -96,9 +118,34 @@ export abstract class BaseEvaluator {
telemetry: Required<TelemetryOptions>;
};

/**
* Static metadata for the evaluator
*
* Concrete evaluators MUST define this property.
*
* @example
* ```typescript
* class MyEvaluator extends BaseEvaluator {
* static readonly metadata = {
* id: 'my-evaluator',
* name: 'My Evaluator',
* description: 'Does something useful',
* supportedGrades: ['3', '4', '5'],
* requiresGoogleKey: true,
* requiresOpenAIKey: false,
* };
* }
* ```
*/
static readonly metadata: EvaluatorMetadata;

constructor(config: BaseEvaluatorConfig) {
// Initialize logger
this.logger = createLogger(config.logger, config.logLevel ?? LogLevel.WARN);

// Validate required API keys based on metadata
this.validateApiKeys(config);

// Normalize telemetry config
const telemetryConfig = this.normalizeTelemetryConfig(config.telemetry);

Expand All @@ -120,6 +167,38 @@ export abstract class BaseEvaluator {
}
}

/**
* Get metadata for this evaluator instance
* @throws {ConfigurationError} If the subclass has not defined static metadata
*/
protected get metadata(): EvaluatorMetadata {
const meta = (this.constructor as typeof BaseEvaluator).metadata;
if (!meta) {
throw new ConfigurationError(
`${this.constructor.name} must define a static readonly metadata block.`
);
}
return meta;
}

/**
* Validate that required API keys are provided based on metadata
* @throws {ConfigurationError} If required API keys are missing
*/
private validateApiKeys(config: BaseEvaluatorConfig): void {
if (this.metadata.requiresGoogleKey && !config.googleApiKey) {
throw new ConfigurationError(
`Google API key is required for ${this.metadata.name} evaluator. Pass googleApiKey in config.`
);
}

if (this.metadata.requiresOpenAIKey && !config.openaiApiKey) {
throw new ConfigurationError(
`OpenAI API key is required for ${this.metadata.name} evaluator. Pass openaiApiKey in config.`
);
}
}
Comment on lines +188 to +200
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseEvaluator assumes every subclass defines static metadata. If a new evaluator forgets to set it, this.metadata.requiresGoogleKey will throw a TypeError instead of a clear configuration error. Consider adding an explicit guard (e.g., verify this.metadata is defined and has required fields) and throw a helpful ConfigurationError when metadata is missing/malformed.

Copilot uses AI. Check for mistakes.

/**
* Normalize telemetry config to standard format
*/
Expand Down Expand Up @@ -149,10 +228,12 @@ export abstract class BaseEvaluator {
}

/**
* Get the evaluator type identifier (e.g., "vocabulary", "sentence-structure")
* Must be implemented by concrete evaluators
* Get the evaluator type identifier from metadata
* @returns The evaluator type ID (e.g., "vocabulary", "sentence-structure")
*/
protected abstract getEvaluatorType(): string;
protected getEvaluatorType(): string {
return this.metadata.id;
}

/**
* Validate text meets requirements
Expand Down
Loading