new langflow endpoint
This commit is contained in:
parent
ea3cf41ab0
commit
2542e1bbe7
4 changed files with 48 additions and 14 deletions
|
|
@ -5,7 +5,7 @@ description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"agentd>=0.2.0.post2",
|
"agentd>=0.2.0.post3",
|
||||||
"aiofiles>=24.1.0",
|
"aiofiles>=24.1.0",
|
||||||
"docling>=2.41.0",
|
"docling>=2.41.0",
|
||||||
"opensearch-py[async]>=3.0.0",
|
"opensearch-py[async]>=3.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
messages = [{"role": "system", "content": "You are a helpful assistant. use your tools to answer questions."}]
|
messages = [{"role": "system", "content": "You are a helpful assistant. Always use the search_tools to answer questions."}]
|
||||||
|
|
||||||
# Async version for web server
|
# Async version for web server
|
||||||
async def async_chat(async_client, prompt: str, model: str = "gpt-4.1-mini", previous_response_id: str = None) -> str:
|
async def async_chat(async_client, prompt: str, model: str = "gpt-4.1-mini", previous_response_id: str = None) -> str:
|
||||||
|
|
|
||||||
50
src/app.py
50
src/app.py
|
|
@ -11,9 +11,6 @@ os.environ['USE_CPU_ONLY'] = 'true'
|
||||||
import hashlib
|
import hashlib
|
||||||
import tempfile
|
import tempfile
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
|
||||||
import json
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from starlette.applications import Starlette
|
from starlette.applications import Starlette
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
@ -27,6 +24,10 @@ from docling.document_converter import DocumentConverter
|
||||||
from agentd.patch import patch_openai_with_mcp
|
from agentd.patch import patch_openai_with_mcp
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
from agentd.tool_decorator import tool
|
from agentd.tool_decorator import tool
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
load_dotenv("../")
|
||||||
|
|
||||||
# Initialize Docling converter
|
# Initialize Docling converter
|
||||||
converter = DocumentConverter() # basic converter; tweak via PipelineOptions if you need OCR, etc.
|
converter = DocumentConverter() # basic converter; tweak via PipelineOptions if you need OCR, etc.
|
||||||
|
|
@ -35,7 +36,12 @@ converter = DocumentConverter() # basic converter; tweak via PipelineOptions if
|
||||||
opensearch_host = os.getenv("OPENSEARCH_HOST", "localhost")
|
opensearch_host = os.getenv("OPENSEARCH_HOST", "localhost")
|
||||||
opensearch_port = int(os.getenv("OPENSEARCH_PORT", "9200"))
|
opensearch_port = int(os.getenv("OPENSEARCH_PORT", "9200"))
|
||||||
opensearch_username = os.getenv("OPENSEARCH_USERNAME", "admin")
|
opensearch_username = os.getenv("OPENSEARCH_USERNAME", "admin")
|
||||||
opensearch_password = os.getenv("OPENSEARCH_PASSWORD", "OSisgendb1!")
|
opensearch_password = os.getenv("OPENSEARCH_PASSWORD")
|
||||||
|
langflow_url = os.getenv("LANGFLOW_URL", "http://localhost:7860")
|
||||||
|
flow_id = os.getenv("FLOW_ID")
|
||||||
|
langflow_key = os.getenv("LANGFLOW_SECRET_KEY")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
es = AsyncOpenSearch(
|
es = AsyncOpenSearch(
|
||||||
hosts=[{"host": opensearch_host, "port": opensearch_port}],
|
hosts=[{"host": opensearch_host, "port": opensearch_port}],
|
||||||
|
|
@ -81,7 +87,11 @@ index_body = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async_client = patch_openai_with_mcp(AsyncOpenAI()) # Get the patched client back
|
langflow_client = AsyncOpenAI(
|
||||||
|
base_url=f"{langflow_url}/api/v1",
|
||||||
|
api_key=langflow_key
|
||||||
|
)
|
||||||
|
patched_async_client = patch_openai_with_mcp(AsyncOpenAI()) # Get the patched client back
|
||||||
|
|
||||||
async def wait_for_opensearch():
|
async def wait_for_opensearch():
|
||||||
"""Wait for OpenSearch to be ready with retries"""
|
"""Wait for OpenSearch to be ready with retries"""
|
||||||
|
|
@ -175,7 +185,7 @@ async def process_file_common(file_path: str, file_hash: str = None):
|
||||||
slim_doc = extract_relevant(full_doc)
|
slim_doc = extract_relevant(full_doc)
|
||||||
|
|
||||||
texts = [c["text"] for c in slim_doc["chunks"]]
|
texts = [c["text"] for c in slim_doc["chunks"]]
|
||||||
resp = await async_client.embeddings.create(model=EMBED_MODEL, input=texts)
|
resp = await patched_async_client.embeddings.create(model=EMBED_MODEL, input=texts)
|
||||||
embeddings = [d.embedding for d in resp.data]
|
embeddings = [d.embedding for d in resp.data]
|
||||||
|
|
||||||
# Index each chunk as a separate document
|
# Index each chunk as a separate document
|
||||||
|
|
@ -264,7 +274,7 @@ async def search_tool(query: str)-> dict[str, Any]:
|
||||||
- {"results": [chunks]} on success
|
- {"results": [chunks]} on success
|
||||||
"""
|
"""
|
||||||
# Embed the query
|
# Embed the query
|
||||||
resp = await async_client.embeddings.create(model=EMBED_MODEL, input=[query])
|
resp = await patched_async_client.embeddings.create(model=EMBED_MODEL, input=[query])
|
||||||
query_embedding = resp.data[0].embedding
|
query_embedding = resp.data[0].embedding
|
||||||
# Search using vector similarity on individual chunks
|
# Search using vector similarity on individual chunks
|
||||||
search_body = {
|
search_body = {
|
||||||
|
|
@ -299,14 +309,38 @@ async def chat_endpoint(request):
|
||||||
if not prompt:
|
if not prompt:
|
||||||
return JSONResponse({"error": "Prompt is required"}, status_code=400)
|
return JSONResponse({"error": "Prompt is required"}, status_code=400)
|
||||||
|
|
||||||
response = await async_chat(async_client, prompt)
|
response = await async_chat(patched_async_client, prompt)
|
||||||
return JSONResponse({"response": response})
|
return JSONResponse({"response": response})
|
||||||
|
|
||||||
|
async def langflow_endpoint(request):
|
||||||
|
data = await request.json()
|
||||||
|
prompt = data.get("prompt", "")
|
||||||
|
|
||||||
|
if not prompt:
|
||||||
|
return JSONResponse({"error": "Prompt is required"}, status_code=400)
|
||||||
|
|
||||||
|
if not langflow_url or not flow_id or not langflow_key:
|
||||||
|
return JSONResponse({"error": "LANGFLOW_URL, FLOW_ID, and LANGFLOW_KEY environment variables are required"}, status_code=500)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await langflow_client.responses.create(
|
||||||
|
model=flow_id,
|
||||||
|
input=prompt
|
||||||
|
)
|
||||||
|
|
||||||
|
response_text = response.output_text
|
||||||
|
|
||||||
|
return JSONResponse({"response": response_text})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse({"error": f"Langflow request failed: {str(e)}"}, status_code=500)
|
||||||
|
|
||||||
app = Starlette(debug=True, routes=[
|
app = Starlette(debug=True, routes=[
|
||||||
Route("/upload", upload, methods=["POST"]),
|
Route("/upload", upload, methods=["POST"]),
|
||||||
Route("/upload_path", upload_path, methods=["POST"]),
|
Route("/upload_path", upload_path, methods=["POST"]),
|
||||||
Route("/search", search, methods=["POST"]),
|
Route("/search", search, methods=["POST"]),
|
||||||
Route("/chat", chat_endpoint, methods=["POST"]),
|
Route("/chat", chat_endpoint, methods=["POST"]),
|
||||||
|
Route("/langflow", langflow_endpoint, methods=["POST"]),
|
||||||
])
|
])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
8
uv.lock
generated
8
uv.lock
generated
|
|
@ -9,7 +9,7 @@ resolution-markers = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agentd"
|
name = "agentd"
|
||||||
version = "0.2.0.post2"
|
version = "0.2.0.post3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "litellm" },
|
{ name = "litellm" },
|
||||||
|
|
@ -18,9 +18,9 @@ dependencies = [
|
||||||
{ name = "openai-agents" },
|
{ name = "openai-agents" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/91/fb/177ce5c7e8f8e8c4a4771b1da26e09e62780bf6f4042622654d05b101534/agentd-0.2.0.post2.tar.gz", hash = "sha256:b4cf8f5b727c1f0c0c9685762415e5affbc501758c0641eb9bd9c7d972c3ef30", size = 114513, upload-time = "2025-07-16T05:13:30.646Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/6e/89/2bc397c80764d6acfeb6de7ac6d3ce8914f6f37f57307d1e2b6f7e8b0923/agentd-0.2.0.post3.tar.gz", hash = "sha256:765cb51798791eed32687b44305b20dd4130990471f0f1914afa2b292d09cb5e", size = 114530, upload-time = "2025-07-16T06:13:11.423Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/5d/232b01286225d5e6eff6e4e741411950d60002eeeceb7b2ab1e84d96cc66/agentd-0.2.0.post2-py3-none-any.whl", hash = "sha256:000a058758843739061c93503db5977c9734f2e690545dd21923bf9ae8a8a161", size = 13266, upload-time = "2025-07-16T05:13:29.389Z" },
|
{ url = "https://files.pythonhosted.org/packages/8c/f8/ced474722557f11e0a2f7e691371cf25c6a823507cc297971baa71bfbaac/agentd-0.2.0.post3-py3-none-any.whl", hash = "sha256:d05c6123a33d9f0b466fba4f7b378618c352ca367c65cd2c5a54f867af7b3cff", size = 13299, upload-time = "2025-07-16T06:13:10.034Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -434,7 +434,7 @@ dependencies = [
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "agentd", specifier = ">=0.2.0.post2" },
|
{ name = "agentd", specifier = ">=0.2.0.post3" },
|
||||||
{ name = "aiofiles", specifier = ">=24.1.0" },
|
{ name = "aiofiles", specifier = ">=24.1.0" },
|
||||||
{ name = "docling", specifier = ">=2.41.0" },
|
{ name = "docling", specifier = ">=2.41.0" },
|
||||||
{ name = "opensearch-py", extras = ["async"], specifier = ">=3.0.0" },
|
{ name = "opensearch-py", extras = ["async"], specifier = ">=3.0.0" },
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue