Instructions to use baya1116/deep-charger with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use baya1116/deep-charger with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir deep-charger baya1116/deep-charger
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
File size: 3,734 Bytes
dd5890c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | import Foundation
#if canImport(PDFKit)
import PDFKit
#endif
/// A local-file retrieval tier: scans a folder for text/markdown/PDF, splits into chunks, embeds
/// each with BGE, and answers cosine queries. Used as an optional knowledge source for `lookup`
/// turns (Mac primarily, but works on iOS too). Nil-by-default in `Assistant` → zero behaviour change.
public final class LocalFileIndex {
public struct Chunk { public let text: String; let emb: [Float]; public let source: String }
private var chunks: [Chunk] = []
private let bge: Embedder
public private(set) var indexedFiles: Int = 0
public init(bge: Embedder) { self.bge = bge }
public var count: Int { chunks.count }
private static let exts: Set<String> = ["txt", "md", "markdown", "text", "pdf"]
/// Recursively index a directory. Replaces any prior index.
@discardableResult
public func build(from dir: URL, maxFiles: Int = 300, maxCharsPerFile: Int = 200_000) async -> Int {
chunks.removeAll(); indexedFiles = 0
let fm = FileManager.default
guard let en = fm.enumerator(at: dir, includingPropertiesForKeys: nil) else { return 0 }
let all = en.allObjects.compactMap { $0 as? URL }
var files = [URL]()
for url in all where Self.exts.contains(url.pathExtension.lowercased()) {
files.append(url); if files.count >= maxFiles { break }
}
for url in files {
guard let raw = extractText(url) else { continue }
let body = String(raw.prefix(maxCharsPerFile))
let name = url.lastPathComponent
for piece in chunkText(body) {
chunks.append(Chunk(text: piece, emb: await bge.encode(piece, isQuery: false), source: name))
}
indexedFiles += 1
}
return chunks.count
}
/// Top-`k` chunks by cosine ≥ `minSim`, as "filename: text".
public func search(_ query: String, k: Int = 3, minSim: Float = 0.5) async
-> [(text: String, source: String, sim: Float)] {
guard !chunks.isEmpty else { return [] }
let qv = await bge.encode(query, isQuery: true)
var scored = chunks.map { (c: $0, s: cosine($0.emb, qv)) }.filter { $0.s >= minSim }
scored.sort { $0.s > $1.s }
return scored.prefix(k).map { ($0.c.text, $0.c.source, $0.s) }
}
// MARK: - extraction / chunking
private func extractText(_ url: URL) -> String? {
if url.pathExtension.lowercased() == "pdf" {
#if canImport(PDFKit)
return PDFDocument(url: url)?.string
#else
return nil
#endif
}
return try? String(contentsOf: url, encoding: .utf8)
}
/// Pack paragraphs into ~`target`-char chunks; hard-split paragraphs longer than that.
private func chunkText(_ text: String, target: Int = 500) -> [String] {
let paras = text.components(separatedBy: "\n\n")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.count >= 20 }
var out = [String](); var cur = ""
func flush() { if !cur.isEmpty { out.append(cur); cur = "" } }
for p in paras {
if p.count > target {
flush()
var s = Substring(p)
while s.count > target {
let idx = s.index(s.startIndex, offsetBy: target)
out.append(String(s[..<idx])); s = s[idx...]
}
cur = String(s)
} else {
if cur.count + p.count + 1 > target { flush() }
cur = cur.isEmpty ? p : cur + "\n" + p
}
}
flush()
return out
}
}
|