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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,8 @@ ollama pull nomic-embed-text

The built-in `ollama` provider uses Ollama's native `/api/embeddings` endpoint and is the simplest setup when you want to use `nomic-embed-text`.

For the built-in Ollama path, the plugin budgets `nomic-embed-text` against an observed effective input limit of about **2048 tokens**, not the model's higher advertised theoretical context. This keeps batching and chunk text generation aligned with real Ollama embedding runtime behavior.

If you want to use a different Ollama embedding model through its OpenAI-compatible API, use the `custom` provider instead and set `customProvider.baseUrl` to `http://127.0.0.1:11434/v1` so the plugin calls `.../v1/embeddings`.

## 📈 Performance
Expand Down
17 changes: 15 additions & 2 deletions native/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,13 @@ pub fn chunk_exists_on_branch(conn: &Connection, branch: &str, chunk_id: &str) -

/// Get all branches
pub fn get_all_branches(conn: &Connection) -> DbResult<Vec<String>> {
let mut stmt = conn.prepare("SELECT DISTINCT branch FROM branch_chunks")?;
let mut stmt = conn.prepare(
r#"
SELECT branch FROM branch_chunks
UNION
SELECT branch FROM branch_symbols
"#,
)?;
let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;

let mut results = Vec::new();
Expand Down Expand Up @@ -1494,7 +1500,14 @@ pub fn get_stats(conn: &Connection) -> DbResult<DbStats> {
let branch_chunk_count: i64 =
conn.query_row("SELECT COUNT(*) FROM branch_chunks", [], |row| row.get(0))?;
let branch_count: i64 = conn.query_row(
"SELECT COUNT(DISTINCT branch) FROM branch_chunks",
r#"
SELECT COUNT(*)
FROM (
SELECT branch FROM branch_chunks
UNION
SELECT branch FROM branch_symbols
)
"#,
[],
|row| row.get(0),
)?;
Expand Down
2 changes: 1 addition & 1 deletion src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const EMBEDDING_MODELS = {
provider: "ollama",
model: "nomic-embed-text",
dimensions: 768,
maxTokens: 8192,
maxTokens: 2048,
costPer1MTokens: 0.00,
},
"mxbai-embed-large": {
Expand Down
82 changes: 58 additions & 24 deletions src/embeddings/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,35 +291,69 @@ class OllamaEmbeddingProvider implements EmbeddingProviderInterface {
};
}

private estimateTokens(text: string): number {
return Math.ceil(text.length / 4);
}

private truncateToTokenLimit(text: string, maxTokens: number): string {
const maxChars = Math.max(1, maxTokens * 4);
if (text.length <= maxChars) {
return text;
}

return `${text.slice(0, Math.max(0, maxChars - 17))}\n... [truncated]`;
}

private async embedSingle(text: string): Promise<{ embedding: number[]; tokensUsed: number }> {
const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: this.modelInfo.model,
prompt: text,
truncate: false,
}),
});

if (!response.ok) {
const error = await response.text();
throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
}

const data = (await response.json()) as {
embedding: number[];
};

return {
embedding: data.embedding,
tokensUsed: this.estimateTokens(text),
};
}

async embedBatch(texts: string[]): Promise<EmbeddingBatchResult> {
const results = await Promise.all(
texts.map(async (text) => {
const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: this.modelInfo.model,
prompt: text,
}),
});
const results: Array<{ embedding: number[]; tokensUsed: number }> = [];

if (!response.ok) {
const error = await response.text();
throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
for (const text of texts) {
try {
results.push(await this.embedSingle(text));
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const shouldRetryWithTruncation = message.includes("input length exceeds the context length");

if (!shouldRetryWithTruncation) {
throw error;
}

const data = (await response.json()) as {
embedding: number[];
};
const truncated = this.truncateToTokenLimit(text, this.modelInfo.maxTokens);
if (truncated === text) {
throw error;
}

return {
embedding: data.embedding,
tokensUsed: Math.ceil(text.length / 4),
};
})
);
results.push(await this.embedSingle(truncated));
}
}

return {
embeddings: results.map((r) => r.embedding),
Expand Down
Loading
Loading