Skip to content

Commit 3f979b6

Browse files
committed
Re-implement math chat plugin in Swift
1 parent 6eef413 commit 3f979b6

File tree

5 files changed

+152
-41
lines changed

5 files changed

+152
-41
lines changed

Core/Sources/ChatPlugin/AskChatGPT.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import Foundation
22
import OpenAIService
33

44
/// Quickly ask a question to ChatGPT.
5-
func askChatGPT(systemPrompt: String, question: String) async throws -> String? {
6-
let service = ChatGPTService(systemPrompt: systemPrompt)
5+
public func askChatGPT(
6+
systemPrompt: String,
7+
question: String,
8+
temperature: Double? = nil
9+
) async throws -> String? {
10+
let service = ChatGPTService(systemPrompt: systemPrompt, temperature: temperature)
711
return try await service.sendAndWait(content: question)
812
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Foundation
2+
import Preferences
3+
4+
@MainActor
5+
var translationCache = [String: String]()
6+
7+
public func translate(text: String, cache: Bool = true) async -> String {
8+
let language = UserDefaults.shared.value(for: \.chatGPTLanguage)
9+
if language.isEmpty { return text }
10+
11+
let key = "\(language)-\(text)"
12+
if cache, let cached = await translationCache[key] {
13+
return cached
14+
}
15+
16+
if let translated = try? await askChatGPT(
17+
systemPrompt: """
18+
You are a translator. Your job is to translate the message into \(language). The reply should only contain the translated content.
19+
User: ###${{some text}}###
20+
Assistant: ${{translated text}}
21+
""",
22+
question: "###\(text)###"
23+
) {
24+
if cache {
25+
let storeTask = Task { @MainActor in
26+
translationCache[key] = translated
27+
}
28+
_ = await storeTask.result
29+
}
30+
return translated
31+
}
32+
return text
33+
}
34+

Core/Sources/ChatPlugins/MathChatPlugin/MathChatPlugin.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ public actor MathChatPlugin: ChatPlugin {
2222
delegate?.pluginDidStartResponding(self)
2323

2424
let id = "\(Self.command)-\(UUID().uuidString)"
25-
var reply = ChatMessage(id: id, role: .assistant, content: "Calculating...")
25+
async let translatedCalculating = translate(text: "Calculating...")
26+
async let translatedAnswer = translate(text: "Answer:")
27+
var reply = ChatMessage(id: id, role: .assistant, content: await translatedCalculating)
2628

2729
await chatGPTService.mutateHistory { history in
2830
history.append(.init(role: .user, content: originalMessage, summary: content))
@@ -31,11 +33,12 @@ public actor MathChatPlugin: ChatPlugin {
3133

3234
do {
3335
let result = try await solveMathProblem(content)
36+
let formattedResult = "\(await translatedAnswer) \(result)"
3437
await chatGPTService.mutateHistory { history in
3538
if history.last?.id == id {
3639
history.removeLast()
3740
}
38-
reply.content = result
41+
reply.content = formattedResult
3942
history.append(reply)
4043
}
4144
} catch {
Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,100 @@
1+
import ChatPlugin
12
import Foundation
23
import LangChain
3-
import PythonHelper
4-
import PythonKit
5-
6-
func solveMathProblem(_ problem: String) async throws -> String {
7-
// #if DEBUG
8-
// let verbose = true
9-
// #else
10-
// let verbose = false
11-
// #endif
12-
//
13-
// struct E: Error, LocalizedError {
14-
// var errorDescription: String? {
15-
// "Failed to parse answer."
16-
// }
17-
// }
18-
//
19-
// let task = Task {
20-
// try runPython {
21-
// let langchain = try Python.attemptImportOnPythonThread("langchain")
22-
// let LLMMathChain = langchain.LLMMathChain
23-
// let llm = try LangChainChatModel.DynamicChatOpenAI(temperature: 0)
24-
// let llmMath = LLMMathChain.from_llm(llm, verbose: verbose)
25-
// let result = try llmMath.run.throwing.dynamicallyCall(withArguments: problem)
26-
// let answer = String(result)
27-
// if let answer { return answer }
28-
//
29-
// throw E()
30-
// }
31-
// }
32-
//
33-
// return try await task.value
34-
return "N/A"
4+
import Logger
5+
import OpenAIService
6+
7+
let systemPrompt = """
8+
Translate a math problem into a expression that can be executed using Python's numexpr library.
9+
Use the output of running this code to answer the question.
10+
11+
Question: ${{Question with math problem.}}
12+
```text
13+
${{single line mathematical expression that solves the problem}}
14+
```
15+
...numexpr.evaluate(text)...
16+
```output
17+
${{Output of running the code}}
18+
```
19+
Answer: ${{Answer}}
20+
21+
Begin.
22+
23+
Question: What is 37593 * 67?
24+
```text
25+
37593 * 67
26+
```
27+
...numexpr.evaluate("37593 * 67")...
28+
```output
29+
2518731
30+
```
31+
Answer: 2518731
32+
33+
Question: 37593^(1/5)
34+
```text
35+
37593**(1/5)
36+
```
37+
...numexpr.evaluate("37593**(1/5)")...
38+
```output
39+
8.222831614237718
40+
```
41+
Answer: 8.222831614237718
42+
"""
43+
44+
/// Extract the math problem with ChatGPT, and pass it to python to get the result.
45+
///
46+
/// [llm_math in
47+
/// LangChain](https://github.com/hwchase17/langchain/blob/master/langchain/chains/llm_math/base.py)
48+
///
49+
/// The logic is basically the same as the LLMMathChain provided in LangChain.
50+
func solveMathProblem(_ question: String) async throws -> String {
51+
guard let reply = try await askChatGPT(
52+
systemPrompt: systemPrompt,
53+
question: "Question: \(question)",
54+
temperature: 0
55+
) else { return "No answer." }
56+
57+
// parse inside text code block
58+
let codeBlockRegex = try NSRegularExpression(pattern: "```text\n(.*?)\n```", options: [])
59+
let codeBlockMatches = codeBlockRegex.matches(
60+
in: reply,
61+
options: [],
62+
range: NSRange(reply.startIndex..<reply.endIndex, in: reply)
63+
)
64+
if let firstMatch = codeBlockMatches.first, let textRange = Range(
65+
firstMatch.range(at: 1),
66+
in: reply
67+
) {
68+
let text = reply[textRange]
69+
let expression = String(text)
70+
let task = Task { try evaluateWithPython(expression) }
71+
if let answer = try await task.value {
72+
return answer
73+
}
74+
}
75+
76+
// parse after Answer:
77+
let answerRegex = try NSRegularExpression(pattern: "Answer: (.*)", options: [])
78+
let answerMatches = answerRegex.matches(
79+
in: reply,
80+
options: [],
81+
range: NSRange(reply.startIndex..<reply.endIndex, in: reply)
82+
)
83+
if let firstMatch = answerMatches.first, let answerRange = Range(
84+
firstMatch.range(at: 1),
85+
in: reply
86+
) {
87+
let answer = reply[answerRange]
88+
return String(answer)
89+
}
90+
91+
return reply
92+
}
93+
94+
func evaluateWithPython(_ expression: String) throws -> String? {
95+
let mathExpression = NSExpression(format: expression)
96+
let value = mathExpression.expressionValue(with: nil, context: nil)
97+
Logger.service.debug(String(describing: value))
98+
return (value as? Int).flatMap(String.init)
3599
}
36100

Core/Sources/GitHubCopilotService/GitHubCopilotService.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public class GitHubCopilotBaseService {
127127
}()
128128
}
129129
let localServer = CopilotLocalProcessServer(executionParameters: executionParams)
130-
130+
131131
localServer.logMessages = UserDefaults.shared.value(for: \.gitHubCopilotVerboseLog)
132132
localServer.notificationHandler = { _, respond in
133133
respond(.timeout)
@@ -162,9 +162,9 @@ public class GitHubCopilotBaseService {
162162

163163
return (server, localServer)
164164
}()
165-
165+
166166
self.server = server
167-
self.localProcessServer = localServer
167+
localProcessServer = localServer
168168
}
169169

170170
public static func createFoldersIfNeeded() throws -> (
@@ -242,6 +242,12 @@ public final class GitHubCopilotAuthService: GitHubCopilotBaseService,
242242
}
243243
}
244244

245+
@globalActor public enum GitHubCopilotSuggestionActor {
246+
public actor TheActor {}
247+
public static let shared = TheActor()
248+
}
249+
250+
@GitHubCopilotSuggestionActor
245251
public final class GitHubCopilotSuggestionService: GitHubCopilotBaseService,
246252
GitHubCopilotSuggestionServiceType
247253
{
@@ -313,7 +319,7 @@ public final class GitHubCopilotSuggestionService: GitHubCopilotBaseService,
313319

314320
return try await task.value
315321
}
316-
322+
317323
public func cancelRequest() async {
318324
await localProcessServer?.cancelOngoingTasks()
319325
}
@@ -380,7 +386,7 @@ public final class GitHubCopilotSuggestionService: GitHubCopilotBaseService,
380386
// Logger.service.debug("Close \(uri)")
381387
try await server.sendNotification(.didCloseTextDocument(.init(uri: uri)))
382388
}
383-
389+
384390
public func terminate() async {
385391
// automatically handled
386392
}

0 commit comments

Comments
 (0)