Skip to content

Commit 38e0b2a

Browse files
committed
add Utility function to handle toolUse response
1 parent 49e1929 commit 38e0b2a

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//
2+
// Utility.swift
3+
// FunctionCalling-AnthropicSwiftSDK
4+
//
5+
// Created by 伊藤史 on 2024/10/31.
6+
//
7+
8+
import AnthropicSwiftSDK
9+
import FunctionCalling
10+
11+
extension ToolContainer {
12+
/// Return the result of tool execution back to the model as a user message
13+
///
14+
/// - Parameters:
15+
/// - toolUseResponse: Message response from API to execute tools with arguments.
16+
/// - messages: An array of Message objects representing the input prompt for message generation.
17+
/// - model: The model to be used for generating the message. Default is `.claude_3_Opus`.
18+
/// - system: The system identifier. Default is `nil`.
19+
/// - maxTokens: The maximum number of tokens in the generated message.
20+
/// - metaData: Additional metadata for the request. Default is `nil`.
21+
/// - stopSequence: An array of strings representing sequences where the message generation should stop.
22+
/// - temperature: The temperature parameter controls the randomness of the generated text. Default is `nil`.
23+
/// - topP: The nucleus sampling parameter. Default is `nil`.
24+
/// - topK: The top-k sampling parameter. Default is `nil`.
25+
/// - toolContainer: The tool provider for `tool_use`. Default is `nil`
26+
/// - toolChoice: The parameter for tool choice. Default is `.auto`
27+
/// - anthropicHeaderProvider: The provider for the anthropic header NOT required for API authentication.
28+
/// - authenticationHeaderProvider: The provider for the authentication header required for API authentication.
29+
/// - Returns: A `MessagesResponse` object representing the response from the Anthropic API.
30+
/// - Throws: An error if the request fails or if there's an issue decoding the response.
31+
func sendToolResultIfNeeded(
32+
_ anthropic: Anthropic,
33+
forResponse response: MessagesResponse,
34+
priviousMessages messages: [Message],
35+
model: Model = .claude_3_Opus,
36+
system: [SystemPrompt] = [],
37+
maxTokens: Int,
38+
metaData: MetaData? = nil,
39+
stopSequence: [String]? = nil,
40+
temperature: Double? = nil,
41+
topP: Double? = nil,
42+
topK: Int? = nil,
43+
tools: [AnthropicSwiftSDK.Tool]? = nil,
44+
toolChoice: ToolChoice = .auto,
45+
anthropicHeaderProvider: AnthropicHeaderProvider,
46+
authenticationHeaderProvider: AuthenticationHeaderProvider
47+
) async throws -> MessagesResponse {
48+
guard case .toolUse = response.stopReason else {
49+
return response
50+
}
51+
52+
// If a `tool_use` response is returned with no `ToolContainer` specified, `tool_use_result` cannot be returned because any tool does not exist.
53+
guard let tools, tools.isEmpty == false else {
54+
throw ClientError.anyToolsAreDefined
55+
}
56+
57+
guard case .toolUse(let toolUseContent) = response.content.first(where: { $0.contentType == .toolUse }) else {
58+
throw ClientError.cannotFindToolUseContentFromResponse(response)
59+
}
60+
61+
let toolResult = await self.execute(methodName: toolUseContent.name, parameters: toolUseContent.input)
62+
let toolResultRequest = messages + [
63+
.init(role: .assistant, content: [.toolUse(toolUseContent)]),
64+
.init(role: .user, content: [
65+
.toolResult(
66+
.init(
67+
toolUseId: toolUseContent.id,
68+
content: [
69+
.text(toolResult)
70+
],
71+
isError: nil
72+
)
73+
)
74+
]
75+
)
76+
]
77+
78+
return try await anthropic.messages.createMessage(
79+
toolResultRequest,
80+
model: model,
81+
system: system,
82+
maxTokens: maxTokens,
83+
metaData: metaData,
84+
stopSequence: stopSequence,
85+
temperature: temperature,
86+
topP: topP,
87+
topK: topK,
88+
tools: tools,
89+
toolChoice: toolChoice,
90+
anthropicHeaderProvider: anthropicHeaderProvider,
91+
authenticationHeaderProvider: authenticationHeaderProvider
92+
)
93+
}
94+
95+
/// Receive response from Claude in Stream format.
96+
///
97+
/// If there is a response related to `tool_use`, the information is compiled and streamed.
98+
///
99+
/// - Parameters:
100+
/// - stream: response stream from Claude Stream API
101+
/// - messages: An array of Message objects representing the input prompt for message generation.
102+
/// - model: The model to be used for generating the message. Default is `.claude_3_Opus`.
103+
/// - system: The system identifier. Default is `nil`.
104+
/// - maxTokens: The maximum number of tokens in the generated message.
105+
/// - metaData: Additional metadata for the request. Default is `nil`.
106+
/// - stopSequence: An array of strings representing sequences where the message generation should stop.
107+
/// - temperature: The temperature parameter controls the randomness of the generated text. Default is `nil`.
108+
/// - topP: The nucleus sampling parameter. Default is `nil`.
109+
/// - topK: The top-k sampling parameter. Default is `nil`.
110+
/// - toolContainer: The tool provider for `tool_use`. Default is `nil`
111+
/// - toolChoice: The parameter for tool choice. Default is `.auto`
112+
/// - anthropicHeaderProvider: The provider for the anthropic header NOT required for API authentication.
113+
/// - authenticationHeaderProvider: The provider for the authentication header required for API authentication.
114+
/// - Returns: Claude Stream API response stream. If `stream` returns `tool_use` content, this method returns re-request new stream.
115+
func streamToolResultIfNeeded(
116+
_ anthropic: Anthropic,
117+
forStream stream: AsyncThrowingStream<StreamingResponse, Error>,
118+
priviousMessages: [Message],
119+
model: Model,
120+
system: [SystemPrompt],
121+
maxTokens: Int,
122+
metaData: MetaData?,
123+
stopSequence: [String]?,
124+
temperature: Double?,
125+
topP: Double?,
126+
topK: Int?,
127+
tools: [AnthropicSwiftSDK.Tool]? = nil,
128+
toolChoice: ToolChoice,
129+
anthropicHeaderProvider: AnthropicHeaderProvider,
130+
authenticationHeaderProvider: AuthenticationHeaderProvider
131+
) async throws -> AsyncThrowingStream<StreamingResponse, Error> {
132+
AsyncThrowingStream { continuation in
133+
Task {
134+
do {
135+
for try await response in stream {
136+
if
137+
let deltaResponse = response as? StreamingMessageDeltaResponse,
138+
let toolUseContent = deltaResponse.toolUseContent,
139+
let toolResultContent = await deltaResponse.getToolResultContent(from: self),
140+
response.isToolUse {
141+
let streamWithToolResult = try await anthropic.messages.streamMessage(
142+
priviousMessages + [
143+
.init(role: .assistant, content: [.toolUse(toolUseContent)]),
144+
.init(role: .user, content: [toolResultContent])
145+
],
146+
model: model,
147+
system: system,
148+
maxTokens: maxTokens,
149+
metaData: metaData,
150+
stopSequence: stopSequence,
151+
temperature: temperature,
152+
topP: topP,
153+
topK: topK,
154+
tools: tools,
155+
toolChoice: toolChoice,
156+
anthropicHeaderProvider: anthropicHeaderProvider,
157+
authenticationHeaderProvider: authenticationHeaderProvider
158+
)
159+
160+
for try await responseWithToolResult in streamWithToolResult {
161+
continuation.yield(responseWithToolResult)
162+
}
163+
} else {
164+
continuation.yield(response)
165+
}
166+
}
167+
continuation.finish()
168+
} catch {
169+
continuation.finish(throwing: error)
170+
}
171+
}
172+
}
173+
}
174+
}
175+
176+
extension StreamingMessageDeltaResponse {
177+
/// If this response contains the `tool_use` property, the result of the `Tool` call is obtained using the `ToolContainer` given in the argument.
178+
/// - Parameter toolContainer: Takes the `tool_use` in the response and returns the result.
179+
/// - Returns: The result of tool use
180+
func getToolResultContent(from toolContainer: ToolContainer) async -> Content? {
181+
guard let toolUseContent else {
182+
return nil
183+
}
184+
185+
let result = await toolContainer.execute(methodName: toolUseContent.name, parameters: toolUseContent.input)
186+
187+
return .toolResult(
188+
.init(
189+
toolUseId: toolUseContent.id,
190+
content: [
191+
.text(result)
192+
],
193+
isError: false
194+
)
195+
)
196+
}
197+
}

0 commit comments

Comments
 (0)