forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRefineDocumentChain.swift
More file actions
192 lines (171 loc) · 6.69 KB
/
Copy pathRefineDocumentChain.swift
File metadata and controls
192 lines (171 loc) · 6.69 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import ChatBasic
import Foundation
import OpenAIService
import Preferences
public final class RefineDocumentChain: Chain {
public struct Input {
var question: String
var documents: [(document: Document, distance: Float)]
}
struct RefinementInput {
var index: Int
var totalCount: Int
var question: String
var previousAnswer: String?
var document: String
var distance: Float
}
public struct IntermediateAnswer: Decodable {
public var answer: String
public var usefulness: Double
public var more: Bool
public enum CodingKeys: String, CodingKey {
case answer
case usefulness
case more
}
init(answer: String, usefulness: Double, more: Bool) {
self.answer = answer
self.usefulness = usefulness
self.more = more
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
answer = try container.decode(String.self, forKey: .answer)
usefulness = (try? container.decode(Double.self, forKey: .usefulness)) ?? 0
more = (try? container.decode(Bool.self, forKey: .more)) ?? true
}
}
class FunctionProvider: ChatGPTFunctionProvider {
var functionCallStrategy: FunctionCallStrategy? = .function(name: "respond")
var functions: [any ChatGPTFunction] = [RespondFunction()]
}
struct RespondFunction: ChatGPTArgumentsCollectingFunction {
typealias Arguments = IntermediateAnswer
var name: String = "respond"
var description: String = "Respond with the refined answer"
var argumentSchema: JSONSchemaValue {
return [
.type: "object",
.properties: [
"answer": [
.type: "string",
.description: "The refined answer",
],
"usefulness": [
.type: "number",
.description: "How useful the page of document is in generating the answer, the higher the better. 0 to 10",
],
"more": [
.type: "boolean",
.description: "Whether you want to read the next page. The next page maybe less relevant to the question",
],
],
.required: ["answer", "more", "usefulness"],
]
}
}
func buildChatModel() -> ChatModelChain<RefinementInput> {
.init(
chatModel: OpenAIChat(
configuration: UserPreferenceChatGPTConfiguration(
chatModelKey: \.preferredChatModelIdForUtilities
)
.overriding {
$0.temperature = 0
$0.runFunctionsAutomatically = false
},
memory: EmptyChatGPTMemory(),
functionProvider: FunctionProvider(),
stream: false
),
promptTemplate: { input in [
.init(
role: .system,
content: {
if let previousAnswer = input.previousAnswer {
return """
I will send you a question about a document, you must refine your previous answer to it only according to the document.
Previous answer:###
\(previousAnswer)
###
Page \(input.index) of \(input.totalCount) of the document:###
\(input.document)
###
"""
} else {
return """
I will send you a question about a document, you must answer it only according to the document.
Page \(input.index) of \(input.totalCount) of the document:###
\(input.document)
###
"""
}
}()
),
.init(role: .user, content: input.question),
] }
)
}
public init() {}
public func callLogic(
_ input: Input,
callbackManagers: [CallbackManager]
) async throws -> String {
var intermediateAnswer: IntermediateAnswer?
for (index, document) in input.documents.enumerated() {
if let intermediateAnswer, !intermediateAnswer.more { break }
let output = try await buildChatModel().call(
.init(
index: index,
totalCount: input.documents.count,
question: input.question,
previousAnswer: intermediateAnswer?.answer,
document: document.document.pageContent,
distance: document.distance
),
callbackManagers: callbackManagers
)
intermediateAnswer = extractAnswer(output)
if let intermediateAnswer {
callbackManagers.send(
\.refineDocumentChainDidGenerateIntermediateAnswer,
intermediateAnswer
)
}
}
return intermediateAnswer?.answer ?? "None"
}
public func parseOutput(_ output: String) -> String {
return output
}
func extractAnswer(_ chatMessage: ChatMessage) -> IntermediateAnswer {
for functionCall in chatMessage.toolCalls?.map(\.function) ?? [] {
do {
let intermediateAnswer = try JSONDecoder().decode(
IntermediateAnswer.self,
from: functionCall.arguments.data(using: .utf8) ?? Data()
)
return intermediateAnswer
} catch {
let intermediateAnswer = IntermediateAnswer(
answer: functionCall.arguments,
usefulness: 0,
more: true
)
return intermediateAnswer
}
}
return .init(answer: chatMessage.content ?? "", usefulness: 0, more: true)
}
}
public extension CallbackEvents {
struct RefineDocumentChainDidGenerateIntermediateAnswer: CallbackEvent {
public let info: RefineDocumentChain.IntermediateAnswer
}
var refineDocumentChainDidGenerateIntermediateAnswer:
RefineDocumentChainDidGenerateIntermediateAnswer.Type
{
RefineDocumentChainDidGenerateIntermediateAnswer.self
}
}