Skip to content

Commit 230e1b1

Browse files
authored
[QUO-898] document metadata support/validation (#7)
* document metadata support/validation * verbose + actionable error msgs * code coverage tests * bug fix on listing logs * test fixes * edge case with empty response * update v num in package.json * update example logs
1 parent 7fe4971 commit 230e1b1

File tree

8 files changed

+350
-23
lines changed

8 files changed

+350
-23
lines changed

examples/example_logs.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ async function main() {
1616
console.log("Logger initialized")
1717

1818
// mock retrieved documents
19-
const retrieved_documents = [{"page_content": "Sample document"}]
20-
const documents = retrieved_documents.map(doc => doc["page_content"])
19+
const retrieved_documents = [
20+
"Sample document 1",
21+
{"page_content": "Sample document 2", "metadata": {"source": "website.com"}},
22+
{"page_content": "Sample document 3"}
23+
]
2124

2225
console.log("Preparing to log with quotient_logger")
2326
try {
2427
const response = await quotient_logger.log({
2528
user_query: "How do I cook a goose?",
2629
model_output: "The capital of France is Paris",
27-
documents: documents,
30+
documents: retrieved_documents,
2831
message_history: [
2932
{"role": "system", "content": "You are an expert on geography."},
3033
{"role": "user", "content": "What is the capital of France?"},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "quotientai",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "TypeScript client for QuotientAI API",
55
"main": "dist/quotientai/index.js",
66
"types": "dist/quotientai/index.d.ts",

quotientai/exceptions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
32

43
export class QuotientAIError extends Error {
@@ -8,6 +7,13 @@ export class QuotientAIError extends Error {
87
}
98
}
109

10+
export class ValidationError extends QuotientAIError {
11+
constructor(message: string) {
12+
super(message);
13+
this.name = 'ValidationError';
14+
}
15+
}
16+
1117
export class APIError extends QuotientAIError {
1218
request: AxiosRequestConfig;
1319
body: any;

quotientai/logger.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { LogEntry, LoggerConfig } from './types';
1+
import { LogEntry, LoggerConfig, LogDocument } from './types';
2+
import { ValidationError } from './exceptions';
23

34
interface LogsResource {
45
create(params: LogEntry): Promise<any>;
@@ -41,6 +42,67 @@ export class QuotientLogger {
4142
return Math.random() < this.sampleRate;
4243
}
4344

45+
// Type guard function to check if an object is a valid LogDocument
46+
private isValidLogDocument(obj: any): { valid: boolean; error?: string } {
47+
try {
48+
// Check if it has the required page_content property
49+
if (!('page_content' in obj)) {
50+
return {
51+
valid: false,
52+
error: "Missing required 'page_content' property"
53+
};
54+
}
55+
56+
// Check if page_content is a string
57+
if (typeof obj.page_content !== 'string') {
58+
return {
59+
valid: false,
60+
error: `The 'page_content' property must be a string, found ${typeof obj.page_content}`
61+
};
62+
}
63+
64+
// If metadata exists, check if it's an object
65+
if ('metadata' in obj && obj.metadata !== null && typeof obj.metadata !== 'object') {
66+
return {
67+
valid: false,
68+
error: `The 'metadata' property must be an object, found ${typeof obj.metadata}`
69+
};
70+
}
71+
72+
return { valid: true };
73+
} catch (error) {
74+
return { valid: false, error: "Unexpected error validating document" };
75+
}
76+
}
77+
78+
// Validate document format
79+
private validateDocuments(documents: (string | LogDocument)[]): void {
80+
if (!documents || documents.length === 0) {
81+
return;
82+
}
83+
84+
for (let i = 0; i < documents.length; i++) {
85+
const doc = documents[i];
86+
if (typeof doc === 'string') {
87+
continue;
88+
} else if (typeof doc === 'object' && doc !== null) {
89+
const validation = this.isValidLogDocument(doc);
90+
if (!validation.valid) {
91+
throw new ValidationError(
92+
`Invalid document format at index ${i}: ${validation.error}. ` +
93+
"Documents must be either strings or JSON objects with a 'page_content' string property and an optional 'metadata' object. " +
94+
"To fix this, ensure each document follows the format: { page_content: 'your text content', metadata?: { key: 'value' } }"
95+
);
96+
}
97+
} else {
98+
throw new ValidationError(
99+
`Invalid document type at index ${i}. Found ${typeof doc}, but documents must be either strings or JSON objects with a 'page_content' property. ` +
100+
"To fix this, provide documents as either simple strings or properly formatted objects: { page_content: 'your text content' }"
101+
);
102+
}
103+
}
104+
}
105+
44106
// log a message
45107
// params: Omit<LogEntry, 'app_name' | 'environment'>
46108
async log(params: Omit<LogEntry, 'app_name' | 'environment'>): Promise<any> {
@@ -52,6 +114,11 @@ export class QuotientLogger {
52114
throw new Error('Logger is not properly configured. app_name and environment must be set.');
53115
}
54116

117+
// Validate documents format
118+
if (params.documents) {
119+
this.validateDocuments(params.documents);
120+
}
121+
55122
// Merge default tags with any tags provided at log time
56123
const mergedTags = { ...this.tags, ...(params.tags || {}) };
57124

quotientai/resources/logs.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BaseQuotientClient } from '../client';
2+
import { LogDocument } from '../types';
23

34
interface LogResponse {
45
id: string;
@@ -8,7 +9,7 @@ interface LogResponse {
89
inconsistency_detection: boolean;
910
user_query: string;
1011
model_output: string;
11-
documents: string[];
12+
documents: (string | LogDocument)[];
1213
message_history: any[] | null;
1314
instructions: string[] | null;
1415
tags: Record<string, any>;
@@ -26,7 +27,7 @@ interface CreateLogParams {
2627
inconsistency_detection: boolean;
2728
user_query: string;
2829
model_output: string;
29-
documents: string[];
30+
documents: (string | LogDocument)[];
3031
message_history?: any[] | null;
3132
instructions?: string[] | null;
3233
tags?: Record<string, any>;
@@ -50,7 +51,7 @@ export class Log {
5051
inconsistency_detection: boolean;
5152
user_query: string;
5253
model_output: string;
53-
documents: string[];
54+
documents: (string | LogDocument)[];
5455
message_history: any[] | null;
5556
instructions: string[] | null;
5657
tags: Record<string, any>;
@@ -109,7 +110,15 @@ export class LogsResource {
109110
if (params.offset !== undefined) queryParams.offset = params.offset;
110111

111112
try {
112-
const response = await this.client.get('/logs', { params: queryParams }) as LogsResponse;
113+
const response = await this.client.get('/logs', queryParams) as LogsResponse;
114+
115+
// Check if response has logs property and it's an array
116+
if (!response || !response.logs || !Array.isArray(response.logs)) {
117+
console.warn('No logs found. Please check your query parameters and try again.');
118+
return [];
119+
}
120+
121+
// Map the logs to Log objects
113122
return response.logs.map(logData => new Log(logData));
114123
} catch (error) {
115124
console.error('Error listing logs:', error);

quotientai/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,17 @@ export interface RunResult {
7575
expected: string;
7676
}
7777

78+
export interface LogDocument {
79+
page_content: string;
80+
metadata?: Record<string, any>;
81+
}
82+
7883
export interface LogEntry {
7984
app_name: string;
8085
environment: string;
8186
user_query: string;
8287
model_output: string;
83-
documents: string[];
88+
documents: (string | LogDocument)[];
8489
message_history?: Array<Record<string, any>> | null;
8590
instructions?: string[] | null;
8691
tags?: Record<string, any>;

0 commit comments

Comments
 (0)