forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSearchQuery.swift
More file actions
121 lines (104 loc) · 3.96 KB
/
SearchQuery.swift
File metadata and controls
121 lines (104 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import BingSearchService
import Foundation
import LangChain
enum SearchEvent {
case startAction(String)
case endAction(String)
case answerToken(String)
case finishAnswer(String, [(title: String, link: String)])
}
func search(_ query: String) async throws
-> (stream: AsyncThrowingStream<SearchEvent, Error>, cancel: () async -> Void)
{
let bingSearch = BingSearchService(
subscriptionKey: UserDefaults.shared.value(for: \.bingSearchSubscriptionKey),
searchURL: UserDefaults.shared.value(for: \.bingSearchEndpoint)
)
final class LinkStorage {
var links = [(title: String, link: String)]()
}
let linkStorage = LinkStorage()
let tools = [
SimpleAgentTool(
name: "Search",
description: "useful for when you need to answer questions about current events. Don't search for the same thing twice",
run: {
linkStorage.links = []
let result = try await bingSearch.search(query: $0, numberOfResult: 5)
let websites = result.webPages.value
var string = ""
for (index, website) in websites.enumerated() {
string.append("[\(index)]:###\(website.snippet)###\n")
linkStorage.links.append((website.name, website.url))
}
return string
}
),
]
let chatModel = OpenAIChat(temperature: 0, stream: true)
let agentExecutor = AgentExecutor(
agent: ChatAgent(chatModel: chatModel, tools: tools),
tools: tools,
maxIteration: UserDefaults.shared.value(for: \.chatSearchPluginMaxIterations),
earlyStopHandleType: .generate
)
class ResultCallbackManager: ChainCallbackManager {
var accumulation: String = ""
var isGeneratingFinalAnswer = false
var onFinalAnswerToken: (String) -> Void
var onAgentActionStart: (String) -> Void
var onAgentActionEnd: (String) -> Void
init(
onFinalAnswerToken: @escaping (String) -> Void,
onAgentActionStart: @escaping (String) -> Void,
onAgentActionEnd: @escaping (String) -> Void
) {
self.onFinalAnswerToken = onFinalAnswerToken
self.onAgentActionStart = onAgentActionStart
self.onAgentActionEnd = onAgentActionEnd
}
func onChainStart<T>(type: T.Type, input: T.Input) where T: LangChain.Chain {}
func onAgentFinish(output: LangChain.AgentFinish) {}
func onAgentActionStart(action: LangChain.AgentAction) {
onAgentActionStart("\(action.toolName): \(action.toolInput)")
}
func onAgentActionEnd(action: LangChain.AgentAction) {
onAgentActionEnd("\(action.toolName): \(action.toolInput)")
}
func onLLMNewToken(token: String) {
if isGeneratingFinalAnswer {
onFinalAnswerToken(token)
return
}
accumulation.append(token)
if accumulation.hasSuffix("Final Answer: ") {
isGeneratingFinalAnswer = true
accumulation = ""
}
}
}
return (AsyncThrowingStream<SearchEvent, Error> { continuation in
let callback = ResultCallbackManager(
onFinalAnswerToken: {
continuation.yield(.answerToken($0))
},
onAgentActionStart: {
continuation.yield(.startAction($0))
},
onAgentActionEnd: {
continuation.yield(.endAction($0))
}
)
Task {
do {
let finalAnswer = try await agentExecutor.run(query, callbackManagers: [callback])
continuation.yield(.finishAnswer(finalAnswer, linkStorage.links))
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}, {
await agentExecutor.cancel()
})
}