Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
38 changes: 1 addition & 37 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ let package = Package(
.default(enabledTraits: []),
],
dependencies: [
.package(url: "https://github.com/huggingface/swift-transformers", from: "1.0.0"),
// fork(main 分支含 underscore 输入名修复 c2cfb40,已 push)
.package(url: "https://github.com/foxterm/swift-transformers.git", branch: "main"),
.package(url: "https://github.com/huggingface/swift-huggingface", from: "1.0.0"),
.package(
url: "https://github.com/mattt/EventSource",
Expand Down Expand Up @@ -100,6 +101,13 @@ let package = Package(
package: "async-http-client",
condition: .when(traits: ["AsyncHTTPClient"])
),
],
swiftSettings: [
// 将启用的 trait 同步为源文件编译条件(#if CoreML / #if MLX / #if Llama)
.define("CoreML", .when(traits: ["CoreML"])),
.define("MLX", .when(traits: ["MLX"])),
.define("Llama", .when(traits: ["Llama"])),
.define("AsyncHTTPClient", .when(traits: ["AsyncHTTPClient"])),
]
),
.macro(
Expand Down
34 changes: 33 additions & 1 deletion Sources/AnyLanguageModel/LanguageModelSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ public final class LanguageModelSession: @unchecked Sendable {

private let model: any LanguageModel

#if Llama
/// 底层模型的本地上下文窗口(仅 Llama 等可查询的本地模型返回非 nil;
/// 来自 GGUF 的 context_length)。
public var modelContextSize: UInt32? {
(model as? LlamaLanguageModel)?.modelContextSize
}

/// 本地 Llama 模型性能快照(权重/参数/内存占用/系统可用/CPU/速度)。
/// 云端模型返回 nil。
public var localModelPerformance: LlamaLanguageModel.LlamaPerformanceSnapshot? {
(model as? LlamaLanguageModel)?.performanceSnapshot()
}
#endif

/// The tools that the model can call during the session.
///
/// For a session created with dynamic instructions, this is empty.
Expand All @@ -97,6 +111,19 @@ public final class LanguageModelSession: @unchecked Sendable {
/// can read the session's tools.
public let tools: [any Tool]

/// 动态工具覆盖:非 nil 时优先用于每次请求(工具开关实时生效,
/// 无需重建会话/重新创建模型;本地 Llama 模型因此不必重载 GGUF)。
@ObservationIgnored private var dynamicTools: [any Tool]?
@ObservationIgnored private let dynamicToolsLock = NSLock()

/// 更新会话工具集(运行中实时生效,不重建模型,不打断上下文)。
/// 下次模型请求(respond / stream)即使用新工具。
nonisolated public func setDynamicTools(_ tools: [any Tool]) {
dynamicToolsLock.lock()
dynamicTools = tools
dynamicToolsLock.unlock()
}

/// The instructions for the session, if any.
///
/// For a session created with dynamic instructions, this is `nil`.
Expand Down Expand Up @@ -167,8 +194,13 @@ public final class LanguageModelSession: @unchecked Sendable {
/// It's public so that language models outside this module
/// can read the inputs for each request.
nonisolated public func resolvedRequestContext() -> RequestContext {
// 动态工具覆盖优先;未设置时用创建时的 tools
dynamicToolsLock.lock()
let effectiveTools = dynamicTools ?? tools
dynamicToolsLock.unlock()

guard let dynamicInstructions else {
return RequestContext(transcript: transcript, instructions: instructions, tools: tools)
return RequestContext(transcript: transcript, instructions: instructions, tools: effectiveTools)
}

// Evaluate the body for this request.
Expand Down
22 changes: 20 additions & 2 deletions Sources/AnyLanguageModel/Models/CoreMLLanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,26 @@
}

do {
// Load the model with the specified compute units
self.model = try Models.LanguageModel.loadCompiled(url: url, computeUnits: computeUnits)
// 本地 tokenizer 优先:模型目录含 tokenizer.json 时直接本地加载,避免从 HuggingFace Hub 下载
// (本地模型名通常是转换产物名,在 Hub 上不存在)。
let modelFolder = url.deletingLastPathComponent()
var localTokenizer: (any Tokenizer)? = nil
if FileManager.default.fileExists(
atPath: modelFolder.appendingPathComponent("tokenizer.json").path
) {
localTokenizer = try? await AutoTokenizer.from(modelFolder: modelFolder)
}
if let localTokenizer {
// Load the model with the specified compute units
self.model = try Models.LanguageModel.loadCompiled(
url: url,
computeUnits: computeUnits,
tokenizer: localTokenizer
)
} else {
// Load the model with the specified compute units
self.model = try Models.LanguageModel.loadCompiled(url: url, computeUnits: computeUnits)
}
} catch {
// Map CoreML errors to our specific error cases
throw CoreMLLanguageModelError.modelInvalid(url, underlyingError: error)
Expand Down
Loading