@layr-labs/agentkit-opacity
Version:
Opacity adapter for verifiable inference in EigenLayer AgentKit
340 lines (298 loc) • 9.34 kB
text/typescript
import {
IDALoggingAdapter,
DALogOptions,
DALogEntry,
DALogStatus,
IVerifiableInferenceAdapter,
GenerateTextOptions,
VerifiableInferenceResult,
ProofGenerationError,
ProofVerificationError,
Proof
} from '@layr-labs/agentkit';
import { ModelProvider, OpacityAdapterConfig, ModelConfig, ChatMessage } from './types';
import { generateProof, verifyProof } from './utils/api';
const DEFAULT_MODELS: Record<ModelProvider, Record<string, ModelConfig>> = {
[ModelProvider.OPENAI]: {
'gpt-4o': {
name: 'gpt-4o',
temperature: 0.7,
maxOutputTokens: 2048,
frequencyPenalty: 0,
presencePenalty: 0,
},
'gpt-4o-mini': {
name: 'gpt-4o-mini',
temperature: 0.7,
maxOutputTokens: 2048,
frequencyPenalty: 0,
presencePenalty: 0,
},
},
[ModelProvider.ANTHROPIC]: {
'claude-3-opus': {
name: 'claude-3-opus',
temperature: 0.7,
maxOutputTokens: 4096,
topP: 0.9,
},
'claude-3-sonnet': {
name: 'claude-3-sonnet',
temperature: 0.7,
maxOutputTokens: 4096,
topP: 0.9,
},
'claude-3-haiku': {
name: 'claude-3-haiku',
temperature: 0.7,
maxOutputTokens: 4096,
topP: 0.9,
},
},
};
interface LogData {
data: unknown;
timestamp: number;
level: 'info' | 'warn' | 'error' | 'debug';
metadata?: Record<string, unknown>;
tags?: string[];
}
interface OpenAIResponse {
choices: Array<{
message: {
content: string;
};
}>;
}
interface AnthropicResponse {
content: string;
}
export class OpacityAdapter implements IVerifiableInferenceAdapter, IDALoggingAdapter {
private readonly config: OpacityAdapterConfig;
constructor(config: OpacityAdapterConfig) {
this.config = {
modelProvider: ModelProvider.OPENAI,
gatewayUrl: `https://gateway.ai.cloudflare.com/v1/${config.teamId}/${config.teamName}`,
...config,
};
}
async initialize(): Promise<void> {
// No initialization needed for Opacity adapter
}
async log(data: unknown, options?: DALogOptions): Promise<DALogEntry> {
const timestamp = Date.now();
const logId = await this.storeLog(data, options);
const status: DALogStatus = {
type: 'opacity',
data: {
logId,
proverUrl: this.config.opacityProverUrl,
},
timestamp,
};
return {
id: logId,
content: data,
timestamp,
status,
options,
};
}
private async storeLog(data: unknown, options?: DALogOptions): Promise<string> {
const response = await fetch(`${this.config.gatewayUrl}/logs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify({
data,
metadata: options?.metadata,
tags: options?.tags,
level: options?.level || 'info',
}),
});
if (!response.ok) {
throw new Error(`Failed to store log: ${response.statusText}`);
}
const logId = response.headers.get('cf-aig-log-id');
if (!logId) {
throw new Error('No log ID received from Cloudflare');
}
return logId;
}
async info(message: string, metadata?: Record<string, unknown>): Promise<DALogEntry> {
return this.log(message, { level: 'info', metadata });
}
async warn(message: string, metadata?: Record<string, unknown>): Promise<DALogEntry> {
return this.log(message, { level: 'warn', metadata });
}
async error(message: string, metadata?: Record<string, unknown>): Promise<DALogEntry> {
return this.log(message, { level: 'error', metadata });
}
async debug(message: string, metadata?: Record<string, unknown>): Promise<DALogEntry> {
return this.log(message, { level: 'debug', metadata });
}
async checkAvailability(status: DALogStatus): Promise<boolean> {
if (status.type !== 'opacity' || !status.data || typeof status.data !== 'object') {
return false;
}
const { logId, proverUrl } = status.data as { logId?: string; proverUrl?: string };
if (!logId || !proverUrl) {
return false;
}
try {
const response = await fetch(`${proverUrl}/api/logs/${logId}`);
return response.ok;
} catch (error) {
console.error('Error checking log availability:', error);
return false;
}
}
async getLogEntry(id: string): Promise<DALogEntry | null> {
try {
const response = await fetch(`${this.config.opacityProverUrl}/api/logs/${id}`);
if (!response.ok) {
return null;
}
const data = await response.json();
return {
id,
content: data.data,
timestamp: data.timestamp,
status: {
type: 'opacity',
data: {
logId: id,
proverUrl: this.config.opacityProverUrl,
},
timestamp: data.timestamp,
},
options: {
level: data.level,
metadata: data.metadata,
tags: data.tags,
},
};
} catch (error) {
console.error('Error retrieving log entry:', error);
return null;
}
}
async shutdown(): Promise<void> {
// No cleanup needed for Opacity adapter
}
async generateText(
prompt: string,
options?: GenerateTextOptions
): Promise<VerifiableInferenceResult> {
try {
const provider = this.config.modelProvider ?? ModelProvider.OPENAI;
const model = options?.model || (provider === ModelProvider.OPENAI ? 'gpt-4o' : 'claude-3-sonnet');
const providerModels = DEFAULT_MODELS[provider];
if (!providerModels) {
throw new Error(`Unsupported model provider: ${provider}`);
}
const modelConfig = providerModels[model];
if (!modelConfig) {
throw new Error(`Unsupported model: ${model}`);
}
const endpoint = `${this.config.gatewayUrl}/${provider}/chat/completions`;
console.log('Generating text with options:', {
modelProvider: provider,
model: model,
endpoint: endpoint
});
// Prepare the request body based on the provider
let body: any;
if (provider === ModelProvider.OPENAI) {
body = {
model: modelConfig.name,
messages: [
{
role: 'user',
content: prompt,
},
],
temperature: options?.temperature ?? modelConfig.temperature,
max_tokens: options?.maxTokens ?? modelConfig.maxOutputTokens,
frequency_penalty: modelConfig.frequencyPenalty,
presence_penalty: modelConfig.presencePenalty,
};
} else if (provider === ModelProvider.ANTHROPIC) {
body = {
model: modelConfig.name,
messages: [
{
role: 'user',
content: prompt,
},
],
temperature: options?.temperature ?? modelConfig.temperature,
max_tokens: options?.maxTokens ?? modelConfig.maxOutputTokens,
top_p: modelConfig.topP,
top_k: modelConfig.topK,
};
}
console.debug('Request body:', JSON.stringify(body, null, 2));
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorText = await response.text();
console.error('API error response:', {
status: response.status,
statusText: response.statusText,
error: errorText
});
throw new Error(`API request failed: ${errorText}`);
}
// Log response details in a TypeScript-friendly way
const headers: Record<string, string> = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
console.debug('API response:', {
status: response.status,
statusText: response.statusText,
headers
});
const logId = response.headers.get('cf-aig-log-id');
if (!logId) {
throw new ProofGenerationError('No log ID received from Cloudflare');
}
const result = await response.json();
const content = provider === ModelProvider.OPENAI
? (result as OpenAIResponse).choices[0]?.message?.content
: (result as AnthropicResponse).content;
if (!content) {
throw new ProofGenerationError('No content returned from API');
}
console.debug('Generating proof for log ID:', logId);
const proof = await generateProof(this.config.opacityProverUrl, logId);
console.debug('Proof generated successfully');
return {
content,
proof,
};
} catch (error) {
console.error('Error in generateText:', error);
if (error instanceof ProofGenerationError) {
throw error;
}
throw new ProofGenerationError('Failed to generate text with proof', error);
}
}
async verifyProof(proof: Proof): Promise<boolean> {
try {
return await verifyProof(this.config.opacityProverUrl, proof);
} catch (error) {
throw new ProofVerificationError('Failed to verify proof', proof, error);
}
}
}