IteratorChain
Labels: name: auto:enhancement

### Feature request

I propose the addition of a foundational chain feature, namely, the IteratorChain to LangChain. Unlike a convenience function, this feature is intended to enhance the flexibility and usability of LangChain by allowing it to handle collections of inputs for any existing chain, propagating them sequentially or asynchronously.

```python
from langchain.chains import IteratorChain, LLMChain

llm_chain = LLMChain(...)
iterator_chain = IteratorChain(llm_chain)

inputs = [{"text": "Hello"}, {"text": "World"}]
outputs = iterator_chain.run(inputs)
```

In the current LangChain framework, when dealing with lists or collections of inputs, developers are required to manually loop through the input list and call the run method for each item. This approach not only results in more code but also creates challenges with LangChain's features like LangSmith logging, especially when lists are dynamically generated from previous chains.

The IteratorChain would encapsulate this looping, resulting in cleaner code and better integration with LangSmith logging and other LangChain features.

### Motivation

The proposed IteratorChain could address a current issue I'm having when dealing with nested lists of inputs. Let's consider the current SequentialChain setup below:

```python
SequentialChain(
    chains=[
        ChainA(input_variables=["inputA"], output_key="outputA"),
        TransformChain(input_variables=["outputA", "auxiliary_input"], output_variables=["outputB"], transform=process_A_to_B),
        TransformChain(input_variables=["outputB", "auxiliary_input"], output_variables=["outputC"], transform=refine_B_to_C),
        TransformChain(input_variables=["outputC", "auxiliary_input"], output_variables=["final_output"], transform=process_C_to_final)
    ],
    input_variables=["inputA", "auxiliary_input"],
    output_variables=["final_output"]
)
```

In this setup, process_A_to_B and refine_B_to_C are both creating lists of inputs for further chains. However, these lists of inputs are currently not being processed elegantly. We have to loop manually over the list and call the underlying chain for each item. This not only leads to cumbersome code, but also hinders proper interaction with the LangSmith logging feature.

```python
def process_A_to_B(params) -> List[Dict[str, Any]]:
    ...
    for item in items:
        chainB = SomeChain(...)
        output = chainB.run({"input": item, "auxiliary_input": auxiliary_input})
        ...
    return {"outputB": outputs}
```

```python
def refine_B_to_C(params) -> List[Dict[str, Any]]:
    ...
    for item in items:
        chainC = AnotherChain(...)
        output = chainC.run({"input": item, "auxiliary_input": auxiliary_input})
        ...
    return {"outputC": outputs}
```

The addition of an IteratorChain feature would address these issues. It will encapsulate the manual loop and make the list handling process more intuitive and efficient, ensuring proper integration with LangSmith logging and other LangChain features.

### Your contribution

I'm prepared to contribute to the development of this feature, if decided that it is a good addition and is not already possible using existing capabilities.