From e39c5b4a770653f7aaaa86039b1949c37166b2bf Mon Sep 17 00:00:00 2001 From: Fumito Ito Date: Fri, 31 Jan 2025 10:04:39 +0900 Subject: [PATCH] add deepseek's function calling support --- Package.resolved | 6 +- .../FunctionCalling_AIProxySwift.swift | 15 +++ .../FunctionCallingAIProxySwiftTests.swift | 91 +++++++++++++++++++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index 44cdd1a..039e58a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "17e03520338e7a0d6ed4d93aa5bebed6964921d053675f7216fa10f835091113", + "originHash" : "154f2f373238187d79c917cc57bfa63e038f0fa9bad055b8b83d2662d59a1001", "pins" : [ { "identity" : "aiproxyswift", "kind" : "remoteSourceControl", "location" : "https://github.com/lzell/AIProxySwift.git", "state" : { - "revision" : "a71eac7de6e1f9a80a0ba659a66bec7db9196bd6", - "version" : "0.49.0" + "revision" : "f118afad4941db0ca9c7b6c7f993c483ffd287c6", + "version" : "0.62.0" } }, { diff --git a/Sources/FunctionCalling-AIProxySwift/FunctionCalling_AIProxySwift.swift b/Sources/FunctionCalling-AIProxySwift/FunctionCalling_AIProxySwift.swift index 9dbfb04..bbd90d8 100644 --- a/Sources/FunctionCalling-AIProxySwift/FunctionCalling_AIProxySwift.swift +++ b/Sources/FunctionCalling-AIProxySwift/FunctionCalling_AIProxySwift.swift @@ -8,6 +8,7 @@ extension ToolContainer { public typealias AIProxyOpenAITool = OpenAIChatCompletionRequestBody.Tool public typealias AIProxyAnthropicTool = AnthropicTool public typealias AIProxyTogetherAITool = TogetherAITool + public typealias AIProxyDeepSeekTool = DeepSeekChatCompletionRequestBody.Tool // swiftlint:disable:next line_length // https://github.com/lzell/AIProxySwift?tab=readme-ov-file#how-to-use-openai-structured-outputs-json-schemas-in-a-tool-call @@ -52,6 +53,20 @@ extension ToolContainer { ) } } + + // swiftlint:disable:next line_length + // https://github.com/lzell/AIProxySwift/blob/f118afad4941db0ca9c7b6c7f993c483ffd287c6/Sources/AIProxy/DeepSeek/DeepSeekChatCompletionRequestBody.swift#L73-L79 + public func toDeepSeekTools() -> [AIProxyDeepSeekTool] { + guard let allTools else { return [] } + + return allTools.map { tool in + AIProxyDeepSeekTool.function( + name: tool.name, + description: tool.description, + parameters: tool.inputSchema.toJSONSchema() + ) + } + } } private extension FunctionCalling.InputSchema { diff --git a/Tests/FunctionCalling-AIProxySwiftTests/FunctionCallingAIProxySwiftTests.swift b/Tests/FunctionCalling-AIProxySwiftTests/FunctionCallingAIProxySwiftTests.swift index 9ae61f4..4aa8391 100644 --- a/Tests/FunctionCalling-AIProxySwiftTests/FunctionCallingAIProxySwiftTests.swift +++ b/Tests/FunctionCalling-AIProxySwiftTests/FunctionCallingAIProxySwiftTests.swift @@ -295,6 +295,97 @@ final class FunctionCallingAIProxySwiftTests: XCTestCase { } XCTAssertEqual(enumValues, ["option1", "option2"]) } + + func testToDeepSeekTools() throws { + let deepSeekTools = toolContainer.toDeepSeekTools() + + XCTAssertEqual(deepSeekTools.count, 1) + + let deepSeekTool = try XCTUnwrap(deepSeekTools.first) + + guard case .function(let name, let description, let parameters) = deepSeekTool else { + XCTFail("Cannot unwrap function") + return + } + + // name + XCTAssertEqual(name, "testTool") + // description + XCTAssertEqual(description, "A test tool") + + // inputSchema + guard let parameters else { + XCTFail("Parameters should be a dictionary") + return + } + + // inputSchema.type + guard case .string(let type) = parameters["type"] else { + XCTFail("Parameters should be a dictionary") + return + } + XCTAssertEqual(type, "object") + + // inputSchema.requiredProperties + guard case .array(let required) = parameters["required"] else { + XCTFail("Parameters should be a dictionary") + return + } + + let requiredProperties = required.compactMap { requiredProperty in + switch requiredProperty { + case .string(let propertyName): + return propertyName + default: + return nil + } + } + XCTAssertEqual(requiredProperties, ["testParam"]) + + // inputSchema.properties + guard case .object(let properties) = parameters["properties"] else { + XCTFail("Parameters should be a dictionary") + return + } + XCTAssertEqual(properties.count, 1) + + // inputSchema.properties.testParam + let testParam = try XCTUnwrap(properties["testParam"]) + + guard case .object(let prop) = testParam else { + XCTFail("Parameters should be a dictionary") + return + } + + guard case .string(let type) = prop["type"] else { + XCTFail("Parameters should be a dictionary") + return + } + XCTAssertEqual(type, "string") + + guard case .string(let description) = prop["description"] else { + XCTFail("Parameters should be a dictionary") + return + } + XCTAssertEqual(description, "A test parameter") + + guard case .array(let enumValueArray) = prop["enum"] else { + XCTFail("Parameters should be a dictionary") + return + } + + XCTAssertEqual(enumValueArray.count, 2) + + let enumValues = enumValueArray.compactMap { enumValue in + switch enumValue { + case .string(let value): + return value + default: + return nil + } + } + XCTAssertEqual(enumValues, ["option1", "option2"]) + } // swiftlint:enable cyclomatic_complexity // swiftlint:enable function_body_length }