Typesense Swift

使用 Typesense 在 iOS 上实现搜索的绝佳新方式 ⚡️🔍✨ Typesense Swift 是一个高阶封装器,可帮助你轻松地使用 Typesense 实现搜索。

安装

Typesense Swift Swift 包添加到你的项目中。你可以参考 Apple 的文档 将 Typesense Swift 作为依赖项添加到你的 iOS 项目中。你也可以通过将以下行添加到 Package.swift 的 dependencies 数组中,将 Typesense 导入到你自己的 Swift 包中

...
dependencies: [
           .package(url: "https://github.com/typesense/typesense-swift", .upToNextMajor(from: "1.0.0"),
],
...

使用方法

设置客户端

将 Typesense 导入到你的 Swift 项目中

import Typesense

声明可用的 Typesense 节点作为 Node 成员

let node1 = Node(url: "https://:8108") // or
let node2 = Node(host: "xxx-1.a1.typesense.net", port: "443", nodeProtocol: "https")

使用提到的节点创建配置,并由此创建客户端

let myConfig = Configuration(nodes: [node1, node2], apiKey: "coolstuff")

let client = Client(config: myConfig)

在创建配置时,你可以使用 Typesense 参数,如 nearestNodeconnectionTimeoutSeconds。你也可以传入 logger 参数来调试代码,如下所示

let myConfig = Configuration(nodes: [node1, node2], apiKey: "coolstuff", logger: Logger(debugMode: true))

索引文档

你可以通过首先定义集合模式来创建一个集合

let myCoolSchema = CollectionSchema(name: "schools", fields: [Field(name: "school_name", type: "string"), Field(name: "num_students", type: "int32"), Field(name: "country", type: "string", facet: true)], defaultSortingField: "num_students")

let (data, response) = try await client.collections.create(schema: myCoolSchema)

根据你的集合定义你的文档结构,并通过将其插入/更新到集合中来索引它

struct School: Codable {
    var id: String
    var school_name: String
    var num_students: Int
    var country: String
}

let document = School(id: "7", school_name: "Hogwarts", num_students: 600, country: "United Kingdom")
let documentData = try JSONEncoder().encode(document)
let (data, response) = try await client.collection(name: "schools").documents().create(document: documentData)
//or
let (data, response) = try await client.collection(name: "schools").documents().upsert(document: documentData)

你可以对属于特定集合的 CollectionsDocuments 执行 CRUD 操作。你也可以在 documents() 方法上使用 .importBatch() 来导入和索引一批文档(以 .jsonl 格式)。

搜索

清楚地定义你的 搜索参数,然后通过提及你的文档类型来执行搜索操作

let searchParameters = SearchParameters(q: "hog", queryBy: "school_name", filterBy: "num_students:>500", sortBy: "num_students:desc")

let (data, response) = try await client.collection(name: "schools").documents().search(searchParameters, for: School.self)

这将返回一个 SearchResult 对象作为数据,可以根据需要进一步解析。

批量导入文档

let jsonL = Data("{}".utf8)
let (data, response) = try await client.collection(name: "companies").documents().importBatch(jsonL, options: ImportDocumentsParameters(
    action: .upsert,
    batchSize: 10,
    dirtyValues: .drop,
    remoteEmbeddingBatchSize: 10,
    returnDoc: true,
    returnId: false
))

通过查询更新多个文档

let (data, response) = try await client.collection(name: "companies").documents().update(
    document: ["company_size": "large"],
    options: UpdateDocumentsByFilterParameters(filterBy: "num_employees:>1000")
)

通过查询删除多个文档

let (data, response) = try await client.collection(name: "companies").documents().delete(
    options: DeleteDocumentsParameters(filterBy: "num_employees:>100")
)

导出文档

let (data, response) = try await client.collection(name: "companies").documents().export(options: ExportDocumentsParameters(excludeFields: "country"))

创建或更新集合别名

let schema = CollectionAliasSchema(collectionName: "companies_june")
let (data, response) = try await client.aliases().upsert(name: "companies", collection: schema)

检索所有别名

let (data, response) = try await client.aliases().retrieve()

检索一个别名

let (data, response) = try await client.aliases().retrieve(name: "companies")

删除一个别名

let (data, response) = try await client.aliases().delete(name: "companies")

创建 API 密钥

let adminKey = ApiKeySchema(_description: "Test key with all privileges", actions: ["*"], collections: ["*"])
let (data, response) = try await client.keys().create(adminKey)

检索所有 API 密钥

let (data, response) = try await client.keys().retrieve()

检索一个 API 密钥

let (data, response) = try await client.keys().retrieve(id: 1)

删除一个 API 密钥

let (data, response) = try await client.keys().delete(id: 1)

创建会话模型

let schema = ConversationModelCreateSchema(
    _id: "conv-model-1",
    modelName: "openai/gpt-3.5-turbo",
    apiKey: "OPENAI_API_KEY",
    historyCollection: "conversation_store",
    systemPrompt: "You are an assistant for question-answering...",
    ttl: 10000,
    maxBytes: 16384
)
let (data, response) = try await client.conversations().models().create(params: schema)

检索所有会话模型

let (data, response) = try await client.conversations().models().retrieve()

检索一个会话模型

let (data, response) = try await client.conversations().model(modelId: "conv-model-1").retrieve()

更新一个会话模型

let (data, response) = try await client.conversations().model(modelId: "conv-model-1").update(params: ConversationModelUpdateSchema(
    systemPrompt: "..."
))

删除一个会话模型

let (data, response) = try await client.conversations().model(modelId: "conv-model-1").delete()

创建或更新覆盖

let schema = SearchOverrideSchema<MetadataType>(
    rule: SearchOverrideRule(tags: ["test"], query: "apple", match: SearchOverrideRule.Match.exact, filterBy: "employees:=50"),
    includes: [SearchOverrideInclude(_id: "include-id", position: 1)],
    excludes: [SearchOverrideExclude(_id: "exclude-id")],
    filterBy: "test:=true",
    removeMatchedTokens: false,
    metadata: MetadataType(message: "test-json"),
    sortBy: "num_employees:desc",
    replaceQuery: "test",
    filterCuratedHits: false,
    effectiveFromTs: 123,
    effectiveToTs: 456,
    stopProcessing: false
)
let (data, response) = try await client.collection(name: "books").overrides().upsert(overrideId: "test-id", params: schema)

检索所有覆盖

let (data, response) = try await client.collection(name: "books").overrides().retrieve(metadataType: Never.self)

检索一个覆盖

let (data, response) = try await client.collection(name: "books").override("test-id").retrieve(metadataType: MetadataType.self)

删除一个覆盖

let (data, response) = try await client.collection(name: "books").override("test-id").delete()

创建或更新预设

let schema = PresetUpsertSchema(
    value: PresetValue.singleCollectionSearch(SearchParameters(q: "apple"))
    // or: value: PresetValue.multiSearch(MultiSearchSearchesParameter(searches: [MultiSearchCollectionParameters(q: "apple")]))
)
let (data, response) = try await client.presets().upsert(presetName: "listing_view", params: schema)

检索所有预设

let (data, response) = try await client.presets().retrieve()

检索一个预设

let (data, response) = try await client.preset("listing_view").retrieve()

switch data?.value {
    case .singleCollectionSearch(let value):
        print(value)
    case .multiSearch(let value):
        print(value)
}

删除一个预设

let (data, response) = try await client.preset("listing_view").delete()

创建或更新停用词集

let schema = StopwordsSetUpsertSchema(
    stopwords: ["states","united"],
    locale: "en"
)
let (data, response) = try await client.stopwords().upsert(stopwordsSetId: "stopword_set1", params: schema)

检索所有停用词集

let (data, response) = try await client.stopwords().retrieve()

检索一个停用词集

let (data, response) = try await client.stopword("stopword_set1").retrieve()

删除一个停用词集

let (data, response) = try await client.stopword("stopword_set1").delete()

创建或更新同义词

let schema = SearchSynonymSchema(synonyms: ["blazer", "coat", "jacket"])
let (data, response) = try await client.collection(name: "products").synonyms().upsert(id: "coat-synonyms", schema)

检索所有同义词

let (data, response) = try await client.collection(name: "products").synonyms().retrieve()

检索一个同义词

let (data, response) = try await client.collection(name: "products").synonyms().retrieve(id: "coat-synonyms")

删除一个同义词

let (data, response) = try await myClient.collection(name: "products").synonyms().delete(id: "coat-synonyms")

检索调试信息

let (data, response) = try await client.operations().getDebug()

检索健康状态

let (data, response) = try await client.operations().getHealth()

检索 API 统计信息

let (data, response) = try await client.operations().getStats()

检索集群指标

let (data, response) = try await client.operations().getMetrics()

重新选举 Leader

let (data, response) = try await client.operations().vote()

切换慢请求日志

let (data, response) = try await client.operations().toggleSlowRequestLog(seconds: 2)

清除缓存

let (data, response) = try await client.operations().clearCache()

创建快照(用于备份)

let (data, response) = try await client.operations().snapshot(path: "/tmp/typesense-data-snapshot")

贡献

欢迎在 GitHub 上提交 Issues 和 Pull Requests 到 Typesense Swift。请注意,Swift 客户端中使用的模型是由 Swagger-Codegen 生成的,并且被自动化修改以防止重大错误。因此,请务必使用仓库中提供的 shell 脚本来生成模型

sh get-models.sh

生成的模型(在 Models 目录中)也需要在源代码的 Models 目录中使用。当 Typesense-Api-Spec 更新时,需要生成模型。

TODO: 功能