SwiftLangChain is a composable framework for building AI agents, tools, and prompt chains on Apple platforms using LLMs like OpenAI, Claude, and local models.
- π€ Build agents with tool-use reasoning
- π Chain prompts, LLMs, and output parsers
- π§ Plug-in memory and chat history
- βοΈ Support multiple LLM providers (OpenAI, Claude, Gemini, Cohere, Hugging Face, Core ML)
- πΌοΈ Image message support for OpenAI GPT-4 Vision
- π Streaming responses for real-time output
- π οΈ Function calling support for OpenAI
- π± Mobile-specific tools (Camera, Location, Contacts, Photos)
- πΎ Caching layer for improved performance
- π Retry logic with exponential backoff
- π¦ Rate limiting for API requests
- π Token counting and cost estimation
- π§ͺ Modular, testable, and async/await powered
.package(url: "https://github.com/av-feaster/SwiftLangChain.git", from: "0.1.0")SwiftLangChain now supports image messages for OpenAI GPT-4 Vision models. You can send images along with text prompts for visual analysis and understanding.
The package supports both image URLs and base64-encoded images:
- Image URLs: Direct links to images on the web
- Base64 Images: Local images converted to base64 strings
import SwiftLangChain
let openAI = OpenAIProvider(
apiKey: "your-api-key-here",
model: "gpt-4-vision-preview"
)
let memory = ContextMemory(
maxTokens: 4000,
maxMessages: 10,
model: .gpt4
)
let conversation = ConversationChain(llm: openAI, memory: memory)
// Analyze image with text prompt
let response = try await conversation.runWithImage(
"What do you see in this image?",
imageUrl: "https://example.com/image.jpg",
imageDetail: "high" // "low", "high", or "auto"
)
print(response)// Convert local image to base64
let image = UIImage(named: "myImage")!
let base64String = ImageUtils.imageToBase64(image, format: .jpeg, quality: 0.8)
// Analyze base64 image with text prompt
let response = try await conversation.runWithBase64Image(
"What do you see in this image?",
imageBase64: base64String,
imageDetail: "high"
)
// Or analyze base64 image only
let response = try await conversation.runWithBase64ImageOnly(
base64String,
imageDetail: "auto"
)// Load image from URL and convert to base64
let base64String = try await ImageUtils.urlStringToBase64(
"https://example.com/image.jpg",
format: .jpeg,
quality: 0.8
)
// Use the base64 string
let response = try await conversation.runWithBase64Image(
"Analyze this image",
imageBase64: base64String
)// Create a message with multiple content items
let textItem = ChatMessage.ContentItem(text: "Compare these images:")
let image1Item = ChatMessage.ContentItem(imageUrl: ChatMessage.ImageContent(url: "https://example.com/image1.jpg"))
let image2Item = ChatMessage.ContentItem(imageUrl: ChatMessage.ImageContent(base64: base64String))
let message = ChatMessage(
role: .user,
content: .mixed([textItem, image1Item, image2Item])
)
let messages: [ChatMessage] = [
ChatMessage(role: .system, content: "You are a helpful assistant that can analyze and compare images."),
message
]
let response = try await openAI.generateWithMessages(messages)// Create messages with images
let messages: [ChatMessage] = [
ChatMessage(role: .system, content: "You are a helpful assistant that can analyze images."),
ChatMessage(
role: .user,
text: "What do you see in this image?",
imageUrl: "https://example.com/image.jpg",
imageDetail: "high"
)
]
let response = try await openAI.generateWithMessages(messages)"low": Faster, less detailed analysis"high": More detailed analysis (higher cost)"auto": Let the model decide (recommended)
- JPEG: Good for photographs, smaller file sizes
- PNG: Good for graphics, supports transparency
- Base64: Any image format that can be encoded as base64
// Convert UIImage to base64
let base64 = ImageUtils.imageToBase64(image, format: .jpeg, quality: 0.8)
// Convert Data to base64
let base64 = ImageUtils.dataToBase64(imageData)
// Convert base64 back to UIImage
let image = ImageUtils.base64ToImage(base64String)
// Load image from URL and convert to base64
let base64 = try await ImageUtils.urlStringToBase64("https://example.com/image.jpg")SwiftLangChain supports multiple LLM providers:
let openAI = OpenAIProvider(
apiKey: "your-api-key",
model: "gpt-4"
)let claude = AnthropicProvider(
apiKey: "your-api-key",
model: "claude-3-5-sonnet-20241022"
)let gemini = GeminiProvider(
apiKey: "your-api-key",
model: "gemini-1.5-pro"
)let cohere = CohereProvider(
apiKey: "your-api-key",
model: "command-r-plus"
)let huggingFace = HuggingFaceProvider(
apiKey: "your-api-key",
model: "meta-llama/Llama-2-7b-chat-hf"
)let coreML = CoreMLProvider(
modelName: "YourModel",
fallbackProvider: openAI // Optional fallback to cloud
)OpenAI provider supports streaming responses for real-time output:
let stream = try await openAI.generateStream(prompt: "Tell me a story")
for await chunk in stream {
print(chunk.content)
if chunk.isComplete {
print("Stream complete")
}
}OpenAI provider supports function calling:
let tool = MyFunctionTool()
let openAITool = OpenAITool(function: tool.functionDefinition)
let choice = try await openAI.generateWithTools(
messages: [message],
tools: [openAITool]
)
if let toolCall = choice.message.toolCalls?.first {
let result = try await tool.executeWithArguments(
try JSONSerialization.jsonObject(with: toolCall.function.arguments.data) as? [String: Any] ?? [:]
)
}SwiftLangChain includes mobile-specific tools for iOS:
let cameraTool = CameraTool()
let photoData = try await cameraTool.execute("capture")let locationTool = LocationTool()
let location = try await locationTool.execute("get")let contactsTool = ContactsTool()
let contacts = try await contactsTool.execute("search")let photosTool = PhotosTool()
let photos = try await photosTool.execute("list")Use the response cache to improve performance:
let cache = ResponseCache(policy: CachePolicy(maxAge: 3600, maxSize: 100))
let key = cache.generateKey(prompt: "Hello", parameters: nil)
if let cached = cache.get(key: key) {
print("Cached: \(cached)")
} else {
let response = try await llm.generate(prompt: "Hello")
cache.set(key: key, value: response)
}Configure retry behavior:
let policy = RetryPolicy(
maxAttempts: 3,
backoffStrategy: .exponential,
jitter: true
)Use rate limiting to prevent API throttling:
let limiter = RateLimiter(maxTokens: 100, refillRate: 10)
if limiter.tryConsume() {
// Make API request
} else {
// Wait for token refill
await limiter.waitForToken()
}Track token usage and costs:
let counter = TokenCounter(model: .gpt4)
let tokens = counter.countTokens(in: "Hello, world!")
let cost = counter.estimateCost(tokens: tokens, model: .gpt4)
let tracker = TokenUsageTracker()
await tracker.trackUsage(sessionId: "session-1", promptTokens: 100, completionTokens: 50, model: .gpt4)SwiftLangChain/
βββ Package.swift # Swift Package manifest
βββ README.md # Project overview and usage
βββ LICENSE # Open-source license (MIT, Apache 2.0 etc.)
βββ .gitignore # Standard Swift ignores
βββ Sources/
β βββ SwiftLangChain/
β βββ SwiftLangChain.swift # Re-export or glue file (empty or entry-point)
β βββ Prompt/
β β βββ PromptTemplate.swift # Template with variables: "Hello, {name}"
β β βββ PromptValue.swift # Optional: encapsulates pre-filled template
β βββ LLMProvider/
β β βββ LLMProvider.swift # Protocol (generate(prompt:) async -> String)
β β βββ OpenAI/ # OpenAI provider with streaming and function calling
β β β βββ OpenAIProvider.swift
β β β βββ OpenAIRequest.swift
β β β βββ FunctionCalling.swift
β β βββ Anthropic/ # Claude/Anthropic provider
β β β βββ AnthropicProvider.swift
β β β βββ AnthropicRequest.swift
β β βββ Gemini/ # Google Gemini provider
β β β βββ GeminiProvider.swift
β β β βββ GeminiRequest.swift
β β βββ Cohere/ # Cohere provider
β β β βββ CohereProvider.swift
β β β βββ CohereRequest.swift
β β βββ HuggingFace/ # Hugging Face provider
β β β βββ HuggingFaceProvider.swift
β β β βββ HuggingFaceRequest.swift
β β βββ CoreML/ # Core ML on-device provider
β β βββ CoreMLProvider.swift
β β βββ CoreMLModelLoader.swift
β βββ Chain/
β β βββ Chain.swift # Protocol for all chains
β β βββ SequentialChain.swift # Runs multiple chains step-by-step
β β βββ ConversationChain.swift # Chat-based chain with memory
β βββ Memory/
β β βββ ChatMessage.swift # Message roles and content (with image support)
β β βββ ContextMemory.swift # Rolling memory or token-limited
β βββ Tools/ # Tool protocol and implementations
β β βββ Tool.swift # Tool protocol
β β βββ FunctionTool.swift # Function calling support
β β βββ Mobile/ # Mobile-specific tools
β β βββ CameraTool.swift
β β βββ LocationTool.swift
β β βββ ContactsTool.swift
β β βββ PhotosTool.swift
β βββ Utils/
β β βββ Cache/ # Caching layer
β β β βββ ResponseCache.swift
β β β βββ CachePolicy.swift
β β βββ Retry/ # Retry logic
β β β βββ RetryPolicy.swift
β β β βββ RetryableError.swift
β β βββ RateLimit/ # Rate limiting
β β β βββ RateLimiter.swift
β β βββ Tokenizer/ # Token counting
β β β βββ TokenCounter.swift
β β βββ NetworkClient.swift # HTTP client for API requests
β βββ Agent/
β βββ ConversationalAgent.swift # Agent with tool use
βββ Tests/
β βββ SwiftLangChainTests/
β βββ PromptTemplateTests.swift
β βββ LLMChainTests.swift
β βββ Mocks/
β β βββ MockLLMProvider.swift # Dummy provider for deterministic unit tests
β βββ TestUtils.swift # Helpers for testing
βββ Examples/ # Playground-style demo code (if any)
β βββ HelloLangChain.swift # Simple chain: "Hello, {name}"
β βββ ChatWithMemory.swift # Stateful memory-backed interaction
β βββ ImageExample.swift # Image message examples