Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trying to sync fork.. #3

Merged
merged 1 commit into from
May 1, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Docs
on:
push:
tags:
- 'langchain-v*.*.*'
- "langchain-v*.*.*"
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
Expand All @@ -27,7 +27,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b

- name: Setup Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b
with:
ref: "${{ github.event.pull_request.base.sha }}" # Required for pull_request_target
fetch-depth: 0

- name: Install Flutter
uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
with:
channel: 'stable'
channel: "stable"

- name: Set-up Flutter
run: |
Expand All @@ -38,7 +38,7 @@ jobs:
run: flutter pub cache clean

- name: Install Melos
uses: bluefireteam/melos-action@5a8367ec4b9942d712528c398ff3f996e03bc230
uses: bluefireteam/melos-action@6085791af7036f6366c9a4b9d55105c0ef9c6388
with:
run-bootstrap: false

Expand Down
1,164 changes: 594 additions & 570 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ linter:
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_final_parameters
# - prefer_final_parameters # adds too much verbosity
- prefer_for_elements_to_map_fromIterable
- prefer_foreach
- prefer_function_declarations_over_variables
Expand Down
15 changes: 12 additions & 3 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
- [Get started](/)
- [Get started](README.md)
- [Installation](/get_started/installation.md)
- [Quickstart](/get_started/quickstart.md)
- [Security](/get_started/security.md)
- [LangChain Expression Language](/expression_language/expression_language.md)
- [Get started](/expression_language/get_started.md)
- [Interface](/expression_language/interface.md)
- [Runnable interface](/expression_language/interface.md)
- [Primitives](/expression_language/primitives.md)
- [Sequence: Chaining runnables](/expression_language/primitives/sequence.md)
- [Map: Formatting inputs & concurrency](/expression_language/primitives/map.md)
- [Passthrough: Passing inputs through](/expression_language/primitives/passthrough.md)
- [Mapper: Mapping inputs](/expression_language/primitives/mapper.md)
- [Function: Run custom logic](/expression_language/primitives/function.md)
- [Binding: Configuring runnables](/expression_language/primitives/binding.md)
- [Router: Routing inputs](/expression_language/primitives/router.md)
- [Streaming](/expression_language/streaming.md)
- Cookbook
- [Prompt + LLM](/expression_language/cookbook/prompt_llm_parser.md)
- [Multiple chains](/expression_language/cookbook/multiple_chains.md)
Expand Down Expand Up @@ -100,7 +109,7 @@
- [LLM](/modules/chains/foundational/llm.md)
- [Sequential](/modules/chains/foundational/sequential.md)
- Documents
- [Stuff](/modules/chains/documents/stuff.md)
- [Stuff](/modules/chains/documents/stuff.md)
- [MapReduce](/modules/chains/documents/map_reduce.md)
- Popular
- [Summarize](/modules/chains/popular/summarize.md)
Expand Down
4 changes: 2 additions & 2 deletions docs/expression_language/cookbook/adding_memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ final promptTemplate = ChatPromptTemplate.fromPromptMessages([

final chain = Runnable.fromMap({
'input': Runnable.passthrough(),
'history': Runnable.fromFunction(
(final _, final __) async {
'history': Runnable.mapInput(
(_) async {
final m = await memory.loadMemoryVariables();
return m['history'];
},
Expand Down
37 changes: 16 additions & 21 deletions docs/expression_language/cookbook/retrieval.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Answer the question based only on the following context:
Question: {question}''');

final chain = Runnable.fromMap<String>({
'context': retriever | Runnable.fromFunction((docs, _) => docs.join('\n')),
'context': retriever | Runnable.mapInput((docs) => docs.join('\n')),
'question': Runnable.passthrough(),
}) | promptTemplate | model | StringOutputParser();

Expand All @@ -42,7 +42,7 @@ await chain.stream('How can I get free shipping?').forEach(stdout.write);
// To get free shipping, you need to place an order over 30€.
```

Imagine that we now want to answer the question in a different language. We will need to pass two parameters when invoking the chain. We can use
Imagine that we now want to answer the question in a different language. We will need to pass two parameters when invoking the chain. We can use

```dart
final promptTemplate = ChatPromptTemplate.fromTemplate('''
Expand All @@ -55,7 +55,7 @@ Answer in the following language: {language}''');

final chain = Runnable.fromMap({
'context': Runnable.getItemFromMap<String>('question') |
(retriever | Runnable.fromFunction((docs, _) => docs.join('\n'))),
(retriever | Runnable.mapInput((docs) => docs.join('\n'))),
'question': Runnable.getItemFromMap('question'),
'language': Runnable.getItemFromMap('language'),
}) |
Expand All @@ -77,11 +77,11 @@ await chain.stream({
// Om gratis verzending te krijgen, moet je bestellingen plaatsen van meer dan 30€.
```

*Note: you may have noticed that we added parentheses around the retriever. This is to workaround the type inference limitations of Dart when using the `|` operator. You won't need them if you use `.pipe` instead.*
_Note: you may have noticed that we added parentheses around the retriever. This is to workaround the type inference limitations of Dart when using the `|` operator. You won't need them if you use `.pipe` instead._

## Conversational Retrieval Chain

Because we can create `Runnable`s from functions we can add in conversation history via a formatting function. This allows us to recreate the popular `ConversationalRetrievalQAChain` to "chat with data":
Because we can create `Runnable`s from functions we can add in conversation history via a formatting function. This allows us to recreate the popular `ConversationalRetrievalQAChain` to "chat with data":

```dart
final retriever = vectorStore.asRetriever();
Expand Down Expand Up @@ -119,9 +119,9 @@ String formatChatHistory(final List<(String, String)> chatHistory) {
final inputs = Runnable.fromMap({
'standalone_question': Runnable.fromMap({
'question': Runnable.getItemFromMap('question'),
'chat_history':
'chat_history':
Runnable.getItemFromMap<List<(String, String)>>('chat_history') |
Runnable.fromFunction((history, _) => formatChatHistory(history)),
Runnable.mapInput(formatChatHistory),
}) |
condenseQuestionPrompt |
model |
Expand All @@ -131,9 +131,7 @@ final inputs = Runnable.fromMap({
final context = Runnable.fromMap({
'context': Runnable.getItemFromMap<String>('standalone_question') |
retriever |
Runnable.fromFunction<List<Document>, String>(
(docs, _) => combineDocuments(docs),
),
Runnable.mapInput<List<Document>, String>(combineDocuments),
'question': Runnable.getItemFromMap('standalone_question'),
});

Expand Down Expand Up @@ -209,26 +207,25 @@ String formatChatHistory(final List<ChatMessage> chatHistory) {
// First, we load the memory
final loadedMemory = Runnable.fromMap({
'question': Runnable.getItemFromMap('question'),
'memory': Runnable.fromFunction((_, __) => memory.loadMemoryVariables()),
'memory': Runnable.mapInput((_) => memory.loadMemoryVariables()),
});

// Next, we get the chat history from the memory
final expandedMemory = Runnable.fromMap({
'question': Runnable.getItemFromMap('question'),
'chat_history': Runnable.getItemFromMap('memory') |
Runnable.fromFunction<MemoryVariables, List<ChatMessage>>(
(input, _) => input['history'],
Runnable.mapInput<MemoryVariables, List<ChatMessage>>(
(final input) => input['history'],
),
});

// Now, we generate a standalone question that includes the
// Now, we generate a standalone question that includes the
// necessary details from the chat history
final standaloneQuestion = Runnable.fromMap({
'standalone_question': Runnable.fromMap({
'question': Runnable.getItemFromMap('question'),
'chat_history': Runnable.getItemFromMap<List<ChatMessage>>(
'chat_history') |
Runnable.fromFunction((history, _) => formatChatHistory(history)),
'chat_history': Runnable.getItemFromMap<List<ChatMessage>>('chat_history') |
Runnable.mapInput(formatChatHistory),
}) |
condenseQuestionPrompt |
model |
Expand All @@ -244,9 +241,7 @@ final retrievedDocs = Runnable.fromMap({
// Construct the inputs for the answer prompt
final finalInputs = Runnable.fromMap({
'context': Runnable.getItemFromMap('docs') |
Runnable.fromFunction<List<Document>, String>(
(docs, _) => combineDocuments(docs),
),
Runnable.mapInput<List<Document>, String>(combineDocuments),
'question': Runnable.getItemFromMap('question'),
});

Expand All @@ -263,7 +258,7 @@ final conversationalQaChain = loadedMemory |
retrievedDocs |
answer;

// If we add some messages to the memory,
// If we add some messages to the memory,
// they will be used in the next invocation
await memory.saveContext(
inputValues: {
Expand Down
4 changes: 2 additions & 2 deletions docs/expression_language/expression_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

LangChain Expression Language, or LCEL, is a declarative way to easily compose chains together. LCEL was designed from day 1 to support putting prototypes in production, with no code changes, from the simplest “prompt + LLM” chain to the most complex chains (we’ve seen folks successfully run LCEL chains with 100s of steps in production). To highlight a few of the reasons you might want to use LCEL:

- **Streaming support:** When you build your chains with LCEL you get the best possible time-to-first-token (time elapsed until the first chunk of output comes out). For some chains this means eg. we stream tokens straight from an LLM to a streaming output parser, and you get back parsed, incremental chunks of output at the same rate as the LLM provider outputs the raw tokens.
- **Optimized parallel execution:** Whenever your LCEL chains have steps that can be executed in parallel (eg if you fetch documents from multiple retrievers) we automatically do it for the smallest possible latency.
- **First-class streaming support:** When you build your chains with LCEL you get the best possible time-to-first-token (time elapsed until the first chunk of output comes out). For some chains this means eg. we stream tokens straight from an LLM to a streaming output parser, and you get back parsed, incremental chunks of output at the same rate as the LLM provider outputs the raw tokens.
- **Optimized concurrent execution:** Whenever your LCEL chains have steps that can be executed concurrently (eg if you fetch documents from multiple retrievers) we automatically do it for the smallest possible latency.
- **Retries and fallbacks:** Configure retries and fallbacks for any part of your LCEL chain. This is a great way to make your chains more reliable at scale.
- **Access intermediate results:** For more complex chains it’s often very useful to access the results of intermediate steps even before the final output is produced. This can be used to let end-users know something is happening, or even just to debug your chain.
121 changes: 96 additions & 25 deletions docs/expression_language/get_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The `PromptValue` is then passed to `model`. In this case our `model` is a `Chat
final chatOutput = await model.invoke(promptValue);
print(chatOutput.output);
// AIChatMessage{
// content: Why did the ice cream truck break down?
// content: Why did the ice cream truck break down?
// Because it couldn't make it over the rocky road!,
// }
```
Expand Down Expand Up @@ -96,15 +96,50 @@ To follow the steps along:
3. The `model` component takes the generated prompt, and passes into the OpenAI chat model for evaluation. The generated output from the model is a `ChatMessage` object (specifically an `AIChatMessage`).
4. Finally, the `outputParser` component takes in a `ChatMessage`, and transforms this into a `String`, which is returned from the invoke method.

![Pipeline](img/pipeline.png)

Note that if you’re curious about the output of any components, you can always test out a smaller version of the chain such as `promptTemplate` or `promptTemplate.pipe(model)` to see the intermediate results.

```dart
final input = {'topic': 'ice cream'};

final res1 = await promptTemplate.invoke(input);
print(res1.toChatMessages());
// [HumanChatMessage{
// content: ChatMessageContentText{
// text: Tell me a joke about ice cream,
// },
// }]

final res2 = await promptTemplate.pipe(model).invoke(input);
print(res2);
// ChatResult{
// id: chatcmpl-9J37Tnjm1dGUXqXBF98k7jfexATZW,
// output: AIChatMessage{
// content: Why did the ice cream cone go to therapy? Because it had too many sprinkles of emotional issues!,
// },
// finishReason: FinishReason.stop,
// metadata: {
// model: gpt-3.5-turbo-0125,
// created: 1714327251,
// system_fingerprint: fp_3b956da36b
// },
// usage: LanguageModelUsage{
// promptTokens: 14,
// promptBillableCharacters: null,
// responseTokens: 21,
// responseBillableCharacters: null,
// totalTokens: 35
// },
// streaming: false
// }
```

## RAG Search Example

For our next example, we want to run a retrieval-augmented generation chain to add some context when responding to questions.

```dart
final openaiApiKey = Platform.environment['OPENAI_API_KEY'];

// 1. Create a vector store and add documents to it
final vectorStore = MemoryVectorStore(
embeddings: OpenAIEmbeddings(apiKey: openaiApiKey),
Expand All @@ -116,44 +151,80 @@ await vectorStore.addDocuments(
],
);

// 2. Construct a RAG prompt template
// 2. Define the retrieval chain
final retriever = vectorStore.asRetriever();
final setupAndRetrieval = Runnable.fromMap<String>({
'context': retriever.pipe(
Runnable.mapInput((docs) => docs.map((d) => d.pageContent).join('\n')),
),
'question': Runnable.passthrough(),
});

// 3. Construct a RAG prompt template
final promptTemplate = ChatPromptTemplate.fromTemplates([
(ChatMessageType.system, 'Answer the question based on only the following context:\n{context}'),
(ChatMessageType.human, '{question}'),
]);

// 3. Create a Runnable that combines the retrieved documents into a single string
final docCombiner = Runnable.fromFunction<List<Document>, String>((docs, _) {
return docs.map((final d) => d.pageContent).join('\n');
});

// 4. Define the RAG pipeline
final chain = Runnable.fromMap<String>({
'context': vectorStore.asRetriever().pipe(docCombiner),
'question': Runnable.passthrough(),
})
// 4. Define the final chain
final model = ChatOpenAI(apiKey: openaiApiKey);
const outputParser = StringOutputParser<ChatResult>();
final chain = setupAndRetrieval
.pipe(promptTemplate)
.pipe(ChatOpenAI(apiKey: openaiApiKey))
.pipe(StringOutputParser());
.pipe(model)
.pipe(outputParser);

// 5. Run the pipeline
final res = await chain.invoke('Who created LangChain.dart?');
print(res);
// David created LangChain.dart
```

In this chain we add some extra logic around retrieving context from a vector store.
In this case, the composed chain is:

```dart
final chain = setupAndRetrieval
.pipe(promptTemplate)
.pipe(model)
.pipe(outputParser);
```

To explain this, we first can see that the prompt template above takes in `context` and `question` as values to be substituted in the prompt. Before building the prompt template, we want to retrieve relevant documents to the search and include them as part of the context.

As a preliminary step, we’ve set up the retriever using an in memory store, which can retrieve documents based on a query. This is a runnable component as well that can be chained together with other components, but you can also try to run it separately:

```dart
final res1 = await retriever.invoke('Who created LangChain.dart?');
print(res1);
// [Document{pageContent: David ported LangChain to Dart in LangChain.dart},
// Document{pageContent: LangChain was created by Harrison, metadata: {}}]
```

We then use the `RunnableMap` to prepare the expected inputs into the prompt by using a string containing the combined retrieved documents as well as the original user question, using the `retriever` for document search, a `RunnableMapInput` to combine the documents and `RunnablePassthrough` to pass the user's question:

We first instantiate our vector store and add some documents to it. Then we define our prompt, which takes in two input variables:
```dart
final setupAndRetrieval = Runnable.fromMap<String>({
'context': retriever.pipe(
Runnable.mapInput((docs) => docs.map((d) => d.pageContent).join('\n')),
),
'question': Runnable.passthrough(),
});
```

- `context` -> this is a string which is returned from our vector store based on a semantic search from the input.
- `question` -> this is the question we want to ask.
To review, the complete chain is:

In our `chain`, we use a `RunnableMap` which is special type of runnable that takes an object of runnables and executes them all in parallel. It then returns an object with the same keys as the input object, but with the values replaced with the output of the runnables.
```dart
final chain = setupAndRetrieval
.pipe(promptTemplate)
.pipe(model)
.pipe(outputParser);
```

In our case, it has two sub-chains to get the data required by our prompt:
With the flow being:

- `context` -> this is a `RunnableFunction` which takes the input from the `.invoke()` call, makes a request to our vector store, and returns the retrieved documents combined in a single String.
- `question` -> this uses a `RunnablePassthrough` which simply passes whatever the input was through to the next step, and in our case it returns it to the key in the object we defined.
1. The first steps create a `RunnableMap` object with two entries. The first entry, `context` will include the combined document results fetched by the retriever. The second entry, `question` will contain the user’s original question. To pass on the `question`, we use `RunnablePassthrough` to copy this entry.
2. Feed the map from the step above to the `promptTemplate` component. It then takes the user input which is `question` as well as the retrieved documents which is `context` to construct a prompt and output a `PromptValue`.
3. The `model` component takes the generated prompt, and passes into the OpenAI LLM model for evaluation. The generated `output` from the model is a `ChatResult` object.
4. Finally, the `outputParser` component takes in the `ChatResult`, and transforms this into a Dart String, which is returned from the invoke method.

Finally, we chain together the prompt, model, and output parser as before.
![RAG Pipeline](img/rag_pipeline.png)
Binary file added docs/expression_language/img/pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/expression_language/img/rag_pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading