Skip to content

Commit f80a964

Browse files
committed
Merge branch 'feature/web-access-for-bot' into develop
2 parents 3cb5a6b + fc301b8 commit f80a964

60 files changed

Lines changed: 105537 additions & 211 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
C8216B802980378300AD38C7 /* Helper in Embed XPCService */ = {isa = PBXBuildFile; fileRef = C8216B70298036EC00AD38C7 /* Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
2525
C8520301293C4D9000460097 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8520300293C4D9000460097 /* Helpers.swift */; };
2626
C861A6A329E5503F005C41A3 /* PromptToCodeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */; };
27+
C861A7AD2A41EDDF006A8698 /* python-stdlib in Resources */ = {isa = PBXBuildFile; fileRef = C83E5DEA2A38CD000071506D /* python-stdlib */; };
28+
C861A7AE2A41EF1A006A8698 /* site-packages in Resources */ = {isa = PBXBuildFile; fileRef = C83E5DE92A38CD000071506D /* site-packages */; };
2729
C861E6112994F6070056CB02 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C861E6102994F6070056CB02 /* AppDelegate.swift */; };
2830
C861E6152994F6080056CB02 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C861E6142994F6080056CB02 /* Assets.xcassets */; };
2931
C861E61E2994F6150056CB02 /* Service in Frameworks */ = {isa = PBXBuildFile; productRef = C861E61D2994F6150056CB02 /* Service */; };
@@ -485,6 +487,8 @@
485487
isa = PBXResourcesBuildPhase;
486488
buildActionMask = 2147483647;
487489
files = (
490+
C861A7AE2A41EF1A006A8698 /* site-packages in Resources */,
491+
C861A7AD2A41EDDF006A8698 /* python-stdlib in Resources */,
488492
C861E6152994F6080056CB02 /* Assets.xcassets in Resources */,
489493
C81291D72994FE6900196E12 /* Main.storyboard in Resources */,
490494
);
@@ -510,7 +514,7 @@
510514
);
511515
runOnlyForDeploymentPostprocessing = 0;
512516
shellPath = /bin/sh;
513-
shellScript = "#set -e\n#echo \"Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)\"\n#find \"$CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\;\n";
517+
shellScript = "set -e\necho \"Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)\"\nfind \"$CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\;\n";
514518
};
515519
C8A3B1782A2894E10046E809 /* Sign Python Site Packages */ = {
516520
isa = PBXShellScriptBuildPhase;

Copilot for Xcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 27 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Core/Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,11 @@ let package = Package(
8080
"SuggestionWidget",
8181
"AXExtension",
8282
"ChatService",
83-
.product(name: "Logger", package: "Tool"),
8483
"PromptToCodeService",
8584
"ServiceUpdateMigration",
8685
"UserDefaultsObserver",
86+
.product(name: "Logger", package: "Tool"),
87+
.product(name: "PythonHelper", package: "Tool"),
8788
.product(name: "OpenAIService", package: "Tool"),
8889
.product(name: "Preferences", package: "Tool"),
8990
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
@@ -321,7 +322,6 @@ let package = Package(
321322
.product(name: "OpenAIService", package: "Tool"),
322323
.product(name: "LangChain", package: "Tool"),
323324
.product(name: "ExternalServices", package: "Tool"),
324-
.product(name: "PythonKit", package: "PythonKit"),
325325
],
326326
path: "Sources/ChatPlugins/SearchChatPlugin"
327327
),
@@ -342,6 +342,7 @@ let package = Package(
342342
name: "WebChatContextCollector",
343343
dependencies: [
344344
"ChatContextCollector",
345+
.product(name: "LangChain", package: "Tool"),
345346
.product(name: "OpenAIService", package: "Tool"),
346347
.product(name: "ExternalServices", package: "Tool"),
347348
.product(name: "Preferences", package: "Tool"),
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import Foundation
2+
import LangChain
3+
import OpenAIService
4+
import Preferences
5+
6+
struct QueryWebsiteFunction: ChatGPTFunction {
7+
struct Arguments: Codable {
8+
var query: String
9+
var urls: [String]
10+
}
11+
12+
struct Result: ChatGPTFunctionResult {
13+
var relevantDocuments: [Document]
14+
15+
var botReadableContent: String {
16+
// don't forget to remove overlaps
17+
if relevantDocuments.isEmpty {
18+
return "No relevant information found"
19+
}
20+
return relevantDocuments.map(\.pageContent).joined(separator: "\n\n")
21+
}
22+
}
23+
24+
var reportProgress: (String) async -> Void = { _ in }
25+
26+
var name: String {
27+
"queryWebsite"
28+
}
29+
30+
var description: String {
31+
"Useful for when you need to answer a question using information from a website."
32+
}
33+
34+
var argumentSchema: JSONSchemaValue {
35+
return [
36+
.type: "object",
37+
.properties: [
38+
"query": [
39+
.type: "string",
40+
.description: "things you want to know about the website",
41+
],
42+
"urls": [
43+
.type: "array",
44+
.description: "urls of the website, you can use urls appearing in the conversation",
45+
.items: [
46+
.type: "string",
47+
],
48+
],
49+
],
50+
.required: ["query", "urls"],
51+
]
52+
}
53+
54+
func prepare() async {
55+
await reportProgress("Reading..")
56+
}
57+
58+
func call(arguments: Arguments) async throws -> Result {
59+
do {
60+
let embedding = OpenAIEmbedding(
61+
configuration: UserPreferenceEmbeddingConfiguration()
62+
)
63+
64+
let queryEmbeddings = try await embedding.embed(query: arguments.query)
65+
let searchCount = UserDefaults.shared.value(for: \.chatGPTMaxToken) > 5000 ? 3 : 2
66+
67+
let result = try await withThrowingTaskGroup(
68+
of: [(document: Document, distance: Float)].self
69+
) { group in
70+
for urlString in arguments.urls {
71+
guard let url = URL(string: urlString) else { continue }
72+
group.addTask {
73+
if let database = await TemporaryUSearch.view(identifier: urlString) {
74+
return try await database.searchWithDistance(
75+
embeddings: queryEmbeddings,
76+
count: searchCount
77+
)
78+
}
79+
// 1. grab the website content
80+
await reportProgress("Loading \(url)..")
81+
print("== load \(url)")
82+
let loader = WebLoader(urls: [url])
83+
let documents = try await loader.load()
84+
await reportProgress("Processing \(url)..")
85+
print("== loaded \(url), documents: \(documents.count)")
86+
// 2. split the content
87+
let splitter = RecursiveCharacterTextSplitter(
88+
chunkSize: 1000,
89+
chunkOverlap: 100
90+
)
91+
let splitDocuments = try await splitter.transformDocuments(documents)
92+
print("== split \(url), documents: \(splitDocuments.count)")
93+
// 3. embedding and store in db
94+
await reportProgress("Embedding \(url)..")
95+
let embeddedDocuments = try await embedding.embed(documents: splitDocuments)
96+
print("== embedded \(url)")
97+
let database = TemporaryUSearch(identifier: urlString)
98+
try await database.set(embeddedDocuments)
99+
print("== save to database \(url)")
100+
let result = try await database.searchWithDistance(
101+
embeddings: queryEmbeddings,
102+
count: searchCount
103+
)
104+
print("== result of \(url): \(result)")
105+
return result
106+
}
107+
}
108+
109+
var all = [(document: Document, distance: Float)]()
110+
for try await result in group {
111+
all.append(contentsOf: result)
112+
}
113+
await reportProgress("Finish reading websites.")
114+
return all
115+
.sorted { $0.distance < $1.distance }
116+
.prefix(searchCount)
117+
}
118+
119+
return .init(relevantDocuments: result.map(\.document))
120+
} catch {
121+
await reportProgress("Failed reading websites.")
122+
throw error
123+
}
124+
}
125+
}
126+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import BingSearchService
2+
import Foundation
3+
import OpenAIService
4+
import Preferences
5+
6+
struct SearchFunction: ChatGPTFunction {
7+
static let dateFormatter = {
8+
let it = DateFormatter()
9+
it.dateFormat = "yyyy-MM-dd"
10+
return it
11+
}()
12+
13+
struct Arguments: Codable {
14+
var query: String
15+
var freshness: String?
16+
}
17+
18+
struct Result: ChatGPTFunctionResult {
19+
var result: BingSearchResult
20+
21+
var botReadableContent: String {
22+
result.webPages.value.enumerated().map {
23+
let (index, page) = $0
24+
return """
25+
\(index + 1). \(page.name) \(page.url)
26+
\(page.snippet)
27+
"""
28+
}.joined(separator: "\n")
29+
}
30+
}
31+
32+
var reportProgress: (String) async -> Void = { _ in }
33+
34+
var name: String {
35+
"searchWeb"
36+
}
37+
38+
var description: String {
39+
"Useful for when you need to answer questions about latest information."
40+
}
41+
42+
var argumentSchema: JSONSchemaValue {
43+
let today = Self.dateFormatter.string(from: Date())
44+
return [
45+
.type: "object",
46+
.properties: [
47+
"query": [
48+
.type: "string",
49+
.description: "the search query",
50+
],
51+
"freshness": [
52+
.type: "string",
53+
.description: .string(
54+
"limit the search result to a specific range, use only when user ask the question about current events. Today is \(today). Format: yyyy-MM-dd..yyyy-MM-dd"
55+
),
56+
.examples: ["1919-10-20..1988-10-20"],
57+
],
58+
],
59+
.required: ["query"],
60+
]
61+
}
62+
63+
func prepare() async {
64+
await reportProgress("Searching..")
65+
}
66+
67+
func call(arguments: Arguments) async throws -> Result {
68+
await reportProgress("Searching \(arguments.query)")
69+
70+
do {
71+
let bingSearch = BingSearchService(
72+
subscriptionKey: UserDefaults.shared.value(for: \.bingSearchSubscriptionKey),
73+
searchURL: UserDefaults.shared.value(for: \.bingSearchEndpoint)
74+
)
75+
let result = try await bingSearch.search(
76+
query: arguments.query,
77+
numberOfResult: UserDefaults.shared.value(for: \.chatGPTMaxToken) > 5000 ? 5 : 3,
78+
freshness: arguments.freshness
79+
)
80+
81+
await reportProgress("""
82+
Finish searching \(arguments.query)
83+
\(
84+
result.webPages.value
85+
.map { "- [\($0.name)](\($0.url))" }
86+
.joined(separator: "\n")
87+
)
88+
""")
89+
90+
return .init(result: result)
91+
} catch {
92+
await reportProgress("Failed searching: \(error.localizedDescription)")
93+
throw error
94+
}
95+
}
96+
}
97+

0 commit comments

Comments
 (0)