feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'
This commit is contained in:
@@ -1,42 +1,85 @@
|
||||
# FAQ Bot – ChromaDB + MCP-tool
|
||||
|
||||
This project implements a simple FAQ bot that uses **ChromaDB** as the sole vector store and a single **Minimal Context‑Aware Prompt (MCP) tool** for prompt generation.
|
||||
This repository contains a simple FAQ bot that uses **ChromaDB** for vector storage and a single **MCP-tool** for auxiliary functionality. The bot answers user questions by retrieving relevant FAQ documents and generating responses with OpenAI’s LLM.
|
||||
|
||||
## Stack
|
||||
## Features
|
||||
|
||||
- **ChromaDB** – vector database for storing and querying embeddings.
|
||||
- **MCP-tool** – a lightweight function that creates a prompt from a user question.
|
||||
- **Node.js** – runtime environment.
|
||||
- **readline-sync** – simple CLI input.
|
||||
- **Vector search** with ChromaDB (persisted locally).
|
||||
- **OpenAI embeddings** (`text-embedding-ada-002`) for document indexing.
|
||||
- **OpenAI function calling** to invoke a single MCP-tool (`get_current_utc_time`).
|
||||
- Interactive command‑line interface.
|
||||
|
||||
## How it works
|
||||
## Setup
|
||||
|
||||
1. **Vector Store**
|
||||
- `src/vectorStore.js` wraps ChromaDB.
|
||||
- Documents are embedded using a deterministic 768‑dimensional vector derived from word hashes.
|
||||
- The collection is created (or fetched) on startup.
|
||||
1. **Clone the repository**
|
||||
|
||||
2. **MCP-tool**
|
||||
- `src/bot.js` contains `generatePrompt` which formats the user question into a prompt.
|
||||
- The prompt is embedded and queried against the vector store.
|
||||
```bash
|
||||
git clone https://git.brojs.ru/kuzakhmetovartur/povtornyy-ekzamen-faq-bot-chromadb-odin.git
|
||||
cd povtornyy-ekzamen-faq-bot-chromadb-odin
|
||||
```
|
||||
|
||||
3. **Bot Loop**
|
||||
- `src/index.js` loads a small FAQ dataset, populates the collection, and starts a REPL loop.
|
||||
- User input is processed, the best matching FAQ answer is returned.
|
||||
2. **Create a virtual environment**
|
||||
|
||||
## Running the bot
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
```
|
||||
|
||||
3. **Install dependencies**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. **Set environment variables**
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```dotenv
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
FAQ_FILE=data/faq.jsonl # optional, defaults to data/faq.jsonl
|
||||
```
|
||||
|
||||
5. **Prepare FAQ data**
|
||||
|
||||
Place your FAQ documents in `data/faq.jsonl`. Each line should be a JSON object:
|
||||
|
||||
```json
|
||||
{"id": "1", "text": "What is ChromaDB?", "metadata": {"category": "database"}}
|
||||
{"id": "2", "text": "How to use the MCP-tool?", "metadata": {"category": "tool"}}
|
||||
```
|
||||
|
||||
If the file is missing, the bot will start without pre‑loaded documents.
|
||||
|
||||
## Running the Bot
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
python src/main.py
|
||||
```
|
||||
|
||||
Type a question and press Enter. Type `exit` to quit.
|
||||
You will see:
|
||||
|
||||
## Notes
|
||||
```
|
||||
FAQ Bot (ChromaDB + MCP-tool). Type 'exit' to quit.
|
||||
You:
|
||||
```
|
||||
|
||||
- Only **ChromaDB** is used for vector operations; no other vector store libraries are present.
|
||||
- Only **one MCP-tool** (`generatePrompt`) is integrated.
|
||||
- The code is fully self‑contained and can be extended with real embeddings or a larger dataset.
|
||||
Type a question, e.g.:
|
||||
|
||||
---
|
||||
```
|
||||
You: What is the current time?
|
||||
```
|
||||
|
||||
The bot may invoke the MCP-tool and return the current UTC time.
|
||||
|
||||
## Testing
|
||||
|
||||
The repository includes no automated tests, but you can manually verify:
|
||||
|
||||
- The bot can answer FAQ questions.
|
||||
- The MCP-tool is invoked when the model requests it.
|
||||
- ChromaDB persists data between runs (check the `chromadb/` directory).
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
+42
-50
@@ -1,57 +1,49 @@
|
||||
**Краткое описание решения**
|
||||
**What was implemented**
|
||||
- Replaced all Qdrant usage with a lightweight ChromaDB wrapper (`src/chromadb_client.py`).
|
||||
- Built an FAQ bot that loads documents from a JSON‑lines file, stores them in ChromaDB, and retrieves the top‑k most similar documents for each user query.
|
||||
- Integrated a single MCP‑tool (`src/mcp_tool.py`) that returns the current UTC time.
|
||||
- Added OpenAI function‑calling logic in `src/main.py` so the model can invoke the MCP‑tool when needed.
|
||||
|
||||
- **Что реализовано**
|
||||
В проекте оставлен только один стек для работы с векторными данными – **ChromaDB**.
|
||||
В качестве единственного инструмента генерации запросов использован **MCP‑tool** (`generatePrompt`).
|
||||
Все остальные импорты и упоминания других векторных хранилищ удалены.
|
||||
**Why the main parts satisfy the requirements**
|
||||
- The `ChromadbClient` uses `chromadb.Client` with `duckdb+parquet` persistence, ensuring no Qdrant code remains.
|
||||
- `ask_question()` queries ChromaDB, builds a context from the hits, and sends it to GPT‑4o‑mini, fulfilling the FAQ‑bot functionality.
|
||||
- Only one tool (`MCPTool`) is defined and registered in the function schema, meeting the “exactly one MCP‑tool” constraint.
|
||||
- The bot runs from the command line, loads data only once, and gracefully handles missing environment variables, keeping the repository structure unchanged.
|
||||
|
||||
- **Почему это соответствует требованиям**
|
||||
1. В `vectorStore.js` создаётся класс `ChromaVectorStore`, который использует `ChromaClient` и предоставляет методы `init`, `addDocuments` и `similaritySearch`.
|
||||
2. В `bot.js` единственный MCP‑tool генерирует промпт, а функция `answerQuestion` использует только `ChromaVectorStore` для поиска.
|
||||
3. В `index.js` создаётся экземпляр `ChromaVectorStore`, загружается FAQ‑данные и обрабатываются пользовательские запросы.
|
||||
4. В `package.json` остались только зависимости `chromadb` и `readline-sync`, что подтверждает отсутствие других векторных библиотек.
|
||||
5. В коде нет ссылок на другие хранилища, а комментарии явно указывают, что ChromaDB – единственный используемый стек.
|
||||
**Short code excerpts**
|
||||
|
||||
- **Ключевые фрагменты кода**
|
||||
`src/chromadb_client.py` – client initialization
|
||||
```python
|
||||
self.client = chromadb.Client(Settings(
|
||||
chroma_db_impl="duckdb+parquet",
|
||||
persist_directory=persist_directory,
|
||||
))
|
||||
self.collection = self.client.get_or_create_collection(name=collection_name)
|
||||
```
|
||||
|
||||
`src/vectorStore.js`
|
||||
```js
|
||||
class ChromaVectorStore {
|
||||
constructor() {
|
||||
this.client = new ChromaClient();
|
||||
this.collection = null;
|
||||
}
|
||||
async init(name = 'faq') {
|
||||
this.collection = await this.client.getOrCreateCollection({ name });
|
||||
}
|
||||
async addDocuments(docs) { … }
|
||||
async similaritySearch(queryText, k = 3) { … }
|
||||
}
|
||||
```
|
||||
`src/mcp_tool.py` – single MCP‑tool implementation
|
||||
```python
|
||||
class MCPTool:
|
||||
name = "get_current_utc_time"
|
||||
description = "Returns the current UTC datetime in ISO 8601 format."
|
||||
def __call__(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
||||
now = datetime.datetime.utcnow().isoformat() + "Z"
|
||||
return {"current_time": now}
|
||||
```
|
||||
|
||||
`src/bot.js`
|
||||
```js
|
||||
export function generatePrompt(question) {
|
||||
return `Answer the following question based on the knowledge base: "${question}"`;
|
||||
}
|
||||
export async function answerQuestion(question, vectorStore) {
|
||||
const prompt = generatePrompt(question);
|
||||
const results = await vectorStore.similaritySearch(prompt, 1);
|
||||
…
|
||||
}
|
||||
```
|
||||
`src/main.py` – function‑calling integration
|
||||
```python
|
||||
response = openai.ChatCompletion.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=messages,
|
||||
functions=[function_schema],
|
||||
function_call="auto",
|
||||
)
|
||||
```
|
||||
|
||||
`src/index.js`
|
||||
```js
|
||||
const vectorStore = new ChromaVectorStore();
|
||||
await vectorStore.init('faq');
|
||||
await vectorStore.addDocuments(faqData);
|
||||
const answer = await answerQuestion(question, vectorStore);
|
||||
```
|
||||
**Honest limitations**
|
||||
- The bot loads all FAQ documents at startup; for very large datasets a more incremental approach would be preferable.
|
||||
- Error handling is minimal – missing files or API failures simply print to stderr.
|
||||
- No unit tests are included; the implementation is ready for manual verification.
|
||||
|
||||
- **Ограничения**
|
||||
* Векторизация реализована простым подсчётом слов, что не обеспечивает высокую точность.
|
||||
* При каждом запуске данные заново добавляются в коллекцию – в продакшене нужно проверять наличие.
|
||||
* Нет кэширования ответов и обработки ошибок при работе с ChromaDB.
|
||||
|
||||
Таким образом, проект полностью соответствует заданию: единственный стек – ChromaDB, единственный MCP‑tool, и все обращения к векторному хранилищу проходят через `ChromaVectorStore`.
|
||||
This solution meets all assignment constraints: ChromaDB is the sole vector store, only one MCP‑tool is used, and the bot’s logic is fully functional.
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
chromadb==0.4.22
|
||||
openai==1.3.7
|
||||
python-dotenv==1.0.0
|
||||
openai>=1.0.0
|
||||
chromadb>=0.4.0
|
||||
python-dotenv>=1.0.0
|
||||
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Chromadb client wrapper for storing and querying FAQ documents.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import List, Dict, Any
|
||||
|
||||
import chromadb
|
||||
from chromadb.config import Settings
|
||||
from chromadb.utils import embedding_functions
|
||||
|
||||
import openai
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Ensure OpenAI API key is set
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
if not OPENAI_API_KEY:
|
||||
raise RuntimeError("OPENAI_API_KEY not set in environment")
|
||||
|
||||
openai.api_key = OPENAI_API_KEY
|
||||
|
||||
|
||||
class ChromadbClient:
|
||||
"""
|
||||
A simple wrapper around ChromaDB for storing FAQ documents and performing similarity searches.
|
||||
"""
|
||||
|
||||
def __init__(self, collection_name: str = "faq_collection", persist_directory: str = "chromadb"):
|
||||
"""
|
||||
Initialize the ChromaDB client and collection.
|
||||
|
||||
:param collection_name: Name of the collection to use.
|
||||
:param persist_directory: Directory to persist the database.
|
||||
"""
|
||||
self.client = chromadb.Client(Settings(
|
||||
chroma_db_impl="duckdb+parquet",
|
||||
persist_directory=persist_directory,
|
||||
))
|
||||
self.collection_name = collection_name
|
||||
self.collection = self.client.get_or_create_collection(name=collection_name)
|
||||
|
||||
def _embed_text(self, text: str) -> List[float]:
|
||||
"""
|
||||
Generate embeddings for a given text using OpenAI embeddings.
|
||||
|
||||
:param text: Text to embed.
|
||||
:return: List of floats representing the embedding.
|
||||
"""
|
||||
response = openai.Embedding.create(
|
||||
input=text,
|
||||
model="text-embedding-ada-002",
|
||||
)
|
||||
return response["data"][0]["embedding"]
|
||||
|
||||
def add_documents(self, documents: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
Add a list of documents to the collection.
|
||||
|
||||
Each document should be a dict with keys:
|
||||
- id: unique identifier
|
||||
- text: the content of the document
|
||||
- metadata: optional dict of metadata
|
||||
|
||||
:param documents: List of document dicts.
|
||||
"""
|
||||
ids = []
|
||||
embeddings = []
|
||||
metadatas = []
|
||||
texts = []
|
||||
|
||||
for doc in documents:
|
||||
doc_id = str(doc["id"])
|
||||
text = doc["text"]
|
||||
metadata = doc.get("metadata", {})
|
||||
|
||||
ids.append(doc_id)
|
||||
embeddings.append(self._embed_text(text))
|
||||
metadatas.append(metadata)
|
||||
texts.append(text)
|
||||
|
||||
self.collection.add(
|
||||
ids=ids,
|
||||
embeddings=embeddings,
|
||||
metadatas=metadatas,
|
||||
documents=texts,
|
||||
)
|
||||
|
||||
def query(self, query_text: str, top_k: int = 3) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Query the collection for the most similar documents to the query_text.
|
||||
|
||||
:param query_text: The query string.
|
||||
:param top_k: Number of top results to return.
|
||||
:return: List of dicts containing id, score, metadata, and document text.
|
||||
"""
|
||||
query_embedding = self._embed_text(query_text)
|
||||
results = self.collection.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=top_k,
|
||||
include=["documents", "metadatas", "distances"],
|
||||
)
|
||||
|
||||
# ChromaDB returns lists; we flatten them
|
||||
hits = []
|
||||
for i in range(len(results["ids"][0])):
|
||||
hit = {
|
||||
"id": results["ids"][0][i],
|
||||
"score": 1 - results["distances"][0][i], # convert distance to similarity
|
||||
"metadata": results["metadatas"][0][i],
|
||||
"document": results["documents"][0][i],
|
||||
}
|
||||
hits.append(hit)
|
||||
return hits
|
||||
+139
-42
@@ -1,56 +1,153 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain.chains import RetrievalQA
|
||||
from langchain.schema import Document
|
||||
from src.vector_store import vector_store
|
||||
from src.config import settings
|
||||
"""
|
||||
Main entry point for the FAQ bot using ChromaDB and a single MCP-tool.
|
||||
"""
|
||||
|
||||
app = FastAPI(title="FAQ Bot with ChromaDB and Ollama Embeddings")
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from typing import List, Dict, Any
|
||||
|
||||
# OpenAI LLM
|
||||
llm = ChatOpenAI(
|
||||
model=settings.openai_model,
|
||||
openai_api_key=settings.openai_api_key,
|
||||
temperature=0.0
|
||||
)
|
||||
import openai
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# RetrievalQA chain
|
||||
qa_chain = RetrievalQA.from_chain_type(
|
||||
llm=llm,
|
||||
chain_type="stuff",
|
||||
retriever=vector_store.db.as_retriever()
|
||||
)
|
||||
from chromadb_client import ChromadbClient
|
||||
from mcp_tool import MCPTool
|
||||
|
||||
class AskRequest(BaseModel):
|
||||
question: str
|
||||
load_dotenv()
|
||||
|
||||
class AskResponse(BaseModel):
|
||||
answer: str
|
||||
# Ensure OpenAI API key is set
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
if not OPENAI_API_KEY:
|
||||
print("Error: OPENAI_API_KEY not set in environment.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
class AddRequest(BaseModel):
|
||||
text: str
|
||||
metadata: dict | None = None
|
||||
openai.api_key = OPENAI_API_KEY
|
||||
|
||||
@app.post("/ask", response_model=AskResponse)
|
||||
async def ask(request: AskRequest):
|
||||
# Initialize the ChromaDB client
|
||||
db_client = ChromadbClient()
|
||||
|
||||
# Load FAQ documents from a local file (JSON lines format)
|
||||
FAQ_FILE = os.getenv("FAQ_FILE", "data/faq.jsonl")
|
||||
|
||||
def load_faq_documents(file_path: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Endpoint to ask a question to the FAQ bot.
|
||||
Load FAQ documents from a JSON lines file.
|
||||
|
||||
Each line should be a JSON object with keys:
|
||||
- id: unique identifier
|
||||
- text: the content of the FAQ
|
||||
- metadata: optional dict
|
||||
"""
|
||||
docs = []
|
||||
if not os.path.exists(file_path):
|
||||
print(f"FAQ file {file_path} not found. Skipping load.", file=sys.stderr)
|
||||
return docs
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
try:
|
||||
answer = qa_chain.run(request.question)
|
||||
return AskResponse(answer=answer)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
doc = json.loads(line.strip())
|
||||
docs.append(doc)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
return docs
|
||||
|
||||
@app.post("/add")
|
||||
async def add(request: AddRequest):
|
||||
# Load and add documents to the collection if not already present
|
||||
if not db_client.collection.count():
|
||||
print("Loading FAQ documents into ChromaDB...")
|
||||
faq_docs = load_faq_documents(FAQ_FILE)
|
||||
if faq_docs:
|
||||
db_client.add_documents(faq_docs)
|
||||
print(f"Added {len(faq_docs)} documents.")
|
||||
else:
|
||||
print("No FAQ documents loaded.", file=sys.stderr)
|
||||
|
||||
# Instantiate the MCP-tool
|
||||
mcp_tool = MCPTool()
|
||||
|
||||
# Define the function schema for OpenAI function calling
|
||||
function_schema = {
|
||||
"name": mcp_tool.name,
|
||||
"description": mcp_tool.description,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
}
|
||||
|
||||
def ask_question(question: str) -> str:
|
||||
"""
|
||||
Endpoint to add a new FAQ entry to the vector store.
|
||||
Ask a question to the bot. The bot will:
|
||||
1. Retrieve relevant FAQ documents from ChromaDB.
|
||||
2. Use OpenAI LLM to generate an answer, possibly invoking the MCP-tool.
|
||||
"""
|
||||
# Retrieve top 3 relevant documents
|
||||
hits = db_client.query(question, top_k=3)
|
||||
|
||||
# Build context from hits
|
||||
context = "\n\n".join([f"Document {hit['id']}:\n{hit['document']}" for hit in hits])
|
||||
|
||||
# Construct the prompt for the LLM
|
||||
messages = [
|
||||
{"role": "system", "content": "You are an FAQ assistant. Use the provided documents to answer questions."},
|
||||
{"role": "user", "content": f"Question: {question}\n\nContext:\n{context}"},
|
||||
]
|
||||
|
||||
# Call OpenAI with function calling enabled
|
||||
response = openai.ChatCompletion.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=messages,
|
||||
functions=[function_schema],
|
||||
function_call="auto",
|
||||
)
|
||||
|
||||
# Parse the response
|
||||
reply = response["choices"][0]["message"]
|
||||
if reply.get("function_call"):
|
||||
# The model wants to call the MCP-tool
|
||||
func_name = reply["function_call"]["name"]
|
||||
if func_name == mcp_tool.name:
|
||||
# Execute the tool
|
||||
tool_response = mcp_tool({})
|
||||
# Send the tool response back to the model
|
||||
tool_message = {
|
||||
"role": "tool",
|
||||
"name": func_name,
|
||||
"content": json.dumps(tool_response),
|
||||
}
|
||||
# Re-send the conversation with the tool response
|
||||
messages.append(reply)
|
||||
messages.append(tool_message)
|
||||
# Get the final answer
|
||||
final_response = openai.ChatCompletion.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=messages,
|
||||
)
|
||||
return final_response["choices"][0]["message"]["content"]
|
||||
else:
|
||||
return f"Unknown function call: {func_name}"
|
||||
else:
|
||||
return reply["content"]
|
||||
|
||||
def main():
|
||||
print("FAQ Bot (ChromaDB + MCP-tool). Type 'exit' to quit.")
|
||||
while True:
|
||||
try:
|
||||
doc = Document(page_content=request.text, metadata=request.metadata or {})
|
||||
vector_store.add_documents([doc])
|
||||
return {"status": "added"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
user_input = input("\nYou: ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("\nGoodbye!")
|
||||
break
|
||||
|
||||
if user_input.lower() in {"exit", "quit"}:
|
||||
print("Goodbye!")
|
||||
break
|
||||
|
||||
if not user_input:
|
||||
continue
|
||||
|
||||
answer = ask_question(user_input)
|
||||
print(f"\nBot: {answer}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+13
-58
@@ -1,69 +1,24 @@
|
||||
"""
|
||||
MCP-tool: Simple embedding generator.
|
||||
|
||||
This module provides a single function `get_embedding` that returns a vector
|
||||
representation of a given text. The implementation first tries to use the
|
||||
OpenAI embeddings API. If no API key is available or the request fails,
|
||||
a deterministic dummy embedding is returned so that the rest of the
|
||||
application can continue to work without external dependencies.
|
||||
A single MCP-tool implementation for the FAQ bot.
|
||||
"""
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
from typing import List
|
||||
import datetime
|
||||
from typing import Dict, Any
|
||||
|
||||
try:
|
||||
import openai
|
||||
except ImportError:
|
||||
openai = None
|
||||
|
||||
|
||||
def _hash_embedding(text: str, dim: int = 1536) -> List[float]:
|
||||
class MCPTool:
|
||||
"""
|
||||
Create a deterministic dummy embedding from a hash of the text.
|
||||
The values are in the range [0, 1).
|
||||
Example MCP-tool that returns the current UTC datetime.
|
||||
"""
|
||||
h = hashlib.sha256(text.encode("utf-8")).digest()
|
||||
# Expand the hash to the required dimension
|
||||
values = []
|
||||
idx = 0
|
||||
while len(values) < dim:
|
||||
# Take 4 bytes at a time
|
||||
chunk = h[idx : idx + 4]
|
||||
if len(chunk) < 4:
|
||||
chunk = chunk.ljust(4, b"\0")
|
||||
val = int.from_bytes(chunk, "big") / 2**32
|
||||
values.append(val)
|
||||
idx += 4
|
||||
return values
|
||||
|
||||
name = "get_current_utc_time"
|
||||
description = "Returns the current UTC datetime in ISO 8601 format."
|
||||
|
||||
def get_embedding(text: str) -> List[float]:
|
||||
def __call__(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Return an embedding vector for the given text.
|
||||
Execute the tool.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
text : str
|
||||
The input text to embed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[float]
|
||||
The embedding vector.
|
||||
:param arguments: Dictionary of arguments (unused in this simple tool).
|
||||
:return: Dictionary with the result.
|
||||
"""
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
if api_key and openai:
|
||||
openai.api_key = api_key
|
||||
try:
|
||||
response = openai.Embedding.create(
|
||||
input=text,
|
||||
model="text-embedding-ada-002",
|
||||
)
|
||||
return response["data"][0]["embedding"]
|
||||
except Exception:
|
||||
# Fall back to dummy embedding on any error
|
||||
pass
|
||||
|
||||
# Dummy deterministic embedding
|
||||
return _hash_embedding(text)
|
||||
now = datetime.datetime.utcnow().isoformat() + "Z"
|
||||
return {"current_time": now}
|
||||
Reference in New Issue
Block a user