mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Refactor Gemini and StreamingService to enhance streamRequest method, improve error handling in parseStreamChunk, and update service registration for StreamingService.
This commit is contained in:
parent
82ee600979
commit
4818741a77
4 changed files with 43 additions and 43 deletions
|
|
@ -5,6 +5,7 @@ import type { IPrompt } from "AIClasses/IPrompt";
|
|||
import { StreamingService, type StreamChunk } from "Services/StreamingService";
|
||||
import type { Conversation } from "Conversations/Conversation";
|
||||
import { Role } from "Enums/Role";
|
||||
import { AIProviderURL } from "Enums/ApiProvider";
|
||||
|
||||
export class Gemini implements IAIClass {
|
||||
private readonly apiKey: string;
|
||||
|
|
@ -14,7 +15,7 @@ export class Gemini implements IAIClass {
|
|||
public constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
this.aiPrompt = Resolve(Services.IPrompt);
|
||||
this.streamingService = new StreamingService();
|
||||
this.streamingService = Resolve(Services.StreamingService);
|
||||
}
|
||||
|
||||
public async* streamRequest(conversation: Conversation): AsyncGenerator<StreamChunk, void, unknown> {
|
||||
|
|
@ -48,6 +49,38 @@ export class Gemini implements IAIClass {
|
|||
]
|
||||
};
|
||||
|
||||
yield* this.streamingService.streamGeminiRequest(this.apiKey, requestBody);
|
||||
yield* this.streamingService.streamRequest(
|
||||
AIProviderURL.Gemini.replace("API_KEY", this.apiKey),
|
||||
requestBody,
|
||||
this.parseStreamChunk
|
||||
);
|
||||
}
|
||||
|
||||
private parseStreamChunk(chunk: string): StreamChunk {
|
||||
try {
|
||||
const data = JSON.parse(chunk);
|
||||
|
||||
let text = "";
|
||||
const candidate = data.candidates?.[0];
|
||||
|
||||
if (candidate) {
|
||||
if (candidate.content?.parts?.[0]?.text) {
|
||||
text = candidate.content.parts[0].text;
|
||||
} else if (candidate.text) {
|
||||
text = candidate.text;
|
||||
}
|
||||
}
|
||||
|
||||
const isComplete = !!candidate?.finishReason;
|
||||
|
||||
return {
|
||||
content: text,
|
||||
isComplete: isComplete,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown parsing error";
|
||||
console.error("Failed to parse stream chunk:", message, "Chunk:", chunk);
|
||||
return { content: "", isComplete: false, error: `Failed to parse chunk: ${message}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { ConversationFileSystemService } from "./ConversationFileSystemService";
|
|||
import { ConversationHistoryModal } from "Modals/ConversationHistoryModal";
|
||||
import type { App } from "obsidian";
|
||||
import { AIFunctionService } from "./AIFunctionService";
|
||||
import { StreamingService } from "./StreamingService";
|
||||
|
||||
export function RegisterDependencies(plugin: AIAgentPlugin) {
|
||||
RegisterSingleton(Services.MessageService, new MessageService());
|
||||
|
|
@ -23,6 +24,7 @@ export function RegisterDependencies(plugin: AIAgentPlugin) {
|
|||
|
||||
RegisterSingleton<IPrompt>(Services.IPrompt, new AIPrompt());
|
||||
RegisterSingleton<AIFunctionService>(Services.AIFunctionService, new AIFunctionService());
|
||||
RegisterSingleton<StreamingService>(Services.StreamingService, new StreamingService());
|
||||
|
||||
RegisterTransient<StreamingMarkdownService>(Services.StreamingMarkdownService, () => new StreamingMarkdownService());
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ export class Services {
|
|||
// interfaces
|
||||
static IAIClass = Symbol("IAIClass");
|
||||
static IPrompt = Symbol("IPrompt");
|
||||
static IActioner = Symbol("IActioner");
|
||||
static IActionDefinitions = Symbol("IActionDefinitions");
|
||||
|
||||
// modals
|
||||
static ConversationHistoryModal = Symbol("ConversationHistoryModal");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { AIProviderURL } from "Enums/ApiProvider";
|
||||
|
||||
export interface StreamChunk {
|
||||
content: string;
|
||||
isComplete: boolean;
|
||||
|
|
@ -7,17 +5,14 @@ export interface StreamChunk {
|
|||
}
|
||||
|
||||
export class StreamingService {
|
||||
/**
|
||||
* Fetches data from Gemini API with streaming support
|
||||
* Since Obsidian's request() doesn't support streaming, we use native fetch
|
||||
*/
|
||||
public async* streamGeminiRequest(
|
||||
apiKey: string,
|
||||
requestBody: unknown
|
||||
public async* streamRequest(
|
||||
url: string,
|
||||
requestBody: unknown,
|
||||
parseStreamChunk: (chunk: string) => StreamChunk
|
||||
): AsyncGenerator<StreamChunk, void, unknown> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
AIProviderURL.Gemini.replace("API_KEY", apiKey),
|
||||
url,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -51,7 +46,7 @@ export interface StreamChunk {
|
|||
for (const line of lines) {
|
||||
if (line.trim().startsWith("data:")) {
|
||||
const jsonStr = line.trim().substring(5);
|
||||
const chunk = this.parseStreamChunk(jsonStr);
|
||||
const chunk = parseStreamChunk(jsonStr);
|
||||
lastChunkWasComplete = chunk.isComplete;
|
||||
yield chunk;
|
||||
}
|
||||
|
|
@ -74,32 +69,4 @@ export interface StreamChunk {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
private parseStreamChunk(chunk: string): StreamChunk {
|
||||
try {
|
||||
const data = JSON.parse(chunk);
|
||||
|
||||
let text = "";
|
||||
const candidate = data.candidates?.[0];
|
||||
|
||||
if (candidate) {
|
||||
if (candidate.content?.parts?.[0]?.text) {
|
||||
text = candidate.content.parts[0].text;
|
||||
} else if (candidate.text) {
|
||||
text = candidate.text;
|
||||
}
|
||||
}
|
||||
|
||||
const isComplete = !!candidate?.finishReason;
|
||||
|
||||
return {
|
||||
content: text,
|
||||
isComplete: isComplete,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown parsing error";
|
||||
console.error("Failed to parse stream chunk:", message, "Chunk:", chunk);
|
||||
return { content: "", isComplete: false, error: `Failed to parse chunk: ${message}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue