merged in main
This commit is contained in:
commit
5f522bf6a9
10 changed files with 965 additions and 672 deletions
|
|
@ -9,7 +9,7 @@ LANGFLOW_SECRET_KEY=
|
|||
LANGFLOW_CHAT_FLOW_ID=1098eea1-6649-4e1d-aed1-b77249fb8dd0
|
||||
LANGFLOW_INGEST_FLOW_ID=5488df7c-b93f-4f87-a446-b67028bc0813
|
||||
# Ingest flow using docling
|
||||
LANGFLOW_INGEST_FLOW_ID=1402618b-e6d1-4ff2-9a11-d6ce71186915
|
||||
# LANGFLOW_INGEST_FLOW_ID=1402618b-e6d1-4ff2-9a11-d6ce71186915
|
||||
NUDGES_FLOW_ID=ebc01d31-1976-46ce-a385-b0240327226c
|
||||
|
||||
# Set a strong admin password for OpenSearch; a bcrypt hash is generated at
|
||||
|
|
|
|||
390
docs/docs/get-started/quickstart.mdx
Normal file
390
docs/docs/get-started/quickstart.mdx
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
---
|
||||
title: Quickstart
|
||||
slug: /quickstart
|
||||
---
|
||||
|
||||
import Icon from "@site/src/components/icon/icon";
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Get started with OpenRAG by loading your knowledge, swapping out your language model, and then chatting with the OpenRAG API.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Install and start OpenRAG
|
||||
|
||||
## Find your way around
|
||||
|
||||
1. In OpenRAG, click <Icon name="MessageSquare" aria-hidden="true"/> **Chat**.
|
||||
2. Ask `What documents are available to you?`
|
||||
The agent responds with a message summarizing the documents that OpenRAG loads by default, which are PDFs about evaluating data quality when using LLMs in health care.
|
||||
3. To confirm the agent is correct, click <Icon name="Library" aria-hidden="true"/> **Knowledge**.
|
||||
The **Knowledge** page lists the documents OpenRAG has ingested into the OpenSearch vector database. Click on a document to display the chunks derived from splitting the default documents into the vector database.
|
||||
|
||||
## Add your own knowledge
|
||||
|
||||
1. To add documents to your knowledge base, click <Icon name="Plus" aria-hidden="true"/> **Add Knowledge**.
|
||||
* Select **Add File** to add a single file from your local machine (mapped with the Docker volume mount).
|
||||
* Select **Process Folder** to process an entire folder of documents from your local machine (mapped with the Docker volume mount).
|
||||
2. Return to the Chat window and ask a question about your loaded data.
|
||||
For example, with a manual about a PC tablet loaded, ask `How do I connect this device to WiFI?`
|
||||
The agent responds with a message indicating it now has your knowledge as context for answering questions.
|
||||
3. Click the <Icon name="Gear" aria-hidden="true"/> **Function Call: search_documents (tool_call)** that is printed in the Playground.
|
||||
These events log the agent's request to the tool and the tool's response, so you have direct visibility into your agent's functionality.
|
||||
If you aren't getting the results you need, you can further tune the knowledge ingestion and agent behavior in the next section.
|
||||
|
||||
## Swap out the language model to modify agent behavior
|
||||
|
||||
To modify the knowledge ingestion or Agent behavior, click <Icon name="Settings" aria-hidden="true"/> **Settings**.
|
||||
|
||||
In this example, you'll try a different LLM to demonstrate how the Agent's response changes.
|
||||
|
||||
1. To edit the Agent's behavior, click **Edit in Langflow**.
|
||||
2. OpenRAG warns you that you're entering Langflow. Click **Proceed**.
|
||||
3. The OpenRAG Open Search Agent flow appears.
|
||||
|
||||

|
||||
|
||||
4. In the **Language Model** component, under **Model Provider**, select **Anthropic**.
|
||||
:::note
|
||||
This guide uses an Anthropic model for demonstration purposes. If you want to use a different provider, change the **Model Provider** and **Model Name** fields, and then provide credentials for your selected provider.
|
||||
:::
|
||||
5. Save your flow with <kbd>Command+S</kbd>.
|
||||
6. In OpenRAG, start a new conversation by clicking the <Icon name="Plus" aria-hidden="true"/> in the **Conversations** tab.
|
||||
7. Ask the same question as before to demonstrate how a different language model changes the results.
|
||||
|
||||
## Integrate OpenRAG into your application
|
||||
|
||||
:::tip
|
||||
Ensure the `openrag-backend` container has port 8000 exposed in your `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
openrag-backend:
|
||||
ports:
|
||||
- "8000:8000"
|
||||
```
|
||||
:::
|
||||
|
||||
OpenRAG provides a REST API that you can call from Python, TypeScript, or any HTTP client to chat with your documents.
|
||||
|
||||
These example requests are run assuming OpenRAG is in "no-auth" mode.
|
||||
For complete API documentation, including authentication, request and response parameters, and example requests, see the API documentation.
|
||||
|
||||
### Chat with your documents
|
||||
|
||||
Prompt OpenRAG at the `/chat` API endpoint.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="python" label="Python">
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "http://localhost:8000/chat"
|
||||
payload = {
|
||||
"prompt": "What documents are available to you?",
|
||||
"previous_response_id": None
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload)
|
||||
print("OpenRAG Response:", response.json())
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
```typescript
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const response = await fetch("http://localhost:8000/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
prompt: "What documents are available to you?",
|
||||
previous_response_id: null
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log("OpenRAG Response:", data);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="curl">
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/chat" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"prompt": "What documents are available to you?",
|
||||
"previous_response_id": null
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<details closed>
|
||||
<summary>Response</summary>
|
||||
|
||||
```
|
||||
{
|
||||
"response": "I have access to a wide range of documents depending on the context and the tools enabled in this environment. Specifically, I can search for and retrieve documents related to various topics such as technical papers, articles, manuals, guides, knowledge base entries, and other text-based resources. If you specify a particular subject or type of document you're interested in, I can try to locate relevant materials for you. Let me know what you need!",
|
||||
"response_id": "resp_68d3fdbac93081958b8781b97919fe7007f98bd83932fa1a"
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Search your documents
|
||||
|
||||
Search your document knowledge base at the `/search` endpoint.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="python" label="Python">
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "http://localhost:8000/search"
|
||||
payload = {"query": "healthcare data quality", "limit": 5}
|
||||
|
||||
response = requests.post(url, json=payload)
|
||||
results = response.json()
|
||||
|
||||
print("Search Results:")
|
||||
for result in results.get("results", []):
|
||||
print(f"- {result.get('filename')}: {result.get('text', '')[:100]}...")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
```typescript
|
||||
const response = await fetch("http://localhost:8000/search", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
query: "healthcare data quality",
|
||||
limit: 5
|
||||
})
|
||||
});
|
||||
|
||||
const results = await response.json();
|
||||
console.log("Search Results:");
|
||||
results.results?.forEach((result, index) => {
|
||||
const filename = result.filename || 'Unknown';
|
||||
const text = result.text?.substring(0, 100) || '';
|
||||
console.log(`${index + 1}. ${filename}: ${text}...`);
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="curl">
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/search" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "healthcare data quality", "limit": 5}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
<details closed>
|
||||
<summary>Example response</summary>
|
||||
|
||||
```
|
||||
Found 5 results
|
||||
1. 2506.08231v1.pdf: variables with high performance metrics. These variables might also require fewer replication analys...
|
||||
2. 2506.08231v1.pdf: on EHR data and may lack the clinical domain knowledge needed to perform well on the tasks where EHR...
|
||||
3. 2506.08231v1.pdf: Abstract Large language models (LLMs) are increasingly used to extract clinical data from electronic...
|
||||
4. 2506.08231v1.pdf: these multidimensional assessments, the framework not only quantifies accuracy, but can also be appl...
|
||||
5. 2506.08231v1.pdf: observed in only the model metrics, but not the abstractor metrics, it indicates that model errors m...
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Use chat and search together
|
||||
|
||||
Create a complete chat application that combines an interactive terminal chat with session continuity and search functionality.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="python" label="Python">
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Configuration
|
||||
OPENRAG_BASE_URL = "http://localhost:8000"
|
||||
CHAT_URL = f"{OPENRAG_BASE_URL}/chat"
|
||||
SEARCH_URL = f"{OPENRAG_BASE_URL}/search"
|
||||
DEFAULT_SEARCH_LIMIT = 5
|
||||
|
||||
def chat_with_openrag(message, previous_response_id=None):
|
||||
try:
|
||||
response = requests.post(CHAT_URL, json={
|
||||
"prompt": message,
|
||||
"previous_response_id": previous_response_id
|
||||
})
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return data.get("response"), data.get("response_id")
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}", None
|
||||
|
||||
def search_documents(query, limit=DEFAULT_SEARCH_LIMIT):
|
||||
try:
|
||||
response = requests.post(SEARCH_URL, json={
|
||||
"query": query,
|
||||
"limit": limit
|
||||
})
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return data.get("results", [])
|
||||
except Exception as e:
|
||||
return []
|
||||
|
||||
# Interactive chat with session continuity and search
|
||||
previous_response_id = None
|
||||
while True:
|
||||
question = input("Your question (or 'search <query>' to search): ").strip()
|
||||
if question.lower() in ['quit', 'exit', 'q']:
|
||||
break
|
||||
if not question:
|
||||
continue
|
||||
|
||||
if question.lower().startswith('search '):
|
||||
query = question[7:].strip()
|
||||
print("Searching documents...")
|
||||
results = search_documents(query)
|
||||
print(f"\nFound {len(results)} results:")
|
||||
for i, result in enumerate(results, 1):
|
||||
filename = result.get('filename', 'Unknown')
|
||||
text = result.get('text', '')[:100]
|
||||
print(f"{i}. {filename}: {text}...")
|
||||
print()
|
||||
else:
|
||||
print("OpenRAG is thinking...")
|
||||
result, response_id = chat_with_openrag(question, previous_response_id)
|
||||
print(f"OpenRAG: {result}\n")
|
||||
previous_response_id = response_id
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
```ts
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
// Configuration
|
||||
const OPENRAG_BASE_URL = "http://localhost:8000";
|
||||
const CHAT_URL = `${OPENRAG_BASE_URL}/chat`;
|
||||
const SEARCH_URL = `${OPENRAG_BASE_URL}/search`;
|
||||
const DEFAULT_SEARCH_LIMIT = 5;
|
||||
|
||||
async function chatWithOpenRAG(message: string, previousResponseId?: string | null) {
|
||||
try {
|
||||
const response = await fetch(CHAT_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
prompt: message,
|
||||
previous_response_id: previousResponseId
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
return [data.response || "No response received", data.response_id || null];
|
||||
} catch (error) {
|
||||
return [`Error: ${error}`, null];
|
||||
}
|
||||
}
|
||||
|
||||
async function searchDocuments(query: string, limit: number = DEFAULT_SEARCH_LIMIT) {
|
||||
try {
|
||||
const response = await fetch(SEARCH_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ query, limit })
|
||||
});
|
||||
const data = await response.json();
|
||||
return data.results || [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Interactive chat with session continuity and search
|
||||
let previousResponseId = null;
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
|
||||
const askQuestion = () => {
|
||||
rl.question("Your question (or 'search <query>' to search): ", async (question) => {
|
||||
if (question.toLowerCase() === 'quit' || question.toLowerCase() === 'exit' || question.toLowerCase() === 'q') {
|
||||
console.log("Goodbye!");
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
if (!question.trim()) {
|
||||
askQuestion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (question.toLowerCase().startsWith('search ')) {
|
||||
const query = question.substring(7).trim();
|
||||
console.log("Searching documents...");
|
||||
const results = await searchDocuments(query);
|
||||
console.log(`\nFound ${results.length} results:`);
|
||||
results.forEach((result, i) => {
|
||||
const filename = result.filename || 'Unknown';
|
||||
const text = result.text?.substring(0, 100) || '';
|
||||
console.log(`${i + 1}. ${filename}: ${text}...`);
|
||||
});
|
||||
console.log();
|
||||
} else {
|
||||
console.log("OpenRAG is thinking...");
|
||||
const [result, responseId] = await chatWithOpenRAG(question, previousResponseId);
|
||||
console.log(`\nOpenRAG: ${result}\n`);
|
||||
previousResponseId = responseId;
|
||||
}
|
||||
askQuestion();
|
||||
});
|
||||
};
|
||||
|
||||
console.log("OpenRAG Chat Interface");
|
||||
console.log("Ask questions about your documents. Type 'quit' to exit.");
|
||||
console.log("Use 'search <query>' to search documents directly.\n");
|
||||
askQuestion();
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<details closed>
|
||||
<summary>Example response</summary>
|
||||
|
||||
```
|
||||
Your question (or 'search <query>' to search): search healthcare
|
||||
Searching documents...
|
||||
|
||||
Found 5 results:
|
||||
1. 2506.08231v1.pdf: variables with high performance metrics. These variables might also require fewer replication analys...
|
||||
2. 2506.08231v1.pdf: on EHR data and may lack the clinical domain knowledge needed to perform well on the tasks where EHR...
|
||||
3. 2506.08231v1.pdf: Abstract Large language models (LLMs) are increasingly used to extract clinical data from electronic...
|
||||
4. 2506.08231v1.pdf: Acknowledgements Darren Johnson for support in publication planning and management. The authors used...
|
||||
5. 2506.08231v1.pdf: Ensuring Reliability of Curated EHR-Derived Data: The Validation of Accuracy for LLM/ML-Extracted In...
|
||||
|
||||
Your question (or 'search <query>' to search): what's the weather today?
|
||||
OpenRAG is thinking...
|
||||
OpenRAG: I don't have access to real-time weather data. Could you please provide me with your location? Then I can help you find the weather information.
|
||||
|
||||
Your question (or 'search <query>' to search): newark nj
|
||||
OpenRAG is thinking...
|
||||
```
|
||||
|
||||
</details>
|
||||
## Next steps
|
||||
|
||||
TBD
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: What is OpenRAG?
|
||||
slug: /what-is-openrag
|
||||
slug: /
|
||||
---
|
||||
|
||||
OpenRAG is an open-source package for building agentic RAG systems.
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const config = {
|
|||
logo: {
|
||||
alt: 'OpenRAG Logo',
|
||||
src: 'img/logo.svg',
|
||||
href: 'what-is-openrag',
|
||||
href: '/',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
|
|
@ -89,7 +89,7 @@ const config = {
|
|||
items: [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
to: 'what-is-openrag',
|
||||
to: '/',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ const sidebars = {
|
|||
id: "get-started/what-is-openrag",
|
||||
label: "Introduction"
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "get-started/quickstart",
|
||||
label: "Quickstart"
|
||||
},
|
||||
|
||||
{
|
||||
type: "doc",
|
||||
id: "get-started/docker",
|
||||
|
|
|
|||
BIN
docs/static/img/opensearch-agent-flow.png
vendored
Normal file
BIN
docs/static/img/opensearch-agent-flow.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 951 KiB |
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
<<<<<<< HEAD
|
||||
import * as React from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import {
|
||||
|
|
@ -9,6 +10,11 @@ import {
|
|||
ChevronUp,
|
||||
LockIcon,
|
||||
} from "lucide-react";
|
||||
=======
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp, Lock } from "lucide-react"
|
||||
>>>>>>> main
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ async def update_settings(request, session_manager):
|
|||
"chunk_size",
|
||||
"chunk_overlap",
|
||||
"doclingPresets",
|
||||
"embedding_model",
|
||||
}
|
||||
|
||||
# Check for invalid fields
|
||||
|
|
@ -202,11 +203,53 @@ async def update_settings(request, session_manager):
|
|||
current_config.agent.llm_model = body["llm_model"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the chat flow with the new model
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_chat_flow_model(body["llm_model"])
|
||||
logger.info(f"Successfully updated chat flow model to '{body['llm_model']}'")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update chat flow model: {str(e)}")
|
||||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
|
||||
if "system_prompt" in body:
|
||||
current_config.agent.system_prompt = body["system_prompt"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the chat flow with the new system prompt
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_chat_flow_system_prompt(body["system_prompt"])
|
||||
logger.info(f"Successfully updated chat flow system prompt")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update chat flow system prompt: {str(e)}")
|
||||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
|
||||
# Update knowledge settings
|
||||
if "embedding_model" in body:
|
||||
if (
|
||||
not isinstance(body["embedding_model"], str)
|
||||
or not body["embedding_model"].strip()
|
||||
):
|
||||
return JSONResponse(
|
||||
{"error": "embedding_model must be a non-empty string"},
|
||||
status_code=400,
|
||||
)
|
||||
current_config.knowledge.embedding_model = body["embedding_model"].strip()
|
||||
config_updated = True
|
||||
|
||||
# Also update the ingest flow with the new embedding model
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_ingest_flow_embedding_model(body["embedding_model"].strip())
|
||||
logger.info(f"Successfully updated ingest flow embedding model to '{body['embedding_model'].strip()}'")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update ingest flow embedding model: {str(e)}")
|
||||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
|
||||
if "doclingPresets" in body:
|
||||
preset_configs = get_docling_preset_configs()
|
||||
valid_presets = list(preset_configs.keys())
|
||||
|
|
@ -222,7 +265,8 @@ async def update_settings(request, session_manager):
|
|||
|
||||
# Also update the flow with the new docling preset
|
||||
try:
|
||||
await _update_flow_docling_preset(body["doclingPresets"], preset_configs[body["doclingPresets"]])
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_flow_docling_preset(body["doclingPresets"], preset_configs[body["doclingPresets"]])
|
||||
logger.info(f"Successfully updated docling preset in flow to '{body['doclingPresets']}'")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update docling preset in flow: {str(e)}")
|
||||
|
|
@ -237,6 +281,16 @@ async def update_settings(request, session_manager):
|
|||
current_config.knowledge.chunk_size = body["chunk_size"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the ingest flow with the new chunk size
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_ingest_flow_chunk_size(body["chunk_size"])
|
||||
logger.info(f"Successfully updated ingest flow chunk size to {body['chunk_size']}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update ingest flow chunk size: {str(e)}")
|
||||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
|
||||
if "chunk_overlap" in body:
|
||||
if not isinstance(body["chunk_overlap"], int) or body["chunk_overlap"] < 0:
|
||||
return JSONResponse(
|
||||
|
|
@ -246,6 +300,16 @@ async def update_settings(request, session_manager):
|
|||
current_config.knowledge.chunk_overlap = body["chunk_overlap"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the ingest flow with the new chunk overlap
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_ingest_flow_chunk_overlap(body["chunk_overlap"])
|
||||
logger.info(f"Successfully updated ingest flow chunk overlap to {body['chunk_overlap']}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update ingest flow chunk overlap: {str(e)}")
|
||||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
|
||||
if not config_updated:
|
||||
return JSONResponse(
|
||||
{"error": "No valid fields provided for update"}, status_code=400
|
||||
|
|
@ -524,48 +588,12 @@ async def onboarding(request, flows_service):
|
|||
)
|
||||
|
||||
|
||||
async def _update_flow_docling_preset(preset: str, preset_config: dict):
|
||||
"""Helper function to update docling preset in the ingest flow"""
|
||||
if not LANGFLOW_INGEST_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||
|
||||
# Get the current flow data from Langflow
|
||||
response = await clients.langflow_request(
|
||||
"GET", f"/api/v1/flows/{LANGFLOW_INGEST_FLOW_ID}"
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get ingest flow: HTTP {response.status_code} - {response.text}")
|
||||
|
||||
flow_data = response.json()
|
||||
|
||||
# Find the target node in the flow using environment variable
|
||||
nodes = flow_data.get("data", {}).get("nodes", [])
|
||||
target_node = None
|
||||
target_node_index = None
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
if node.get("id") == DOCLING_COMPONENT_ID:
|
||||
target_node = node
|
||||
target_node_index = i
|
||||
break
|
||||
|
||||
if target_node is None:
|
||||
raise Exception(f"Docling component '{DOCLING_COMPONENT_ID}' not found in ingest flow")
|
||||
|
||||
# Update the docling_serve_opts value directly in the existing node
|
||||
if (target_node.get("data", {}).get("node", {}).get("template", {}).get("docling_serve_opts")):
|
||||
flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"]["docling_serve_opts"]["value"] = preset_config
|
||||
else:
|
||||
raise Exception(f"docling_serve_opts field not found in node '{DOCLING_COMPONENT_ID}'")
|
||||
|
||||
# Update the flow via PATCH request
|
||||
patch_response = await clients.langflow_request(
|
||||
"PATCH", f"/api/v1/flows/{LANGFLOW_INGEST_FLOW_ID}", json=flow_data
|
||||
)
|
||||
|
||||
if patch_response.status_code != 200:
|
||||
raise Exception(f"Failed to update ingest flow: HTTP {patch_response.status_code} - {patch_response.text}")
|
||||
def _get_flows_service():
|
||||
"""Helper function to get flows service instance"""
|
||||
from services.flows_service import FlowsService
|
||||
return FlowsService()
|
||||
|
||||
|
||||
async def update_docling_preset(request, session_manager):
|
||||
|
|
@ -595,7 +623,8 @@ async def update_docling_preset(request, session_manager):
|
|||
preset_config = preset_configs[preset]
|
||||
|
||||
# Use the helper function to update the flow
|
||||
await _update_flow_docling_preset(preset, preset_config)
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_flow_docling_preset(preset, preset_config)
|
||||
|
||||
logger.info(f"Successfully updated docling preset to '{preset}' in ingest flow")
|
||||
|
||||
|
|
|
|||
|
|
@ -400,6 +400,128 @@ class FlowsService:
|
|||
return node
|
||||
return None
|
||||
|
||||
def _find_node_in_flow(self, flow_data, node_id=None, display_name=None):
|
||||
"""
|
||||
Helper function to find a node in flow data by ID or display name.
|
||||
Returns tuple of (node, node_index) or (None, None) if not found.
|
||||
"""
|
||||
nodes = flow_data.get("data", {}).get("nodes", [])
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
node_data = node.get("data", {})
|
||||
node_template = node_data.get("node", {})
|
||||
|
||||
# Check by ID if provided
|
||||
if node_id and node_data.get("id") == node_id:
|
||||
return node, i
|
||||
|
||||
# Check by display_name if provided
|
||||
if display_name and node_template.get("display_name") == display_name:
|
||||
return node, i
|
||||
|
||||
return None, None
|
||||
|
||||
async def _update_flow_field(self, flow_id: str, field_name: str, field_value: str, node_display_name: str = None, node_id: str = None):
|
||||
"""
|
||||
Generic helper function to update any field in any Langflow component.
|
||||
|
||||
Args:
|
||||
flow_id: The ID of the flow to update
|
||||
field_name: The name of the field to update (e.g., 'model_name', 'system_message', 'docling_serve_opts')
|
||||
field_value: The new value to set
|
||||
node_display_name: The display name to search for (optional)
|
||||
node_id: The node ID to search for (optional, used as fallback or primary)
|
||||
"""
|
||||
if not flow_id:
|
||||
raise ValueError("flow_id is required")
|
||||
|
||||
# Get the current flow data from Langflow
|
||||
response = await clients.langflow_request(
|
||||
"GET", f"/api/v1/flows/{flow_id}"
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get flow: HTTP {response.status_code} - {response.text}")
|
||||
|
||||
flow_data = response.json()
|
||||
|
||||
# Find the target component by display name first, then by ID as fallback
|
||||
target_node, target_node_index = None, None
|
||||
if node_display_name:
|
||||
target_node, target_node_index = self._find_node_in_flow(flow_data, display_name=node_display_name)
|
||||
|
||||
if target_node is None and node_id:
|
||||
target_node, target_node_index = self._find_node_in_flow(flow_data, node_id=node_id)
|
||||
|
||||
if target_node is None:
|
||||
identifier = node_display_name or node_id
|
||||
raise Exception(f"Component '{identifier}' not found in flow {flow_id}")
|
||||
|
||||
# Update the field value directly in the existing node
|
||||
template = target_node.get("data", {}).get("node", {}).get("template", {})
|
||||
if template.get(field_name):
|
||||
flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name]["value"] = field_value
|
||||
else:
|
||||
identifier = node_display_name or node_id
|
||||
raise Exception(f"{field_name} field not found in {identifier} component")
|
||||
|
||||
# Update the flow via PATCH request
|
||||
patch_response = await clients.langflow_request(
|
||||
"PATCH", f"/api/v1/flows/{flow_id}", json=flow_data
|
||||
)
|
||||
|
||||
if patch_response.status_code != 200:
|
||||
raise Exception(f"Failed to update flow: HTTP {patch_response.status_code} - {patch_response.text}")
|
||||
|
||||
async def update_chat_flow_model(self, model_name: str):
|
||||
"""Helper function to update the model in the chat flow"""
|
||||
if not LANGFLOW_CHAT_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured")
|
||||
await self._update_flow_field(LANGFLOW_CHAT_FLOW_ID, "model_name", model_name,
|
||||
node_display_name="Language Model",
|
||||
node_id="LanguageModelComponent-0YME7")
|
||||
|
||||
async def update_chat_flow_system_prompt(self, system_prompt: str):
|
||||
"""Helper function to update the system prompt in the chat flow"""
|
||||
if not LANGFLOW_CHAT_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured")
|
||||
await self._update_flow_field(LANGFLOW_CHAT_FLOW_ID, "system_message", system_prompt,
|
||||
node_display_name="Language Model",
|
||||
node_id="LanguageModelComponent-0YME7")
|
||||
|
||||
async def update_flow_docling_preset(self, preset: str, preset_config: dict):
|
||||
"""Helper function to update docling preset in the ingest flow"""
|
||||
if not LANGFLOW_INGEST_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||
|
||||
from config.settings import DOCLING_COMPONENT_ID
|
||||
await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "docling_serve_opts", preset_config,
|
||||
node_id=DOCLING_COMPONENT_ID)
|
||||
|
||||
async def update_ingest_flow_chunk_size(self, chunk_size: int):
|
||||
"""Helper function to update chunk size in the ingest flow"""
|
||||
if not LANGFLOW_INGEST_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||
await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "chunk_size", chunk_size,
|
||||
node_display_name="Split Text",
|
||||
node_id="SplitText-3ZI5B")
|
||||
|
||||
async def update_ingest_flow_chunk_overlap(self, chunk_overlap: int):
|
||||
"""Helper function to update chunk overlap in the ingest flow"""
|
||||
if not LANGFLOW_INGEST_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||
await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "chunk_overlap", chunk_overlap,
|
||||
node_display_name="Split Text",
|
||||
node_id="SplitText-3ZI5B")
|
||||
|
||||
async def update_ingest_flow_embedding_model(self, embedding_model: str):
|
||||
"""Helper function to update embedding model in the ingest flow"""
|
||||
if not LANGFLOW_INGEST_FLOW_ID:
|
||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||
await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "model", embedding_model,
|
||||
node_display_name="Embedding Model",
|
||||
node_id="EmbeddingModel-eZ6bT")
|
||||
|
||||
def _replace_node_in_flow(self, flow_data, old_id, new_node):
|
||||
"""Replace a node in the flow data"""
|
||||
nodes = flow_data.get("data", {}).get("nodes", [])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue