Skip to content
Draft
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
24 changes: 18 additions & 6 deletions chains/sequential.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,29 @@ func (c *SequentialChain) validateSeqChain() error {
// not be called directly. Use rather the Call, Run or Predict functions that
// handles the memory and other aspects of the chain.
func (c *SequentialChain) Call(ctx context.Context, inputs map[string]any, options ...ChainCallOption) (map[string]any, error) { //nolint:lll
var outputs map[string]any
var err error
knownValues := make(map[string]any, len(inputs))
for k, v := range inputs {
knownValues[k] = v
}

for _, chain := range c.chains {
outputs, err = Call(ctx, chain, inputs, options...)
outputs, err := Call(ctx, chain, knownValues, options...)
if err != nil {
return nil, err
}
// Set the input for the next chain to the output of the current chain
inputs = outputs
// Merge outputs into known values so subsequent chains
// and the final result can access all intermediate outputs.
for k, v := range outputs {
knownValues[k] = v
}
}

// Return only the declared output keys.
result := make(map[string]any, len(c.outputKeys))
for _, key := range c.outputKeys {
result[key] = knownValues[key]
}
return outputs, nil
return result, nil
}

// GetMemory gets the memory of the chain.
Expand Down
44 changes: 44 additions & 0 deletions chains/sequential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,50 @@ func TestSequentialChain(t *testing.T) {
assert.Equal(t, "Vey legit", res[_llmChainDefaultOutputKey])
}

func TestSequentialChainIntermediateOutputs(t *testing.T) {
ctx := context.Background()
t.Parallel()

// Reproduce issue #1095: intermediate outputs (e.g. "synopsis") should be
// accessible when declared in outputKeys.
testLLM1 := &testLanguageModel{expResult: "A gripping tale of mystery"}
testLLM2 := &testLanguageModel{expResult: "Five stars, must watch"}

chain1 := NewLLMChain(
testLLM1,
prompts.NewPromptTemplate("Write a synopsis for {{.title}} set in {{.era}}", []string{"title", "era"}),
)
chain1.OutputKey = "synopsis"

chain2 := NewLLMChain(
testLLM2,
prompts.NewPromptTemplate("Review this synopsis: {{.synopsis}}", []string{"synopsis"}),
)
chain2.OutputKey = "review"

// Request BOTH intermediate and final outputs.
seqChain, err := NewSequentialChain(
[]Chain{chain1, chain2},
[]string{"title", "era"},
[]string{"synopsis", "review"},
)
require.NoError(t, err)

res, err := Call(ctx, seqChain, map[string]any{
"title": "Mystery in the haunted mansion",
"era": "1930s in Haiti",
})
require.NoError(t, err)

// Both intermediate and final outputs must be present.
assert.Equal(t, "A gripping tale of mystery", res["synopsis"])
assert.Equal(t, "Five stars, must watch", res["review"])

// chain2 must have received chain1's output as its prompt input.
expPrompt := "Review this synopsis: A gripping tale of mystery"
assert.Equal(t, expPrompt, testLLM2.recordedPrompt[0].String())
}

func TestSequentialChainErrors(t *testing.T) {
ctx := context.Background()
t.Parallel()
Expand Down
Loading