Skip to content

Commit 05da4f7

Browse files
authored
Merge pull request #25 from mkaramuk/main
chore: peerBenchJS release 0.0.15
2 parents ef8523f + 2d8c9ef commit 05da4f7

61 files changed

Lines changed: 2240 additions & 448 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

peerBenchJS/packages/sdk/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# peerBench
1+
# peerBench SDK
22

3-
TypeScript SDK for peerBench framework
3+
A TypeScript SDK for building AI evaluation benchmarks and data processing pipelines.
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Implementing a Collector
2+
3+
This guide demonstrates how to implement a new Collector for the peerBench SDK.
4+
5+
## Overview
6+
7+
Collectors are responsible for taking a source input and creating a structured data that later can be used by the Generators to generate new Prompts.
8+
9+
## Basic Example
10+
11+
Here's a simple collector implementation that demonstrates the basic structure:
12+
13+
```typescript
14+
import { AbstractCollector } from "@/collectors/abstract/abstract-collector";
15+
16+
interface MyCollectedData {
17+
id: string;
18+
title: string;
19+
content: string;
20+
metadata: Record<string, any>;
21+
}
22+
23+
export class SimpleAPICollector extends AbstractCollector<MyCollectedData[]> {
24+
readonly identifier = "simple-api-collector";
25+
26+
async collect(
27+
source: unknown,
28+
options?: Record<string, any>
29+
): Promise<MyCollectedData[] | undefined> {
30+
// Type guard for input validation
31+
if (typeof source !== "string") {
32+
throw new Error("Source must be a string URL");
33+
}
34+
35+
// Fetch data from external source
36+
const response = await fetch(source);
37+
if (!response.ok) {
38+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
39+
}
40+
41+
// Parse and transform the response
42+
const rawData = await response.json();
43+
return this.transformData(rawData);
44+
}
45+
46+
private transformData(rawData: any): MyCollectedData[] {
47+
return rawData.map((item: any) => ({
48+
id: item.id,
49+
title: item.title,
50+
content: item.content,
51+
metadata: item.metadata || {},
52+
}));
53+
}
54+
}
55+
```
56+
57+
### Required Properties
58+
59+
**`readonly identifier: string`**
60+
61+
- A unique string that identifies your Collector
62+
- Should be descriptive and unique across all Collectors
63+
- Useful when you try to find this Collector among the others
64+
65+
### Abstract Methods
66+
67+
**`async collect(source: unknown, options?: Record<string, any>): Promise<T | undefined>`**
68+
69+
- This is the main collection method you must implement
70+
- Takes a `source` as the input
71+
- Interpretation of what `source` parameter is depends on the implementation
72+
- For example if it is a URL, you should validate that it is a real URL string and fetch data from it
73+
- Or if it is a file path, you should be checking that file is exist
74+
- Optional `options` parameter for configurable collection behavior
75+
- Must return data matching your Collector's output type `T`, or `undefined` if the process fails
76+
- The generic type `T` represents what your collector **outputs**
77+
78+
## Advanced Patterns
79+
80+
### RSS Feed Collector
81+
82+
For RSS feeds, extend `AbstractRSSCollector` instead:
83+
84+
```typescript
85+
import { AbstractRSSCollector } from "@/collectors/abstract/abstract-rss-collector";
86+
import { z } from "zod";
87+
88+
export class MyRSSCollector extends AbstractRSSCollector<MyRSSData[]> {
89+
readonly identifier = "my-rss-collector";
90+
91+
// Define the expected RSS structure using Zod
92+
feedSchema = z.object({
93+
rss: z.object({
94+
channel: z.object({
95+
title: z.string(),
96+
item: z.array(
97+
z.object({
98+
title: z.string(),
99+
description: z.string(),
100+
link: z.string(),
101+
pubDate: z.string(),
102+
})
103+
),
104+
}),
105+
}),
106+
});
107+
108+
async collect(url: string): Promise<MyRSSData[] | undefined> {
109+
const feed = await this.parseFeedXML(await this.fetchFeed(url));
110+
111+
// Process the validated RSS data
112+
return feed.rss.channel.item.map((item) => ({
113+
title: item.title,
114+
description: item.description,
115+
link: item.link,
116+
publishedAt: new Date(item.pubDate),
117+
}));
118+
}
119+
}
120+
```
121+
122+
### Collector with Authentication
123+
124+
For APIs requiring authentication:
125+
126+
```typescript
127+
export class AuthenticatedAPICollector extends AbstractCollector<MyData[]> {
128+
readonly identifier = "authenticated-api";
129+
130+
async collect(
131+
source: unknown,
132+
options: {
133+
includeSensitiveContent?: boolean;
134+
limit?: number;
135+
sortBy?: "relevance" | "date" | "popularity";
136+
language?: string;
137+
} = {}
138+
): Promise<MyData[] | undefined> {
139+
if (typeof source !== "string") {
140+
throw new Error("Source must be a search query string");
141+
}
142+
143+
// Build the API request parameters using options
144+
const params = new URLSearchParams();
145+
params.set("q", source);
146+
147+
// Apply options to customize the API request
148+
if (options.includeSensitiveContent) {
149+
params.set("sensitive", "true");
150+
}
151+
if (options.limit) {
152+
params.set("limit", options.limit.toString());
153+
}
154+
if (options.sortBy) {
155+
params.set("sortBy", options.sortBy);
156+
}
157+
if (options.language) {
158+
params.set("language", options.language);
159+
}
160+
161+
const headers = {
162+
Authorization: `Bearer ${this.apiKey}`,
163+
"Content-Type": "application/json",
164+
};
165+
166+
const response = await fetch(
167+
`${this.baseUrl}/search?${params.toString()}`,
168+
{ headers }
169+
);
170+
171+
if (!response.ok) {
172+
throw new Error(`API request failed: ${response.status}`);
173+
}
174+
175+
const data = await response.json();
176+
return this.transformData(data, options.limit);
177+
}
178+
179+
private transformData(rawData: any, limit?: number): MyData[] {
180+
let results = rawData.results?.map(this.mapToMyData) || [];
181+
182+
if (limit && results.length > limit) {
183+
results = results.slice(0, limit);
184+
}
185+
186+
return results;
187+
}
188+
}
189+
```
190+
191+
## Examples
192+
193+
See the `examples/collectors/` directory for complete working examples:
194+
195+
- `file-system-collector.ts` - Local file collection
196+
- `news-api-collector.ts` - API-based collection
197+
198+
These examples demonstrate real-world implementations and can serve as templates for your own collectors.
199+
200+
## What's Next?
201+
202+
Now that you understand how to implement a Collector, you're ready to learn about the next component in the peerBench SDK: **Generators**.
203+
204+
**Next Documentation**: [Implementing a Generator](./implementing-a-generator.md)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Implementing a Generator
2+
3+
This guide demonstrates how to implement a new Generator for the peerBench SDK.
4+
5+
## Overview
6+
7+
Generators are responsible for taking the collected data from a Collector and generate one or more Prompts based on that data.
8+
9+
## Basic Example
10+
11+
Here's a simple generator implementation that demonstrates the basic structure:
12+
13+
```typescript
14+
import { AbstractGenerator } from "@/generators/abstract/abstract-generator";
15+
import { Prompt, PromptTypes } from "@/types";
16+
import { z } from "zod";
17+
18+
export class SimpleQuestionGenerator extends AbstractGenerator {
19+
readonly identifier = "simple-question-generator";
20+
21+
inputSchema = z.object({
22+
id: z.string(),
23+
content: z.string(),
24+
metadata: z.record(z.any()),
25+
});
26+
27+
protected async generatePrompts(
28+
input: z.infer<(typeof this)["inputSchema"]>,
29+
options?: Record<string, any>
30+
): Promise<Prompt[]> {
31+
// Transform validated input into Prompts
32+
const question = `Answer the following question: ${input.content}`;
33+
const fullPrompt = question;
34+
35+
// Use the buildPrompt helper method for Prompt creation
36+
const prompt = await this.buildPrompt({
37+
question,
38+
correctAnswer: "Your expected answer here",
39+
fullPrompt,
40+
type: PromptTypes.Text,
41+
metadata: input.metadata,
42+
});
43+
44+
return [prompt];
45+
}
46+
}
47+
```
48+
49+
### Required Properties
50+
51+
**`readonly identifier: string`**
52+
53+
- A unique string that identifies your Generator
54+
- Should be descriptive and unique across all Generators
55+
- Useful when you try to find this Generator among the others
56+
57+
**`inputSchema: z.ZodSchema`**
58+
59+
- A Zod schema that validates the input data of the `generatePrompts` method
60+
- If you want to process data from different Collectors, simply you need to make this schema compatible with the output types of those Collectors
61+
- Base class handles the validation of the input so you just need to define the schema
62+
63+
### Abstract Methods
64+
65+
**`protected async generatePrompts(input: z.infer<(typeof this)["inputSchema"]>, options?: Record<string, any>): Promise<Prompt[]>`**
66+
67+
- This is the main generation method you must implement
68+
- Takes validated input data that matches your `inputSchema`
69+
- Optional `options` parameter for configurable generation behavior
70+
- Must return an array of `Prompt` objects
71+
- The input is already validated by the base class
72+
73+
## Type Compatibility Between Collectors and Generators
74+
75+
The key to making Collectors and Generators work together is ensuring schema compatibility. When you define a Generator, the `inputSchema` specifies what input data structure the Generator expects.
76+
77+
**Example**: If you have a Collector that outputs data matching your Generator's `inputSchema`, they are compatible:
78+
79+
```typescript
80+
// Collector outputs data matching the schema
81+
export class FileCollector extends AbstractCollector<FileData[]> {
82+
// ... implementation
83+
}
84+
85+
// Generator expects data matching inputSchema
86+
export class QuestionGenerator extends AbstractGenerator {
87+
inputSchema = z.object({
88+
id: z.string(),
89+
content: z.string(),
90+
metadata: z.record(z.any()),
91+
});
92+
93+
// ... implementation
94+
}
95+
96+
// These can be used together if the Collector output matches the Generator's inputSchema
97+
const collector = new FileCollector();
98+
const generator = new QuestionGenerator();
99+
100+
const collectedData = await collector.collect("path/to/file");
101+
const prompts = await generator.generate(collectedData); // Schema validation ensures compatibility
102+
```
103+
104+
## Examples
105+
106+
See the `examples/generators/` directory for complete working examples:
107+
108+
- `multiple-choice-generator.ts` - Multiple choice question generation
109+
- `sentence-ordering-generator.ts` - Sentence ordering task generation
110+
111+
These examples demonstrate real-world implementations and can serve as templates for your own Generators.
112+
113+
## What's Next?
114+
115+
Now that you understand how to implement a Generator, you're ready to learn about the next component in the peerBench SDK: **Scorers**.
116+
117+
**Next Documentation**: [Implementing a Scorer](./implementing-a-scorer.md)

0 commit comments

Comments
 (0)