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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ jobs:

- name: Check formatting
run: cargo fmt --all -- --check

- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
5 changes: 4 additions & 1 deletion crates/langgraph-checkpoint-sqlite/src/saver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ use langgraph_checkpoint::serde::jsonplus::JsonPlusSerializer;

use crate::queries::*;

/// A dumped blob row: (thread_id, checkpoint_ns, channel, type, checkpoint_id, data)
type BlobDumpRow = (String, String, String, String, String, Option<Vec<u8>>);

/// Async SQLite checkpoint saver using sqlx.
///
/// Uses a three-table schema (`checkpoints`, `checkpoint_blobs`,
Expand Down Expand Up @@ -218,7 +221,7 @@ impl SqliteSaver {
checkpoint_ns: &str,
values: &HashMap<String, JsonValue>,
versions: &ChannelVersions,
) -> Vec<(String, String, String, String, String, Option<Vec<u8>>)> {
) -> Vec<BlobDumpRow> {
let mut result = Vec::new();
for (channel, ver) in versions {
let ver_str = match ver {
Expand Down
8 changes: 5 additions & 3 deletions crates/langgraph-checkpoint/src/cache/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use parking_lot::RwLock;
use serde_json::Value as JsonValue;
use std::collections::HashMap;

/// namespace -> key -> (type_tag, bytes, expire_at_unix_secs)
type CacheMap = HashMap<CacheNamespace, HashMap<String, (String, Vec<u8>, Option<f64>)>>;

/// In-memory cache implementation
pub struct InMemoryCache {
/// namespace -> key -> (type_tag, bytes, expire_at_unix_secs)
cache: RwLock<HashMap<CacheNamespace, HashMap<String, (String, Vec<u8>, Option<f64>)>>>,
cache: RwLock<CacheMap>,
}

impl InMemoryCache {
Expand Down Expand Up @@ -127,7 +129,7 @@ mod tests {
cache
.set(&[((ns.clone(), "k1".to_string()), serde_json::json!(1), None)])
.unwrap();
cache.clear(Some(&[ns.clone()])).unwrap();
cache.clear(Some(std::slice::from_ref(&ns))).unwrap();
let result = cache.get(&[(ns, "k1".to_string())]).unwrap();
assert!(result.is_empty());
}
Expand Down
11 changes: 7 additions & 4 deletions crates/langgraph-checkpoint/src/checkpoint/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ use std::collections::HashMap;
type StorageKey = (String, String, String); // (thread_id, checkpoint_ns, checkpoint_id)
type WriteKey = (String, String, String, i64); // (thread_id, checkpoint_ns, checkpoint_id, idx)

/// (thread_id, checkpoint_ns, checkpoint_id) -> (checkpoint_json, metadata_json, parent_checkpoint_id)
type StorageValue = (JsonValue, JsonValue, Option<String>);
/// (thread_id, checkpoint_ns, checkpoint_id, idx) -> (task_id, channel, value_json, task_path)
type WriteValue = (String, String, JsonValue, String);

/// In-memory checkpoint saver for testing and development.
///
/// Stores checkpoints, blobs, and writes in memory using DashMap
/// for concurrent access.
pub struct InMemorySaver {
// (thread_id, checkpoint_ns, checkpoint_id) -> (checkpoint_json, metadata_json, parent_checkpoint_id)
storage: RwLock<HashMap<StorageKey, (JsonValue, JsonValue, Option<String>)>>,
// (thread_id, checkpoint_ns, checkpoint_id, idx) -> (task_id, channel, value_json, task_path)
writes: RwLock<HashMap<WriteKey, (String, String, JsonValue, String)>>,
storage: RwLock<HashMap<StorageKey, StorageValue>>,
writes: RwLock<HashMap<WriteKey, WriteValue>>,
}

impl InMemorySaver {
Expand Down
2 changes: 1 addition & 1 deletion crates/langgraph-checkpoint/src/store/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl BaseStore for InMemoryStore {
}
}

items.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
items.sort_by_key(|b| std::cmp::Reverse(b.updated_at));
let total = items.len();
let start = search_op.offset.min(total);
let end = (start + search_op.limit).min(total);
Expand Down
1 change: 1 addition & 0 deletions crates/langgraph-core/src/channels/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub trait Channel: Send + Sync + 'static {
fn checkpoint(&self) -> Option<JsonValue>;

/// Restore channel state from a checkpoint.
#[allow(clippy::wrong_self_convention)]
fn from_checkpoint(&self, checkpoint: Option<&JsonValue>) -> Box<dyn Channel>;

/// Apply a batch of updates. Returns true if the channel was modified.
Expand Down
15 changes: 8 additions & 7 deletions crates/langgraph-core/src/graph/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ impl StateGraph {
}

/// Internal: compile with explicit parameters.
#[allow(clippy::too_many_arguments)]
fn compile_with(
&mut self,
checkpointer: Option<Arc<dyn BaseCheckpointSaver>>,
Expand Down Expand Up @@ -596,7 +597,7 @@ impl CompiledStateGraph {
for (tid, chan, val) in pending {
if tid == NULL_TASK_ID {
if let Some(ch) = channels.get(chan) {
ch.update(&[val.clone()]).ok();
ch.update(std::slice::from_ref(val)).ok();
}
}
}
Expand Down Expand Up @@ -793,7 +794,7 @@ impl CompiledStateGraph {
if let Some(obj) = values.as_object() {
for (key, val) in obj {
if let Some(ch) = channels.get(key) {
ch.update(&[val.clone()]).ok();
ch.update(std::slice::from_ref(val)).ok();
// Bump the channel version
let new_version = channel_versions
.get(key)
Expand Down Expand Up @@ -883,12 +884,12 @@ impl CompiledStateGraph {
}
if tid == NULL_TASK_ID {
if let Some(ch) = channels.get(chan) {
ch.update(&[val.clone()]).ok();
ch.update(std::slice::from_ref(val)).ok();
}
continue;
}
if let Some(ch) = channels.get(chan) {
ch.update(&[val.clone()]).ok();
ch.update(std::slice::from_ref(val)).ok();
}
}
}
Expand Down Expand Up @@ -1372,7 +1373,7 @@ impl CompiledStateGraph {
for (_task_id, channel, value) in pending {
if channel != RESUME {
if let Some(ch) = restored.get(channel) {
ch.update(&[value.clone()]).ok();
ch.update(std::slice::from_ref(value)).ok();
}
}
}
Expand Down Expand Up @@ -1443,14 +1444,14 @@ impl CompiledStateGraph {
let input_writes = map_input(&[START.to_string()], input);
for (chan, val) in &input_writes {
if let Some(ch) = channels.get(chan) {
ch.update(&[val.clone()]).ok();
ch.update(std::slice::from_ref(val)).ok();
}
}
if let Some(obj) = input.as_object() {
for (key, val) in obj {
if key != START && !key.starts_with("branch:") && !key.starts_with("join:") {
if let Some(ch) = channels.get(key) {
ch.update(&[val.clone()]).ok();
ch.update(std::slice::from_ref(val)).ok();
}
}
}
Expand Down
11 changes: 5 additions & 6 deletions crates/langgraph-core/src/pregel/algo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fn version_gt(a: &JsonValue, b: &JsonValue) -> bool {
///
/// This is the "Plan" phase of the BSP cycle. It checks which nodes
/// have trigger channels with newer versions than what the node last saw.
#[allow(clippy::too_many_arguments)]
pub fn prepare_next_tasks(
nodes: &HashMap<String, PregelNode>,
channels: &HashMap<String, Box<dyn Channel>>,
Expand Down Expand Up @@ -292,12 +293,10 @@ pub fn apply_writes(
// This allows ephemeral channels to clear themselves and notify downstream.
if bump_step {
for (chan, ch) in channels.iter() {
if ch.is_available() && !updated.contains(chan) {
if ch.update(&[]).unwrap_or(false) {
channel_versions.insert(chan.clone(), next_version.clone());
if ch.is_available() {
updated.insert(chan.clone());
}
if ch.is_available() && !updated.contains(chan) && ch.update(&[]).unwrap_or(false) {
channel_versions.insert(chan.clone(), next_version.clone());
if ch.is_available() {
updated.insert(chan.clone());
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/langgraph-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ fn impl_tool_macro(name_lit: &Option<Lit>, desc_lit: &Option<Lit>, func: &ItemFn
continue;
}
if !extracted_desc.is_empty() {
extracted_desc.push_str(" ");
extracted_desc.push(' ');
}
extracted_desc.push_str(trimmed);
}
Expand Down
5 changes: 4 additions & 1 deletion crates/langgraph-prebuilt/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,15 @@ impl BaseChatModel for Box<dyn BaseChatModel> {
}
}

/// A tool function: maps an input JSON value to an output JSON value.
pub type ToolFn = Box<dyn Fn(&JsonValue) -> Result<JsonValue, ToolError> + Send + Sync>;

/// A simple tool implemented as a closure.
pub struct ClosureTool {
tool_name: String,
tool_description: String,
tool_parameters: Option<JsonValue>,
func: Box<dyn Fn(&JsonValue) -> Result<JsonValue, ToolError> + Send + Sync>,
func: ToolFn,
}

impl ClosureTool {
Expand Down
4 changes: 3 additions & 1 deletion crates/langgraph-providers/src/anthropic/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ enum ContentBlockStart {

#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
enum ContentBlockDelta {
TextDelta { text: String },
InputJsonDelta { partial_json: String },
Expand Down Expand Up @@ -687,7 +688,8 @@ impl BaseChatModel for AnthropicModel {
"message_stop" => {
break;
}
"ping" | _ => {}
"ping" => {}
_ => {}
}
}
}
Expand Down
15 changes: 6 additions & 9 deletions crates/langgraph-providers/src/openai/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,17 +447,14 @@ impl OpenAIModel {
reserved_keys: &[&str],
) -> Option<serde_json::Value> {
let mut extra = extra?;
if let Some(obj) = extra.as_object_mut() {
for key in reserved_keys {
obj.remove(*key);
}
if obj.is_empty() {
return None;
}
return Some(serde_json::Value::Object(obj.clone()));
} else {
let obj = extra.as_object_mut()?;
for key in reserved_keys {
obj.remove(*key);
}
if obj.is_empty() {
return None;
}
Some(serde_json::Value::Object(obj.clone()))
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/langgraph-tracing/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl TracingStore for InMemoryTracingStore {
.collect();

// Sort newest first
summaries.sort_by(|a, b| b.start_time.cmp(&a.start_time));
summaries.sort_by_key(|b| std::cmp::Reverse(b.start_time));

let offset = filter.offset.unwrap_or(0);
let limit = filter.limit.unwrap_or(summaries.len());
Expand Down
1 change: 0 additions & 1 deletion examples/interactive_chat_with_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use langgraph::prebuilt::{
use langgraph::prelude::*;
use langgraph::providers::openai::{OpenAIModel, OpenAIModelConfig};
use langgraph::{langgraph_state, tool, Traceable};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value as JsonValue};
use std::io::{self, Write};
use std::sync::Arc;
Expand Down
1 change: 0 additions & 1 deletion examples/parallel_interrupt_hitl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ async fn worker_b_node(
}

/// Output node: triggered only by worker_a's edge.

async fn output_node(
_input: JsonValue,
_config: RunnableConfig,
Expand Down
Loading