Skip to content

feat: add run mode: step#343

Merged
brainlid merged 1 commit into
brainlid:mainfrom
cloudwalk:cm-add-step-run-mode
Jul 28, 2025
Merged

feat: add run mode: step#343
brainlid merged 1 commit into
brainlid:mainfrom
cloudwalk:cm-add-step-run-mode

Conversation

@CaiqueMitsuoka

Copy link
Copy Markdown
Contributor

Hey 👋

This PR introduces a new mode called step for LangChain.Chains.LLMChain.run/2.

Context

At Cloudwalk, we are using Langchain to serve 4M users, all with an active assistant.
With that, we have been hitting a few use cases that are not supported by langchain which has made us use a fork with an option to raise an error from tool calls.
This has been hard to keep up with new releases and also contribute back to the project.

The problem

The existing chain execution modes (:until_success, :while_needs_response, and the default mode) don't provide the granular control we need for deterministic workflows in regulated environments.
Our core challenge: We need to conditionally stop execution based on which tools are called, but the current modes force us to decide the execution strategy upfront, before we know what tools the LLM will choose.
Consider this hipothetical scenario: We have a chain with two tools:

  • wire_transfer(amount, account) - Executes a money transfer. Must stop for user confirmation.
  • user_debts() - Returns outstanding debts. Should continue to let LLM explain the results.

The problem becomes apparent when a user says "Pay my debts":

  1. LLM calls user_debts() to check what's owed ✅
  2. Tool returns debt information ✅
  3. LLM calls wire_transfer() to pay the debts ⚠️
  4. We needed to stop here for confirmation, but we can't

Why existing modes fail:

:until_success - Stops after the first successful tool call. Works for wire_transfer but fails for user_debts (No explanation of the result).
:while_needs_response - Continues until LLM provides a final response. Works for user_debts but fails for wire_transfer (Will explain the tool result, it shouldn't).
Default mode - Behaves like :until_success, same limitations.

Use Case :until_success :while_needs_response What We Need
Wire Transfer ✅ Stops correctly ❌ Continues, executes transfer Stop after tool call
Check Debts ❌ No LLM explanation ✅ Provides explanation Continue after tool call
Pay Debts ❌ Stops too early ❌ Doesn't stop for confirmation Conditional stopping

The fundamental issue: We can't know which execution mode we need until we see which tools the LLM decides to call. By then, we're already committed to a mode that might be wrong for the scenario.

What we need is the ability to inspect the chain state after each step and decide whether to continue - giving us the control to implement the deterministic behavior our regulated environment requires.

The solution

Optionally giving control of the loop. While the loop has served and still is serving us well for some use cases it imposes the aforementioned limitations.
A stepped execution will give clients control over execution to implement their own loops. While still being able to call other modes in the middle of the execution.

With this mode we are able to stop on the major chain events: assistant response, tool calls and tool responses.
While still leveraging fallback models and all the data modeling from Langchain.

def custom_run(chain, opts \\ []) do
  {:ok, updated_chain} = LLMChain.run(chain, opts ++ [mode: :step])

  if check_some_condition(updated_chain) do
    {:ok, updated_chain}
  else
    custom_run(updated_chain, opts)
  end
end

This allows clients to inspect the entire chain state and even alter chain state between executions.
For our use case, this will allow us to achieve the UX we need, and will allow new use cases like extending the chain with dynamic few-shot examples/tools in mid execution for example.

@nallwhy

nallwhy commented Jul 27, 2025

Copy link
Copy Markdown
Contributor

This PR is likely to resolve #293 also.

@brainlid

Copy link
Copy Markdown
Owner

This may also be a way to resolve #345.

@brainlid brainlid left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thanks for the great background on the problem being solved and how this approach helps.
❤️💛💙💜

@brainlid
brainlid merged commit efe71a7 into brainlid:main Jul 28, 2025
2 checks passed
@brainlid

Copy link
Copy Markdown
Owner

I'm considering adding an option to with something like:

LLMChain.run(chain, mode: :step, should_continue?: &check_some_condition/1)

It would basically be like the custom_run example, but it could do it internally based on the result of the function.

Would that be helpful in your situation as well?

@CaiqueMitsuoka

Copy link
Copy Markdown
Contributor Author

Thank you @brainlid 😄

It does! And looks super clean. 💪

CaiqueMitsuoka added a commit to CaiqueMitsuoka/langchain that referenced this pull request Aug 18, 2025
…cally

As suggested by @Branlid at brainlid#343.
This adds a new option for `mode: :step` to use a `should_continue?`
function.
This allows fine grained control of the `run/2` loop when no change to
the chain is required.
CaiqueMitsuoka added a commit to CaiqueMitsuoka/langchain that referenced this pull request Aug 18, 2025
…cally

As suggested by @Branlid at brainlid#343.
This adds a new option for `mode: :step` to use a `should_continue?`
function.
This allows fine grained control of the `run/2` loop when no change to
the chain is required.
CaiqueMitsuoka added a commit to CaiqueMitsuoka/langchain that referenced this pull request Aug 18, 2025
…cally

As suggested by @Branlid at brainlid#343.
This adds a new option for `mode: :step` to use a `should_continue?`
function.
This allows fine grained control of the `run/2` loop when no change to
the chain is required.
CaiqueMitsuoka added a commit to CaiqueMitsuoka/langchain that referenced this pull request Aug 21, 2025
…cally

As suggested by @Branlid at brainlid#343.
This adds a new option for `mode: :step` to use a `should_continue?`
function.
This allows fine grained control of the `run/2` loop when no change to
the chain is required.
brainlid pushed a commit that referenced this pull request Sep 8, 2025
…cally (#361)

As suggested by @Branlid at #343.
This adds a new option for `mode: :step` to use a `should_continue?`
function.
This allows fine grained control of the `run/2` loop when no change to
the chain is required.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants