Normalize fetched model types with AgentKit 0.6.0
This commit is contained in:
1 parent
d2f3a80d8d
commit
2afc70a285
4 files changed
+53
-9
No files matched your search
@@ -155,6 +155,13 @@ adapters at the LLM boundary. Models without tool capability receive no tools.
|
||||
Stop cancels the active stream and actions that have not started; steering
|
||||
messages are consumed at the next turn boundary.
|
||||
|
||||
Model purposes are `chat`, `embedding`, `rerank`, `image_generation`, and
|
||||
`video_generation`. Only recognised purpose values from model discovery are
|
||||
asserted as reported metadata. Unknown values, including Anthropic's `model`
|
||||
object discriminator, leave catalog and name inference available and otherwise
|
||||
default to chat. Only chat models enter conversation pickers. Persisted unknown
|
||||
purpose strings also default to chat; there are no legacy purpose aliases.
|
||||
|
||||
A turn's tool calls run concurrently only when every call in it is marked
|
||||
parallel; one sequential call makes the whole turn serial rather than being
|
||||
fenced into its own segment. Concurrency is declared per tool and may be
|
||||
|
||||
@@ -675,7 +675,7 @@
|
||||
repositoryURL = "https://github.com/AkinoKaede/AgentKit.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.5.0;
|
||||
minimumVersion = 0.6.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/AkinoKaede/AgentKit.git",
|
||||
"state" : {
|
||||
"revision" : "a7345ee68fd92b52589cecfc812517c089f39368",
|
||||
"version" : "0.5.0"
|
||||
"revision" : "ad0ab2f4f707657611faa671d41f229ff019d4e8",
|
||||
"version" : "0.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -336,6 +336,32 @@ struct AIProviderPersistenceTests {
|
||||
#expect(store.resolve(conversation: nil)?.model.id == hidden.id)
|
||||
}
|
||||
|
||||
@Test(arguments: ["image", "other", "unknown-purpose"])
|
||||
func unrecognisedStoredModelKindsLoadAsChat(kind: String) throws {
|
||||
let container = try makeContainer("UnknownModelKind-" + kind)
|
||||
let provider = ModelProvider(name: "Gateway", models: [AIModel(id: "private-chat")])
|
||||
let store = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
|
||||
store.upsert(provider)
|
||||
let row = try #require(container.mainContext.fetch(FetchDescriptor<PersistentAIModel>()).first)
|
||||
row.kind = kind
|
||||
try container.mainContext.save()
|
||||
|
||||
let reloaded = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
|
||||
let restored = try #require(reloaded.modelProvider(provider.id))
|
||||
#expect(restored.models.first?.kind == .chat)
|
||||
#expect(restored.assistantModels.map(\.id) == ["private-chat"])
|
||||
}
|
||||
|
||||
@Test(arguments: AIModel.Kind.allCases)
|
||||
func modelPurposesRoundTripThroughPersistence(kind: AIModel.Kind) throws {
|
||||
let container = try makeContainer("ModelPurpose-" + kind.rawValue)
|
||||
let provider = ModelProvider(name: "Gateway", models: [AIModel(id: "custom", kind: kind)])
|
||||
let store = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
|
||||
store.upsert(provider)
|
||||
let reloaded = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
|
||||
#expect(reloaded.modelProvider(provider.id)?.models.first?.kind == kind)
|
||||
}
|
||||
|
||||
private func makeContainer(_ name: String) throws -> ModelContainer {
|
||||
let configuration = localModelConfiguration(
|
||||
name, schema: HostPersistence.schema, isStoredInMemoryOnly: true
|
||||
@@ -368,6 +394,17 @@ private enum TestPersistenceError: Error {
|
||||
|
||||
@Suite
|
||||
struct ModelCapabilityResolverTests {
|
||||
@Test(arguments: ["model", "unknown-purpose", "", " \n"])
|
||||
func unrecognisedWireTypeAllowsCatalogClassification(type: String) throws {
|
||||
let data = try JSONSerialization.data(withJSONObject: [
|
||||
"data": [["id": "gpt-5.4", "type": type]]
|
||||
])
|
||||
let model = try #require(ModelCatalogClient.parseOpenAIPage(data).models.first)
|
||||
#expect(model.kind == .chat)
|
||||
#expect(model.contextLength == 272_000)
|
||||
#expect(model.abilities.contains(.toolCall))
|
||||
}
|
||||
|
||||
@Test
|
||||
func bundledPiCatalogEnrichesKnownModelWithoutRegex() {
|
||||
let provider = ModelProvider(name: "OpenAI")
|
||||
@@ -376,8 +413,8 @@ struct ModelCapabilityResolverTests {
|
||||
provider: provider
|
||||
)
|
||||
|
||||
#expect(ModelCapabilityResolver.catalogMetadata?.version == "0.84.3")
|
||||
#expect(ModelCapabilityResolver.catalogMetadata?.modelCount == 1_312)
|
||||
#expect(ModelCapabilityResolver.catalogMetadata?.version == "0.85.1")
|
||||
#expect(ModelCapabilityResolver.catalogMetadata?.modelCount == 1_354)
|
||||
#expect(resolution.catalogSource == .init(provider: "openai", modelID: "gpt-5.4"))
|
||||
#expect(resolution.model.input == [.text, .image])
|
||||
// `webSearch` is the one ability that does come from the model id, and
|
||||
@@ -542,7 +579,7 @@ struct ModelCapabilityResolverTests {
|
||||
func reportedFieldsWinOverCodexAutoReviewOverride() {
|
||||
let provider = ModelProvider(name: "OpenAI")
|
||||
var model = AIModel(id: "codex-auto-review")
|
||||
model.kind = .other
|
||||
model.kind = .embedding
|
||||
model.input = [.audio]
|
||||
model.output = [.image]
|
||||
model.abilities = []
|
||||
@@ -555,7 +592,7 @@ struct ModelCapabilityResolverTests {
|
||||
reported: [.kind, .input, .output, .abilities]
|
||||
)
|
||||
|
||||
#expect(resolution.model.kind == .other)
|
||||
#expect(resolution.model.kind == .embedding)
|
||||
#expect(resolution.model.input == [.audio])
|
||||
#expect(resolution.model.output == [.image])
|
||||
#expect(resolution.model.abilities.isEmpty)
|
||||
@@ -568,7 +605,7 @@ struct ModelCapabilityResolverTests {
|
||||
func explicitlyReportedFieldsWinOverPiCatalog() {
|
||||
let provider = ModelProvider(name: "OpenAI")
|
||||
var model = AIModel(id: "gpt-5.4")
|
||||
model.kind = .other
|
||||
model.kind = .embedding
|
||||
model.input = [.audio]
|
||||
model.output = [.image]
|
||||
model.abilities = []
|
||||
@@ -581,7 +618,7 @@ struct ModelCapabilityResolverTests {
|
||||
reported: [.kind, .input, .output, .abilities]
|
||||
)
|
||||
|
||||
#expect(resolution.model.kind == .other)
|
||||
#expect(resolution.model.kind == .embedding)
|
||||
#expect(resolution.model.input == [.audio])
|
||||
#expect(resolution.model.output == [.image])
|
||||
// Reported abilities still win — `toolCall` and `reasoning` stay off
|
||||
|
||||
Reference in new issue
Block a user