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
129 lines (112 loc) · 4.26 KB
/
SearchQuery.swift
File metadata and controls
129 lines (112 loc) · 4.26 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
121
122
123
124
125
126
127
128
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 -> AsyncThrowingStream<SearchEvent, Error> {
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: 2,
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 {
print("Chain \(type) is started with input \(input).")
}
func onAgentFinish(output: LangChain.AgentFinish) {
print("Agent is finished: \(output.returnValue)")
}
func onAgentActionStart(action: LangChain.AgentAction) {
print("Agent runs action: \(action.toolName) with input \(action.toolInput)")
onAgentActionStart("\(action.toolName): \(action.toolInput)")
}
func onAgentActionEnd(action: LangChain.AgentAction) {
print(
"""
Agent finish running action: \
\(action.toolName) with observation \
\(action.observation ?? "")
"""
)
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)
}
}
}
}