Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions .github/workflows/build_soundness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Build And Soundness checks

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run tests
working-directory: backend
run: swift build

soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
license_header_check_enabled: true
license_header_check_project_name: "Swift Foundation Models Playground"
shell_check_enabled: false
python_lint_check_enabled: false
api_breakage_check_enabled: false
# api_breakage_check_container_image: "swift:6.0-noble"
docs_check_container_image: "swift:6.0-noble"
format_check_container_image: "swift:6.0-noble"
yamllint_check_enabled: true
48 changes: 0 additions & 48 deletions .github/workflows/build_test_soundness.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Package.resolved
.serverless
.vscode
.env
Makefile

# backend
backend/.DS_Store
Expand All @@ -27,6 +28,7 @@ backend/.env.*
backend/.env
backend/img/generated_images
backend/.vscode
backend/Makefile

# frontend
frontend/node_modules
Expand Down Expand Up @@ -54,3 +56,4 @@ frontend/.vercel

frontend/*.tsbuildinfo
frontend/next-env.d.ts
frontend/Makefile
2 changes: 1 addition & 1 deletion backend/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"),
.package(url: "https://github.com/monadierickx/swift-bedrock-library.git", branch: "week16"),
.package(url: "https://github.com/sebsto/swift-bedrock-library.git", branch: "main"),
],
targets: [
.executableTarget(
Expand Down
6 changes: 5 additions & 1 deletion backend/Sources/PlaygroundAPI/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ struct AppCommand: AsyncParsableCommand, AppArguments {
@Option(name: .shortAndLong)
var logLevel: Logger.Level?

@Flag var sso = false
@Flag(name: .shortAndLong, help: "Use SSO authentication (default: false))")
var sso: Bool = false

@Option(name: [.customShort("n"), .long], help: "The name of the profile to use (default: default)")
var profileName: String = "default"

func run() async throws {
let app = try await buildApplication(self)
Expand Down
81 changes: 64 additions & 17 deletions backend/Sources/PlaygroundAPI/Application+build.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public protocol AppArguments {
var port: Int { get }
var logLevel: Logger.Level? { get }
var sso: Bool { get }
var profileName: String { get }
}

// Request context used by application
Expand All @@ -46,7 +47,7 @@ public func buildApplication(
arguments.logLevel ?? environment.get("LOG_LEVEL").flatMap {
Logger.Level(rawValue: $0)
} ?? .info
let router = try await buildRouter(useSSO: arguments.sso, logger: logger)
let router = try await buildRouter(useSSO: arguments.sso, logger: logger, profileName: arguments.profileName)
let app = Application(
router: router,
configuration: .init(
Expand All @@ -59,7 +60,7 @@ public func buildApplication(
}

/// Build router
func buildRouter(useSSO: Bool, logger: Logger) async throws -> Router<AppRequestContext> {
func buildRouter(useSSO: Bool, logger: Logger, profileName: String) async throws -> Router<AppRequestContext> {
let router = Router(context: AppRequestContext.self)

// CORS
Expand All @@ -76,7 +77,22 @@ func buildRouter(useSSO: Bool, logger: Logger) async throws -> Router<AppRequest
}

// SwiftBedrock
let bedrock = try await BedrockService(useSSO: useSSO)
var auth: BedrockAuthentication = .default
if useSSO {
auth = .sso(profileName: profileName)
}
let bedrock = try await BedrockService(authentication: auth)

// Error handling
@Sendable func handleBedrockServiceError(_ error: Error, context: String) throws {
if let bedrockServiceError = error as? BedrockServiceError {
logger.trace("BedrockServiceError while \(context)", metadata: ["error": "\(error)"])
throw HTTPError(.badRequest, message: bedrockServiceError.message)
} else {
logger.trace("Error while \(context)", metadata: ["error": "\(error)"])
throw HTTPError(.internalServerError, message: "Error: \(error)")
}
}

// List models
// GET /foundation-models lists all models
Expand Down Expand Up @@ -173,25 +189,56 @@ func buildRouter(useSSO: Bool, logger: Logger) async throws -> Router<AppRequest
throw HTTPError(.badRequest, message: "Model \(modelId) does not support converse.")
}
let input = try await request.decode(as: ChatInput.self, context: context)
return try await bedrock.converse(
with: model,
prompt: input.prompt,
imageFormat: input.imageFormat ?? .jpeg, // default to simplify frontend
imageBytes: input.imageBytes,
history: input.history ?? [],
maxTokens: input.maxTokens,
temperature: input.temperature,
topP: input.topP,
stopSequences: input.stopSequences,
systemPrompts: input.systemPrompts,
tools: input.tools,
toolResult: input.toolResult
)
var image: ImageBlock? = nil
if let imageBytes = input.imageBytes {
image = try ImageBlock(format: input.imageFormat ?? .jpeg, source: imageBytes)
}
var document: DocumentBlock? = nil
if let documentBytes = input.documentBytes, let name = input.documentName {
document = try DocumentBlock(name: name, format: input.documentFormat ?? .pdf, source: documentBytes)
}
var builder = try ConverseRequestBuilder(with: model)
.withHistory(input.history ?? [])
.withMaxTokens(input.maxTokens)
.withTemperature(input.temperature)
.withTopP(input.topP)

if let stopSequences = input.stopSequences,
!stopSequences.isEmpty
{
builder = try builder.withStopSequences(stopSequences)
}
if let systemPrompts = input.systemPrompts,
!systemPrompts.isEmpty
{
builder = try builder.withStopSequences(systemPrompts)
}
if let prompt = input.prompt {
builder = try builder.withPrompt(prompt)
}
if let tools = input.tools {
builder = try builder.withTools(tools)
}
if let toolResult = input.toolResult {
builder = try builder.withToolResult(toolResult)
}
if let document {
builder = try builder.withDocument(document)
}
if let image {
builder = try builder.withImage(image)
}
if let enableReasoning = input.enableReasoning, enableReasoning {
builder = try builder.withReasoning()
.withMaxReasoningTokens(input.maxReasoningTokens)
}
return try await bedrock.converse(with: builder)
} catch {
logger.info(
"An error occured while generating chat",
metadata: ["url": "/foundation-models/chat/:modelId", "error": "\(error)"]
)
try handleBedrockServiceError(error, context: "/foundation-models/chat/:modelId")
throw HTTPError(.internalServerError, message: "Error: \(error)")
}
}
Expand Down
5 changes: 5 additions & 0 deletions backend/Sources/PlaygroundAPI/Types/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ struct ChatInput: Codable {
let history: [Message]?
let imageFormat: ImageBlock.Format?
let imageBytes: String?
let documentName: String?
let documentFormat: DocumentBlock.Format?
let documentBytes: String?
let maxTokens: Int?
let temperature: Double?
let topP: Double?
let stopSequences: [String]?
let systemPrompts: [String]?
let tools: [Tool]?
let toolResult: ToolResultBlock?
let enableReasoning: Bool?
let maxReasoningTokens: Int?
}

extension ConverseReply: @retroactive ResponseCodable {}
15 changes: 15 additions & 0 deletions frontend/app/reasoning_chat/loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function Loading() {
return (
<div className="container px-6 py-8 mx-auto">
<div className="text-center">
<div role="status">
<svg aria-hidden="true" className="inline w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span className="sr-only">Loading...</span>
</div>
</div>
</div>
)
}
9 changes: 9 additions & 0 deletions frontend/app/reasoning_chat/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use client";

import ReasoningChatComponent from "@/components/reasoningChatPlayground/ReasoningChatComponent";

export default async function Chat() {
return (
<ReasoningChatComponent />
)
}
8 changes: 6 additions & 2 deletions frontend/components/Navigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ export default function Navigation() {
className="flex items-center px-6 py-2 mt-4 text-gray-400 hover:bg-gray-700 hover:bg-opacity-25 hover:text-gray-100">
<span className="mx-3">Chat Playground</span>
</Link>
<Link href="/text"
<Link href="/reasoning_chat"
className="flex items-center px-6 py-2 mt-4 text-gray-400 hover:bg-gray-700 hover:bg-opacity-25 hover:text-gray-100">
<span className="mx-3">Text Playground</span>
<span className="mx-3">Reasoning Chat Playground</span>
</Link>
{/* <Link href="/text"
className="flex items-center px-6 py-2 mt-4 text-gray-400 hover:bg-gray-700 hover:bg-opacity-25 hover:text-gray-100">
<span className="mx-3">Text Playground</span>
</Link> */}
<Link href="/image"
className="flex items-center px-6 py-2 mt-4 text-gray-400 hover:bg-gray-700 hover:bg-opacity-25 hover:text-gray-100">
<span className="mx-3">Image Playground</span>
Expand Down
Loading