Every chain (LLMChain, ConversationalRetrievalChain etc) should return stream without CallbackHandler same as llm ChatModel
Labels: name: auto:improvement, name: area: models

### Feature request

All chains inherit from `Runnable` class and It has the `stream` method. But no chain has it's own `stream` method. That's why we have to pass a callback handler every time when work on streaming. We need to write additional logic to handle stream. There should be a functionality where we can just call the stream method and it will return the Generator same as ChatModel (ChatOpenAI).

### Motivation

I believe Chain is the core of LangChain. This is so much frustrating when work with stream. 
Every time I work with streaming I have to write something like this.  I have to run the qa task in the background using task or thread which is not suitable If I using a framework. Framework should handle this. New developers will struggle if they need to handle additional logic even though they used a framework. 

 ```
class StreamingLLMCallbackHandler(BaseCallbackHandler):
    """Callback handler for streaming LLM responses to a queue."""

    def __init__(self):
        self._is_done = False
        self._queue = Queue()

    def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
        """Run on new LLM token. Only available when streaming is enabled."""
        # print(token)
        self._queue.put(EventData(content=token))

    def on_llm_end(self, response: LLMResult, *, run_id: UUID, parent_run_id: Optional[UUID] = None, **kwargs: Any) -> Any:
        """Run when LLM ends running."""

        self._queue.put(EventData(content=None, finish_reason='done'))
        self._is_done = True

    def add_new_token_to_stream(self, data: EventData, is_done=True):
        self._queue.put(data)
        self._is_done = is_done

    @property
    def stream_gen(self):
        while not self._is_done or not self._queue.empty():
            try:
                delta: EventData = self._queue.get()
                if delta.data.get('finish_reason') == 'error':
                    yield str(StreamData(event_data=delta, event=EnumStreamEventType.ERROR))
                else:
                    yield str(StreamData(event_data=delta))
            except Empty:
                continue
 class LangChainChatService(ChatBaseService):
    def __init__(self, model: LangchainChatModel, tool_query=None):
        super().__init__()
        self.model = model
        self.request_manager = api_request_manager_var.get()
        self.tool_query = tool_query

    def _qa_task(self, streaming_handler: StreamingLLMCallbackHandler, qa: BaseConversationalRetrievalChain, formatted_chat_history: list[BaseMessage]):
        try:
            question = self.tool_query or self.model.query
            answer = qa.run(question=question, chat_history=formatted_chat_history)
            self._publish_chat_history(answer)
        except Exception as ex:
            streaming_handler.add_new_token_to_stream(EventData(content=get_user_message_on_exception(ex), error=build_error_details(ex), finish_reason='error'))
            logger.exception(ex)

    async def stream_chat_async(self):
        streaming_handler = StreamingLLMCallbackHandler()
        try:
            formatted_chat_history = [] if self.tool_query else self.get_formatted_chat_history(self.model.chat_history)
            qa = self._get_qa_chain(callbacks=[streaming_handler])
            asyncio.create_task(asyncio.to_thread(self._qa_task, streaming_handler, qa, formatted_chat_history))
            return streaming_handler.stream_gen
        except Exception as ex:
            logger.exception(ex)
            streaming_handler.add_new_token_to_stream(EventData(content=get_user_message_on_exception(ex), error=build_error_details(ex), finish_reason='error'))
            return streaming_handler.stream_gen
```

### Your contribution

N/A