Skip to content

Commit 32c388a

Browse files
committed
Update
1 parent b32c782 commit 32c388a

File tree

6 files changed

+177
-33
lines changed

6 files changed

+177
-33
lines changed

Core/Package.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ let package = Package(
182182

183183
// plugins
184184
"MathChatPlugin",
185+
"SearchChatPlugin",
185186

186187
.product(name: "Preferences", package: "Tool"),
187188
]
@@ -319,6 +320,17 @@ let package = Package(
319320
],
320321
path: "Sources/ChatPlugins/MathChatPlugin"
321322
),
323+
324+
.target(
325+
name: "SearchChatPlugin",
326+
dependencies: [
327+
"ChatPlugin",
328+
"OpenAIService",
329+
.product(name: "LangChain", package: "Tool"),
330+
.product(name: "PythonKit", package: "PythonKit"),
331+
],
332+
path: "Sources/ChatPlugins/SearchChatPlugin"
333+
),
322334
]
323335
)
324336

Core/Sources/ChatPlugin/SearchChatPlugin.swift

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import Foundation
2+
import LangChain
3+
import PythonHelper
4+
import PythonKit
5+
6+
func search(_ query: String) async throws -> String {
7+
#if DEBUG
8+
let verbose = true
9+
#else
10+
let verbose = false
11+
#endif
12+
13+
let task = Task {
14+
try runPython {
15+
let llm = try LangChainChatModel.DynamicChatOpenAI(temperature: 0)
16+
let utilities = try Python.attemptImportOnPythonThread("langchain.utilities")
17+
let BingSearchAPIWrapper = utilities.BingSearchAPIWrapper
18+
let agents = try Python.attemptImportOnPythonThread("langchain.agents")
19+
let Tool = agents.Tool
20+
let initializeAgent = agents.initialize_agent
21+
let AgentType = agents.AgentType
22+
23+
let bingSearch = BingSearchAPIWrapper(
24+
bing_subscription_key: "f1eaef707e9443ddb08df2cfb6ac1eb5",
25+
bing_search_url: "https://api.bing.microsoft.com/v7.0/search/",
26+
k: 5
27+
)
28+
29+
var links = [String]()
30+
31+
let getSearchResult = PythonInstanceMethod { arguments -> String in
32+
guard let query = arguments.first else { return "Empty" }
33+
let results = bingSearch.results(query, 5)
34+
let resultString = results.enumerated().map { "[\($0)]:###\($1["snippet"])###" }
35+
.joined(separator: "\n")
36+
links = results.map {
37+
let url = String($0["link"]) ?? "N/A"
38+
let title = String($0["title"]) ?? "Unknown Title"
39+
return "[\(title)](\(url))"
40+
}
41+
return resultString
42+
}
43+
44+
let ff = PythonClass("FF", members: [
45+
"run": PythonInstanceMethod { arguments -> String in
46+
guard let query = arguments.first else { return "Empty" }
47+
let results = bingSearch.results(query, 5)
48+
let resultString = results.enumerated().map { "[\($0)]:###\($1["snippet"])###" }
49+
.joined(separator: "\n")
50+
links = results.map {
51+
let url = String($0["link"]) ?? "N/A"
52+
let title = String($0["title"]) ?? "Unknown Title"
53+
return "[\(title)](\(url))"
54+
}
55+
return resultString
56+
}
57+
])
58+
59+
let fi = ff.pythonObject()
60+
61+
print(fi.run)
62+
print(getSearchResult.pythonObject)
63+
64+
let tools = [
65+
Tool(
66+
name: "Search",
67+
func: fi.run,
68+
description: "useful for when you need to answer questions about current events. You should ask targeted questions"
69+
),
70+
]
71+
72+
let chain = initializeAgent(
73+
tools, llm,
74+
agent: AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
75+
verbose: verbose,
76+
max_iterations: 1,
77+
early_stopping_method: "generate",
78+
agent_kwargs: ["system_message_prefix": "Respond to the human as helpfully and accurately as possible. Wrap any code block in thought in <code></code>. Format final answer to be more readable, in a ordered list if possible. You have access to the following tools:"]
79+
)
80+
81+
let trimmedQuery = query.trimmingCharacters(in: [" ", "\n"])
82+
do {
83+
let result = try chain.run.throwing.dynamicallyCall(withArguments: trimmedQuery)
84+
return (String(result) ?? "", links)
85+
} catch {
86+
return (error.localizedDescription, links)
87+
}
88+
}
89+
}
90+
91+
let (answer, links) = try await task.value
92+
93+
return """
94+
\(answer)
95+
------
96+
\(links.map { "- \($0)" }.joined(separator: "\n"))
97+
"""
98+
}
99+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import ChatPlugin
2+
import Environment
3+
import Foundation
4+
import OpenAIService
5+
6+
public actor SearchChatPlugin: ChatPlugin {
7+
public static var command: String { "search" }
8+
public nonisolated var name: String { "Search" }
9+
10+
let chatGPTService: any ChatGPTServiceType
11+
var isCancelled = false
12+
weak var delegate: ChatPluginDelegate?
13+
14+
public init(inside chatGPTService: any ChatGPTServiceType, delegate: ChatPluginDelegate) {
15+
self.chatGPTService = chatGPTService
16+
self.delegate = delegate
17+
}
18+
19+
public func send(content: String, originalMessage: String) async {
20+
delegate?.pluginDidStart(self)
21+
delegate?.pluginDidStartResponding(self)
22+
23+
let id = "\(Self.command)-\(UUID().uuidString)"
24+
var reply = ChatMessage(id: id, role: .assistant, content: "Calculating...")
25+
26+
await chatGPTService.mutateHistory { history in
27+
history.append(.init(role: .user, content: originalMessage, summary: content))
28+
history.append(reply)
29+
}
30+
31+
do {
32+
let result = try await search(content)
33+
await chatGPTService.mutateHistory { history in
34+
if history.last?.id == id {
35+
history.removeLast()
36+
}
37+
reply.content = result
38+
history.append(reply)
39+
}
40+
} catch {
41+
await chatGPTService.mutateHistory { history in
42+
if history.last?.id == id {
43+
history.removeLast()
44+
}
45+
reply.content = error.localizedDescription
46+
history.append(reply)
47+
}
48+
}
49+
50+
delegate?.pluginDidEndResponding(self)
51+
delegate?.pluginDidEnd(self)
52+
}
53+
54+
public func cancel() async {
55+
isCancelled = true
56+
}
57+
58+
public func stopResponding() async {
59+
isCancelled = true
60+
}
61+
}
62+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import ChatPlugin
22
import MathChatPlugin
3+
import SearchChatPlugin
34

45
let allPlugins: [ChatPlugin.Type] = [
56
TerminalChatPlugin.self,
67
AITerminalChatPlugin.self,
78
MathChatPlugin.self,
9+
SearchChatPlugin.self,
810
]

Tool/Sources/PythonHelper/RunPython.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ var gilStateEnsure: (() -> Any)!
55
var gilStateRelease: ((Any) -> Void)!
66
func gilStateGuard<T>(_ closure: @escaping () throws -> T) throws -> T {
77
let state = gilStateEnsure()
8+
defer { gilStateRelease(state) }
89
do {
910
let result = try closure()
10-
gilStateRelease(state)
1111
return result
1212
} catch {
13-
gilStateRelease(state)
1413
throw error
1514
}
1615
}
@@ -30,6 +29,7 @@ public func initializePython<GilState, ThreadState>(
3029
guard !isPythonInitialized else { return }
3130
setenv("PYTHONHOME", stdLibPath, 1)
3231
setenv("PYTHONPATH", "\(stdLibPath):\(libDynloadPath):\(sitePackagePath)", 1)
32+
setenv("PYTHONIOENCODING", "utf-8", 1)
3333
isPythonInitialized = true
3434
// Initialize python
3535
Py_Initialize()

0 commit comments

Comments
 (0)