Skip to content

Commit 59ae064

Browse files
committed
Update QA and WebScrapper to work properly
1 parent 8560aee commit 59ae064

10 files changed

Lines changed: 306 additions & 152 deletions

File tree

Playground.playground/Pages/Hello.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 84 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,98 @@
1+
import AppKit
12
import LangChain
23
import OpenAIService
34
import PlaygroundSupport
45
import SwiftUI
56

6-
let memory = ConversationChatGPTMemory(systemPrompt: "")
7-
let chatGPTConfiguration = UserPreferenceChatGPTConfiguration().overriding {
8-
$0.temperature = 0.2
9-
}
7+
struct QAForm: View {
8+
@State var intermediateAnswers = [String]()
9+
@State var answer: String = ""
10+
@State var question: String = "What is Swift macros?"
11+
@State var isProcessing: Bool = false
12+
@State var url: String = "https://developer.apple.com/documentation/swift/applying-macros"
13+
14+
var body: some View {
15+
Form {
16+
Section(header: Text("Input")) {
17+
TextField("URL", text: $url)
18+
TextField("Question", text: $question)
19+
Button("Ask") {
20+
Task {
21+
do {
22+
try await ask()
23+
} catch {
24+
answer = error.localizedDescription
25+
}
26+
}
27+
}
28+
.disabled(isProcessing)
29+
}
30+
Section(header: Text("Answer")) {
31+
Text(answer)
32+
}
33+
Section(header: Text("Intermediate Answers")) {
34+
ForEach(intermediateAnswers, id: \.self) { answer in
35+
Text(answer)
36+
Divider()
37+
}
38+
}
39+
}
40+
.formStyle(.grouped)
41+
}
1042

11-
let embeddingConfiguration = UserPreferenceEmbeddingConfiguration().overriding()
43+
func ask() async throws {
44+
intermediateAnswers = []
45+
isProcessing = true
46+
defer { isProcessing = false }
47+
guard let url = URL(string: url) else {
48+
answer = "Invalid URL"
49+
return
50+
}
51+
let chatGPTConfiguration = UserPreferenceChatGPTConfiguration()
52+
.overriding { $0.temperature = 0 }
53+
let embeddingConfiguration = UserPreferenceEmbeddingConfiguration().overriding()
54+
let embedding = OpenAIEmbedding(configuration: embeddingConfiguration)
55+
let store: VectorStore = try await {
56+
if let store = await TemporaryUSearch.view(identifier: url.absoluteString) {
57+
return store
58+
} else {
59+
let webLoader = WebLoader(urls: [url])
60+
let store = TemporaryUSearch(identifier: url.absoluteString)
61+
let webDocuments = try await webLoader.load()
62+
let splitter = RecursiveCharacterTextSplitter(
63+
chunkSize: 1000,
64+
chunkOverlap: 100
65+
)
66+
let splitDocuments = try await splitter.transformDocuments(webDocuments)
67+
let embeddedDocuments = try await embedding.embed(documents: splitDocuments)
68+
try await store.set(embeddedDocuments)
69+
return store
70+
}
71+
}()
1272

13-
struct FakeVectorStore: VectorStore {
14-
func add(_: [EmbeddedDocument]) async throws {}
15-
func set(_: [EmbeddedDocument]) async throws {}
16-
func clear() async throws {}
17-
func searchWithDistance(embeddings: [Float], count: Int) async throws
18-
-> [(document: Document, distance: Float)]
19-
{
20-
return [
21-
(
22-
document: .init(
23-
pageContent: """
24-
Snoopy is an anthropomorphic beagle[5] in the comic strip Peanuts by Charles M. Schulz. He can also be found in all of the Peanuts films and television specials. Since his debut on October 4, 1950, Snoopy has become one of the most recognizable and iconic characters in the comic strip and is considered more famous than Charlie Brown in some countries. The original drawings of Snoopy were inspired by Spike, one of Schulz's childhood dogs.
25-
""",
26-
metadata: [:]
27-
),
28-
distance: 0.2
29-
),
30-
(
31-
document: .init(
32-
pageContent: """
33-
Snoopy is a loyal, imaginative, and good-natured beagle who is prone to imagining fantasy lives, including being an author, a college student known as "Joe Cool", an attorney, and a World War I flying ace. He is perhaps best known in this last persona, wearing an aviator's helmet and goggles and a scarf while carrying a swagger stick (like a stereotypical British Army officer of World War I and II).
34-
""",
35-
metadata: [:]
36-
),
37-
distance: 0.2
38-
),
39-
(
40-
document: .init(
41-
pageContent: """
42-
Snoopy can be selfish, gluttonous and lazy at times, and occasionally mocks his owner, Charlie Brown. But on the whole, he shows great love, care, and loyalty for his owner (even though he cannot even remember his name and always refers to him as "the round-headed kid"). In the 1990s comic strips, he is obsessed with cookies, particularly the chocolate-chip variety. This, and other instances in which he indulges in large chocolate-based meals and snacks, shows resistance to theobromine unheard of in other dogs.
43-
""",
44-
metadata: [:]
45-
),
46-
distance: 0.2
47-
),
48-
(
49-
document: .init(
50-
pageContent: """
51-
First appearance October 4, 1950 (comic strip)
52-
Last appearance February 13, 2000 (comic strip)
53-
Created by Charles M. Schulz
54-
Voiced by
55-
- Bill Melendez (1959–2008; 2015 archival recordings used in Peanuts Motion Comics, Snoopy's Grand Adventure,[1] and The Peanuts Movie)
56-
- Bill Hinnant (1966; You're a Good Man, Charlie Brown)[2]
57-
- Jim Campbell (1967; You're a Good Man, Charlie Brown)[3]
58-
- Robert Towers (1985)
59-
- Cam Clarke (1986–1989)
60-
- Gerald Paradies (2002)[4]
61-
- Andy Beall (2011)
62-
- Dylan Jones (2018–present)
63-
- Terry McGurrin (2019–present)
64-
Aliases
65-
- Joe Cool
66-
- World Famous World War I Flying Ace
67-
- The World's Greatest Writer
68-
- The World Famous Attorney
69-
- The World Famous Tennis Pro
70-
Species Dog (Beagle)
71-
Gender Male
72-
Family
73-
- Brothers: Spike, Andy, Olaf, Marbles, Rover
74-
- Sisters: Belle, Molly
75-
- Owner: Charlie Brown
76-
- Sally Brown
77-
- Lila (previously)
78-
- Clara ("the annoying girl")
79-
""",
80-
metadata: [:]
81-
),
82-
distance: 0.2
83-
),
84-
]
73+
let qa = RetrievalQAChain(
74+
vectorStore: store,
75+
embedding: embedding,
76+
chatModelFactory: { OpenAIChat(configuration: chatGPTConfiguration, stream: false) }
77+
)
78+
answer = try await qa.run(
79+
question,
80+
callbackManagers: [
81+
.init {
82+
$0.on(CallbackEvents.RetrievalQADidGenerateIntermediateAnswer.self) {
83+
intermediateAnswers.append($0)
84+
}
85+
},
86+
]
87+
)
8588
}
8689
}
8790

88-
let qa = RetrievalQAChain(
89-
vectorStore: FakeVectorStore(),
90-
embedding: OpenAIEmbedding(configuration: embeddingConfiguration),
91-
chatModelFactory: { OpenAIChat(configuration: chatGPTConfiguration, stream: false) }
91+
let hostingView = NSHostingController(
92+
rootView: QAForm()
93+
.frame(width: 600, height: 800)
9294
)
9395

94-
let answer = try await qa.run("Who is the creator of Snoopy?")
95-
9696
PlaygroundPage.current.needsIndefiniteExecution = true
97+
PlaygroundPage.current.liveView = hostingView
9798

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import AppKit
2+
import LangChain
3+
import PlaygroundSupport
4+
import SwiftUI
5+
6+
struct ScrapperForm: View {
7+
@State var webDocuments: [Document] = []
8+
@State var isProcessing: Bool = false
9+
@State var url: String = "https://developer.apple.com/documentation/swift/applying-macros"
10+
11+
var body: some View {
12+
Form {
13+
Section(header: Text("Input")) {
14+
TextField("URL", text: $url)
15+
Button("Scrap") {
16+
Task {
17+
do {
18+
try await scrap()
19+
} catch {
20+
webDocuments =
21+
[.init(pageContent: error.localizedDescription, metadata: [:])]
22+
}
23+
}
24+
}
25+
.disabled(isProcessing)
26+
}
27+
Section(header: Text("Web Content")) {
28+
ForEach(webDocuments, id: \.pageContent) { document in
29+
VStack(alignment: .leading) {
30+
Text(document.pageContent)
31+
.font(.body)
32+
}
33+
Divider()
34+
}
35+
}
36+
}
37+
.formStyle(.grouped)
38+
}
39+
40+
func scrap() async throws {
41+
webDocuments = []
42+
isProcessing = true
43+
defer { isProcessing = false }
44+
guard let url = URL(string: url) else { return }
45+
let webLoader = WebLoader(urls: [url])
46+
webDocuments = try await webLoader.load()
47+
}
48+
}
49+
50+
let hostingView = NSHostingController(
51+
rootView: ScrapperForm()
52+
.frame(width: 600, height: 800)
53+
)
54+
55+
PlaygroundPage.current.needsIndefiniteExecution = true
56+
PlaygroundPage.current.liveView = hostingView
57+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
22
<playground version='6.0' target-platform='macos' buildActiveScheme='true' importAppTypes='true'>
33
<pages>
4-
<page name='Untitled Page 2'/>
54
<page name='RetrievalQAChain'/>
5+
<page name='WebScrapper'/>
66
</pages>
77
</playground>

Tool/Sources/LangChain/Callback.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public protocol CallbackEvent {
88
public enum CallbackEvents {}
99

1010
public struct CallbackManager {
11-
private var observers = [Any]()
11+
fileprivate var observers = [Any]()
1212

1313
public init() {}
1414

@@ -31,3 +31,13 @@ public struct CallbackManager {
3131
}
3232
}
3333
}
34+
35+
public extension [CallbackManager] {
36+
func send<Event: CallbackEvent>(_ event: Event) {
37+
for cb in self {
38+
for case let observer as ((Event.Info) -> Void) in cb.observers {
39+
observer(event.info)
40+
}
41+
}
42+
}
43+
}

Tool/Sources/LangChain/Chain.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,11 @@ public extension Chain {
1717
}
1818

1919
func call(_ input: Input, callbackManagers: [CallbackManager] = []) async throws -> Output {
20-
for callbackManager in callbackManagers {
21-
callbackManager
22-
.send(CallbackEvents.ChainDidStart(info: (type: Self.self, input: input)))
23-
}
20+
callbackManagers
21+
.send(CallbackEvents.ChainDidStart(info: (type: Self.self, input: input)))
2422
defer {
25-
for callbackManager in callbackManagers {
26-
callbackManager
27-
.send(CallbackEvents.ChainDidEnd(info: (type: Self.self, input: input)))
28-
}
23+
callbackManagers
24+
.send(CallbackEvents.ChainDidEnd(info: (type: Self.self, input: input)))
2925
}
3026
return try await callLogic(input, callbackManagers: callbackManagers)
3127
}

Tool/Sources/LangChain/Chains/RetrievalQA.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ public final class RetrievalQAChain: Chain {
3030
count: 10
3131
)
3232
let refinementChain = RefineDocumentChain(chatModelFactory: chatModelFactory)
33-
let answer = try await refinementChain.run(.init(question: input, documents: documents))
33+
let answer = try await refinementChain.run(
34+
.init(question: input, documents: documents),
35+
callbackManagers: callbackManagers
36+
)
3437

3538
return .init(answer: answer, sourceDocuments: documents.map(\.document))
3639
}
@@ -40,6 +43,12 @@ public final class RetrievalQAChain: Chain {
4043
}
4144
}
4245

46+
public extension CallbackEvents {
47+
struct RetrievalQADidGenerateIntermediateAnswer: CallbackEvent {
48+
public let info: String
49+
}
50+
}
51+
4352
public final class RefineDocumentChain: Chain {
4453
public struct Input {
4554
var question: String
@@ -79,7 +88,8 @@ public final class RefineDocumentChain: Chain {
7988
chatModel: chatModelFactory(),
8089
promptTemplate: { input in [
8190
.init(role: .system, content: """
82-
The user will send you a question, you must update your previous answer to it at your best.
91+
The user will send you a question, you must refine your previous answer to it at your best.
92+
You should focus on answering the question, there is no need to add extra details in other topics.
8393
Previous answer:###
8494
\(input.previousAnswer)
8595
###
@@ -107,6 +117,7 @@ public final class RefineDocumentChain: Chain {
107117
),
108118
callbackManagers: callbackManagers
109119
)
120+
callbackManagers.send(CallbackEvents.RetrievalQADidGenerateIntermediateAnswer(info: output))
110121
for document in input.documents.dropFirst(1) {
111122
output = try await refinementChatModel.call(
112123
.init(
@@ -117,6 +128,8 @@ public final class RefineDocumentChain: Chain {
117128
),
118129
callbackManagers: callbackManagers
119130
)
131+
callbackManagers
132+
.send(CallbackEvents.RetrievalQADidGenerateIntermediateAnswer(info: output))
120133
}
121134
return output
122135
}

0 commit comments

Comments
 (0)