Skip to content

Commit

Permalink
feat: add AppendableValue for managing AgentState property array
Browse files Browse the repository at this point in the history
  • Loading branch information
bsorrentino committed Mar 15, 2024
1 parent ef5ba26 commit 4cb667e
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 12 deletions.
79 changes: 79 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/LangGraph.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "LangGraph"
BuildableName = "LangGraph"
BlueprintName = "LangGraph"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "LangGraphTests"
BuildableName = "LangGraphTests"
BlueprintName = "LangGraphTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "LangGraph"
BuildableName = "LangGraph"
BlueprintName = "LangGraph"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
50 changes: 45 additions & 5 deletions Sources/LangGraph/LangGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,44 @@ public typealias PartialAgentState = [String: Any]
public typealias NodeAction<Action: AgentState> = ( Action ) async throws -> PartialAgentState
public typealias EdgeCondition<Action: AgentState> = ( Action ) async throws -> String

public struct AppendableValue {
var array: [Any]

mutating func append( values: [Any] ) {
array.append(contentsOf: values)
}
mutating func append( value: Any ) {
array.append(value)
}

public init() {
array = []
}

public init( values: [Any] ) {
array = values
}
}

public protocol AgentState {

var data: [String: Any] { get }
var data: [String: Any] { get set }

// subscript(key: String) -> Any? { get }

init()
init( _ initState: [String: Any] )
init()
}

extension AgentState {

public func value<T>( _ key: String ) -> T? {
return data[ key ] as? T
}

public func appendableValue<T>( _ key: String ) -> [T]? {
return (data[ key ] as? AppendableValue)?.array as? [T]
}
}

public struct BaseAgentState : AgentState {
Expand Down Expand Up @@ -139,8 +168,19 @@ public class GraphState<State: AgentState> {
if partialState.isEmpty {
return currentState
}
let newState = currentState.data.merging(partialState, uniquingKeysWith: { (current, _) in
return current
let newState = currentState.data.merging(partialState, uniquingKeysWith: {
(current, new) in

if var appender = current as? AppendableValue {
if let newValue = new as? [Any] {
appender.append(values: newValue)
}
else {
appender.append(value: new)
}
return appender
}
return new
})
return State.init(newState)
}
Expand All @@ -167,7 +207,7 @@ public class GraphState<State: AgentState> {

public func invoke( inputs: PartialAgentState, verbose:Bool = false ) async throws -> State {

var currentState = self.stateType.init( inputs )
var currentState = mergeState( currentState: self.stateType.init(), partialState: inputs)
var currentNodeId = entryPoint

repeat {
Expand Down
85 changes: 78 additions & 7 deletions Tests/LangGraphTests/LangGraphTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,39 @@ import XCTest
final class LangGraphTests: XCTestCase {


func compareAsEquatable(_ value: Any, _ expectedValue: Any) -> Bool {
if let value1 = value as? Int, let value2 = expectedValue as? Int {
return value1 == value2
}
if let value1 = value as? String, let value2 = expectedValue as? String {
return value1 == value2
}
if let values2 = expectedValue as? [Any] {
if let values1 = value as? [Any] {
if values1.count == values2.count {
for ( v1, v2) in zip(values1, values2) {
return compareAsEquatable( v1, v2 )
}
}
}
if let value1 = value as? AppendableValue {
if value1.array.count == values2.count {
for ( v1, v2) in zip(value1.array, values2) {
return compareAsEquatable( v1, v2 )
}
}

}
}
return false
}

func assertDictionaryOfAnyEqual( _ expected: [String:Any], _ current: [String:Any] ) {
XCTAssertEqual(current.count, expected.count, "the dictionaries have different size")
for (key, value) in current {

if let value1 = value as? Int, let value2 = expected[key] as? Int {
XCTAssertTrue( value1 == value2 )
}
if let value1 = value as? String, let value2 = expected[key] as? String {
XCTAssertTrue( value1 == value2 )
}
XCTAssertTrue( compareAsEquatable(value, expected[key]!) )

}

}
Expand Down Expand Up @@ -253,6 +276,54 @@ final class LangGraphTests: XCTestCase {
assertDictionaryOfAnyEqual(["op": "sum", "add1": 37, "add2": 10, "result": 47 ], resultAdd.data)
}


struct AgentStateWithAppender : AgentState {
var data: [String : Any]

init() {
self.init( ["messages": AppendableValue()] )
}

init(_ initState: [String : Any]) {
data = initState
}
var messages:[String]? {
appendableValue("messages")
}
}

func testAppender() async throws {

let workflow = GraphState( stateType: AgentStateWithAppender.self )

try workflow.addNode("agent_1") { state in

print( "agent_1", state )
return ["messages": "message1"]
}
try workflow.addNode("agent_2") { state in

print( "agent_2", state )
return ["messages": ["message2", "message3"] ]
}
try workflow.addNode("agent_3") { state in
print( "agent_3", state )
return ["result": state.messages?.count ?? 0]
}

try workflow.addEdge(sourceId: "agent_1", targetId: "agent_2")
try workflow.addEdge(sourceId: "agent_2", targetId: "agent_3")

try workflow.setEntryPoint("agent_1")
workflow.setFinishPoint("agent_3")

let app = try workflow.compile()

let result = try await app.invoke(inputs: [ : ] )

print( result.data )
assertDictionaryOfAnyEqual( ["messages": [ "message1", "message2", "message3"], "result": 3 ], result.data )

}


}

0 comments on commit 4cb667e

Please sign in to comment.