From 8d9f559a646a2f2ea8bc6ace39d01da9ddaf7537 Mon Sep 17 00:00:00 2001 From: "estevez.sebastian@gmail.com" Date: Fri, 11 Jul 2025 12:15:09 -0400 Subject: [PATCH] agent / chat backend --- pyproject.toml | 2 +- src/agent.py | 23 ++++++++++++++++++++++ src/app.py | 52 ++++++++++++++++++++++++++++++++++++++------------ uv.lock | 8 ++++---- 4 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/agent.py diff --git a/pyproject.toml b/pyproject.toml index 13ae7705..89f6257f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ - "agentd>=0.1.7", + "agentd>=0.1.8", "aiofiles>=24.1.0", "docling>=2.41.0", "opensearch-py[async]>=3.0.0", diff --git a/src/agent.py b/src/agent.py new file mode 100644 index 00000000..e72873bc --- /dev/null +++ b/src/agent.py @@ -0,0 +1,23 @@ +import asyncio + +from agentd.patch import patch_openai_with_mcp + +messages = [{"role": "system", "content": "You are a helpful assistant. use your tools to answer questions."}] + +# Async version for web server +async def async_chat(async_client, prompt: str) -> str: + global messages + messages += [{"role": "user", "content": prompt}] + response = await async_client.chat.completions.create( + model="gpt-4.1-mini", + messages=messages, + mcp_strict=True + ) + + response_text = response.choices[0].message.content + print(f"user ==> {prompt}") + print(f"agent ==> {response_text}") + return response_text + +if __name__ == "__main__": + asyncio.run(async_chat("What pods are there?")) \ No newline at end of file diff --git a/src/app.py b/src/app.py index 3f68a171..6890fa45 100644 --- a/src/app.py +++ b/src/app.py @@ -2,6 +2,9 @@ import os from collections import defaultdict +from typing import Any + +from agent import async_chat os.environ['USE_CPU_ONLY'] = 'true' @@ -20,7 +23,8 @@ from opensearchpy import AsyncOpenSearch from opensearchpy._async.http_aiohttp import AIOHttpConnection from docling.document_converter import DocumentConverter from agentd.patch import patch_openai_with_mcp -from openai import OpenAI +from openai import AsyncOpenAI +from agentd.tool_decorator import tool # Initialize Docling converter converter = DocumentConverter() # basic converter; tweak via PipelineOptions if you need OCR, etc. @@ -75,7 +79,7 @@ index_body = { } } -client = patch_openai_with_mcp(OpenAI()) # Get the patched client back +async_client = patch_openai_with_mcp(AsyncOpenAI()) # Get the patched client back async def wait_for_opensearch(): """Wait for OpenSearch to be ready with retries""" @@ -170,7 +174,7 @@ async def process_file_common(file_path: str, file_hash: str = None): slim_doc = extract_relevant(full_doc) texts = [c["text"] for c in slim_doc["chunks"]] - resp = client.embeddings.create(model=EMBED_MODEL, input=texts) + resp = await async_client.embeddings.create(model=EMBED_MODEL, input=texts) embeddings = [d.embedding for d in resp.data] # Index each chunk as a separate document @@ -236,15 +240,31 @@ async def upload_path(request: Request): return JSONResponse({"results": results}) async def search(request: Request): + payload = await request.json() query = payload.get("query") if not query: return JSONResponse({"error": "Query is required"}, status_code=400) + return JSONResponse(await search_tool(query)) + +@tool +async def search_tool(query: str)-> dict[str, Any]: + """ + Use this tool to search for documents relevant to the query. + + This endpoint accepts POST requests with a query string, + + Args: + query (str): query string to search the corpus + + Returns: + dict (str, Any) + - {"results": [chunks]} on success + """ # Embed the query - resp = client.embeddings.create(model=EMBED_MODEL, input=[query]) + resp = await async_client.embeddings.create(model=EMBED_MODEL, input=[query]) query_embedding = resp.data[0].embedding - # Search using vector similarity on individual chunks search_body = { "query": { @@ -258,9 +278,7 @@ async def search(request: Request): "_source": ["filename", "mimetype", "page", "text"], "size": 10 } - results = await es.search(index=INDEX_NAME, body=search_body) - # Transform results to match expected format chunks = [] for hit in results["hits"]["hits"]: @@ -271,13 +289,23 @@ async def search(request: Request): "text": hit["_source"]["text"], "score": hit["_score"] }) - - return JSONResponse({"results": chunks}) + return {"results": chunks} + +async def chat_endpoint(request): + data = await request.json() + prompt = data.get("prompt", "") + + if not prompt: + return JSONResponse({"error": "Prompt is required"}, status_code=400) + + response = await async_chat(async_client, prompt) + return JSONResponse({"response": response}) app = Starlette(debug=True, routes=[ - Route("/upload", upload, methods=["POST"]), - Route("/upload_path", upload_path, methods=["POST"]), - Route("/search", search, methods=["POST"]), + Route("/upload", upload, methods=["POST"]), + Route("/upload_path", upload_path, methods=["POST"]), + Route("/search", search, methods=["POST"]), + Route("/chat", chat_endpoint, methods=["POST"]), ]) if __name__ == "__main__": diff --git a/uv.lock b/uv.lock index 48f99088..e107ffa2 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "agentd" -version = "0.1.7" +version = "0.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "litellm" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "openai-agents" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/f6/6fc7c171e0c874e816ec063bf3cbabda4f05c6f4f1f15b65955255951027/agentd-0.1.7.tar.gz", hash = "sha256:cdcbf56dfdc0ca56067f354d890841bd71ee52210c12e468e711554007a6724c", size = 114216, upload-time = "2025-06-05T04:50:52.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/e1/a3d0d2ddb6639be34d906f13a2edc91dbf174f8dcf97a68705f3a613ff8d/agentd-0.1.8.tar.gz", hash = "sha256:9278916d228d23d67283aed0e420d14f3b6862499df5cc5a8adb92ab3583ed17", size = 114252, upload-time = "2025-07-11T16:06:57.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/c7/e786759ebd86afba150ccc1e8189e8515c760445f04374e2c4d72701c2aa/agentd-0.1.7-py3-none-any.whl", hash = "sha256:39332b8cf57dfc14b48628dfeb588afb6dbe05405945389fbe2925f3358b7c65", size = 13446, upload-time = "2025-06-05T04:50:51.046Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ca/9caa1253ab3ba151e725ea02aba334c29d659568b5341e5d886dbb394d85/agentd-0.1.8-py3-none-any.whl", hash = "sha256:15cc05ccbedfa9df8983a7a67c274c0c5a7ef029e55e6c0d7639106022c5cf06", size = 13472, upload-time = "2025-07-11T16:06:56.014Z" }, ] [[package]] @@ -434,7 +434,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "agentd", specifier = ">=0.1.7" }, + { name = "agentd", specifier = ">=0.1.8" }, { name = "aiofiles", specifier = ">=24.1.0" }, { name = "docling", specifier = ">=2.41.0" }, { name = "opensearch-py", extras = ["async"], specifier = ">=3.0.0" },