Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0b19d78
Add Package.swift in root
danielnugraha Mar 10, 2025
311bb66
Fix sendable issue
danielnugraha Mar 11, 2025
d4245ac
Merge branch 'main' into add-swift-package
Mar 11, 2025
2c92f7b
Add to gitignore
danielnugraha Mar 11, 2025
afb76c2
Merge remote-tracking branch 'refs/remotes/origin/add-swift-package' …
danielnugraha Mar 11, 2025
7d1c3f3
Format
danielnugraha Mar 11, 2025
66f1e85
Format
danielnugraha Mar 11, 2025
7b16105
Add ci checks and tests
danielnugraha Mar 11, 2025
6292a64
use swift 6.0
danielnugraha Mar 11, 2025
a1efd50
Merge branch 'main' into add-swift-ci
Mar 11, 2025
43e373b
Merge main
danielnugraha Mar 11, 2025
ec3d12b
Merge remote-tracking branch 'refs/remotes/origin/add-swift-ci' into …
danielnugraha Mar 11, 2025
675f4af
Add docs generation for Swift
danielnugraha Mar 11, 2025
c94b633
Add sourcedocs
danielnugraha Mar 11, 2025
cfb60af
Use macos
danielnugraha Mar 11, 2025
2f10d52
Change runner
danielnugraha Mar 11, 2025
f5ef188
Change to xcodebuild test
danielnugraha Mar 11, 2025
3c1e3bd
Merge branch 'main' into add-swift-ci
Mar 11, 2025
42287b0
Use brew
danielnugraha Mar 11, 2025
6f3a4b8
Merge remote-tracking branch 'refs/remotes/origin/add-swift-ci' into …
danielnugraha Mar 11, 2025
717e190
Add check
danielnugraha Mar 11, 2025
25cd9be
Upgrade xcode
danielnugraha Mar 11, 2025
d9d8bea
Upgrade xcode
danielnugraha Mar 11, 2025
2215048
Upgrade xcode
danielnugraha Mar 11, 2025
293f78c
Upgrade xcode
danielnugraha Mar 11, 2025
0e3bc4c
Merge branch 'main' into add-swift-ci
Mar 11, 2025
e0c3152
Merge branch 'main' into add-swift-ci
Mar 11, 2025
bc94e08
Merge branch 'main' into add-swift-ci
danieljanes Mar 11, 2025
3b0fda8
Merge branch 'main' into add-swift-ci
Mar 11, 2025
ff37690
Merge branch 'main' into add-swift-ci
Mar 11, 2025
f2e0a6c
Merge branch 'main' into add-swift-ci
danieljanes Mar 11, 2025
404c3eb
Merge branch 'main' into add-swift-ci
Mar 11, 2025
ba5e15f
Merge branch 'main' into add-swift-ci
Mar 11, 2025
4b42960
Merge branch 'main' into add-swift-ci
Mar 11, 2025
e1724f8
Merge branch 'main' into add-swift-ci
Mar 11, 2025
fdfb396
Merge branch 'main' into add-swift-ci
Mar 12, 2025
359d05a
Merge branch 'main' into add-swift-ci
Mar 12, 2025
8e1bfbd
Merge branch 'main' into add-swift-ci
tanertopal Mar 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/intelligence-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,28 @@ env:

jobs:
build_and_deploy:
runs-on: ubuntu-22.04
runs-on: macos-14
name: Build and deploy
steps:
- uses: actions/checkout@v4
- name: Bootstrap
uses: ./.github/actions/bootstrap
- name: Install pandoc
run: sudo apt install pandoc
run: brew install pandoc
- name: Install Flower dependencies (mandatory only)
run: python -m poetry install --extras "simulation"
- uses: swift-actions/setup-swift@v2
with:
swift-version: "6.0.0"
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.2"
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22.14.0'
- name: Install SourceDocs
run: brew install sourcedocs
- name: Install pnpm
run: |
npm install -g pnpm
Expand Down
55 changes: 55 additions & 0 deletions .github/workflows/intelligence-swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Intelligence Swift

on:
push:
branches:
- main
paths:
- 'intelligence/swift/**/*'
- '.github/workflows/intelligence-swift.yml'
pull_request:
branches:
- main
paths:
- 'intelligence/swift/**/*'
- '.github/workflows/intelligence-swift.yml'

concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
FLWR_TELEMETRY_ENABLED: 0

jobs:
fi_swift_format_lint:
name: Format and Lint Check
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install SwiftFormat
run: brew install swift-format

- name: Check formatting and lint
working-directory: intelligence/swift
run: swift-format lint --recursive .

fi_swift_tests:
name: Tests
runs-on: macos-14
steps:
- name: Checkout code
uses: actions/checkout@v4

- uses: swift-actions/setup-swift@v2
with:
swift-version: "6.0.0"

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.2"

- name: Run tests
run: xcodebuild test -scheme flower -destination 'platform=macOS'
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ let package = Package(
.product(name: "MLXLLM", package: "mlx-swift-examples"),
.product(name: "Crypto", package: "swift-crypto")
],
path: "intelligence/swift/src"),
path: "intelligence/swift/src"
),
.testTarget(
name: "FlowerIntelligenceTests",
dependencies: ["FlowerIntelligence"],
path: "intelligence/swift/tests"
)
]
)
14 changes: 12 additions & 2 deletions intelligence/dev/build-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@
set -e
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/../

# Build TS docs
cd ts && \
pnpm build:docs --readme none --name "TypeScript API" && \
cd ..

cd docs
# Build Swift docs
cd .. && \
sourcedocs generate --module-name FlowerIntelligence -- -scheme flower -destination 'platform=macOS' && \
mkdir -p intelligence/docs/source/swift-api-ref && \
cp -r Documentation/Reference/* intelligence/docs/source/swift-api-ref/ && \
cd intelligence/docs && \
mv source/swift-api-ref/README.md source/swift-api-ref/index.md && \
sed -i.bak '1 s/^# .*/# Swift API/' source/swift-api-ref/index.md && \
sed -i.bak '/^This file was generated by/d' source/swift-api-ref/index.md && \
rm -f source/swift-api-ref/index.md.bak

{
echo ''
Expand All @@ -17,6 +27,6 @@ cd docs
echo ''
echo '*/*'
echo '```'
} | tee -a source/ts-api-ref/index.md
} | tee -a source/ts-api-ref/index.md source/swift-api-ref/index.md

make html
137 changes: 137 additions & 0 deletions intelligence/swift/tests/FlowerIntelligenceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import Testing
@testable import FlowerIntelligence

class MockMlxEngine: Engine {
var lastMessages: [Message]?
var lastModel: String?
var lastTemperature: Float?
var lastMaxCompletionTokens: Int?
var lastStream: Bool?
var lastTools: [Tool]?

func chat(
_ messages: [Message],
model: String?,
temperature: Float?,
maxCompletionTokens: Int?,
stream: Bool,
onStreamEvent: ((StreamEvent) -> Void)?,
tools: [Tool]?
) async throws -> Message {
lastMessages = messages
return Message(role: "assistant", content: "Mock Local Engine Response")
}
}

class MockRemoteEngine: RemoteEngineProtocol {
var apiKey: String = ""
var lastMessages: [Message]?
var lastModel: String?
var lastTemperature: Float?
var lastMaxCompletionTokens: Int?
var lastStream: Bool?
var lastTools: [Tool]?

func chat(
_ messages: [Message],
model: String?,
temperature: Float?,
maxCompletionTokens: Int?,
stream: Bool,
onStreamEvent: ((StreamEvent) -> Void)?,
tools: [Tool]?
) async throws -> Message {
lastMessages = messages
return Message(role: "assistant", content: "Mock Remote Engine Response")
}
}

class FlowerIntelligenceTests {
var flowerIntelligence: FlowerIntelligence
var mockMlxEngine: MockMlxEngine
var mockRemoteEngine: MockRemoteEngine

init() {
mockMlxEngine = MockMlxEngine()
mockRemoteEngine = MockRemoteEngine()
flowerIntelligence = FlowerIntelligence(mlxEngine: mockMlxEngine, remoteEngine: mockRemoteEngine)
}

@Test
func testUsesCorrectMlxEngine() async throws {
flowerIntelligence.remoteHandoff = false
_ = await flowerIntelligence.chat("Hello")
try #require(mockMlxEngine.lastMessages != nil)
try #require(mockRemoteEngine.lastMessages == nil)
}

@Test
func testUsesCorrectRemoteEngine() async throws {
flowerIntelligence.remoteHandoff = true
_ = await flowerIntelligence.chat("Hello")
try #require(mockRemoteEngine.lastMessages != nil)
try #require(mockMlxEngine.lastMessages == nil)
}

@Test
func testForceRemoteOverridesRemoteHandoff() async throws {
flowerIntelligence.remoteHandoff = false
_ = await flowerIntelligence.chat("Hello", maybeOptions: ChatOptions(forceRemote: true))
try #require(mockRemoteEngine.lastMessages != nil)
try #require(mockMlxEngine.lastMessages == nil)
}

@Test
func testForceLocalOverridesRemoteHandoff() async throws {
flowerIntelligence.remoteHandoff = true
_ = await flowerIntelligence.chat("Hello", maybeOptions: ChatOptions(forceLocal: true))
try #require(mockMlxEngine.lastMessages != nil)
try #require(mockRemoteEngine.lastMessages == nil)
}

@Test
func testForceRemoteAndForceLocalReturnFailure() async throws {
let result = await flowerIntelligence.chat("Hello", maybeOptions: ChatOptions(forceRemote: true, forceLocal: true))
switch result {
case .failure(let error):
try #require(error.message == "Cannot set both forceRemote and forceLocal to true")
default:
Issue.record("Expected failure but got success")
}
}

@Test
func testChatFunctionWithSingleMessage() async throws {
let result = await flowerIntelligence.chat("Test message")
switch result {
case .success(let response):
let messages = try #require(mockMlxEngine.lastMessages)
try #require(messages.first?.content == "Test message")
try #require(response.role == "assistant")
try #require(response.content == "Mock Local Engine Response")
default:
Issue.record("Expected success but got failure")
}
}

@Test
func testChatFunctionWithArrayOfMessages() async throws {
let messages = [
Message(role: "system", content: "You are an AI"),
Message(role: "user", content: "What is Swift?")
]
let options = ChatOptions(model: "meta/llama3.2-1b")
let result = await flowerIntelligence.chat(options: (messages, options))

switch result {
case .success(let response):
let messages = try #require(mockMlxEngine.lastMessages)
try #require(messages.count == 2)
try #require(messages.last?.content == "What is Swift?")
try #require(response.role == "assistant")
try #require(response.content == "Mock Local Engine Response")
default:
Issue.record("Expected success but got failure")
}
}
}