diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index fe31f6412..bad5fbb9b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "mem0", "source": "./mem0-plugin", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.", - "version": "0.1.3" + "version": "0.2.7" } ] } diff --git a/.codex-plugin/marketplace.json b/.codex-plugin/marketplace.json new file mode 100644 index 000000000..6e0e240a1 --- /dev/null +++ b/.codex-plugin/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "mem0-plugins", + "interface": { + "displayName": "Mem0 Plugins" + }, + "plugins": [ + { + "name": "mem0", + "source": { + "source": "local", + "path": "./mem0-plugin" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index e5e868c49..78541d76f 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "mem0", "source": "./mem0-plugin", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.", - "version": "0.1.1" + "version": "0.2.7" } ] } diff --git a/.github/workflows/opencode-plugin-cd.yml b/.github/workflows/opencode-plugin-cd.yml new file mode 100644 index 000000000..746569aa8 --- /dev/null +++ b/.github/workflows/opencode-plugin-cd.yml @@ -0,0 +1,44 @@ +name: Publish @mem0/opencode-plugin 📦 to npm + +on: + release: + types: [published] + +jobs: + build-n-publish: + name: Build and publish @mem0/opencode-plugin 📦 to npm + if: startsWith(github.event.release.tag_name, 'opencode-v') + runs-on: ubuntu-latest + permissions: + id-token: write + defaults: + run: + working-directory: mem0-plugin/.opencode-plugin + steps: + - uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build + run: bun run build + + - name: Publish to npm + run: | + if [ "${{ github.event.release.prerelease }}" = "true" ]; then + PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") + npx npm@latest publish --provenance --access public --tag "$PREID" + else + npx npm@latest publish --provenance --access public + fi diff --git a/.github/workflows/opencode-plugin-checks.yml b/.github/workflows/opencode-plugin-checks.yml new file mode 100644 index 000000000..032e8817a --- /dev/null +++ b/.github/workflows/opencode-plugin-checks.yml @@ -0,0 +1,40 @@ +name: opencode-plugin checks + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - 'mem0-plugin/.opencode-plugin/**' + - '.github/workflows/opencode-plugin-checks.yml' + pull_request: + paths: + - 'mem0-plugin/.opencode-plugin/**' + - '.github/workflows/opencode-plugin-checks.yml' + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: mem0-plugin/.opencode-plugin + steps: + - uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Type check + run: bun run type-check + + - name: Build + run: bun run build + + - name: Verify dist output exists + run: | + test -f dist/index.js || (echo "Build output missing: dist/index.js" && exit 1) diff --git a/.gitignore b/.gitignore index f8fd29897..760e3c59d 100644 --- a/.gitignore +++ b/.gitignore @@ -170,7 +170,6 @@ cython_debug/ # Database db test-db -!embedchain/embedchain/core/db/ .vscode .idea/ diff --git a/AGENTS.md b/AGENTS.md index 52d8ffc1e..bc40190b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,6 @@ This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs, | `evaluation/` | Benchmarking framework — LOCOMO evals, experiment runner, score generation | | `examples/` | Sample projects — demo apps, Chrome extension, multi-agent patterns | | `cookbooks/` | Jupyter notebooks — customer support chatbot, AutoGen integration | -| `embedchain/` | Legacy Embedchain RAG framework (maintained separately, Poetry-based) | | `pr-reviews/` | Pull request review materials | | `scripts/` | Repo-wide utility scripts (e.g., `check-llms-txt-coverage.py` for docs/llms.txt sync) | @@ -330,7 +329,7 @@ make run-openai # OpenAI comparison - Root SDK: line length **120** - Python CLI: line length **100** with extended rule set (UP, B, SIM, RUF) - **isort** with `profile = "black"` for import sorting. -- Ruff excludes `embedchain/` and `openmemory/` from root config. +- Ruff excludes `openmemory/` from root config. ### TypeScript Conventions @@ -414,7 +413,7 @@ To add a new LLM, embedding, vector store, or reranker provider: | Python CLI | `cli-python-ci.yml` | Push to `cli/python/`, PRs, manual | Ruff lint + pytest + hatch build on Python 3.10, 3.11, 3.12 | | Node CLI | `cli-node-ci.yml` | Push to `cli/node/`, PRs, manual | Biome lint + tsc + vitest + tsup build on Node 20, 22 | | OpenClaw | `openclaw-checks.yml` | Push to `openclaw/`, PRs, manual | tsc + vitest (with Codecov) + tsup build on Node 20, 22 | -| Embedchain | `ci.yml` (shared) | PRs on `embedchain/` | Ruff + pytest + coverage on Python 3.9–3.12 | +| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to `mem0-plugin/.opencode-plugin/`, PRs, manual | Bun: tsc type-check + build + dist artifact check | ### CD Workflows (automated publishing) @@ -426,6 +425,7 @@ To add a new LLM, embedding, vector store, or reranker provider: | Node CLI | `cli-node-cd.yml` | `cli-node-v*` | npm (`@mem0/cli`) | | Vercel AI SDK | `vercel-ai-cd.yml` | `vercel-ai-v*` | npm (`@mem0/vercel-ai-provider`) | | OpenClaw | `openclaw-cd.yml` | `openclaw-v*` | npm (`@mem0/openclaw-mem0`) | +| OpenCode Plugin | `opencode-plugin-cd.yml` | `opencode-v*` | npm (`@mem0/opencode-plugin`) | - All publishing uses **OIDC trusted publishing** — no tokens or secrets required. - First publish of a new npm package must be done manually; OIDC works for subsequent versions. @@ -576,7 +576,6 @@ N/A - Modify CI/CD workflows without explicit approval. - Add new Python dependencies to the core `dependencies` list in `pyproject.toml` without discussion — use optional dependency groups instead. - Commit `.env` files, API keys, or credentials. -- Modify `embedchain/` unless specifically working on that package — it has its own build system (Poetry). - Skip pre-commit hooks. - Use npm or yarn in TypeScript packages — this repo uses pnpm exclusively. - Use `require()` for imports in TypeScript — use ES module `import` syntax. diff --git a/MIGRATION_GUIDE_v1.0.md b/MIGRATION_GUIDE_v1.0.md deleted file mode 100644 index b13197630..000000000 --- a/MIGRATION_GUIDE_v1.0.md +++ /dev/null @@ -1,221 +0,0 @@ -# Migration Guide: Upgrading to mem0 1.0.0 - -## TL;DR - -**What changed?** We simplified the API by removing confusing version parameters. Now everything returns a consistent format: `{"results": [...]}`. - -**What you need to do:** -1. Upgrade: `pip install mem0ai==1.0.0` -2. Remove `version` and `output_format` parameters from your code -3. Update response handling to use `result["results"]` instead of treating responses as lists - -**Time needed:** ~5-10 minutes for most projects - ---- - -## Quick Migration Guide - -### 1. Install the Update - -```bash -pip install mem0ai==1.0.0 -``` - -### 2. Update Your Code - -**If you're using the Memory API:** - -```python -# Before -memory = Memory(config=MemoryConfig(version="v1.1")) -result = memory.add("I like pizza") - -# After -memory = Memory() # That's it - version is automatic now -result = memory.add("I like pizza") -``` - -**If you're using the Client API:** - -```python -# Before -client.add(messages, output_format="v1.1") -client.search(query, version="v2", output_format="v1.1") - -# After -client.add(messages) # Just remove those extra parameters -client.search(query) -``` - -### 3. Update How You Handle Responses - -All responses now use the same format: a dictionary with `"results"` key. - -```python -# Before - you might have done this -result = memory.add("I like pizza") -for item in result: # Treating it as a list - print(item) - -# After - do this instead -result = memory.add("I like pizza") -for item in result["results"]: # Access the results key - print(item) - -# Graph relations (if you use them) -if "relations" in result: - for relation in result["relations"]: - print(relation) -``` - ---- - -## Enhanced Message Handling - -The platform client (MemoryClient) now supports the same flexible message formats as the OSS version: - -```python -from mem0 import MemoryClient - -client = MemoryClient(api_key="your-key") - -# All three formats now work: - -# 1. Single string (automatically converted to user message) -client.add("I like pizza", user_id="alice") - -# 2. Single message dictionary -client.add({"role": "user", "content": "I like pizza"}, user_id="alice") - -# 3. List of messages (conversation) -client.add([ - {"role": "user", "content": "I like pizza"}, - {"role": "assistant", "content": "I'll remember that!"} -], user_id="alice") -``` - -### Async Mode Configuration - -The `async_mode` parameter now defaults to `True` but can be configured: - -```python -# Default behavior (async_mode=True) -client.add(messages, user_id="alice") - -# Explicitly set async mode -client.add(messages, user_id="alice", async_mode=True) - -# Disable async mode if needed -client.add(messages, user_id="alice", async_mode=False) -``` - -**Note:** `async_mode=True` provides better performance for most use cases. Only set it to `False` if you have specific synchronous processing requirements. - ---- - -## That's It! - -For most users, that's all you need to know. The changes are: -- ✅ No more `version` or `output_format` parameters -- ✅ Consistent `{"results": [...]}` response format -- ✅ Cleaner, simpler API - ---- - -## Common Issues - -**Getting `KeyError: 'results'`?** - -Your code is still treating the response as a list. Update it: -```python -# Change this: -for memory in response: - -# To this: -for memory in response["results"]: -``` - -**Getting `TypeError: unexpected keyword argument`?** - -You're still passing old parameters. Remove them: -```python -# Change this: -client.add(messages, output_format="v1.1") - -# To this: -client.add(messages) -``` - -**Seeing deprecation warnings?** - -Remove any explicit `version="v1.0"` from your config: -```python -# Change this: -memory = Memory(config=MemoryConfig(version="v1.0")) - -# To this: -memory = Memory() -``` - ---- - -## What's New in 1.0.0 - -- **Better vector stores:** Fixed OpenSearch and improved reliability across all stores -- **Cleaner API:** One way to do things, no more confusing options -- **Enhanced GCP support:** Better Vertex AI configuration options -- **Flexible message input:** Platform client now accepts strings, dicts, and lists (aligned with OSS) -- **Configurable async_mode:** Now defaults to `True` but users can override if needed - ---- - -## Need Help? - -- Check [GitHub Issues](https://github.com/mem0ai/mem0/issues) -- Read the [documentation](https://docs.mem0.ai/) -- Open a new issue if you're stuck - ---- - -## Advanced: Configuration Changes - -**If you configured vector stores with version:** - -```python -# Before -config = MemoryConfig( - version="v1.1", - vector_store=VectorStoreConfig(...) -) - -# After -config = MemoryConfig( - vector_store=VectorStoreConfig(...) -) -``` - ---- - -## Testing Your Migration - -Quick sanity check: - -```python -from mem0 import Memory - -memory = Memory() - -# Add should return a dict with "results" -result = memory.add("I like pizza", user_id="test") -assert "results" in result - -# Search should return a dict with "results" -search = memory.search("food", user_id="test") -assert "results" in search - -# Get all should return a dict with "results" -all_memories = memory.get_all(user_id="test") -assert "results" in all_memories - -print("✅ Migration successful!") -``` diff --git a/cli/node/src/commands/memory.ts b/cli/node/src/commands/memory.ts index dd3a08211..3b7b67b4f 100644 --- a/cli/node/src/commands/memory.ts +++ b/cli/node/src/commands/memory.ts @@ -46,7 +46,7 @@ export async function cmdAdd( file?: string; metadata?: string; immutable: boolean; - noInfer: boolean; + infer?: boolean; expires?: string; categories?: string; output: string; @@ -136,7 +136,7 @@ export async function cmdAdd( runId: opts.runId, metadata: meta, immutable: opts.immutable, - infer: !opts.noInfer, + infer: opts.infer !== false, expires: opts.expires, categories: cats, }); diff --git a/cli/node/tests/commands.test.ts b/cli/node/tests/commands.test.ts index 0d67a03af..4cd43d1be 100644 --- a/cli/node/tests/commands.test.ts +++ b/cli/node/tests/commands.test.ts @@ -3,6 +3,7 @@ */ import { describe, it, expect, vi, beforeEach } from "vitest"; +import { Command } from "commander"; import { createMockBackend } from "./setup.js"; import type { Backend } from "../src/backend/base.js"; import { setAgentMode } from "../src/state.js"; @@ -41,8 +42,6 @@ describe("cmdAdd", () => { await cmdAdd(mockBackend, "I prefer dark mode", { userId: "alice", immutable: false, - noInfer: false, - output: "text", }); expect(mockBackend.add).toHaveBeenCalledOnce(); @@ -54,8 +53,6 @@ describe("cmdAdd", () => { userId: "alice", messages: JSON.stringify([{ role: "user", content: "I love Python" }]), immutable: false, - noInfer: false, - output: "text", }); expect(mockBackend.add).toHaveBeenCalledOnce(); @@ -66,8 +63,6 @@ describe("cmdAdd", () => { await cmdAdd(mockBackend, "test", { userId: "alice", immutable: false, - noInfer: false, - output: "json", }); expect(output).toContain("results"); @@ -78,14 +73,59 @@ describe("cmdAdd", () => { await cmdAdd(mockBackend, "test", { userId: "alice", immutable: false, - noInfer: false, - output: "quiet", }); expect(output).not.toContain("dark mode"); }); }); +describe("cmdAdd forwards --no-infer (regression for #5261)", () => { + it("forwards infer: false when --no-infer is set", async () => { + const { cmdAdd } = await import("../src/commands/memory.js"); + // `infer: false` is the shape Commander produces for `--no-infer`. + await cmdAdd(mockBackend, "store me verbatim", { + userId: "alice", + immutable: false, + infer: false, + output: "text", + }); + expect(mockBackend.add).toHaveBeenCalledWith( + "store me verbatim", + undefined, + expect.objectContaining({ infer: false }), + ); + }); + + it("forwards infer: true by default (flag absent)", async () => { + const { cmdAdd } = await import("../src/commands/memory.js"); + await cmdAdd(mockBackend, "infer me", { + userId: "alice", + immutable: false, + output: "text", + }); + expect(mockBackend.add).toHaveBeenCalledWith( + "infer me", + undefined, + expect.objectContaining({ infer: true }), + ); + }); + + it("Commander stores --no-infer as opts.infer, not opts.noInfer", () => { + // Pins the assumption the fix relies on: Commander's `--no-X` option + // populates the positive camelCase key (`infer`), never `noInfer`. + const withFlag = new Command(); + withFlag.option("--no-infer", "Skip inference, store raw.").action(() => {}); + withFlag.parse(["--no-infer"], { from: "user" }); + expect(withFlag.opts().infer).toBe(false); + expect(withFlag.opts().noInfer).toBeUndefined(); + + const withoutFlag = new Command(); + withoutFlag.option("--no-infer", "Skip inference, store raw.").action(() => {}); + withoutFlag.parse([], { from: "user" }); + expect(withoutFlag.opts().infer).toBe(true); + }); +}); + describe("cmdAdd deduplicates PENDING", () => { const DUPLICATE_PENDING = { results: [ @@ -100,8 +140,6 @@ describe("cmdAdd deduplicates PENDING", () => { await cmdAdd(mockBackend, "test", { userId: "alice", immutable: false, - noInfer: false, - output: "text", }); expect(output.match(/Queued/g)?.length).toBe(1); @@ -113,8 +151,6 @@ describe("cmdAdd deduplicates PENDING", () => { await cmdAdd(mockBackend, "test", { userId: "alice", immutable: false, - noInfer: false, - output: "json", }); const data = JSON.parse(output); @@ -129,8 +165,6 @@ describe("cmdAdd deduplicates PENDING", () => { await cmdAdd(mockBackend, "test", { userId: "alice", immutable: false, - noInfer: false, - output: "agent", }); const data = JSON.parse(output); @@ -315,8 +349,6 @@ describe("agent mode", () => { await cmdAdd(mockBackend, "test preference", { userId: "alice", immutable: false, - noInfer: false, - output: "agent", }); const parsed = JSON.parse(output.trim()); diff --git a/docs/api-reference/organizations-projects.mdx b/docs/api-reference/organizations-projects.mdx index d4958b260..c1cc0c6b2 100644 --- a/docs/api-reference/organizations-projects.mdx +++ b/docs/api-reference/organizations-projects.mdx @@ -79,7 +79,7 @@ new_project = client.project.create( ### Update Project Settings -Modify project configuration including custom instructions, categories, graph settings, and language preferences: +Modify project configuration including custom instructions, categories, and language preferences: ```python # Update project with custom categories diff --git a/docs/changelog/sdk.mdx b/docs/changelog/sdk.mdx index b84f34e71..566ce2415 100644 --- a/docs/changelog/sdk.mdx +++ b/docs/changelog/sdk.mdx @@ -7,6 +7,21 @@ mode: "wide" + + +**New Features:** +- **Client:** `delete()` and async `delete()` accept `delete_linked` (default `False`). When `True`, deleting a memory also removes the older memories it superseded (the v3 `linked_memory_ids` chain), transitively — the delete-side counterpart of `latest_only`, so a superseded memory does not resurface after the current one is deleted ([#5270](https://github.com/mem0ai/mem0/pull/5270)) + + + + + +**Bug Fixes:** +- **Vector Stores:** PGVector adapter now supports rich filter operators (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains`, `icontains`, wildcard `*`, `$or`, `$not`) in `search()`, `keyword_search()`, and `list()`. Previously only exact-equality filters worked — operator dicts were silently stringified and returned zero results ([#5263](https://github.com/mem0ai/mem0/pull/5263)) +- **Server:** Fixed `/search` endpoint returning 502 when `user_id`, `agent_id`, or `run_id` are sent as top-level request fields. The server now maps these into the `filters` dict before calling `Memory.search()`, matching the v3 API contract. Top-level entity ID fields are marked as deprecated in the OpenAPI schema and emit a warning log — clients should migrate to `filters={"user_id": "..."}` ([#5263](https://github.com/mem0ai/mem0/pull/5263)) + + + **Bug Fixes:** @@ -924,6 +939,20 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to- + + +**New Features:** +- **Client:** `delete()` accepts an options object with `deleteLinked` (serialized as `delete_linked`, default `false`). When `true`, deleting a memory also removes the older memories it superseded (the v3 linked chain), transitively — the delete-side counterpart of `latestOnly`, so a superseded memory does not resurface after the current one is deleted ([#5270](https://github.com/mem0ai/mem0/pull/5270)) + + + + + +**Bug Fixes:** +- **Vector Stores:** PGVector adapter now supports rich filter operators (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains`, `icontains`, wildcard `*`, `$or`, `$not`) in `search()`, `keywordSearch()`, and `list()`. Previously only exact-equality filters worked — operator objects were passed as raw values and returned incorrect results ([#5263](https://github.com/mem0ai/mem0/pull/5263)) + + + **Bug Fixes:** diff --git a/docs/cookbooks/essentials/controlling-memory-ingestion.mdx b/docs/cookbooks/essentials/controlling-memory-ingestion.mdx index 05227bd17..d63436af4 100644 --- a/docs/cookbooks/essentials/controlling-memory-ingestion.mdx +++ b/docs/cookbooks/essentials/controlling-memory-ingestion.mdx @@ -323,10 +323,6 @@ Metadata: {'verified': True, 'updated_date': '2025-04-02'} That “no duplicates” promise comes from the inference pipeline. Keep `infer=True` when you rely on automatic updates. Raw imports (`infer=False`) skip conflict checks, so mixing the two modes for the same fact will create duplicates. -**Maintains relationships:** - -- If using graph memory, connections to other entities persist - ### Pick the right inference mode | Mode | What it does | Best for | Watch out for | diff --git a/docs/cookbooks/frameworks/gemini-3-with-mem0-mcp.mdx b/docs/cookbooks/frameworks/gemini-3-with-mem0-mcp.mdx index 4ed81087e..d638ce934 100644 --- a/docs/cookbooks/frameworks/gemini-3-with-mem0-mcp.mdx +++ b/docs/cookbooks/frameworks/gemini-3-with-mem0-mcp.mdx @@ -216,7 +216,6 @@ This information was retrieved from your memory history where you previously men - **Smart Memory Management** - Organizes memories into searchable information *without setting up vector databases* - **Fast Retrieval** - Instant lookups with *sub-millisecond ping*, handles large datasets -- **Graph Capabilities** - Builds knowledge *automatically* as you push information - **Simple Integration** - Uses Mem0 API in the backend, works with *any MCP client* with just a few lines of code ### Gemini 3 + Mem0 Benefits diff --git a/docs/cookbooks/overview.mdx b/docs/cookbooks/overview.mdx index d69c59efc..dd08c8cfc 100644 --- a/docs/cookbooks/overview.mdx +++ b/docs/cookbooks/overview.mdx @@ -156,14 +156,7 @@ Here are some examples of how Mem0 can be integrated into various applications: icon="aws" href="/cookbooks/integrations/aws-bedrock" > - Mem0 with AWS Bedrock and Neptune. - - - Graph memory with Neptune Analytics. + Mem0 with AWS Bedrock. diff --git a/docs/core-concepts/memory-evaluation.mdx b/docs/core-concepts/memory-evaluation.mdx index 1d04446d8..d03e8f21f 100644 --- a/docs/core-concepts/memory-evaluation.mdx +++ b/docs/core-concepts/memory-evaluation.mdx @@ -47,7 +47,7 @@ When a query arrives, the retrieval pipeline scores candidates across three sign 1. **Semantic Search** — Vector similarity scoring against memory embeddings 2. **Keyword Search** — Normalized term matching via BM25 with verb-form lemmatization -3. **Entity Search** — Entity graph matching boosts memories linked to query entities +3. **Entity Search** — Entity matching boosts memories linked to query entities Results are fused via rank scoring into a final top-K set. Different query types lean on different signals: diff --git a/docs/core-concepts/memory-operations/add.mdx b/docs/core-concepts/memory-operations/add.mdx index 45adc300d..38ca686c7 100644 --- a/docs/core-concepts/memory-operations/add.mdx +++ b/docs/core-concepts/memory-operations/add.mdx @@ -27,15 +27,11 @@ Adding memory is how Mem0 captures useful details from a conversation so your ag Mem0 offers two flows: -- **Mem0 Platform** – Fully managed API with dashboard, scaling, and graph features. +- **Mem0 Platform** – Fully managed API with dashboard and scaling. - **Mem0 Open Source** – Local SDK that you run in your own environment. Both flows take the same payload and pass it through the same pipeline. - - - - Mem0 sends the messages through an LLM that pulls out key facts, decisions, or preferences to remember. @@ -44,7 +40,7 @@ Mem0 sends the messages through an LLM that pulls out key facts, decisions, or p Existing memories are checked for duplicates or contradictions so the latest truth wins. -The resulting memories land in managed vector storage (and optional graph storage) so future searches return them quickly. +The resulting memories land in managed vector storage so future searches return them quickly. @@ -177,7 +173,7 @@ For full list of supported fields, required formats, and advanced options, see t ## Put it into practice -- Review the Advanced Memory Operations guide to layer metadata, rerankers, and graph toggles. +- Review the Advanced Memory Operations guide to layer metadata and rerankers. - Explore the Add Memories API reference for every request/response field. ## See it live diff --git a/docs/core-concepts/memory-operations/search.mdx b/docs/core-concepts/memory-operations/search.mdx index 57f3f14e4..8972924f3 100644 --- a/docs/core-concepts/memory-operations/search.mdx +++ b/docs/core-concepts/memory-operations/search.mdx @@ -25,10 +25,6 @@ Mem0's search operation lets agents ask natural-language questions and get back ## Architecture - - - - Mem0 cleans and enriches your natural-language query so the downstream embedding search is accurate. diff --git a/docs/core-concepts/memory-types.mdx b/docs/core-concepts/memory-types.mdx index 8ab425eae..9be7fc412 100644 --- a/docs/core-concepts/memory-types.mdx +++ b/docs/core-concepts/memory-types.mdx @@ -104,7 +104,7 @@ results = memory.search( ## Put it into practice - Use the Add Memory guide to persist user preferences. -- Follow Advanced Memory Operations to tune metadata and graph writes. +- Follow Advanced Memory Operations to tune metadata and retrieval. ## See it live diff --git a/docs/docs.json b/docs/docs.json index 30acad425..8a4c95865 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -452,7 +452,9 @@ "pages": [ "integrations/claude-code", "integrations/cursor", - "integrations/codex" + "integrations/codex", + "integrations/opencode", + "integrations/antigravity" ] }, { diff --git a/docs/images/add_architecture.png b/docs/images/add_architecture.png deleted file mode 100644 index 39792f34a..000000000 Binary files a/docs/images/add_architecture.png and /dev/null differ diff --git a/docs/images/search_architecture.png b/docs/images/search_architecture.png deleted file mode 100644 index 1f4f5361c..000000000 Binary files a/docs/images/search_architecture.png and /dev/null differ diff --git a/docs/integrations/antigravity.mdx b/docs/integrations/antigravity.mdx new file mode 100644 index 000000000..3a6836f58 --- /dev/null +++ b/docs/integrations/antigravity.mdx @@ -0,0 +1,82 @@ +--- +title: Antigravity +description: "Add persistent memory to Google Antigravity with the Mem0 plugin — MCP server, lifecycle hooks, and slash commands." +--- + +Add persistent memory to [**Google Antigravity**](https://antigravity.google) (`agy` CLI and Desktop IDE) with the Mem0 plugin. Your agent forgets everything between sessions — Mem0 fixes that by storing decisions, preferences, and learnings so they carry over automatically. + +## Prerequisites + +1. A Mem0 API key (starts with `m0-`): + - Get your API key (free sign-up at app.mem0.ai) + +2. Add it to your shell profile so it persists across sessions: + + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc && source ~/.zshrc +``` + +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc +``` + + +## Installation + +**Option A — degit** (recommended): + +```bash +# Install the plugin (MCP server, hooks, scripts) +npx degit mem0ai/mem0/mem0-plugin ~/.gemini/config/plugins/mem0 +``` + +This installs the MCP server, lifecycle hooks, and shared scripts. + +## What's Included + +| Component | Included | +|-----------|:--------:| +| MCP Server (9 memory tools) | Yes | +| Lifecycle Hooks | Yes | +| 16 Slash Commands | Yes | + +## Available MCP Tools + +| Tool | Description | +|------|-------------| +| `add_memory` | Save text or conversation history for a user/agent | +| `search_memories` | Semantic search across memories with filters | +| `get_memories` | List memories with filters and pagination | +| `get_memory` | Retrieve a specific memory by ID | +| `update_memory` | Overwrite a memory's text by ID | +| `delete_memory` | Delete a single memory by ID | +| `delete_all_memories` | Bulk delete all memories in scope | +| `delete_entities` | Delete a user/agent/app/run entity and its memories | +| `list_entities` | List users/agents/apps/runs stored in Mem0 | + +## Lifecycle Hooks + +The plugin uses the same shell scripts as Claude Code, Cursor, and Codex — hooks bridge environment variables using `${extensionPath}` (Antigravity's plugin-root token). + +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `SessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message | +| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools | +| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | + +## Troubleshooting + +- **No tools appearing** — Restart your Antigravity session after installation +- **"Connection failed"** — Verify your key is set: `echo $MEM0_API_KEY` +- **MCP 401 Unauthorized** — If `${MEM0_API_KEY}` interpolation doesn't work in your `agy` version, replace with your literal key in `mcp_config.json` + + + + Detailed MCP configuration for all clients + + + Add Mem0 memory to OpenCode workflows + + diff --git a/docs/integrations/claude-code.mdx b/docs/integrations/claude-code.mdx index 782e3d05b..3a3516725 100644 --- a/docs/integrations/claude-code.mdx +++ b/docs/integrations/claude-code.mdx @@ -5,13 +5,6 @@ description: "Add persistent memory to Claude Code and Claude Cowork with the Me Add persistent memory to [**Claude Code**](https://docs.anthropic.com/en/docs/claude-code) (CLI) and **Claude Cowork** (desktop app) with the Mem0 plugin. Your agent forgets everything between sessions — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP, automatically capturing learnings at key lifecycle points, and retrieving relevant context before every response. -## Overview - -1. **MCP Server** — Connect to Mem0's remote MCP server for memory tools (add, search, update, delete) -2. **Lifecycle Hooks** — Automatic memory capture at session start, context compaction, task completion, and session end -3. **SDK Skill** — Teaches the agent how to integrate the Mem0 SDK into your applications -4. **Zero local dependencies** — Cloud-hosted MCP server, no local setup required - ## Prerequisites Before setting up Mem0 with Claude Code, ensure you have: @@ -22,10 +15,25 @@ Before setting up Mem0 with Claude Code, ensure you have: 2. Claude Code CLI or Claude Cowork desktop app installed -3. Your API key exported in your shell: +3. Your API key added to your shell profile (persists across sessions): + + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc +source ~/.zshrc +``` + +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc +source ~/.bashrc +``` + + +Confirm it's set: ```bash -export MEM0_API_KEY="m0-your-api-key" +echo $MEM0_API_KEY +# Should print: m0-your-api-key ``` ## Installation @@ -84,6 +92,22 @@ Add to your Claude Code MCP config (`.mcp.json`): Start a new session and ask: *"List my mem0 entities"* or *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set. +## Post-Installation: Run `/mem0:onboard` + +After installing the plugin, start a new Claude Code session and run: + +``` +/mem0:onboard +``` + +This runs the setup wizard which: +1. Verifies your API key and MCP connection +2. Detects and imports project files (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`) +3. Installs coding-optimized memory categories +4. Shows your identity (user ID, project scope, branch) + +The onboarding is idempotent — safe to re-run anytime. It auto-triggers on first session in a new project, but you can always invoke it manually. + ## What's Included | Component | Plugin Install | MCP Only | @@ -112,20 +136,13 @@ Once installed, the following tools are available in every Claude Code session: When installed via the plugin marketplace, Mem0 hooks into Claude Code's lifecycle to automatically manage memory: -### Session Start -On every new session, the plugin prompts Claude to call `search_memories` to load relevant context from prior sessions. On resumed or post-compaction sessions, it adjusts the prompt accordingly. - -### User Prompt -Before processing each user message, the plugin searches Mem0 for memories relevant to the current prompt and injects them into context. Short prompts (< 20 characters) are skipped to minimize latency. - -### Pre-Compaction -Before context compaction, the plugin prompts Claude to store a comprehensive session summary — including goals, accomplishments, decisions, modified files, and current state — so nothing is lost. - -### Task Completed -After each task completion, the plugin prompts Claude to extract and store key learnings: successful strategies, failed approaches, architectural decisions, and new conventions. - -### Session End -When Claude finishes responding, the plugin prompts for any unstored learnings and captures transcript state via the Mem0 REST API as a background safety net. +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `SessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message; skips short prompts | +| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls | +| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | +| **Pre-compact** | `PreCompact` | Stores a session summary before context compaction | ## Example Workflow @@ -149,9 +166,10 @@ You: Add refresh token rotation to the auth system. ## Troubleshooting -- **"Connection failed"** — Verify `MEM0_API_KEY` is set in your shell: `echo $MEM0_API_KEY` +- **"Connection failed"** — Verify `MEM0_API_KEY` is set in your shell: `echo $MEM0_API_KEY`. If empty, add it to your shell profile (see Prerequisites) - **No tools appearing** — Restart your Claude Code session after installation -- **Memories not being captured** — Ensure you installed via the plugin marketplace (Option A) for lifecycle hooks. MCP-only installs require manual memory operations. +- **Memories not being captured** — Ensure you installed via the plugin marketplace (Option A) for lifecycle hooks. MCP-only installs require manual memory operations +- **"Mem0 Inactive" banner every session** — Your API key isn't persisting. Add `export MEM0_API_KEY="m0-..."` to your `~/.zshrc` (or `~/.bashrc`) and run `source ~/.zshrc` diff --git a/docs/integrations/codex.mdx b/docs/integrations/codex.mdx index 0a8831250..f7f1226fe 100644 --- a/docs/integrations/codex.mdx +++ b/docs/integrations/codex.mdx @@ -1,16 +1,9 @@ --- title: Codex -description: "Add persistent memory to OpenAI Codex with the Mem0 plugin — MCP server, memory protocol skill, and plugin marketplace support." +description: "Add persistent memory to OpenAI Codex with the Mem0 plugin — MCP server, lifecycle hooks, and SDK skill." --- -Add persistent memory to [**OpenAI Codex**](https://openai.com/index/codex/) with the Mem0 plugin. Codex forgets everything between tasks — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP and using a skill-based memory protocol to automatically retrieve context and store learnings. - -## Overview - -1. **MCP Server** — Connect to Mem0's remote MCP server for memory tools (add, search, update, delete) -2. **Memory Protocol Skill** — Instructs the agent to retrieve memories at task start, store learnings on completion, and capture session state before context loss -3. **Plugin Marketplace** — Install via Codex's repo-level or personal plugin marketplace -4. **Zero local dependencies** — Cloud-hosted MCP server, no local setup required +Add persistent memory to [**OpenAI Codex**](https://openai.com/index/codex/) with the Mem0 plugin. Codex forgets everything between tasks — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP, automatically capturing learnings at key lifecycle points, and retrieving relevant context before every response. ## Prerequisites @@ -22,17 +15,41 @@ Before setting up Mem0 with Codex, ensure you have: 2. OpenAI Codex access -3. Your API key exported in your shell: +3. Your API key added to your shell profile (persists across sessions): -```bash -export MEM0_API_KEY="m0-your-api-key" + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc +source ~/.zshrc ``` +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc +source ~/.bashrc +``` + + ## Installation -### Option A — Direct MCP (Recommended) +### Option A — Plugin Marketplace (Recommended) -The fastest way to connect Codex to Mem0 — no downloads, no marketplace. Codex reads MCP servers from `~/.codex/config.toml` as TOML. Add: +Install the full plugin including MCP server, lifecycle hooks, and SDK skill. + +1. Add the Mem0 marketplace: + + ```bash + codex plugin marketplace add mem0ai/mem0 + ``` + +2. Restart Codex, open the Plugin Directory, browse the **Mem0 Plugins** marketplace, and install **Mem0**. + + + Do not combine with Option B. The plugin manifest auto-registers the `mem0` MCP server, so adding both will create a duplicate registration. + + +### Option B — Direct MCP + +The fastest way to connect Codex to Mem0 — no plugin, no marketplace. Add to `~/.codex/config.toml`: ```toml [mcp_servers.mem0] @@ -46,71 +63,16 @@ Make sure `MEM0_API_KEY` is exported in the shell you launch Codex from, then re Codex's `codex mcp add` CLI only supports stdio MCP servers. Because Mem0's MCP is HTTP/streamable, you configure it by editing `config.toml` directly (or via the **Plugins → Connect to a custom MCP → Streamable HTTP** UI in the Codex app). -### Option B — Sideload the Plugin (Advanced) - -For the full plugin experience — MCP server **plus** the Mem0 SDK skill, memory protocol skill, and opt-in lifecycle hooks — sideload the plugin from a local clone. The Mem0 repo already ships a marketplace manifest at [`.agents/plugins/marketplace.json`](https://github.com/mem0ai/mem0/blob/main/.agents/plugins/marketplace.json), so there's no JSON to author by hand. This follows the Codex [build-plugins](https://developers.openai.com/codex/plugins/build) local-testing workflow. - - - Don't combine Option B with Option A. The plugin manifest declares its MCP server via [`.codex-mcp.json`](https://github.com/mem0ai/mem0/blob/main/mem0-plugin/.codex-mcp.json), so Codex auto-registers the `mem0` MCP server when the plugin loads. Adding the same `[mcp_servers.mem0]` block to `~/.codex/config.toml` will create a duplicate registration. - - -**Step 1.** Clone the Mem0 repository anywhere on disk: - -```bash -git clone https://github.com/mem0ai/mem0.git ~/codex-plugins/mem0-source -``` - -**Step 2.** Register the bundled marketplace with Codex's CLI: - -```bash -codex plugin marketplace add ~/codex-plugins/mem0-source -``` - -This points Codex at the repo's `.agents/plugins/marketplace.json`. The bundled file uses `path: "./mem0-plugin"`, which Codex resolves relative to the clone root. - - - **Why we recommend this over hand-authoring `~/.agents/plugins/marketplace.json`:** Codex requires `source.path` in any marketplace manifest to be **relative** (starting with `./`) and **inside the marketplace root**. The repo's bundled manifest already satisfies this — the marketplace root is the clone directory, and `mem0-plugin/` lives inside it. With a personal `~/.agents/plugins/marketplace.json`, the root is `~/` and the clone has to live under `~/` too. The CLI form sidesteps that constraint. - - -**Step 3.** Restart Codex, run `/plugins`, browse the `Mem0 Plugins` marketplace, and install **Mem0**. - -**Step 4 (optional) — enable lifecycle hooks.** Codex doesn't auto-wire hooks from plugin manifests; it only reads them from `~/.codex/hooks.json` (or `/.codex/hooks.json`). Run the bundled installer once to merge the Mem0 entries into your global hooks file: - -```bash -python3 ~/codex-plugins/mem0-source/mem0-plugin/scripts/install_codex_hooks.py -``` - -Then enable the hooks feature flag in `~/.codex/config.toml`: - -```toml -[features] -codex_hooks = true -``` - -Restart Codex. The installer registers three hooks pointing at scripts inside your clone: - -| Event | Behavior | -|-------|----------| -| `SessionStart` | Loads prior memories as bootstrap context | -| `UserPromptSubmit` | Injects relevant memories before each prompt | -| `Stop` | Reminds the agent to persist learnings at turn end | - -Re-running the installer is idempotent. To remove the hooks: `python3 ~/codex-plugins/mem0-source/mem0-plugin/scripts/install_codex_hooks.py --uninstall`. - - - The hooks file stores absolute paths into your clone (e.g. `~/codex-plugins/mem0-source/mem0-plugin/scripts/...`). If you move or delete the clone, the hooks will break silently — re-run the installer from the new location, or run `--uninstall` first. - +This gives you the MCP tools but not the lifecycle hooks or SDK skill. ### Managing the Plugin -Codex provides CLI commands for managing marketplaces after install: - ```bash codex plugin marketplace upgrade # pull latest plugin versions codex plugin marketplace remove mem0-plugins # unregister the marketplace ``` -To pull updates to the plugin source itself, `git pull` inside your clone (`~/codex-plugins/mem0-source`) and then run `codex plugin marketplace upgrade` to refresh Codex's plugin cache. Plugins are cached at `~/.codex/plugins/cache////`. +To update, run `codex plugin marketplace upgrade` to pull the latest from the Mem0 repo. After either option, start a new Codex task and ask: *"List my mem0 entities"* or *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set. @@ -118,12 +80,11 @@ To pull updates to the plugin source itself, `git pull` inside your clone (`~/co ## What's Included -| Component | Sideloaded Plugin | Direct MCP | -|-----------|:-----------------:|:----------:| +| Component | Plugin Install | MCP Only | +|-----------|:--------------:|:--------:| | MCP Server (9 memory tools) | Yes | Yes | -| Memory Protocol Skill | Yes | No | +| Lifecycle Hooks | Yes | No | | Mem0 SDK Skill | Yes | No | -| Lifecycle Hooks (opt-in) | Yes | No | ## Available MCP Tools @@ -141,49 +102,17 @@ Once installed, the following tools are available in every Codex session: | `delete_entities` | Delete a user/agent/app/run entity and its memories | | `list_entities` | List users/agents/apps/runs stored in Mem0 | -## Memory Protocol Skill +## Lifecycle Hooks -When the plugin is sideloaded, the memory protocol skill instructs the agent to: +When installed via the plugin marketplace, Mem0 hooks into Codex's lifecycle to automatically manage memory: -### On Every New Task -1. Call `search_memories` with a query related to the current task to load relevant context -2. Review returned memories to understand what was learned in prior sessions -3. Optionally call `get_memories` to browse all stored memories - -### After Completing Significant Work -Store key learnings using `add_memory` with structured metadata: - -| What to store | Metadata type | -|--------------|---------------| -| Architectural decisions | `{"type": "decision"}` | -| Strategies that worked | `{"type": "task_learning"}` | -| Failed approaches | `{"type": "anti_pattern"}` | -| User preferences observed | `{"type": "user_preference"}` | -| Environment discoveries | `{"type": "environmental"}` | -| Conventions established | `{"type": "convention"}` | - -### Before Losing Context -Store a comprehensive session summary including goals, accomplishments, decisions, files modified, and current state with metadata `{"type": "session_state"}`. - -## Plugin Manifest - -The Codex plugin manifest (`.codex-plugin/plugin.json`) follows the Codex plugin specification: - -```json -{ - "name": "mem0", - "version": "0.1.0", - "description": "Mem0 memory layer for AI applications.", - "skills": "./skills/", - "mcpServers": "./.codex-mcp.json", - "interface": { - "displayName": "Mem0", - "shortDescription": "Persistent memory layer for AI coding workflows", - "category": "Productivity", - "capabilities": ["Read", "Write"] - } -} -``` +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `SessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message | +| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls | +| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | +| **Pre-compact** | `PreCompact` | Stores a session summary before context compaction | ## Example Workflow @@ -206,14 +135,10 @@ You: Add WebSocket support for real-time notification delivery. ## Troubleshooting -- **"Connection failed"** — Verify `MEM0_API_KEY` is set in your shell: `echo $MEM0_API_KEY` -- **No tools appearing** — Restart your Codex session after plugin installation -- **Duplicate `mem0` MCP server / "tool collision" errors** — You combined Option A (Direct MCP) with Option B (sideload). The sideloaded plugin auto-registers `mem0` from `.codex-mcp.json`, so remove the `[mcp_servers.mem0]` block from `~/.codex/config.toml`. -- **`plugin/read failed in TUI`** — Codex can't find the plugin directory the marketplace points at. If you used `codex plugin marketplace add `, confirm the path is your clone root and that `/.agents/plugins/marketplace.json` exists. If you hand-authored `~/.agents/plugins/marketplace.json`, `source.path` must be relative (start with `./`), inside the marketplace root (`~/` for personal installs), and end in `mem0-plugin` — e.g. `"./codex-plugins/mem0-source/mem0-plugin"`. -- **Plugin not found in `/plugins`** — Run `codex plugin marketplace add ~/path/to/clone` again, or confirm the marketplace was registered with `codex plugin marketplace remove mem0-plugins` then re-add. -- **Skills not loading** — Verify the `skills` field in `plugin.json` points to a valid directory containing `SKILL.md` files. -- **Hooks not firing** — Confirm `codex_hooks = true` is in `~/.codex/config.toml` under `[features]`, and that `~/.codex/hooks.json` contains the Mem0 entries (re-run the installer if not). Restart Codex after enabling the flag. -- **Hooks broke after moving the clone** — The installer bakes absolute paths into `~/.codex/hooks.json` pointing at scripts inside your clone. If you moved or renamed the clone directory, run `python3 /mem0-plugin/scripts/install_codex_hooks.py` from the new location — the installer is idempotent and replaces the old entries. +- **"Connection failed"** — Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY` +- **No tools appearing** — Restart your Codex session after installation +- **Duplicate `mem0` MCP / "tool collision" errors** — You combined Option A with Option B. Remove the `[mcp_servers.mem0]` block from `~/.codex/config.toml`; the plugin registers it automatically +- **Hooks not firing** — Ensure the plugin is installed via the marketplace (Option A). MCP-only installs do not include hooks diff --git a/docs/integrations/cursor.mdx b/docs/integrations/cursor.mdx index 61e7a8dec..1e516af72 100644 --- a/docs/integrations/cursor.mdx +++ b/docs/integrations/cursor.mdx @@ -5,13 +5,6 @@ description: "Add persistent memory to Cursor with the Mem0 plugin — MCP serve Add persistent memory to [**Cursor**](https://cursor.com) with the Mem0 plugin. Your AI assistant forgets everything between sessions — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP, automatically capturing learnings at key lifecycle points, and retrieving relevant context before every response. -## Overview - -1. **MCP Server** — Connect to Mem0's remote MCP server for memory tools (add, search, update, delete) -2. **Lifecycle Hooks** — Automatic memory capture at session start, compaction, and user prompts (Marketplace install) -3. **SDK Skill** — Teaches the agent how to integrate the Mem0 SDK into your applications -4. **Zero local dependencies** — Cloud-hosted MCP server, no local setup required - ## Prerequisites Before setting up Mem0 with Cursor, ensure you have: @@ -22,12 +15,20 @@ Before setting up Mem0 with Cursor, ensure you have: 2. Cursor installed ([cursor.com](https://cursor.com)) -3. Your API key exported in your shell: +3. Your API key added to your shell profile (persists across sessions): -```bash -export MEM0_API_KEY="m0-your-api-key" + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc +source ~/.zshrc ``` +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc +source ~/.bashrc +``` + + Already have `mem0` configured as an MCP server in Cursor? Remove the existing entry from your Cursor MCP settings before installing to avoid duplicate tools. @@ -103,14 +104,13 @@ Once installed, the following tools are available in every Cursor session: When installed via the Cursor Marketplace, Mem0 hooks into Cursor's lifecycle: -### Session Start -On every new session, the plugin prompts the agent to call `search_memories` to load relevant context from prior sessions. - -### User Prompt -Before processing each user message, the plugin searches Mem0 for relevant memories and injects them into context. Short prompts are skipped to minimize latency. - -### Pre-Compaction -Before context compaction, the plugin captures a comprehensive session summary so nothing is lost when the context window resets. +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `sessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `beforeSubmitPrompt` | Searches relevant memories before each message; skips short prompts | +| **Pre-tool (2 handlers)** | `preToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls | +| **Post-tool (2 handlers)** | `postToolUse` | Tracks stats, scans bash errors for related memories | +| **Pre-compact** | `preCompact` | Stores a session summary before context compaction | ## Example Workflow diff --git a/docs/integrations/opencode.mdx b/docs/integrations/opencode.mdx new file mode 100644 index 000000000..e069ab8e6 --- /dev/null +++ b/docs/integrations/opencode.mdx @@ -0,0 +1,119 @@ +--- +title: OpenCode +description: "Add persistent memory to OpenCode with the Mem0 plugin — MCP server, lifecycle hooks, and slash commands." +--- + +Add persistent memory to [**OpenCode**](https://opencode.ai) with the Mem0 plugin. Your agent forgets everything between sessions — Mem0 fixes that by storing decisions, preferences, and learnings so they carry over automatically. + +## Prerequisites + +1. A Mem0 API key (starts with `m0-`): + - Get your API key (free sign-up at app.mem0.ai) + +2. Add it to your shell profile so it persists across sessions: + + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc && source ~/.zshrc +``` + +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc +``` + + +## Installation + +### Option A — Plugin Install (Recommended) + +```bash +opencode plugin @mem0/opencode-plugin +``` + + +Or using this command which does the same thing: + + +```bash +bunx @mem0/opencode-plugin@latest install +``` + + + +**Or let your agent do it** — paste this into OpenCode: + +``` +Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/mem0-plugin/.opencode-plugin/README.md +``` + +All commands auto-add the plugin and MCP server to your `~/.config/opencode/opencode.json`. Restart OpenCode — you get the MCP server, lifecycle hooks, and all `/mem0:` slash commands. + +### Option B — MCP Only + +If you only need the memory tools without hooks or skills, add this to your `opencode.json` (project-level or global at `~/.config/opencode/opencode.json`): + +```json +{ + "mcp": { + "mem0": { + "type": "remote", + "url": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token {env:MEM0_API_KEY}" + }, + "oauth": false + } + } +} +``` + +## What's Included + +| Component | Plugin (A) | MCP Only (B) | +|-----------|:----------:|:------------:| +| MCP Server (9 memory tools) | Yes | Yes | +| Lifecycle Hooks | Yes | No | +| 16 Slash Commands | Yes | No | + +## Available MCP Tools + +| Tool | Description | +|------|-------------| +| `add_memory` | Save text or conversation history for a user/agent | +| `search_memories` | Semantic search across memories with filters | +| `get_memories` | List memories with filters and pagination | +| `get_memory` | Retrieve a specific memory by ID | +| `update_memory` | Overwrite a memory's text by ID | +| `delete_memory` | Delete a single memory by ID | +| `delete_all_memories` | Bulk delete all memories in scope | +| `delete_entities` | Delete a user/agent/app/run entity and its memories | +| `list_entities` | List users/agents/apps/runs stored in Mem0 | + +## Lifecycle Hooks + +The plugin uses the [mem0ai](https://www.npmjs.com/package/mem0ai) TypeScript SDK directly — pure TypeScript, no Python, no shell scripts. + +| OpenCode Event | Hook | What happens | +|----------------|------|-------------| +| `chat.message` | **Chat message** | Searches prior memories on session start, searches relevant memories before each prompt, auto-captures learnings periodically | +| `tool.execute.before` | **Pre-tool** | Blocks MEMORY.md writes, injects `user_id`/`app_id` on mem0 tool calls | +| `tool.execute.after` | **Post-tool** | Tracks stats, scans Bash errors and pre-fetches related error memories | +| `experimental.chat.system.transform` | **System transform** | Injects memory context (session memories, search results, error lookups) into the system prompt | +| `experimental.session.compacting` | **Compaction** | Stores session state memory, then injects prior memories into compaction context so nothing is lost | +| `shell.env` | **Shell env** | Exports `MEM0_USER_ID`, `MEM0_APP_ID`, `MEM0_SESSION_ID`, and `MEM0_BRANCH` to all shell executions | + +## Troubleshooting + +- **No tools appearing** — Restart OpenCode after installing +- **"Connection failed"** — Verify your key is set: `echo $MEM0_API_KEY` +- **Plugin not loading** — Run `opencode plugin @mem0/opencode-plugin` again, then restart +- **Hooks not firing** — Hooks require the plugin install (Option A). MCP-only installs don't include hooks. + + + + Detailed MCP configuration for all clients + + + Add Mem0 memory to Google Antigravity + + diff --git a/docs/introduction.mdx b/docs/introduction.mdx index cf9912484..b53fe948b 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -12,7 +12,7 @@ mode: "custom"

- Universal, Self-improving memory layer for LLM applications. + Universal, self-improving memory layer for LLM applications.

AI Coding Tools`): - `integrations/claude-code` [Both] - `integrations/cursor` [Both] - `integrations/codex` [Both] +- `integrations/opencode` [Both] +- `integrations/antigravity` [Both] - `integrations/openclaw` [Both] ### MCP Endpoints diff --git a/docs/open-source/features/overview.mdx b/docs/open-source/features/overview.mdx index f7afa5950..073e0ce73 100644 --- a/docs/open-source/features/overview.mdx +++ b/docs/open-source/features/overview.mdx @@ -6,7 +6,7 @@ icon: "list" # Self-Hosting Features Overview -Mem0 Open Source ships with capabilities that adapt memory behavior for production workloads—async operations, graph relationships, multimodal inputs, and fine-tuned retrieval. Configure these features with code or YAML to match your application's needs. +Mem0 Open Source ships with capabilities that adapt memory behavior for production workloads—async operations, multimodal inputs, and fine-tuned retrieval. Configure these features with code or YAML to match your application's needs. Start with the Python quickstart to validate basic memory operations, then enable the features below when you need them. diff --git a/docs/open-source/features/rest-api.mdx b/docs/open-source/features/rest-api.mdx index a6ef488c4..5a318f1f7 100644 --- a/docs/open-source/features/rest-api.mdx +++ b/docs/open-source/features/rest-api.mdx @@ -119,7 +119,7 @@ docker build -t mem0-api-server . - The REST server reads the same configuration you use locally, so you can point it at your preferred LLM, vector store, graph backend, and reranker without changing code. + The REST server reads the same configuration you use locally, so you can point it at your preferred LLM, vector store, and reranker without changing code. --- @@ -319,7 +319,7 @@ The `/auth/*`, `/api-keys`, `/requests`, and `/entities` routes are new to the s - Fine-tune LLMs, vector stores, and graph backends that power the REST server. + Fine-tune LLMs, vector stores, and rerankers that power the REST server. See how services call the REST endpoints as part of an automation pipeline. diff --git a/docs/platform/advanced-memory-operations.mdx b/docs/platform/advanced-memory-operations.mdx index 36c47a78c..cbbb5efd6 100644 --- a/docs/platform/advanced-memory-operations.mdx +++ b/docs/platform/advanced-memory-operations.mdx @@ -64,7 +64,7 @@ const memory = new Memory({ apiKey: process.env.MEM0_API_KEY!, async: true });
-## Add memories with metadata and graph context +## Add memories with metadata @@ -109,7 +109,7 @@ const result = await memory.add(conversation, { - Successful calls return memories tagged with the metadata you passed. In the dashboard, confirm a graph edge between “Morgan” and “Tokyo” and verify the `trip=japan-2025` tag exists. + Successful calls return memories tagged with the metadata you passed. In the dashboard, verify the `trip=japan-2025` tag exists on the new memory. ## Retrieve and refine @@ -201,7 +201,7 @@ await memory.deleteAll({ userId: "traveler-42", runId: "planning-call-1" }); /> diff --git a/docs/platform/cli.mdx b/docs/platform/cli.mdx index 79642aca0..accd9801b 100644 --- a/docs/platform/cli.mdx +++ b/docs/platform/cli.mdx @@ -121,7 +121,6 @@ echo "Loves hiking on weekends" | mem0 add --user-id alice | `-f, --file` | Read messages from a JSON file | | `-m, --metadata` | Custom metadata as JSON | | `--categories` | Categories (JSON array or comma-separated) | -| `--graph / --no-graph` | Enable or disable graph memory extraction | | `-o, --output` | Output format: `text`, `json`, `quiet` | ### `mem0 search` @@ -141,7 +140,6 @@ mem0 search "preferred tools" --user-id alice --output json --top-k 5 | `--rerank` | Enable reranking | | `--keyword` | Use keyword search instead of semantic | | `--filter` | Advanced filter expression (JSON) | -| `--graph / --no-graph` | Enable or disable graph in search | | `-o, --output` | Output format: `text`, `json`, `table` | ### `mem0 list` @@ -436,7 +434,6 @@ For non-interactive environments (CI, agent runtimes), set credentials via `mem0 | `MEM0_AGENT_ID` | Default agent ID | | `MEM0_APP_ID` | Default app ID | | `MEM0_RUN_ID` | Default run ID | -| `MEM0_ENABLE_GRAPH` | Enable graph memory (`true` / `false`) | Environment variables take precedence over values in the config file, which take precedence over defaults. diff --git a/docs/platform/faqs.mdx b/docs/platform/faqs.mdx index 5c933245d..eaee31ab3 100644 --- a/docs/platform/faqs.mdx +++ b/docs/platform/faqs.mdx @@ -9,7 +9,7 @@ iconType: "solid" Mem0 utilizes a sophisticated hybrid database system to efficiently manage and retrieve memories for AI agents and assistants. Each memory is linked to a unique identifier, such as a user ID or agent ID, enabling Mem0 to organize and access memories tailored to specific individuals or contexts. - When a message is added to Mem0 via the `add` method, the system extracts pertinent facts and preferences, distributing them across various data stores: a vector database and a graph database. This hybrid strategy ensures that diverse types of information are stored optimally, facilitating swift and effective searches. + When a message is added to Mem0 via the `add` method, the system extracts pertinent facts and preferences, distributing them in a managed vector store. This strategy ensures that diverse types of information are stored optimally, facilitating swift and effective searches. When an AI agent or LLM needs to access memories, it employs the `search` method. Mem0 conducts a comprehensive search across these data stores, retrieving relevant information from each. diff --git a/docs/platform/features/mcp-integration.mdx b/docs/platform/features/mcp-integration.mdx index bab1502d9..8334fa539 100644 --- a/docs/platform/features/mcp-integration.mdx +++ b/docs/platform/features/mcp-integration.mdx @@ -130,7 +130,6 @@ The Mem0 MCP server enables powerful memory capabilities for your AI application ## Performance tips -- Enable graph memories for relationship-aware recall - Use specific filters when searching large memory sets - Batch operations when adding multiple memories - Monitor memory usage in the Mem0 dashboard diff --git a/docs/platform/features/platform-overview.mdx b/docs/platform/features/platform-overview.mdx index 162807846..510b62808 100644 --- a/docs/platform/features/platform-overview.mdx +++ b/docs/platform/features/platform-overview.mdx @@ -1,10 +1,10 @@ --- title: Overview -description: "See how Mem0 Platform features evolve from baseline filters to graph-powered retrieval." +description: "See how Mem0 Platform features evolve from baseline filters to advanced retrieval." icon: "list" --- -Mem0 Platform features help managed deployments scale from basic filtering to graph-powered retrieval and data governance. Use this page to pick the right feature lane for your team. +Mem0 Platform features help managed deployments scale from basic filtering to advanced retrieval and data governance. Use this page to pick the right feature lane for your team. New to the platform? Start with the Platform quickstart, diff --git a/docs/platform/overview.mdx b/docs/platform/overview.mdx index 20bc7fbd0..5a15c88d1 100644 --- a/docs/platform/overview.mdx +++ b/docs/platform/overview.mdx @@ -11,7 +11,7 @@ Mem0 is the memory engine that keeps conversations contextual so users never rep ## Why it matters - **Personalized replies**: Memories persist across users and agents, cutting prompt bloat and repeat questions. -- **Hosted stack**: Mem0 runs the vector store, graph services, and rerankers—no provisioning, tuning, or maintenance. +- **Hosted stack**: Mem0 runs the vector store and rerankers—no provisioning, tuning, or maintenance. - **Enterprise controls**: Audit logs and workspace governance ship by default for production readiness. @@ -21,7 +21,7 @@ Mem0 is the memory engine that keeps conversations contextual so users never rep | --- | --- | | Fast setup | Add a few lines of code and you’re production-ready—no vector database or LLM configuration required. | | Production scale | Automatic scaling, high availability, and managed infrastructure so you focus on product work. | - | Advanced features | Graph memory, webhooks, multimodal support, and custom categories are ready to enable. | + | Advanced features | webhooks, multimodal support, and custom categories are ready to enable. | | Enterprise ready | Audit logs, workspace governance, and dedicated support keep security and governance covered. | @@ -49,7 +49,7 @@ Mem0 is the memory engine that keeps conversations contextual so users never rep Add, search, update, and delete workflows. - Graph memory, async clients, and rerankers. + async clients and rerankers. Metadata filters and per-request toggles. diff --git a/docs/platform/platform-vs-oss.mdx b/docs/platform/platform-vs-oss.mdx index 0dc73a53c..f8f395f59 100644 --- a/docs/platform/platform-vs-oss.mdx +++ b/docs/platform/platform-vs-oss.mdx @@ -57,7 +57,6 @@ Mem0 offers two powerful ways to add memory to your AI applications. Choose base | Feature | Platform | Open Source | |---------|----------|-------------| - | **Graph Memory** | ✅ (Managed) | ✅ (Self-configured) | | **Multimodal support** | ✅ | ✅ | | **Custom categories** | ✅ | Limited | | **Advanced retrieval** | ✅ | ✅ | diff --git a/docs/platform/quickstart.mdx b/docs/platform/quickstart.mdx index 67628d842..3618427b0 100644 --- a/docs/platform/quickstart.mdx +++ b/docs/platform/quickstart.mdx @@ -155,7 +155,7 @@ Learn how to search, update, and delete memories with complete CRUD operations - Explore advanced features like metadata filtering, graph memory, and webhooks + Explore advanced features like metadata filtering and webhooks diff --git a/docs/vibecoding.mdx b/docs/vibecoding.mdx index 118f587ec..1de91f645 100644 --- a/docs/vibecoding.mdx +++ b/docs/vibecoding.mdx @@ -96,7 +96,7 @@ applications that gives agents persistent context across sessions. Mem0 is a memory layer for AI apps — managed (Mem0 Platform) or self-hosted (Open Source). It stores, retrieves, and manages user memories so agents remember preferences, learn from interactions, and personalize over time. -Sub-50ms retrieval. Dual storage: vector embeddings + graph databases. +Sub-50ms retrieval. Storage: vector embeddings. **Architecture Overview:** - Memory is scoped by user_id, agent_id, or run_id diff --git a/embedchain/CITATION.cff b/embedchain/CITATION.cff deleted file mode 100644 index 8b93297cd..000000000 --- a/embedchain/CITATION.cff +++ /dev/null @@ -1,8 +0,0 @@ -cff-version: 1.2.0 -message: "If you use this software, please cite it as below." -authors: -- family-names: "Singh" - given-names: "Taranjeet" -title: "Embedchain" -date-released: 2023-06-20 -url: "https://github.com/embedchain/embedchain" \ No newline at end of file diff --git a/embedchain/CONTRIBUTING.md b/embedchain/CONTRIBUTING.md deleted file mode 100644 index a0d7c12e8..000000000 --- a/embedchain/CONTRIBUTING.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributing to embedchain - -Let us make contribution easy, collaborative and fun. - -## Submit your Contribution through PR - -To make a contribution, follow these steps: - -1. Fork and clone this repository -2. Do the changes on your fork with dedicated feature branch `feature/f1` -3. If you modified the code (new feature or bug-fix), please add tests for it -4. Include proper documentation / docstring and examples to run the feature -5. Check the linting -6. Ensure that all tests pass -7. Submit a pull request - -For more details about pull requests, please read [GitHub's guides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). - - -### 📦 Package manager - -We use `poetry` as our package manager. You can install poetry by following the instructions [here](https://python-poetry.org/docs/#installation). - -Please DO NOT use pip or conda to install the dependencies. Instead, use poetry: - -```bash -make install_all - -#activate - -poetry shell -``` - -### 📌 Pre-commit - -To ensure our standards, make sure to install pre-commit before starting to contribute. - -```bash -pre-commit install -``` - -### 🧹 Linting - -We use `ruff` to lint our code. You can run the linter by running the following command: - -```bash -make lint -``` - -Make sure that the linter does not report any errors or warnings before submitting a pull request. - -### Code Formatting with `black` - -We use `black` to reformat the code by running the following command: - -```bash -make format -``` - -### 🧪 Testing - -We use `pytest` to test our code. You can run the tests by running the following command: - -```bash -poetry run pytest -``` - - -Several packages have been removed from Poetry to make the package lighter. Therefore, it is recommended to run `make install_all` to install the remaining packages and ensure all tests pass. - - -Make sure that all tests pass before submitting a pull request. - -## 🚀 Release Process - -At the moment, the release process is manual. We try to make frequent releases. Usually, we release a new version when we have a new feature or bugfix. A developer with admin rights to the repository will create a new release on GitHub, and then publish the new version to PyPI. diff --git a/embedchain/Makefile b/embedchain/Makefile deleted file mode 100644 index f9ecc81fc..000000000 --- a/embedchain/Makefile +++ /dev/null @@ -1,56 +0,0 @@ -# Variables -PYTHON := python3 -PIP := $(PYTHON) -m pip -PROJECT_NAME := embedchain - -# Targets -.PHONY: install format lint clean test ci_lint ci_test coverage - -install: - poetry install - -# TODO: use a more efficient way to install these packages -install_all: - poetry install --all-extras - poetry run pip install ruff==0.6.9 pinecone-text pinecone-client langchain-anthropic "unstructured[local-inference, all-docs]" ollama langchain_together==0.1.3 \ - langchain_cohere==0.1.5 deepgram-sdk==3.2.7 langchain-huggingface psutil clarifai==10.0.1 flask==2.3.3 twilio==8.5.0 fastapi-poe==0.0.16 discord==2.3.2 \ - slack-sdk==3.21.3 huggingface_hub==0.23.0 gitpython==3.1.38 yt_dlp==2023.11.14 PyGithub==1.59.1 feedparser==6.0.10 newspaper3k==0.2.8 listparser==0.19 \ - modal==0.56.4329 dropbox==11.36.2 boto3==1.34.20 youtube-transcript-api==0.6.1 pytube==15.0.0 beautifulsoup4==4.12.3 - -install_es: - poetry install --extras elasticsearch - -install_opensearch: - poetry install --extras opensearch - -install_milvus: - poetry install --extras milvus - -shell: - poetry shell - -py_shell: - poetry run python - -format: - $(PYTHON) -m black . - $(PYTHON) -m isort . - -clean: - rm -rf dist build *.egg-info - -lint: - poetry run ruff . - -build: - poetry build - -publish: - poetry publish - -# for example: make test file=tests/test_factory.py -test: - poetry run pytest $(file) - -coverage: - poetry run pytest --cov=$(PROJECT_NAME) --cov-report=xml diff --git a/embedchain/README.md b/embedchain/README.md deleted file mode 100644 index 8b072ed87..000000000 --- a/embedchain/README.md +++ /dev/null @@ -1,125 +0,0 @@ -

- Embedchain Logo -

- -

- - PyPI - - - Downloads - - - Slack - - - Discord - - - Twitter - - - Open in Colab - - - codecov - -

- -
- -## What is Embedchain? - -Embedchain is an Open Source Framework for personalizing LLM responses. It makes it easy to create and deploy personalized AI apps. At its core, Embedchain follows the design principle of being *"Conventional but Configurable"* to serve both software engineers and machine learning engineers. - -Embedchain streamlines the creation of personalized LLM applications, offering a seamless process for managing various types of unstructured data. It efficiently segments data into manageable chunks, generates relevant embeddings, and stores them in a vector database for optimized retrieval. With a suite of diverse APIs, it enables users to extract contextual information, find precise answers, or engage in interactive chat conversations, all tailored to their own data. - -## 🔧 Quick install - -### Python API - -```bash -pip install embedchain -``` - -## ✨ Live demo - -Checkout the [Chat with PDF](https://embedchain.ai/demo/chat-pdf) live demo we created using Embedchain. You can find the source code [here](https://github.com/mem0ai/mem0/tree/main/embedchain/examples/chat-pdf). - -## 🔍 Usage - - -

- Embedchain Demo -

- -For example, you can create an Elon Musk bot using the following code: - -```python -import os -from embedchain import App - -# Create a bot instance -os.environ["OPENAI_API_KEY"] = "" -app = App() - -# Embed online resources -app.add("https://en.wikipedia.org/wiki/Elon_Musk") -app.add("https://www.forbes.com/profile/elon-musk") - -# Query the app -app.query("How many companies does Elon Musk run and name those?") -# Answer: Elon Musk currently runs several companies. As of my knowledge, he is the CEO and lead designer of SpaceX, the CEO and product architect of Tesla, Inc., the CEO and founder of Neuralink, and the CEO and founder of The Boring Company. However, please note that this information may change over time, so it's always good to verify the latest updates. -``` - -You can also try it in your browser with Google Colab: - -[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/17ON1LPonnXAtLaZEebnOktstB_1cJJmh?usp=sharing) - -## 📖 Documentation -Comprehensive guides and API documentation are available to help you get the most out of Embedchain: - -- [Introduction](https://docs.embedchain.ai/get-started/introduction#what-is-embedchain) -- [Getting Started](https://docs.embedchain.ai/get-started/quickstart) -- [Examples](https://docs.embedchain.ai/examples) -- [Supported data types](https://docs.embedchain.ai/components/data-sources/overview) - -## 🔗 Join the Community - -* Connect with fellow developers by joining our [Slack Community](https://embedchain.ai/slack) or [Discord Community](https://embedchain.ai/discord). - -* Dive into [GitHub Discussions](https://github.com/embedchain/embedchain/discussions), ask questions, or share your experiences. - -## 🤝 Schedule a 1-on-1 Session - -Book a [1-on-1 Session](https://cal.com/taranjeetio/ec) with the founders, to discuss any issues, provide feedback, or explore how we can improve Embedchain for you. - -## 🌐 Contributing - -Contributions are welcome! Please check out the issues on the repository, and feel free to open a pull request. -For more information, please see the [contributing guidelines](CONTRIBUTING.md). - -For more reference, please go through [Development Guide](https://docs.embedchain.ai/contribution/dev) and [Documentation Guide](https://docs.embedchain.ai/contribution/docs). - - - - - -## Anonymous Telemetry - -We collect anonymous usage metrics to enhance our package's quality and user experience. This includes data like feature usage frequency and system info, but never personal details. The data helps us prioritize improvements and ensure compatibility. If you wish to opt-out, set the environment variable `EC_TELEMETRY=false`. We prioritize data security and don't share this data externally. - -## Citation - -If you utilize this repository, please consider citing it with: - -``` -@misc{embedchain, - author = {Taranjeet Singh, Deshraj Yadav}, - title = {Embedchain: The Open Source RAG Framework}, - year = {2023}, - publisher = {GitHub}, - journal = {GitHub repository}, - howpublished = {\url{https://github.com/embedchain/embedchain}}, -} -``` diff --git a/embedchain/configs/anthropic.yaml b/embedchain/configs/anthropic.yaml deleted file mode 100644 index 395125f99..000000000 --- a/embedchain/configs/anthropic.yaml +++ /dev/null @@ -1,8 +0,0 @@ -llm: - provider: anthropic - config: - model: 'claude-instant-1' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false diff --git a/embedchain/configs/aws_bedrock.yaml b/embedchain/configs/aws_bedrock.yaml deleted file mode 100644 index 824ab0fff..000000000 --- a/embedchain/configs/aws_bedrock.yaml +++ /dev/null @@ -1,15 +0,0 @@ -llm: - provider: aws_bedrock - config: - model: amazon.titan-text-express-v1 - deployment_name: your_llm_deployment_name - temperature: 0.5 - max_tokens: 8192 - top_p: 1 - stream: false - -embedder:: - provider: aws_bedrock - config: - model: amazon.titan-embed-text-v2:0 - deployment_name: you_embedding_model_deployment_name \ No newline at end of file diff --git a/embedchain/configs/azure_openai.yaml b/embedchain/configs/azure_openai.yaml deleted file mode 100644 index 50eaff0c8..000000000 --- a/embedchain/configs/azure_openai.yaml +++ /dev/null @@ -1,19 +0,0 @@ -app: - config: - id: azure-openai-app - -llm: - provider: azure_openai - config: - model: gpt-35-turbo - deployment_name: your_llm_deployment_name - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: azure_openai - config: - model: text-embedding-ada-002 - deployment_name: you_embedding_model_deployment_name diff --git a/embedchain/configs/chroma.yaml b/embedchain/configs/chroma.yaml deleted file mode 100644 index 142eb05fc..000000000 --- a/embedchain/configs/chroma.yaml +++ /dev/null @@ -1,24 +0,0 @@ -app: - config: - id: 'my-app' - -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -vectordb: - provider: chroma - config: - collection_name: 'my-app' - dir: db - allow_reset: true - -embedder: - provider: openai - config: - model: 'text-embedding-ada-002' diff --git a/embedchain/configs/chunker.yaml b/embedchain/configs/chunker.yaml deleted file mode 100644 index 63cf3f82c..000000000 --- a/embedchain/configs/chunker.yaml +++ /dev/null @@ -1,4 +0,0 @@ -chunker: - chunk_size: 100 - chunk_overlap: 20 - length_function: 'len' diff --git a/embedchain/configs/clarifai.yaml b/embedchain/configs/clarifai.yaml deleted file mode 100644 index 0c52ba007..000000000 --- a/embedchain/configs/clarifai.yaml +++ /dev/null @@ -1,12 +0,0 @@ -llm: - provider: clarifai - config: - model: "https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct" - model_kwargs: - temperature: 0.5 - max_tokens: 1000 - -embedder: - provider: clarifai - config: - model: "https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15" diff --git a/embedchain/configs/cohere.yaml b/embedchain/configs/cohere.yaml deleted file mode 100644 index 0edd4e8fd..000000000 --- a/embedchain/configs/cohere.yaml +++ /dev/null @@ -1,7 +0,0 @@ -llm: - provider: cohere - config: - model: large - temperature: 0.5 - max_tokens: 1000 - top_p: 1 diff --git a/embedchain/configs/full-stack.yaml b/embedchain/configs/full-stack.yaml deleted file mode 100644 index 978722eac..000000000 --- a/embedchain/configs/full-stack.yaml +++ /dev/null @@ -1,40 +0,0 @@ -app: - config: - id: 'full-stack-app' - -chunker: - chunk_size: 100 - chunk_overlap: 20 - length_function: 'len' - -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - prompt: | - Use the following pieces of context to answer the query at the end. - If you don't know the answer, just say that you don't know, don't try to make up an answer. - - $context - - Query: $query - - Helpful Answer: - system_prompt: | - Act as William Shakespeare. Answer the following questions in the style of William Shakespeare. - -vectordb: - provider: chroma - config: - collection_name: 'my-collection-name' - dir: db - allow_reset: true - -embedder: - provider: openai - config: - model: 'text-embedding-ada-002' diff --git a/embedchain/configs/google.yaml b/embedchain/configs/google.yaml deleted file mode 100644 index 4f6a46553..000000000 --- a/embedchain/configs/google.yaml +++ /dev/null @@ -1,13 +0,0 @@ -llm: - provider: google - config: - model: gemini-pro - max_tokens: 1000 - temperature: 0.9 - top_p: 1.0 - stream: false - -embedder: - provider: google - config: - model: models/embedding-001 diff --git a/embedchain/configs/gpt4.yaml b/embedchain/configs/gpt4.yaml deleted file mode 100644 index e06c60de6..000000000 --- a/embedchain/configs/gpt4.yaml +++ /dev/null @@ -1,8 +0,0 @@ -llm: - provider: openai - config: - model: 'gpt-4' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false \ No newline at end of file diff --git a/embedchain/configs/gpt4all.yaml b/embedchain/configs/gpt4all.yaml deleted file mode 100644 index 048239334..000000000 --- a/embedchain/configs/gpt4all.yaml +++ /dev/null @@ -1,11 +0,0 @@ -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: gpt4all diff --git a/embedchain/configs/huggingface.yaml b/embedchain/configs/huggingface.yaml deleted file mode 100644 index 508c9d778..000000000 --- a/embedchain/configs/huggingface.yaml +++ /dev/null @@ -1,8 +0,0 @@ -llm: - provider: huggingface - config: - model: 'google/flan-t5-xxl' - temperature: 0.5 - max_tokens: 1000 - top_p: 0.5 - stream: false diff --git a/embedchain/configs/jina.yaml b/embedchain/configs/jina.yaml deleted file mode 100644 index 11627059b..000000000 --- a/embedchain/configs/jina.yaml +++ /dev/null @@ -1,7 +0,0 @@ -llm: - provider: jina - config: - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false diff --git a/embedchain/configs/llama2.yaml b/embedchain/configs/llama2.yaml deleted file mode 100644 index 61b3b9253..000000000 --- a/embedchain/configs/llama2.yaml +++ /dev/null @@ -1,8 +0,0 @@ -llm: - provider: llama2 - config: - model: 'a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5' - temperature: 0.5 - max_tokens: 1000 - top_p: 0.5 - stream: false diff --git a/embedchain/configs/ollama.yaml b/embedchain/configs/ollama.yaml deleted file mode 100644 index 7ec5def54..000000000 --- a/embedchain/configs/ollama.yaml +++ /dev/null @@ -1,14 +0,0 @@ -llm: - provider: ollama - config: - model: 'llama2' - temperature: 0.5 - top_p: 1 - stream: true - base_url: http://localhost:11434 - -embedder: - provider: ollama - config: - model: 'mxbai-embed-large:latest' - base_url: http://localhost:11434 diff --git a/embedchain/configs/opensearch.yaml b/embedchain/configs/opensearch.yaml deleted file mode 100644 index 94a27b29f..000000000 --- a/embedchain/configs/opensearch.yaml +++ /dev/null @@ -1,33 +0,0 @@ -app: - config: - id: 'my-app' - log_level: 'WARNING' - collect_metrics: true - collection_name: 'my-app' - -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -vectordb: - provider: opensearch - config: - opensearch_url: 'https://localhost:9200' - http_auth: - - admin - - admin - vector_dimension: 1536 - collection_name: 'my-app' - use_ssl: false - verify_certs: false - -embedder: - provider: openai - config: - model: 'text-embedding-ada-002' - deployment_name: 'my-app' diff --git a/embedchain/configs/opensource.yaml b/embedchain/configs/opensource.yaml deleted file mode 100644 index e2d40c135..000000000 --- a/embedchain/configs/opensource.yaml +++ /dev/null @@ -1,25 +0,0 @@ -app: - config: - id: 'open-source-app' - collect_metrics: false - -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -vectordb: - provider: chroma - config: - collection_name: 'open-source-app' - dir: db - allow_reset: true - -embedder: - provider: gpt4all - config: - deployment_name: 'test-deployment' diff --git a/embedchain/configs/pinecone.yaml b/embedchain/configs/pinecone.yaml deleted file mode 100644 index 24e33c11a..000000000 --- a/embedchain/configs/pinecone.yaml +++ /dev/null @@ -1,6 +0,0 @@ -vectordb: - provider: pinecone - config: - metric: cosine - vector_dimension: 1536 - collection_name: my-pinecone-index diff --git a/embedchain/configs/pipeline.yaml b/embedchain/configs/pipeline.yaml deleted file mode 100644 index e34866716..000000000 --- a/embedchain/configs/pipeline.yaml +++ /dev/null @@ -1,26 +0,0 @@ -pipeline: - config: - name: Example pipeline - id: pipeline-1 # Make sure that id is different every time you create a new pipeline - -vectordb: - provider: chroma - config: - collection_name: pipeline-1 - dir: db - allow_reset: true - -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedding_model: - provider: gpt4all - config: - model: 'all-MiniLM-L6-v2' - deployment_name: null diff --git a/embedchain/configs/together.yaml b/embedchain/configs/together.yaml deleted file mode 100644 index b19bc07ff..000000000 --- a/embedchain/configs/together.yaml +++ /dev/null @@ -1,6 +0,0 @@ -llm: - provider: together - config: - model: mistralai/Mixtral-8x7B-Instruct-v0.1 - temperature: 0.5 - max_tokens: 1000 diff --git a/embedchain/configs/vertexai.yaml b/embedchain/configs/vertexai.yaml deleted file mode 100644 index f303654c0..000000000 --- a/embedchain/configs/vertexai.yaml +++ /dev/null @@ -1,6 +0,0 @@ -llm: - provider: vertexai - config: - model: 'chat-bison' - temperature: 0.5 - top_p: 0.5 diff --git a/embedchain/configs/vllm.yaml b/embedchain/configs/vllm.yaml deleted file mode 100644 index 536a589a1..000000000 --- a/embedchain/configs/vllm.yaml +++ /dev/null @@ -1,14 +0,0 @@ -llm: - provider: vllm - config: - model: 'meta-llama/Llama-2-70b-hf' - temperature: 0.5 - top_p: 1 - top_k: 10 - stream: true - trust_remote_code: true - -embedder: - provider: huggingface - config: - model: 'BAAI/bge-small-en-v1.5' diff --git a/embedchain/configs/weaviate.yaml b/embedchain/configs/weaviate.yaml deleted file mode 100644 index a27623ab9..000000000 --- a/embedchain/configs/weaviate.yaml +++ /dev/null @@ -1,4 +0,0 @@ -vectordb: - provider: weaviate - config: - collection_name: my_weaviate_index diff --git a/embedchain/docs/Makefile b/embedchain/docs/Makefile deleted file mode 100644 index 0db640d0e..000000000 --- a/embedchain/docs/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -install: - npm i -g mintlify - -run_local: - mintlify dev - -troubleshoot: - mintlify install - -.PHONY: install run_local troubleshoot diff --git a/embedchain/docs/README.md b/embedchain/docs/README.md deleted file mode 100644 index e322686dc..000000000 --- a/embedchain/docs/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing to embedchain docs - - -### 👩‍💻 Development - -Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command - -``` -npm i -g mintlify -``` - -Run the following command at the root of your documentation (where mint.json is) - -``` -mintlify dev -``` - -### 😎 Publishing Changes - -Changes will be deployed to production automatically after your PR is merged to the main branch. - -#### Troubleshooting - -- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. -- Page loads as a 404 - Make sure you are running in a folder with `mint.json` diff --git a/embedchain/docs/_snippets/get-help.mdx b/embedchain/docs/_snippets/get-help.mdx deleted file mode 100644 index 6f57e5ce5..000000000 --- a/embedchain/docs/_snippets/get-help.mdx +++ /dev/null @@ -1,11 +0,0 @@ - - - Schedule a call - - - Join our slack community - - - Join our discord community - - diff --git a/embedchain/docs/_snippets/missing-data-source-tip.mdx b/embedchain/docs/_snippets/missing-data-source-tip.mdx deleted file mode 100644 index b0e189553..000000000 --- a/embedchain/docs/_snippets/missing-data-source-tip.mdx +++ /dev/null @@ -1,19 +0,0 @@ -

If you can't find the specific data source, please feel free to request through one of the following channels and help us prioritize.

- - - - Fill out this form - - - Let us know on our slack community - - - Let us know on discord community - - - Open an issue on our GitHub - - - Schedule a call with Embedchain founder - - diff --git a/embedchain/docs/_snippets/missing-llm-tip.mdx b/embedchain/docs/_snippets/missing-llm-tip.mdx deleted file mode 100644 index 7d2782d38..000000000 --- a/embedchain/docs/_snippets/missing-llm-tip.mdx +++ /dev/null @@ -1,16 +0,0 @@ -

If you can't find the specific LLM you need, no need to fret. We're continuously expanding our support for additional LLMs, and you can help us prioritize by opening an issue on our GitHub or simply reaching out to us on our Slack or Discord community.

- - - - Let us know on our slack community - - - Let us know on discord community - - - Open an issue on our GitHub - - - Schedule a call with Embedchain founder - - diff --git a/embedchain/docs/_snippets/missing-vector-db-tip.mdx b/embedchain/docs/_snippets/missing-vector-db-tip.mdx deleted file mode 100644 index 2edbbe4b0..000000000 --- a/embedchain/docs/_snippets/missing-vector-db-tip.mdx +++ /dev/null @@ -1,18 +0,0 @@ - - -

If you can't find specific feature or run into issues, please feel free to reach out through one of the following channels.

- - - - Let us know on our slack community - - - Let us know on discord community - - - Open an issue on our GitHub - - - Schedule a call with Embedchain founder - - diff --git a/embedchain/docs/api-reference/advanced/configuration.mdx b/embedchain/docs/api-reference/advanced/configuration.mdx deleted file mode 100644 index 568ea567e..000000000 --- a/embedchain/docs/api-reference/advanced/configuration.mdx +++ /dev/null @@ -1,273 +0,0 @@ ---- -title: 'Custom configurations' ---- - -Embedchain offers several configuration options for your LLM, vector database, and embedding model. All of these configuration options are optional and have sane defaults. - -You can configure different components of your app (`llm`, `embedding model`, or `vector database`) through a simple yaml configuration that Embedchain offers. Here is a generic full-stack example of the yaml config: - - - -Embedchain applications are configurable using YAML file, JSON file or by directly passing the config dictionary. Checkout the [docs here](/api-reference/app/overview#usage) on how to use other formats. - - - -```yaml config.yaml -app: - config: - name: 'full-stack-app' - -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - api_key: sk-xxx - model_kwargs: - response_format: - type: json_object - api_version: 2024-02-01 - http_client_proxies: http://testproxy.mem0.net:8000 - prompt: | - Use the following pieces of context to answer the query at the end. - If you don't know the answer, just say that you don't know, don't try to make up an answer. - - $context - - Query: $query - - Helpful Answer: - system_prompt: | - Act as William Shakespeare. Answer the following questions in the style of William Shakespeare. - -vectordb: - provider: chroma - config: - collection_name: 'full-stack-app' - dir: db - allow_reset: true - -embedder: - provider: openai - config: - model: 'text-embedding-ada-002' - api_key: sk-xxx - http_client_proxies: http://testproxy.mem0.net:8000 - -chunker: - chunk_size: 2000 - chunk_overlap: 100 - length_function: 'len' - min_chunk_size: 0 - -cache: - similarity_evaluation: - strategy: distance - max_distance: 1.0 - config: - similarity_threshold: 0.8 - auto_flush: 50 - -memory: - top_k: 10 -``` - -```json config.json -{ - "app": { - "config": { - "name": "full-stack-app" - } - }, - "llm": { - "provider": "openai", - "config": { - "model": "gpt-4o-mini", - "temperature": 0.5, - "max_tokens": 1000, - "top_p": 1, - "stream": false, - "prompt": "Use the following pieces of context to answer the query at the end.\nIf you don't know the answer, just say that you don't know, don't try to make up an answer.\n$context\n\nQuery: $query\n\nHelpful Answer:", - "system_prompt": "Act as William Shakespeare. Answer the following questions in the style of William Shakespeare.", - "api_key": "sk-xxx", - "model_kwargs": {"response_format": {"type": "json_object"}}, - "api_version": "2024-02-01", - "http_client_proxies": "http://testproxy.mem0.net:8000" - } - }, - "vectordb": { - "provider": "chroma", - "config": { - "collection_name": "full-stack-app", - "dir": "db", - "allow_reset": true - } - }, - "embedder": { - "provider": "openai", - "config": { - "model": "text-embedding-ada-002", - "api_key": "sk-xxx", - "http_client_proxies": "http://testproxy.mem0.net:8000" - } - }, - "chunker": { - "chunk_size": 2000, - "chunk_overlap": 100, - "length_function": "len", - "min_chunk_size": 0 - }, - "cache": { - "similarity_evaluation": { - "strategy": "distance", - "max_distance": 1.0 - }, - "config": { - "similarity_threshold": 0.8, - "auto_flush": 50 - } - }, - "memory": { - "top_k": 10 - } -} -``` - -```python config.py -config = { - 'app': { - 'config': { - 'name': 'full-stack-app' - } - }, - 'llm': { - 'provider': 'openai', - 'config': { - 'model': 'gpt-4o-mini', - 'temperature': 0.5, - 'max_tokens': 1000, - 'top_p': 1, - 'stream': False, - 'prompt': ( - "Use the following pieces of context to answer the query at the end.\n" - "If you don't know the answer, just say that you don't know, don't try to make up an answer.\n" - "$context\n\nQuery: $query\n\nHelpful Answer:" - ), - 'system_prompt': ( - "Act as William Shakespeare. Answer the following questions in the style of William Shakespeare." - ), - 'api_key': 'sk-xxx', - "model_kwargs": {"response_format": {"type": "json_object"}}, - "http_client_proxies": "http://testproxy.mem0.net:8000", - } - }, - 'vectordb': { - 'provider': 'chroma', - 'config': { - 'collection_name': 'full-stack-app', - 'dir': 'db', - 'allow_reset': True - } - }, - 'embedder': { - 'provider': 'openai', - 'config': { - 'model': 'text-embedding-ada-002', - 'api_key': 'sk-xxx', - "http_client_proxies": "http://testproxy.mem0.net:8000", - } - }, - 'chunker': { - 'chunk_size': 2000, - 'chunk_overlap': 100, - 'length_function': 'len', - 'min_chunk_size': 0 - }, - 'cache': { - 'similarity_evaluation': { - 'strategy': 'distance', - 'max_distance': 1.0, - }, - 'config': { - 'similarity_threshold': 0.8, - 'auto_flush': 50, - }, - }, - 'memory': { - 'top_k': 10, - }, -} -``` - - -Alright, let's dive into what each key means in the yaml config above: - -1. `app` Section: - - `config`: - - `name` (String): The name of your full-stack application. - - `id` (String): The id of your full-stack application. - Only use this to reload already created apps. We recommend users not to create their own ids. - - `collect_metrics` (Boolean): Indicates whether metrics should be collected for the app, defaults to `True` - - `log_level` (String): The log level for the app, defaults to `WARNING` -2. `llm` Section: - - `provider` (String): The provider for the language model, which is set to 'openai'. You can find the full list of llm providers in [our docs](/components/llms). - - `config`: - - `model` (String): The specific model being used, 'gpt-4o-mini'. - - `temperature` (Float): Controls the randomness of the model's output. A higher value (closer to 1) makes the output more random. - - `max_tokens` (Integer): Controls how many tokens are used in the response. - - `top_p` (Float): Controls the diversity of word selection. A higher value (closer to 1) makes word selection more diverse. - - `stream` (Boolean): Controls if the response is streamed back to the user (set to false). - - `online` (Boolean): Controls whether to use internet to get more context for answering query (set to false). - - `token_usage` (Boolean): Controls whether to use token usage for the querying models (set to false). - - `prompt` (String): A prompt for the model to follow when generating responses, requires `$context` and `$query` variables. - - `system_prompt` (String): A system prompt for the model to follow when generating responses, in this case, it's set to the style of William Shakespeare. - - `number_documents` (Integer): Number of documents to pull from the vectordb as context, defaults to 1 - - `api_key` (String): The API key for the language model. - - `model_kwargs` (Dict): Keyword arguments to pass to the language model. Used for `aws_bedrock` provider, since it requires different arguments for each model. - - `http_client_proxies` (Dict | String): The proxy server settings used to create `self.http_client` using `httpx.Client(proxies=http_client_proxies)` - - `http_async_client_proxies` (Dict | String): The proxy server settings for async calls used to create `self.http_async_client` using `httpx.AsyncClient(proxies=http_async_client_proxies)` -3. `vectordb` Section: - - `provider` (String): The provider for the vector database, set to 'chroma'. You can find the full list of vector database providers in [our docs](/components/vector-databases). - - `config`: - - `collection_name` (String): The initial collection name for the vectordb, set to 'full-stack-app'. - - `dir` (String): The directory for the local database, set to 'db'. - - `allow_reset` (Boolean): Indicates whether resetting the vectordb is allowed, set to true. - - `batch_size` (Integer): The batch size for docs insertion in vectordb, defaults to `100` - We recommend you to checkout vectordb specific config [here](https://docs.embedchain.ai/components/vector-databases) -4. `embedder` Section: - - `provider` (String): The provider for the embedder, set to 'openai'. You can find the full list of embedding model providers in [our docs](/components/embedding-models). - - `config`: - - `model` (String): The specific model used for text embedding, 'text-embedding-ada-002'. - - `vector_dimension` (Integer): The vector dimension of the embedding model. [Defaults](https://github.com/embedchain/embedchain/blob/main/embedchain/models/vector_dimensions.py) - - `api_key` (String): The API key for the embedding model. - - `endpoint` (String): The endpoint for the HuggingFace embedding model. - - `deployment_name` (String): The deployment name for the embedding model. - - `title` (String): The title for the embedding model for Google Embedder. - - `task_type` (String): The task type for the embedding model for Google Embedder. - - `model_kwargs` (Dict): Used to pass extra arguments to embedders. - - `http_client_proxies` (Dict | String): The proxy server settings used to create `self.http_client` using `httpx.Client(proxies=http_client_proxies)` - - `http_async_client_proxies` (Dict | String): The proxy server settings for async calls used to create `self.http_async_client` using `httpx.AsyncClient(proxies=http_async_client_proxies)` -5. `chunker` Section: - - `chunk_size` (Integer): The size of each chunk of text that is sent to the language model. - - `chunk_overlap` (Integer): The amount of overlap between each chunk of text. - - `length_function` (String): The function used to calculate the length of each chunk of text. In this case, it's set to 'len'. You can also use any function import directly as a string here. - - `min_chunk_size` (Integer): The minimum size of each chunk of text that is sent to the language model. Must be less than `chunk_size`, and greater than `chunk_overlap`. -6. `cache` Section: (Optional) - - `similarity_evaluation` (Optional): The config for similarity evaluation strategy. If not provided, the default `distance` based similarity evaluation strategy is used. - - `strategy` (String): The strategy to use for similarity evaluation. Currently, only `distance` and `exact` based similarity evaluation is supported. Defaults to `distance`. - - `max_distance` (Float): The bound of maximum distance. Defaults to `1.0`. - - `positive` (Boolean): If the larger distance indicates more similar of two entities, set it `True`, otherwise `False`. Defaults to `False`. - - `config` (Optional): The config for initializing the cache. If not provided, sensible default values are used as mentioned below. - - `similarity_threshold` (Float): The threshold for similarity evaluation. Defaults to `0.8`. - - `auto_flush` (Integer): The number of queries after which the cache is flushed. Defaults to `20`. -7. `memory` Section: (Optional) - - `top_k` (Integer): The number of top-k results to return. Defaults to `10`. - - If you provide a cache section, the app will automatically configure and use a cache to store the results of the language model. This is useful if you want to speed up the response time and save inference cost of your app. - -If you have questions about the configuration above, please feel free to reach out to us using one of the following methods: - - \ No newline at end of file diff --git a/embedchain/docs/api-reference/app/add.mdx b/embedchain/docs/api-reference/app/add.mdx deleted file mode 100644 index 21b24de34..000000000 --- a/embedchain/docs/api-reference/app/add.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: '📊 add' ---- - -`add()` method is used to load the data sources from different data sources to a RAG pipeline. You can find the signature below: - -### Parameters - - - The data to embed, can be a URL, local file or raw content, depending on the data type.. You can find the full list of supported data sources [here](/components/data-sources/overview). - - - Type of data source. It can be automatically detected but user can force what data type to load as. - - - Any metadata that you want to store with the data source. Metadata is generally really useful for doing metadata filtering on top of semantic search to yield faster search and better results. - - - This parameter instructs Embedchain to retrieve all the context and information from the specified link, as well as from any reference links on the page. - - -## Usage - -### Load data from webpage - -```python Code example -from embedchain import App - -app = App() -app.add("https://www.forbes.com/profile/elon-musk") -# Inserting batches in chromadb: 100%|███████████████| 1/1 [00:00<00:00, 1.19it/s] -# Successfully saved https://www.forbes.com/profile/elon-musk (DataType.WEB_PAGE). New chunks count: 4 -``` - -### Load data from sitemap - -```python Code example -from embedchain import App - -app = App() -app.add("https://python.langchain.com/sitemap.xml", data_type="sitemap") -# Loading pages: 100%|█████████████| 1108/1108 [00:47<00:00, 23.17it/s] -# Inserting batches in chromadb: 100%|█████████| 111/111 [04:41<00:00, 2.54s/it] -# Successfully saved https://python.langchain.com/sitemap.xml (DataType.SITEMAP). New chunks count: 11024 -``` - -You can find complete list of supported data sources [here](/components/data-sources/overview). diff --git a/embedchain/docs/api-reference/app/chat.mdx b/embedchain/docs/api-reference/app/chat.mdx deleted file mode 100644 index f12b09793..000000000 --- a/embedchain/docs/api-reference/app/chat.mdx +++ /dev/null @@ -1,175 +0,0 @@ ---- -title: '💬 chat' ---- - -`chat()` method allows you to chat over your data sources using a user-friendly chat API. You can find the signature below: - -### Parameters - - - Question to ask - - - Configure different llm settings such as prompt, temprature, number_documents etc. - - - The purpose is to test the prompt structure without actually running LLM inference. Defaults to `False` - - - A dictionary of key-value pairs to filter the chunks from the vector database. Defaults to `None` - - - Session ID of the chat. This can be used to maintain chat history of different user sessions. Default value: `default` - - - Return citations along with the LLM answer. Defaults to `False` - - -### Returns - - - If `citations=False`, return a stringified answer to the question asked.
- If `citations=True`, returns a tuple with answer and citations respectively. -
- -## Usage - -### With citations - -If you want to get the answer to question and return both answer and citations, use the following code snippet: - -```python With Citations -from embedchain import App - -# Initialize app -app = App() - -# Add data source -app.add("https://www.forbes.com/profile/elon-musk") - -# Get relevant answer for your query -answer, sources = app.chat("What is the net worth of Elon?", citations=True) -print(answer) -# Answer: The net worth of Elon Musk is $221.9 billion. - -print(sources) -# [ -# ( -# 'Elon Musk PROFILEElon MuskCEO, Tesla$247.1B$2.3B (0.96%)Real Time Net Worthas of 12/7/23 ...', -# { -# 'url': 'https://www.forbes.com/profile/elon-musk', -# 'score': 0.89, -# ... -# } -# ), -# ( -# '74% of the company, which is now called X.Wealth HistoryHOVER TO REVEAL NET WORTH BY YEARForbes ...', -# { -# 'url': 'https://www.forbes.com/profile/elon-musk', -# 'score': 0.81, -# ... -# } -# ), -# ( -# 'founded in 2002, is worth nearly $150 billion after a $750 million tender offer in June 2023 ...', -# { -# 'url': 'https://www.forbes.com/profile/elon-musk', -# 'score': 0.73, -# ... -# } -# ) -# ] -``` - - -When `citations=True`, note that the returned `sources` are a list of tuples where each tuple has two elements (in the following order): -1. source chunk -2. dictionary with metadata about the source chunk - - `url`: url of the source - - `doc_id`: document id (used for book keeping purposes) - - `score`: score of the source chunk with respect to the question - - other metadata you might have added at the time of adding the source - - - -### Without citations - -If you just want to return answers and don't want to return citations, you can use the following example: - -```python Without Citations -from embedchain import App - -# Initialize app -app = App() - -# Add data source -app.add("https://www.forbes.com/profile/elon-musk") - -# Chat on your data using `.chat()` -answer = app.chat("What is the net worth of Elon?") -print(answer) -# Answer: The net worth of Elon Musk is $221.9 billion. -``` - -### With session id - -If you want to maintain chat sessions for different users, you can simply pass the `session_id` keyword argument. See the example below: - -```python With session id -from embedchain import App - -app = App() -app.add("https://www.forbes.com/profile/elon-musk") - -# Chat on your data using `.chat()` -app.chat("What is the net worth of Elon Musk?", session_id="user1") -# 'The net worth of Elon Musk is $250.8 billion.' -app.chat("What is the net worth of Bill Gates?", session_id="user2") -# "I don't know the current net worth of Bill Gates." -app.chat("What was my last question", session_id="user1") -# 'Your last question was "What is the net worth of Elon Musk?"' -``` - -### With custom context window - -If you want to customize the context window that you want to use during chat (default context window is 3 document chunks), you can do using the following code snippet: - -```python with custom chunks size -from embedchain import App -from embedchain.config import BaseLlmConfig - -app = App() -app.add("https://www.forbes.com/profile/elon-musk") - -query_config = BaseLlmConfig(number_documents=5) -app.chat("What is the net worth of Elon Musk?", config=query_config) -``` - -### With Mem0 to store chat history - -Mem0 is a cutting-edge long-term memory for LLMs to enable personalization for the GenAI stack. It enables LLMs to remember past interactions and provide more personalized responses. - -In order to use Mem0 to enable memory for personalization in your apps: -- Install the [`mem0`](https://docs.mem0.ai/) package using `pip install mem0ai`. -- Prepare config for `memory`, refer [Configurations](docs/api-reference/advanced/configuration.mdx). - -```python with mem0 -from embedchain import App - -config = { - "memory": { - "top_k": 5 - } -} - -app = App.from_config(config=config) -app.add("https://www.forbes.com/profile/elon-musk") - -app.chat("What is the net worth of Elon Musk?") -``` - -## How Mem0 works: -- Mem0 saves context derived from each user question into its memory. -- When a user poses a new question, Mem0 retrieves relevant previous memories. -- The `top_k` parameter in the memory configuration specifies the number of top memories to consider during retrieval. -- Mem0 generates the final response by integrating the user's question, context from the data source, and the relevant memories. diff --git a/embedchain/docs/api-reference/app/delete.mdx b/embedchain/docs/api-reference/app/delete.mdx deleted file mode 100644 index d1f2ceda4..000000000 --- a/embedchain/docs/api-reference/app/delete.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: 🗑 delete ---- - -## Delete Document - -`delete()` method allows you to delete a document previously added to the app. - -### Usage - -```python -from embedchain import App - -app = App() - -forbes_doc_id = app.add("https://www.forbes.com/profile/elon-musk") -wiki_doc_id = app.add("https://en.wikipedia.org/wiki/Elon_Musk") - -app.delete(forbes_doc_id) # deletes the forbes document -``` - - - If you do not have the document id, you can use `app.db.get()` method to get the document and extract the `hash` key from `metadatas` dictionary object, which serves as the document id. - - - -## Delete Chat Session History - -`delete_session_chat_history()` method allows you to delete all previous messages in a chat history. - -### Usage - -```python -from embedchain import App - -app = App() - -app.add("https://www.forbes.com/profile/elon-musk") - -app.chat("What is the net worth of Elon Musk?") - -app.delete_session_chat_history() -``` - - - `delete_session_chat_history(session_id="session_1")` method also accepts `session_id` optional param for deleting chat history of a specific session. - It assumes the default session if no `session_id` is provided. - \ No newline at end of file diff --git a/embedchain/docs/api-reference/app/deploy.mdx b/embedchain/docs/api-reference/app/deploy.mdx deleted file mode 100644 index 7cb8ff5e8..000000000 --- a/embedchain/docs/api-reference/app/deploy.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 🚀 deploy ---- - -The `deploy()` method is currently available on an invitation-only basis. To request access, please submit your information via the provided [Google Form](https://forms.gle/vigN11h7b4Ywat668). We will review your request and respond promptly. diff --git a/embedchain/docs/api-reference/app/evaluate.mdx b/embedchain/docs/api-reference/app/evaluate.mdx deleted file mode 100644 index 64cb612ca..000000000 --- a/embedchain/docs/api-reference/app/evaluate.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: '📝 evaluate' ---- - -`evaluate()` method is used to evaluate the performance of a RAG app. You can find the signature below: - -### Parameters - - - A question or a list of questions to evaluate your app on. - - - The metrics to evaluate your app on. Defaults to all metrics: `["context_relevancy", "answer_relevancy", "groundedness"]` - - - Specify the number of threads to use for parallel processing. - - -### Returns - - - Returns the metrics you have chosen to evaluate your app on as a dictionary. - - -## Usage - -```python -from embedchain import App - -app = App() - -# add data source -app.add("https://www.forbes.com/profile/elon-musk") - -# run evaluation -app.evaluate("what is the net worth of Elon Musk?") -# {'answer_relevancy': 0.958019958036268, 'context_relevancy': 0.12903225806451613} - -# or -# app.evaluate(["what is the net worth of Elon Musk?", "which companies does Elon Musk own?"]) -``` diff --git a/embedchain/docs/api-reference/app/get.mdx b/embedchain/docs/api-reference/app/get.mdx deleted file mode 100644 index 252c78508..000000000 --- a/embedchain/docs/api-reference/app/get.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: 📄 get ---- - -## Get data sources - -`get_data_sources()` returns a list of all the data sources added in the app. - - -### Usage - -```python -from embedchain import App - -app = App() - -app.add("https://www.forbes.com/profile/elon-musk") -app.add("https://en.wikipedia.org/wiki/Elon_Musk") - -data_sources = app.get_data_sources() -# [ -# { -# 'data_type': 'web_page', -# 'data_value': 'https://en.wikipedia.org/wiki/Elon_Musk', -# 'metadata': 'null' -# }, -# { -# 'data_type': 'web_page', -# 'data_value': 'https://www.forbes.com/profile/elon-musk', -# 'metadata': 'null' -# } -# ] -``` \ No newline at end of file diff --git a/embedchain/docs/api-reference/app/overview.mdx b/embedchain/docs/api-reference/app/overview.mdx deleted file mode 100644 index 8c369cbf8..000000000 --- a/embedchain/docs/api-reference/app/overview.mdx +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "App" ---- - -Create a RAG app object on Embedchain. This is the main entrypoint for a developer to interact with Embedchain APIs. An app configures the llm, vector database, embedding model, and retrieval strategy of your choice. - -### Attributes - - - App ID - - - Name of the app - - - Configuration of the app - - - Configured LLM for the RAG app - - - Configured vector database for the RAG app - - - Configured embedding model for the RAG app - - - Chunker configuration - - - Client object (used to deploy an app to Embedchain platform) - - - Logger object - - -## Usage - -You can create an app instance using the following methods: - -### Default setting - -```python Code Example -from embedchain import App -app = App() -``` - - -### Python Dict - -```python Code Example -from embedchain import App - -config_dict = { - 'llm': { - 'provider': 'gpt4all', - 'config': { - 'model': 'orca-mini-3b-gguf2-q4_0.gguf', - 'temperature': 0.5, - 'max_tokens': 1000, - 'top_p': 1, - 'stream': False - } - }, - 'embedder': { - 'provider': 'gpt4all' - } -} - -# load llm configuration from config dict -app = App.from_config(config=config_dict) -``` - -### YAML Config - - - -```python main.py -from embedchain import App - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: gpt4all -``` - - - -### JSON Config - - - -```python main.py -from embedchain import App - -# load llm configuration from config.json file -app = App.from_config(config_path="config.json") -``` - -```json config.json -{ - "llm": { - "provider": "gpt4all", - "config": { - "model": "orca-mini-3b-gguf2-q4_0.gguf", - "temperature": 0.5, - "max_tokens": 1000, - "top_p": 1, - "stream": false - } - }, - "embedder": { - "provider": "gpt4all" - } -} -``` - - diff --git a/embedchain/docs/api-reference/app/query.mdx b/embedchain/docs/api-reference/app/query.mdx deleted file mode 100644 index f1d94aa8f..000000000 --- a/embedchain/docs/api-reference/app/query.mdx +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: '❓ query' ---- - -`.query()` method empowers developers to ask questions and receive relevant answers through a user-friendly query API. Function signature is given below: - -### Parameters - - - Question to ask - - - Configure different llm settings such as prompt, temprature, number_documents etc. - - - The purpose is to test the prompt structure without actually running LLM inference. Defaults to `False` - - - A dictionary of key-value pairs to filter the chunks from the vector database. Defaults to `None` - - - Return citations along with the LLM answer. Defaults to `False` - - -### Returns - - - If `citations=False`, return a stringified answer to the question asked.
- If `citations=True`, returns a tuple with answer and citations respectively. -
- -## Usage - -### With citations - -If you want to get the answer to question and return both answer and citations, use the following code snippet: - -```python With Citations -from embedchain import App - -# Initialize app -app = App() - -# Add data source -app.add("https://www.forbes.com/profile/elon-musk") - -# Get relevant answer for your query -answer, sources = app.query("What is the net worth of Elon?", citations=True) -print(answer) -# Answer: The net worth of Elon Musk is $221.9 billion. - -print(sources) -# [ -# ( -# 'Elon Musk PROFILEElon MuskCEO, Tesla$247.1B$2.3B (0.96%)Real Time Net Worthas of 12/7/23 ...', -# { -# 'url': 'https://www.forbes.com/profile/elon-musk', -# 'score': 0.89, -# ... -# } -# ), -# ( -# '74% of the company, which is now called X.Wealth HistoryHOVER TO REVEAL NET WORTH BY YEARForbes ...', -# { -# 'url': 'https://www.forbes.com/profile/elon-musk', -# 'score': 0.81, -# ... -# } -# ), -# ( -# 'founded in 2002, is worth nearly $150 billion after a $750 million tender offer in June 2023 ...', -# { -# 'url': 'https://www.forbes.com/profile/elon-musk', -# 'score': 0.73, -# ... -# } -# ) -# ] -``` - - -When `citations=True`, note that the returned `sources` are a list of tuples where each tuple has two elements (in the following order): -1. source chunk -2. dictionary with metadata about the source chunk - - `url`: url of the source - - `doc_id`: document id (used for book keeping purposes) - - `score`: score of the source chunk with respect to the question - - other metadata you might have added at the time of adding the source - - -### Without citations - -If you just want to return answers and don't want to return citations, you can use the following example: - -```python Without Citations -from embedchain import App - -# Initialize app -app = App() - -# Add data source -app.add("https://www.forbes.com/profile/elon-musk") - -# Get relevant answer for your query -answer = app.query("What is the net worth of Elon?") -print(answer) -# Answer: The net worth of Elon Musk is $221.9 billion. -``` - diff --git a/embedchain/docs/api-reference/app/reset.mdx b/embedchain/docs/api-reference/app/reset.mdx deleted file mode 100644 index 07e136d86..000000000 --- a/embedchain/docs/api-reference/app/reset.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: 🔄 reset ---- - -`reset()` method allows you to wipe the data from your RAG application and start from scratch. - -## Usage - -```python -from embedchain import App - -app = App() -app.add("https://www.forbes.com/profile/elon-musk") - -# Reset the app -app.reset() -``` \ No newline at end of file diff --git a/embedchain/docs/api-reference/app/search.mdx b/embedchain/docs/api-reference/app/search.mdx deleted file mode 100644 index db4eee1b2..000000000 --- a/embedchain/docs/api-reference/app/search.mdx +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: '🔍 search' ---- - -`.search()` enables you to uncover the most pertinent context by performing a semantic search across your data sources based on a given query. Refer to the function signature below: - -### Parameters - - - Question - - - Number of relevant documents to fetch. Defaults to `3` - - - Key value pair for metadata filtering. - - - Pass raw filter query based on your vector database. - Currently, `raw_filter` param is only supported for Pinecone vector database. - - -### Returns - - - Return list of dictionaries that contain the relevant chunk and their source information. - - -## Usage - -### Basic - -Refer to the following example on how to use the search api: - -```python Code example -from embedchain import App - -app = App() -app.add("https://www.forbes.com/profile/elon-musk") - -context = app.search("What is the net worth of Elon?", num_documents=2) -print(context) -``` - -### Advanced - -#### Metadata filtering using `where` params - -Here is an advanced example of `search()` API with metadata filtering on pinecone database: - -```python -import os - -from embedchain import App - -os.environ["PINECONE_API_KEY"] = "xxx" - -config = { - "vectordb": { - "provider": "pinecone", - "config": { - "metric": "dotproduct", - "vector_dimension": 1536, - "index_name": "ec-test", - "serverless_config": {"cloud": "aws", "region": "us-west-2"}, - }, - } -} - -app = App.from_config(config=config) - -app.add("https://www.forbes.com/profile/bill-gates", metadata={"type": "forbes", "person": "gates"}) -app.add("https://en.wikipedia.org/wiki/Bill_Gates", metadata={"type": "wiki", "person": "gates"}) - -results = app.search("What is the net worth of Bill Gates?", where={"person": "gates"}) -print("Num of search results: ", len(results)) -``` - -#### Metadata filtering using `raw_filter` params - -Following is an example of metadata filtering by passing the raw filter query that pinecone vector database follows: - -```python -import os - -from embedchain import App - -os.environ["PINECONE_API_KEY"] = "xxx" - -config = { - "vectordb": { - "provider": "pinecone", - "config": { - "metric": "dotproduct", - "vector_dimension": 1536, - "index_name": "ec-test", - "serverless_config": {"cloud": "aws", "region": "us-west-2"}, - }, - } -} - -app = App.from_config(config=config) - -app.add("https://www.forbes.com/profile/bill-gates", metadata={"year": 2022, "person": "gates"}) -app.add("https://en.wikipedia.org/wiki/Bill_Gates", metadata={"year": 2024, "person": "gates"}) - -print("Filter with person: gates and year > 2023") -raw_filter = {"$and": [{"person": "gates"}, {"year": {"$gt": 2023}}]} -results = app.search("What is the net worth of Bill Gates?", raw_filter=raw_filter) -print("Num of search results: ", len(results)) -``` diff --git a/embedchain/docs/api-reference/overview.mdx b/embedchain/docs/api-reference/overview.mdx deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/docs/api-reference/store/ai-assistants.mdx b/embedchain/docs/api-reference/store/ai-assistants.mdx deleted file mode 100644 index 09c6122a4..000000000 --- a/embedchain/docs/api-reference/store/ai-assistants.mdx +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: 'AI Assistant' ---- - -The `AIAssistant` class, an alternative to the OpenAI Assistant API, is designed for those who prefer using large language models (LLMs) other than those provided by OpenAI. It facilitates the creation of AI Assistants with several key benefits: - -- **Visibility into Citations**: It offers transparent access to the sources and citations used by the AI, enhancing the understanding and trustworthiness of its responses. - -- **Debugging Capabilities**: Users have the ability to delve into and debug the AI's processes, allowing for a deeper understanding and fine-tuning of its performance. - -- **Customizable Prompts**: The class provides the flexibility to modify and tailor prompts according to specific needs, enabling more precise and relevant interactions. - -- **Chain of Thought Integration**: It supports the incorporation of a 'chain of thought' approach, which helps in breaking down complex queries into simpler, sequential steps, thereby improving the clarity and accuracy of responses. - -It is ideal for those who value customization, transparency, and detailed control over their AI Assistant's functionalities. - -### Arguments - - - Name for your AI assistant - - - - How the Assistant and model should behave or respond - - - - Load existing AI Assistant. If you pass this, you don't have to pass other arguments. - - - - Existing thread id if exists - - - - Embedchain pipeline config yaml path to use. This will define the configuration of the AI Assistant (such as configuring the LLM, vector database, and embedding model) - - - - Add data sources to your assistant. You can add in the following format: `[{"source": "https://example.com", "data_type": "web_page"}]` - - - - Anonymous telemetry (doesn't collect any user information or user's files). Used to improve the Embedchain package utilization. Default is `True`. - - - -## Usage - -For detailed guidance on creating your own AI Assistant, click the link below. It provides step-by-step instructions to help you through the process: - - - Learn how to build a customized AI Assistant using the `AIAssistant` class. - diff --git a/embedchain/docs/api-reference/store/openai-assistant.mdx b/embedchain/docs/api-reference/store/openai-assistant.mdx deleted file mode 100644 index 1ab21aa1f..000000000 --- a/embedchain/docs/api-reference/store/openai-assistant.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: 'OpenAI Assistant' ---- - -### Arguments - - - Name for your AI assistant - - - - how the Assistant and model should behave or respond - - - - Load existing OpenAI Assistant. If you pass this, you don't have to pass other arguments. - - - - Existing OpenAI thread id if exists - - - - OpenAI model to use - - - - OpenAI tools to use. Default set to `[{"type": "retrieval"}]` - - - - Add data sources to your assistant. You can add in the following format: `[{"source": "https://example.com", "data_type": "web_page"}]` - - - - Anonymous telemetry (doesn't collect any user information or user's files). Used to improve the Embedchain package utilization. Default is `True`. - - -## Usage - -For detailed guidance on creating your own OpenAI Assistant, click the link below. It provides step-by-step instructions to help you through the process: - - - Learn how to build an OpenAI Assistant using the `OpenAIAssistant` class. - diff --git a/embedchain/docs/community/connect-with-us.mdx b/embedchain/docs/community/connect-with-us.mdx deleted file mode 100644 index e08dfd1c7..000000000 --- a/embedchain/docs/community/connect-with-us.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: 🤝 Connect with Us ---- - -We believe in building a vibrant and supportive community around embedchain. There are various channels through which you can connect with us, stay updated, and contribute to the ongoing discussions: - - - - Follow us on Twitter - - - Join our slack community - - - Join our discord community - - - Connect with us on LinkedIn - - - Schedule a call with Embedchain founder - - - Subscribe to our newsletter - - - -We look forward to connecting with you and seeing how we can create amazing things together! diff --git a/embedchain/docs/components/data-sources/audio.mdx b/embedchain/docs/components/data-sources/audio.mdx deleted file mode 100644 index 5f2772a71..000000000 --- a/embedchain/docs/components/data-sources/audio.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "🎤 Audio" ---- - - -To use an audio as data source, just add `data_type` as `audio` and pass in the path of the audio (local or hosted). - -We use [Deepgram](https://developers.deepgram.com/docs/introduction) to transcribe the audiot to text, and then use the generated text as the data source. - -You would require an Deepgram API key which is available [here](https://console.deepgram.com/signup?jump=keys) to use this feature. - -### Without customization - -```python -import os -from embedchain import App - -os.environ["DEEPGRAM_API_KEY"] = "153xxx" - -app = App() -app.add("introduction.wav", data_type="audio") -response = app.query("What is my name and how old am I?") -print(response) -# Answer: Your name is Dave and you are 21 years old. -``` diff --git a/embedchain/docs/components/data-sources/beehiiv.mdx b/embedchain/docs/components/data-sources/beehiiv.mdx deleted file mode 100644 index 5a94cf1fe..000000000 --- a/embedchain/docs/components/data-sources/beehiiv.mdx +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "🐝 Beehiiv" ---- - -To add any Beehiiv data sources to your app, just add the base url as the source and set the data_type to `beehiiv`. - -```python -from embedchain import App - -app = App() - -# source: just add the base url and set the data_type to 'beehiiv' -app.add('https://aibreakfast.beehiiv.com', data_type='beehiiv') -app.query("How much is OpenAI paying developers?") -# Answer: OpenAI is aggressively recruiting Google's top AI researchers with offers ranging between $5 to $10 million annually, primarily in stock options. -``` diff --git a/embedchain/docs/components/data-sources/csv.mdx b/embedchain/docs/components/data-sources/csv.mdx deleted file mode 100644 index 07663a3b1..000000000 --- a/embedchain/docs/components/data-sources/csv.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: '📊 CSV' ---- - -You can load any csv file from your local file system or through a URL. Headers are included for each line, so if you have an `age` column, `18` will be added as `age: 18`. - -## Usage - -### Load from a local file - -```python -from embedchain import App -app = App() -app.add('/path/to/file.csv', data_type='csv') -``` - -### Load from URL - -```python -from embedchain import App -app = App() -app.add('https://people.sc.fsu.edu/~jburkardt/data/csv/airtravel.csv', data_type="csv") -``` - - -There is a size limit allowed for csv file beyond which it can throw error. This limit is set by the LLMs. Please consider chunking large csv files into smaller csv files. - - diff --git a/embedchain/docs/components/data-sources/custom.mdx b/embedchain/docs/components/data-sources/custom.mdx deleted file mode 100644 index 40a8c75e1..000000000 --- a/embedchain/docs/components/data-sources/custom.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: '⚙️ Custom' ---- - -When we say "custom", we mean that you can customize the loader and chunker to your needs. This is done by passing a custom loader and chunker to the `add` method. - -```python -from embedchain import App -import your_loader -from my_module import CustomLoader -from my_module import CustomChunker - -app = App() -loader = CustomLoader() -chunker = CustomChunker() - -app.add("source", data_type="custom", loader=loader, chunker=chunker) -``` - - - The custom loader and chunker must be a class that inherits from the [`BaseLoader`](https://github.com/embedchain/embedchain/blob/main/embedchain/loaders/base_loader.py) and [`BaseChunker`](https://github.com/embedchain/embedchain/blob/main/embedchain/chunkers/base_chunker.py) classes respectively. - - - - If the `data_type` is not a valid data type, the `add` method will fallback to the `custom` data type and expect a custom loader and chunker to be passed by the user. - - -Example: - -```python -from embedchain import App -from embedchain.loaders.github import GithubLoader - -app = App() - -loader = GithubLoader(config={"token": "ghp_xxx"}) - -app.add("repo:embedchain/embedchain type:repo", data_type="github", loader=loader) - -app.query("What is Embedchain?") -# Answer: Embedchain is a Data Platform for Large Language Models (LLMs). It allows users to seamlessly load, index, retrieve, and sync unstructured data in order to build dynamic, LLM-powered applications. There is also a JavaScript implementation called embedchain-js available on GitHub. -``` diff --git a/embedchain/docs/components/data-sources/data-type-handling.mdx b/embedchain/docs/components/data-sources/data-type-handling.mdx deleted file mode 100644 index d939537af..000000000 --- a/embedchain/docs/components/data-sources/data-type-handling.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: 'Data type handling' ---- - -## Automatic data type detection - -The add method automatically tries to detect the data_type, based on your input for the source argument. So `app.add('https://www.youtube.com/watch?v=dQw4w9WgXcQ')` is enough to embed a YouTube video. - -This detection is implemented for all formats. It is based on factors such as whether it's a URL, a local file, the source data type, etc. - -### Debugging automatic detection - -Set `log_level: DEBUG` in the config yaml to debug if the data type detection is done right or not. Otherwise, you will not know when, for instance, an invalid filepath is interpreted as raw text instead. - -### Forcing a data type - -To omit any issues with the data type detection, you can **force** a data_type by adding it as a `add` method argument. -The examples below show you the keyword to force the respective `data_type`. - -Forcing can also be used for edge cases, such as interpreting a sitemap as a web_page, for reading its raw text instead of following links. - -## Remote data types - - -**Use local files in remote data types** - -Some data_types are meant for remote content and only work with URLs. -You can pass local files by formatting the path using the `file:` [URI scheme](https://en.wikipedia.org/wiki/File_URI_scheme), e.g. `file:///info.pdf`. - - -## Reusing a vector database - -Default behavior is to create a persistent vector db in the directory **./db**. You can split your application into two Python scripts: one to create a local vector db and the other to reuse this local persistent vector db. This is useful when you want to index hundreds of documents and separately implement a chat interface. - -Create a local index: - -```python -from embedchain import App - -config = { - "app": { - "config": { - "id": "app-1" - } - } -} -naval_chat_bot = App.from_config(config=config) -naval_chat_bot.add("https://www.youtube.com/watch?v=3qHkcs3kG44") -naval_chat_bot.add("https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf") -``` - -You can reuse the local index with the same code, but without adding new documents: - -```python -from embedchain import App - -config = { - "app": { - "config": { - "id": "app-1" - } - } -} -naval_chat_bot = App.from_config(config=config) -print(naval_chat_bot.query("What unique capacity does Naval argue humans possess when it comes to understanding explanations or concepts?")) -``` - -## Resetting an app and vector database - -You can reset the app by simply calling the `reset` method. This will delete the vector database and all other app related files. - -```python -from embedchain import App - -app = App()config = { - "app": { - "config": { - "id": "app-1" - } - } -} -naval_chat_bot = App.from_config(config=config) -app.add("https://www.youtube.com/watch?v=3qHkcs3kG44") -app.reset() -``` diff --git a/embedchain/docs/components/data-sources/directory.mdx b/embedchain/docs/components/data-sources/directory.mdx deleted file mode 100644 index 33c1e9b73..000000000 --- a/embedchain/docs/components/data-sources/directory.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: '📁 Directory/Folder' ---- - -To use an entire directory as data source, just add `data_type` as `directory` and pass in the path of the local directory. - -### Without customization - -```python -import os -from embedchain import App - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -app = App() -app.add("./elon-musk", data_type="directory") -response = app.query("list all files") -print(response) -# Answer: Files are elon-musk-1.txt, elon-musk-2.pdf. -``` - -### Customization - -```python -import os -from embedchain import App -from embedchain.loaders.directory_loader import DirectoryLoader - -os.environ["OPENAI_API_KEY"] = "sk-xxx" -lconfig = { - "recursive": True, - "extensions": [".txt"] -} -loader = DirectoryLoader(config=lconfig) -app = App() -app.add("./elon-musk", loader=loader) -response = app.query("what are all the files related to?") -print(response) - -# Answer: The files are related to Elon Musk. -``` diff --git a/embedchain/docs/components/data-sources/discord.mdx b/embedchain/docs/components/data-sources/discord.mdx deleted file mode 100644 index 2c8780210..000000000 --- a/embedchain/docs/components/data-sources/discord.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: "💬 Discord" ---- - -To add any Discord channel messages to your app, just add the `channel_id` as the source and set the `data_type` to `discord`. - - - This loader requires a Discord bot token with read messages access. - To obtain the token, follow the instructions provided in this tutorial: - How to Get a Discord Bot Token?. - - -```python -import os -from embedchain import App - -# add your discord "BOT" token -os.environ["DISCORD_TOKEN"] = "xxx" - -app = App() - -app.add("1177296711023075338", data_type="discord") - -response = app.query("What is Joe saying about Elon Musk?") - -print(response) -# Answer: Joe is saying "Elon Musk is a genius". -``` diff --git a/embedchain/docs/components/data-sources/discourse.mdx b/embedchain/docs/components/data-sources/discourse.mdx deleted file mode 100644 index 4ba0a36ce..000000000 --- a/embedchain/docs/components/data-sources/discourse.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: '🗨️ Discourse' ---- - -You can now easily load data from your community built with [Discourse](https://discourse.org/). - -## Example - -1. Setup the Discourse Loader with your community url. -```Python -from embedchain.loaders.discourse import DiscourseLoader - -dicourse_loader = DiscourseLoader(config={"domain": "https://community.openai.com"}) -``` - -2. Once you setup the loader, you can create an app and load data using the above discourse loader -```Python -import os -from embedchain.pipeline import Pipeline as App - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -app = App() - -app.add("openai after:2023-10-1", data_type="discourse", loader=dicourse_loader) - -question = "Where can I find the OpenAI API status page?" -app.query(question) -# Answer: You can find the OpenAI API status page at https:/status.openai.com/. -``` - -NOTE: The `add` function of the app will accept any executable search query to load data. Refer [Discourse API Docs](https://docs.discourse.org/#tag/Search) to learn more about search queries. - -3. We automatically create a chunker to chunk your discourse data, however if you wish to provide your own chunker class. Here is how you can do that: -```Python - -from embedchain.chunkers.discourse import DiscourseChunker -from embedchain.config.add_config import ChunkerConfig - -discourse_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) -discourse_chunker = DiscourseChunker(config=discourse_chunker_config) - -app.add("openai", data_type='discourse', loader=dicourse_loader, chunker=discourse_chunker) -``` \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/docs-site.mdx b/embedchain/docs/components/data-sources/docs-site.mdx deleted file mode 100644 index 342bbdc85..000000000 --- a/embedchain/docs/components/data-sources/docs-site.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: '📚 Code Docs website' ---- - -To add any code documentation website as a loader, use the data_type as `docs_site`. Eg: - -```python -from embedchain import App - -app = App() -app.add("https://docs.embedchain.ai/", data_type="docs_site") -app.query("What is Embedchain?") -# Answer: Embedchain is a platform that utilizes various components, including paid/proprietary ones, to provide what is believed to be the best configuration available. It uses LLM (Language Model) providers such as OpenAI, Anthpropic, Vertex_AI, GPT4ALL, Azure_OpenAI, LLAMA2, JINA, Ollama, Together and COHERE. Embedchain allows users to import and utilize these LLM providers for their applications.' -``` diff --git a/embedchain/docs/components/data-sources/docx.mdx b/embedchain/docs/components/data-sources/docx.mdx deleted file mode 100644 index cc459621f..000000000 --- a/embedchain/docs/components/data-sources/docx.mdx +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: '📄 Docx file' ---- - -### Docx file - -To add any doc/docx file, use the data_type as `docx`. `docx` allows remote urls and conventional file paths. Eg: - -```python -from embedchain import App - -app = App() -app.add('https://example.com/content/intro.docx', data_type="docx") -# Or add file using the local file path on your system -# app.add('content/intro.docx', data_type="docx") - -app.query("Summarize the docx data?") -``` diff --git a/embedchain/docs/components/data-sources/dropbox.mdx b/embedchain/docs/components/data-sources/dropbox.mdx deleted file mode 100644 index bb2800bf8..000000000 --- a/embedchain/docs/components/data-sources/dropbox.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: '💾 Dropbox' ---- - -To load folders or files from your Dropbox account, configure the `data_type` parameter as `dropbox` and specify the path to the desired file or folder, starting from the root directory of your Dropbox account. - -For Dropbox access, an **access token** is required. Obtain this token by visiting [Dropbox Developer Apps](https://www.dropbox.com/developers/apps). There, create a new app and generate an access token for it. - -Ensure your app has the following settings activated: - -- In the Permissions section, enable `files.content.read` and `files.metadata.read`. - -## Usage - -Install the `dropbox` pypi package: - -```bash -pip install dropbox -``` - -Following is an example of how to use the dropbox loader: - -```python -import os -from embedchain import App - -os.environ["DROPBOX_ACCESS_TOKEN"] = "sl.xxx" -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -app = App() - -# any path from the root of your dropbox account, you can leave it "" for the root folder -app.add("/test", data_type="dropbox") - -print(app.query("Which two celebrities are mentioned here?")) -# The two celebrities mentioned in the given context are Elon Musk and Jeff Bezos. -``` diff --git a/embedchain/docs/components/data-sources/excel-file.mdx b/embedchain/docs/components/data-sources/excel-file.mdx deleted file mode 100644 index af8a2cd62..000000000 --- a/embedchain/docs/components/data-sources/excel-file.mdx +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: '📄 Excel file' ---- - -### Excel file - -To add any xlsx/xls file, use the data_type as `excel_file`. `excel_file` allows remote urls and conventional file paths. Eg: - -```python -from embedchain import App - -app = App() -app.add('https://example.com/content/intro.xlsx', data_type="excel_file") -# Or add file using the local file path on your system -# app.add('content/intro.xls', data_type="excel_file") - -app.query("Give brief information about data.") -``` diff --git a/embedchain/docs/components/data-sources/github.mdx b/embedchain/docs/components/data-sources/github.mdx deleted file mode 100644 index 14791aca4..000000000 --- a/embedchain/docs/components/data-sources/github.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: 📝 Github ---- - -1. Setup the Github loader by configuring the Github account with username and personal access token (PAT). Check out [this](https://docs.github.com/en/enterprise-server@3.6/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token) link to learn how to create a PAT. -```Python -from embedchain.loaders.github import GithubLoader - -loader = GithubLoader( - config={ - "token":"ghp_xxxx" - } - ) -``` - -2. Once you setup the loader, you can create an app and load data using the above Github loader -```Python -import os -from embedchain.pipeline import Pipeline as App - -os.environ["OPENAI_API_KEY"] = "sk-xxxx" - -app = App() - -app.add("repo:embedchain/embedchain type:repo", data_type="github", loader=loader) - -response = app.query("What is Embedchain?") -# Answer: Embedchain is a Data Platform for Large Language Models (LLMs). It allows users to seamlessly load, index, retrieve, and sync unstructured data in order to build dynamic, LLM-powered applications. There is also a JavaScript implementation called embedchain-js available on GitHub. -``` -The `add` function of the app will accept any valid github query with qualifiers. It only supports loading github code, repository, issues and pull-requests. - -You must provide qualifiers `type:` and `repo:` in the query. The `type:` qualifier can be a combination of `code`, `repo`, `pr`, `issue`, `branch`, `file`. The `repo:` qualifier must be a valid github repository name. - - - - - `repo:embedchain/embedchain type:repo` - to load the repository - - `repo:embedchain/embedchain type:branch name:feature_test` - to load the branch of the repository - - `repo:embedchain/embedchain type:file path:README.md` - to load the specific file of the repository - - `repo:embedchain/embedchain type:issue,pr` - to load the issues and pull-requests of the repository - - `repo:embedchain/embedchain type:issue state:closed` - to load the closed issues of the repository - - -3. We automatically create a chunker to chunk your GitHub data, however if you wish to provide your own chunker class. Here is how you can do that: -```Python -from embedchain.chunkers.common_chunker import CommonChunker -from embedchain.config.add_config import ChunkerConfig - -github_chunker_config = ChunkerConfig(chunk_size=2000, chunk_overlap=0, length_function=len) -github_chunker = CommonChunker(config=github_chunker_config) - -app.add(load_query, data_type="github", loader=loader, chunker=github_chunker) -``` diff --git a/embedchain/docs/components/data-sources/gmail.mdx b/embedchain/docs/components/data-sources/gmail.mdx deleted file mode 100644 index aaaf002ed..000000000 --- a/embedchain/docs/components/data-sources/gmail.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: '📬 Gmail' ---- - -To use GmailLoader you must install the extra dependencies with `pip install --upgrade embedchain[gmail]`. - -The `source` must be a valid Gmail search query, you can refer `https://support.google.com/mail/answer/7190?hl=en` to build a query. - -To load Gmail messages, you MUST use the data_type as `gmail`. Otherwise the source will be detected as simple `text`. - -To use this you need to save `credentials.json` in the directory from where you will run the loader. Follow these steps to get the credentials - -1. Go to the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). -2. Create a project if you don't have one already. -3. Create an `OAuth Consent Screen` in the project. You may need to select the `external` option. -4. Make sure the consent screen is published. -5. Enable the [Gmail API](https://console.cloud.google.com/apis/api/gmail.googleapis.com) -6. Create credentials from the `Credentials` tab. -7. Select the type `OAuth Client ID`. -8. Choose the application type `Web application`. As a name you can choose `embedchain` or any other name as per your use case. -9. Add an authorized redirect URI for `http://localhost:8080/`. -10. You can leave everything else at default, finish the creation. -11. When you are done, a modal opens where you can download the details in `json` format. -12. Put the `.json` file in your current directory and rename it to `credentials.json` - -```python -from embedchain import App - -app = App() - -gmail_filter = "to: me label:inbox" -app.add(gmail_filter, data_type="gmail") -app.query("Summarize my email conversations") -``` \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/google-drive.mdx b/embedchain/docs/components/data-sources/google-drive.mdx deleted file mode 100644 index 5dcf4e45f..000000000 --- a/embedchain/docs/components/data-sources/google-drive.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: 'Google Drive' ---- - -To use GoogleDriveLoader you must install the extra dependencies with `pip install --upgrade embedchain[googledrive]`. - -The data_type must be `google_drive`. Otherwise, it will be considered a regular web page. - -Google Drive requires the setup of credentials. This can be done by following the steps below: - -1. Go to the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). -2. Create a project if you don't have one already. -3. Enable the [Google Drive API](https://console.cloud.google.com/flows/enableapi?apiid=drive.googleapis.com) -4. [Authorize credentials for desktop app](https://developers.google.com/drive/api/quickstart/python#authorize_credentials_for_a_desktop_application) -5. When done, you will be able to download the credentials in `json` format. Rename the downloaded file to `credentials.json` and save it in `~/.credentials/credentials.json` -6. Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS=~/.credentials/credentials.json` - -The first time you use the loader, you will be prompted to enter your Google account credentials. - - -```python -from embedchain import App - -app = App() - -url = "https://drive.google.com/drive/u/0/folders/xxx-xxx" -app.add(url, data_type="google_drive") -``` diff --git a/embedchain/docs/components/data-sources/image.mdx b/embedchain/docs/components/data-sources/image.mdx deleted file mode 100644 index b79043660..000000000 --- a/embedchain/docs/components/data-sources/image.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: "🖼️ Image" ---- - - -To use an image as data source, just add `data_type` as `image` and pass in the path of the image (local or hosted). - -We use [GPT4 Vision](https://platform.openai.com/docs/guides/vision) to generate meaning of the image using a custom prompt, and then use the generated text as the data source. - -You would require an OpenAI API key with access to `gpt-4-vision-preview` model to use this feature. - -### Without customization - -```python -import os -from embedchain import App - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -app = App() -app.add("./Elon-Musk.webp", data_type="image") -response = app.query("Describe the man in the image.") -print(response) -# Answer: The man in the image is dressed in formal attire, wearing a dark suit jacket and a white collared shirt. He has short hair and is standing. He appears to be gazing off to the side with a reflective expression. The background is dark with faint, warm-toned vertical lines, possibly from a lit environment behind the individual or reflections. The overall atmosphere is somewhat moody and introspective. -``` - -### Customization - -```python -import os -from embedchain import App -from embedchain.loaders.image import ImageLoader - -image_loader = ImageLoader( - max_tokens=100, - api_key="sk-xxx", - prompt="Is the person looking wealthy? Structure your thoughts around what you see in the image.", -) - -app = App() -app.add("./Elon-Musk.webp", data_type="image", loader=image_loader) -response = app.query("Describe the man in the image.") -print(response) -# Answer: The man in the image appears to be well-dressed in a suit and shirt, suggesting that he may be in a professional or formal setting. His composed demeanor and confident posture further indicate a sense of self-assurance. Based on these visual cues, one could infer that the man may have a certain level of economic or social status, possibly indicating wealth or professional success. -``` diff --git a/embedchain/docs/components/data-sources/json.mdx b/embedchain/docs/components/data-sources/json.mdx deleted file mode 100644 index 4d38a0a55..000000000 --- a/embedchain/docs/components/data-sources/json.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: '📃 JSON' ---- - -To add any json file, use the data_type as `json`. Headers are included for each line, so for example if you have a json like `{"age": 18}`, then it will be added as `age: 18`. - -Here are the supported sources for loading `json`: - -``` -1. URL - valid url to json file that ends with ".json" extension. -2. Local file - valid url to local json file that ends with ".json" extension. -3. String - valid json string (e.g. - app.add('{"foo": "bar"}')) -``` - - -If you would like to add other data structures (e.g. list, dict etc.), convert it to a valid json first using `json.dumps()` function. - - -## Example - - - -```python python -from embedchain import App - -app = App() - -# Add json file -app.add("temp.json") - -app.query("What is the net worth of Elon Musk as of October 2023?") -# As of October 2023, Elon Musk's net worth is $255.2 billion. -``` - - -```json temp.json -{ - "question": "What is your net worth, Elon Musk?", - "answer": "As of October 2023, Elon Musk's net worth is $255.2 billion, making him one of the wealthiest individuals in the world." -} -``` - - - diff --git a/embedchain/docs/components/data-sources/mdx.mdx b/embedchain/docs/components/data-sources/mdx.mdx deleted file mode 100644 index c59569e50..000000000 --- a/embedchain/docs/components/data-sources/mdx.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: '📝 Mdx file' ---- - -To add any `.mdx` file to your app, use the data_type (first argument to `.add()` method) as `mdx`. Note that this supports support mdx file present on machine, so this should be a file path. Eg: - -```python -from embedchain import App - -app = App() -app.add('path/to/file.mdx', data_type='mdx') - -app.query("What are the docs about?") -``` diff --git a/embedchain/docs/components/data-sources/mysql.mdx b/embedchain/docs/components/data-sources/mysql.mdx deleted file mode 100644 index 2a5cb7a01..000000000 --- a/embedchain/docs/components/data-sources/mysql.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: '🐬 MySQL' ---- - -1. Setup the MySQL loader by configuring the SQL db. -```Python -from embedchain.loaders.mysql import MySQLLoader - -config = { - "host": "host", - "port": "port", - "database": "database", - "user": "username", - "password": "password", -} - -mysql_loader = MySQLLoader(config=config) -``` - -For more details on how to setup with valid config, check MySQL [documentation](https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html). - -2. Once you setup the loader, you can create an app and load data using the above MySQL loader -```Python -from embedchain.pipeline import Pipeline as App - -app = App() - -app.add("SELECT * FROM table_name;", data_type='mysql', loader=mysql_loader) -# Adds `(1, 'What is your net worth, Elon Musk?', "As of October 2023, Elon Musk's net worth is $255.2 billion.")` - -response = app.query(question) -# Answer: As of October 2023, Elon Musk's net worth is $255.2 billion. -``` - -NOTE: The `add` function of the app will accept any executable query to load data. DO NOT pass the `CREATE`, `INSERT` queries in `add` function. - -3. We automatically create a chunker to chunk your SQL data, however if you wish to provide your own chunker class. Here is how you can do that: -```Python - -from embedchain.chunkers.mysql import MySQLChunker -from embedchain.config.add_config import ChunkerConfig - -mysql_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) -mysql_chunker = MySQLChunker(config=mysql_chunker_config) - -app.add("SELECT * FROM table_name;", data_type='mysql', loader=mysql_loader, chunker=mysql_chunker) -``` diff --git a/embedchain/docs/components/data-sources/notion.mdx b/embedchain/docs/components/data-sources/notion.mdx deleted file mode 100644 index d6c616df8..000000000 --- a/embedchain/docs/components/data-sources/notion.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: '📓 Notion' ---- - -To use notion you must install the extra dependencies with `pip install --upgrade embedchain[community]`. - -To load a notion page, use the data_type as `notion`. Since it is hard to automatically detect, it is advised to specify the `data_type` when adding a notion document. -The next argument must **end** with the `notion page id`. The id is a 32-character string. Eg: - -```python -from embedchain import App - -app = App() - -app.add("cfbc134ca6464fc980d0391613959196", data_type="notion") -app.add("my-page-cfbc134ca6464fc980d0391613959196", data_type="notion") -app.add("https://www.notion.so/my-page-cfbc134ca6464fc980d0391613959196", data_type="notion") - -app.query("Summarize the notion doc") -``` diff --git a/embedchain/docs/components/data-sources/openapi.mdx b/embedchain/docs/components/data-sources/openapi.mdx deleted file mode 100644 index 84bc966b2..000000000 --- a/embedchain/docs/components/data-sources/openapi.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: 🙌 OpenAPI ---- - -To add any OpenAPI spec yaml file (currently the json file will be detected as JSON data type), use the data_type as 'openapi'. 'openapi' allows remote urls and conventional file paths. - -```python -from embedchain import App - -app = App() - -app.add("https://github.com/openai/openai-openapi/blob/master/openapi.yaml", data_type="openapi") -# Or add using the local file path -# app.add("configs/openai_openapi.yaml", data_type="openapi") - -app.query("What can OpenAI API endpoint do? Can you list the things it can learn from?") -# Answer: The OpenAI API endpoint allows users to interact with OpenAI's models and perform various tasks such as generating text, answering questions, summarizing documents, translating languages, and more. The specific capabilities and tasks that the API can learn from may vary depending on the models and features provided by OpenAI. For more detailed information, it is recommended to refer to the OpenAI API documentation at https://platform.openai.com/docs/api-reference. -``` - - -The yaml file added to the App must have the required OpenAPI fields otherwise the adding OpenAPI spec will fail. Please refer to [OpenAPI Spec Doc](https://spec.openapis.org/oas/v3.1.0) - \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/overview.mdx b/embedchain/docs/components/data-sources/overview.mdx deleted file mode 100644 index 66f5948a3..000000000 --- a/embedchain/docs/components/data-sources/overview.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Overview ---- - -Embedchain comes with built-in support for various data sources. We handle the complexity of loading unstructured data from these data sources, allowing you to easily customize your app through a user-friendly interface. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/embedchain/docs/components/data-sources/pdf-file.mdx b/embedchain/docs/components/data-sources/pdf-file.mdx deleted file mode 100644 index 9cc45910a..000000000 --- a/embedchain/docs/components/data-sources/pdf-file.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: '📰 PDF' ---- - -You can load any pdf file from your local file system or through a URL. - -## Usage - -### Load from a local file - -```python -from embedchain import App -app = App() -app.add('/path/to/file.pdf', data_type='pdf_file') -``` - -### Load from URL - -```python -from embedchain import App -app = App() -app.add('https://arxiv.org/pdf/1706.03762.pdf', data_type='pdf_file') -app.query("What is the paper 'attention is all you need' about?", citations=True) -# Answer: The paper "Attention Is All You Need" proposes a new network architecture called the Transformer, which is based solely on attention mechanisms. It suggests that complex recurrent or convolutional neural networks can be replaced with a simpler architecture that connects the encoder and decoder through attention. The paper discusses how this approach can improve sequence transduction models, such as neural machine translation. -# Contexts: -# [ -# ( -# 'Provided proper attribution is ...', -# { -# 'page': 0, -# 'url': 'https://arxiv.org/pdf/1706.03762.pdf', -# 'score': 0.3676220203221626, -# ... -# } -# ), -# ] -``` - -We also store the page number under the key `page` with each chunk that helps understand where the answer is coming from. You can fetch the `page` key while during retrieval (refer to the example given above). - - -Note that we do not support password protected pdf files. - diff --git a/embedchain/docs/components/data-sources/postgres.mdx b/embedchain/docs/components/data-sources/postgres.mdx deleted file mode 100644 index 9cb5d0e6e..000000000 --- a/embedchain/docs/components/data-sources/postgres.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: '🐘 Postgres' ---- - -1. Setup the Postgres loader by configuring the postgres db. -```Python -from embedchain.loaders.postgres import PostgresLoader - -config = { - "host": "host_address", - "port": "port_number", - "dbname": "database_name", - "user": "username", - "password": "password", -} - -""" -config = { - "url": "your_postgres_url" -} -""" - -postgres_loader = PostgresLoader(config=config) - -``` - -You can either setup the loader by passing the postgresql url or by providing the config data. -For more details on how to setup with valid url and config, check postgres [documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING:~:text=34.1.1.%C2%A0Connection%20Strings-,%23,-Several%20libpq%20functions). - -NOTE: if you provide the `url` field in config, all other fields will be ignored. - -2. Once you setup the loader, you can create an app and load data using the above postgres loader -```Python -import os -from embedchain.pipeline import Pipeline as App - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -app = App() - -question = "What is Elon Musk's networth?" -response = app.query(question) -# Answer: As of September 2021, Elon Musk's net worth is estimated to be around $250 billion, making him one of the wealthiest individuals in the world. However, please note that net worth can fluctuate over time due to various factors such as stock market changes and business ventures. - -app.add("SELECT * FROM table_name;", data_type='postgres', loader=postgres_loader) -# Adds `(1, 'What is your net worth, Elon Musk?', "As of October 2023, Elon Musk's net worth is $255.2 billion.")` - -response = app.query(question) -# Answer: As of October 2023, Elon Musk's net worth is $255.2 billion. -``` - -NOTE: The `add` function of the app will accept any executable query to load data. DO NOT pass the `CREATE`, `INSERT` queries in `add` function as they will result in not adding any data, so it is pointless. - -3. We automatically create a chunker to chunk your postgres data, however if you wish to provide your own chunker class. Here is how you can do that: -```Python - -from embedchain.chunkers.postgres import PostgresChunker -from embedchain.config.add_config import ChunkerConfig - -postgres_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) -postgres_chunker = PostgresChunker(config=postgres_chunker_config) - -app.add("SELECT * FROM table_name;", data_type='postgres', loader=postgres_loader, chunker=postgres_chunker) -``` \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/qna.mdx b/embedchain/docs/components/data-sources/qna.mdx deleted file mode 100644 index 3efaa47ff..000000000 --- a/embedchain/docs/components/data-sources/qna.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: '❓💬 Question and answer pair' ---- - -QnA pair is a local data type. To supply your own QnA pair, use the data_type as `qna_pair` and enter a tuple. Eg: - -```python -from embedchain import App - -app = App() - -app.add(("Question", "Answer"), data_type="qna_pair") -``` diff --git a/embedchain/docs/components/data-sources/sitemap.mdx b/embedchain/docs/components/data-sources/sitemap.mdx deleted file mode 100644 index 96b47ef1c..000000000 --- a/embedchain/docs/components/data-sources/sitemap.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: '🗺️ Sitemap' ---- - -Add all web pages from an xml-sitemap. Filters non-text files. Use the data_type as `sitemap`. Eg: - -```python -from embedchain import App - -app = App() - -app.add('https://example.com/sitemap.xml', data_type='sitemap') -``` \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/slack.mdx b/embedchain/docs/components/data-sources/slack.mdx deleted file mode 100644 index 7b879fd6d..000000000 --- a/embedchain/docs/components/data-sources/slack.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: '🤖 Slack' ---- - -## Pre-requisite -- Download required packages by running `pip install --upgrade "embedchain[slack]"`. -- Configure your slack bot token as environment variable `SLACK_USER_TOKEN`. - - Find your user token on your [Slack Account](https://api.slack.com/authentication/token-types) - - Make sure your slack user token includes [search](https://api.slack.com/scopes/search:read) scope. - -## Example - -### Get Started - -This will automatically retrieve data from the workspace associated with the user's token. - -```python -import os -from embedchain import App - -os.environ["SLACK_USER_TOKEN"] = "xoxp-xxx" -app = App() - -app.add("in:general", data_type="slack") - -result = app.query("what are the messages in general channel?") - -print(result) -``` - - -### Customize your SlackLoader -1. Setup the Slack loader by configuring the Slack Webclient. -```Python -from embedchain.loaders.slack import SlackLoader - -os.environ["SLACK_USER_TOKEN"] = "xoxp-*" - -config = { - 'base_url': slack_app_url, - 'headers': web_headers, - 'team_id': slack_team_id, -} - -loader = SlackLoader(config) -``` - -NOTE: you can also pass the `config` with `base_url`, `headers`, `team_id` to setup your SlackLoader. - -2. Once you setup the loader, you can create an app and load data using the above slack loader -```Python -import os -from embedchain.pipeline import Pipeline as App - -app = App() - -app.add("in:random", data_type="slack", loader=loader) -question = "Which bots are available in the slack workspace's random channel?" -# Answer: The available bot in the slack workspace's random channel is the Embedchain bot. -``` - -3. We automatically create a chunker to chunk your slack data, however if you wish to provide your own chunker class. Here is how you can do that: -```Python -from embedchain.chunkers.slack import SlackChunker -from embedchain.config.add_config import ChunkerConfig - -slack_chunker_config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) -slack_chunker = SlackChunker(config=slack_chunker_config) - -app.add(slack_chunker, data_type="slack", loader=loader, chunker=slack_chunker) -``` \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/substack.mdx b/embedchain/docs/components/data-sources/substack.mdx deleted file mode 100644 index dd10a9e7d..000000000 --- a/embedchain/docs/components/data-sources/substack.mdx +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "📝 Substack" ---- - -To add any Substack data sources to your app, just add the main base url as the source and set the data_type to `substack`. - -```python -from embedchain import App - -app = App() - -# source: for any substack just add the root URL -app.add('https://www.lennysnewsletter.com', data_type='substack') -app.query("Who is Brian Chesky?") -# Answer: Brian Chesky is the co-founder and CEO of Airbnb. -``` diff --git a/embedchain/docs/components/data-sources/text-file.mdx b/embedchain/docs/components/data-sources/text-file.mdx deleted file mode 100644 index 14b48c005..000000000 --- a/embedchain/docs/components/data-sources/text-file.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: '📄 Text file' ---- - -To add a .txt file, specify the data_type as `text_file`. The URL provided in the first parameter of the `add` function, should be a local path. Eg: - -```python -from embedchain import App - -app = App() -app.add('path/to/file.txt', data_type="text_file") - -app.query("Summarize the information of the text file") -``` \ No newline at end of file diff --git a/embedchain/docs/components/data-sources/text.mdx b/embedchain/docs/components/data-sources/text.mdx deleted file mode 100644 index 0fda6f573..000000000 --- a/embedchain/docs/components/data-sources/text.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: '📝 Text' ---- - -### Text - -Text is a local data type. To supply your own text, use the data_type as `text` and enter a string. The text is not processed, this can be very versatile. Eg: - -```python -from embedchain import App - -app = App() - -app.add('Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.', data_type='text') -``` - -Note: This is not used in the examples because in most cases you will supply a whole paragraph or file, which did not fit. diff --git a/embedchain/docs/components/data-sources/web-page.mdx b/embedchain/docs/components/data-sources/web-page.mdx deleted file mode 100644 index f4a50a923..000000000 --- a/embedchain/docs/components/data-sources/web-page.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: '🌐 HTML Web page' ---- - -To add any web page, use the data_type as `web_page`. Eg: - -```python -from embedchain import App - -app = App() - -app.add('a_valid_web_page_url', data_type='web_page') -``` diff --git a/embedchain/docs/components/data-sources/xml.mdx b/embedchain/docs/components/data-sources/xml.mdx deleted file mode 100644 index afe9a4124..000000000 --- a/embedchain/docs/components/data-sources/xml.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: '🧾 XML file' ---- - -### XML file - -To add any xml file, use the data_type as `xml`. Eg: - -```python -from embedchain import App - -app = App() - -app.add('content/data.xml') -``` - -Note: Only the text content of the xml file will be added to the app. The tags will be ignored. diff --git a/embedchain/docs/components/data-sources/youtube-channel.mdx b/embedchain/docs/components/data-sources/youtube-channel.mdx deleted file mode 100644 index d9f037ff0..000000000 --- a/embedchain/docs/components/data-sources/youtube-channel.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: '📽️ Youtube Channel' ---- - -## Setup - -Make sure you have all the required packages installed before using this data type. You can install them by running the following command in your terminal. - -```bash -pip install -U "embedchain[youtube]" -``` - -## Usage - -To add all the videos from a youtube channel to your app, use the data_type as `youtube_channel`. - -```python -from embedchain import App - -app = App() -app.add("@channel_name", data_type="youtube_channel") -``` diff --git a/embedchain/docs/components/data-sources/youtube-video.mdx b/embedchain/docs/components/data-sources/youtube-video.mdx deleted file mode 100644 index 01ac52406..000000000 --- a/embedchain/docs/components/data-sources/youtube-video.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: '📺 Youtube Video' ---- - -## Setup - -Make sure you have all the required packages installed before using this data type. You can install them by running the following command in your terminal. - -```bash -pip install -U "embedchain[youtube]" -``` - -## Usage - -To add any youtube video to your app, use the data_type as `youtube_video`. Eg: - -```python -from embedchain import App - -app = App() -app.add('a_valid_youtube_url_here', data_type='youtube_video') -``` diff --git a/embedchain/docs/components/embedding-models.mdx b/embedchain/docs/components/embedding-models.mdx deleted file mode 100644 index 7af84236b..000000000 --- a/embedchain/docs/components/embedding-models.mdx +++ /dev/null @@ -1,470 +0,0 @@ ---- -title: 🧩 Embedding models ---- - -## Overview - -Embedchain supports several embedding models from the following providers: - - - - - - - - - - - - - - - -## OpenAI - -To use OpenAI embedding function, you have to set the `OPENAI_API_KEY` environment variable. You can obtain the OpenAI API key from the [OpenAI Platform](https://platform.openai.com/account/api-keys). - -Once you have obtained the key, you can use it like this: - - - -```python main.py -import os -from embedchain import App - -os.environ['OPENAI_API_KEY'] = 'xxx' - -# load embedding model configuration from config.yaml file -app = App.from_config(config_path="config.yaml") - -app.add("https://en.wikipedia.org/wiki/OpenAI") -app.query("What is OpenAI?") -``` - -```yaml config.yaml -embedder: - provider: openai - config: - model: 'text-embedding-3-small' -``` - - - -* OpenAI announced two new embedding models: `text-embedding-3-small` and `text-embedding-3-large`. Embedchain supports both these models. Below you can find YAML config for both: - - - -```yaml text-embedding-3-small.yaml -embedder: - provider: openai - config: - model: 'text-embedding-3-small' -``` - -```yaml text-embedding-3-large.yaml -embedder: - provider: openai - config: - model: 'text-embedding-3-large' -``` - - - -## Google AI - -To use Google AI embedding function, you have to set the `GOOGLE_API_KEY` environment variable. You can obtain the Google API key from the [Google Maker Suite](https://makersuite.google.com/app/apikey) - - -```python main.py -import os -from embedchain import App - -os.environ["GOOGLE_API_KEY"] = "xxx" - -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -embedder: - provider: google - config: - model: 'models/embedding-001' - task_type: "retrieval_document" - title: "Embeddings for Embedchain" -``` - -
- -For more details regarding the Google AI embedding model, please refer to the [Google AI documentation](https://ai.google.dev/tutorials/python_quickstart#use_embeddings). - - -## AWS Bedrock - -To use AWS Bedrock embedding function, you have to set the AWS environment variable. - - -```python main.py -import os -from embedchain import App - -os.environ["AWS_ACCESS_KEY_ID"] = "xxx" -os.environ["AWS_SECRET_ACCESS_KEY"] = "xxx" -os.environ["AWS_REGION"] = "us-west-2" - -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -embedder: - provider: aws_bedrock - config: - model: 'amazon.titan-embed-text-v2:0' - vector_dimension: 1024 - task_type: "retrieval_document" - title: "Embeddings for Embedchain" -``` - -
- -For more details regarding the AWS Bedrock embedding model, please refer to the [AWS Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html). - - -## Azure OpenAI - -To use Azure OpenAI embedding model, you have to set some of the azure openai related environment variables as given in the code block below: - - - -```python main.py -import os -from embedchain import App - -os.environ["OPENAI_API_TYPE"] = "azure" -os.environ["AZURE_OPENAI_ENDPOINT"] = "https://xxx.openai.azure.com/" -os.environ["AZURE_OPENAI_API_KEY"] = "xxx" -os.environ["OPENAI_API_VERSION"] = "xxx" - -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: azure_openai - config: - model: gpt-35-turbo - deployment_name: your_llm_deployment_name - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: azure_openai - config: - model: text-embedding-ada-002 - deployment_name: you_embedding_model_deployment_name -``` - - -You can find the list of models and deployment name on the [Azure OpenAI Platform](https://oai.azure.com/portal). - -## GPT4ALL - -GPT4All supports generating high quality embeddings of arbitrary length documents of text using a CPU optimized contrastively trained Sentence Transformer. - - - -```python main.py -from embedchain import App - -# load embedding model configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: gpt4all -``` - - - -## Hugging Face - -Hugging Face supports generating embeddings of arbitrary length documents of text using Sentence Transformer library. Example of how to generate embeddings using hugging face is given below: - - - -```python main.py -from embedchain import App - -# load embedding model configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: huggingface - config: - model: 'google/flan-t5-xxl' - temperature: 0.5 - max_tokens: 1000 - top_p: 0.5 - stream: false - -embedder: - provider: huggingface - config: - model: 'sentence-transformers/all-mpnet-base-v2' - model_kwargs: - trust_remote_code: True # Only use if you trust your embedder -``` - - - -## Vertex AI - -Embedchain supports Google's VertexAI embeddings model through a simple interface. You just have to pass the `model_name` in the config yaml and it would work out of the box. - - - -```python main.py -from embedchain import App - -# load embedding model configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: vertexai - config: - model: 'chat-bison' - temperature: 0.5 - top_p: 0.5 - -embedder: - provider: vertexai - config: - model: 'textembedding-gecko' -``` - - - -## NVIDIA AI - -[NVIDIA AI Foundation Endpoints](https://www.nvidia.com/en-us/ai-data-science/foundation-models/) let you quickly use NVIDIA's AI models, such as Mixtral 8x7B, Llama 2 etc, through our API. These models are available in the [NVIDIA NGC catalog](https://catalog.ngc.nvidia.com/ai-foundation-models), fully optimized and ready to use on NVIDIA's AI platform. They are designed for high speed and easy customization, ensuring smooth performance on any accelerated setup. - - -### Usage - -In order to use embedding models and LLMs from NVIDIA AI, create an account on [NVIDIA NGC Service](https://catalog.ngc.nvidia.com/). - -Generate an API key from their dashboard. Set the API key as `NVIDIA_API_KEY` environment variable. Note that the `NVIDIA_API_KEY` will start with `nvapi-`. - -Below is an example of how to use LLM model and embedding model from NVIDIA AI: - - - -```python main.py -import os -from embedchain import App - -os.environ['NVIDIA_API_KEY'] = 'nvapi-xxxx' - -config = { - "app": { - "config": { - "id": "my-app", - }, - }, - "llm": { - "provider": "nvidia", - "config": { - "model": "nemotron_steerlm_8b", - }, - }, - "embedder": { - "provider": "nvidia", - "config": { - "model": "nvolveqa_40k", - "vector_dimension": 1024, - }, - }, -} - -app = App.from_config(config=config) - -app.add("https://www.forbes.com/profile/elon-musk") -answer = app.query("What is the net worth of Elon Musk today?") -# Answer: The net worth of Elon Musk is subject to fluctuations based on the market value of his holdings in various companies. -# As of March 1, 2024, his net worth is estimated to be approximately $210 billion. However, this figure can change rapidly due to stock market fluctuations and other factors. -# Additionally, his net worth may include other assets such as real estate and art, which are not reflected in his stock portfolio. -``` - - - -## Cohere - -To use embedding models and LLMs from COHERE, create an account on [COHERE](https://dashboard.cohere.com/welcome/login?redirect_uri=%2Fapi-keys). - -Generate an API key from their dashboard. Set the API key as `COHERE_API_KEY` environment variable. - -Once you have obtained the key, you can use it like this: - - - -```python main.py -import os -from embedchain import App - -os.environ['COHERE_API_KEY'] = 'xxx' - -# load embedding model configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -embedder: - provider: cohere - config: - model: 'embed-english-light-v3.0' -``` - - - -* Cohere has few embedding models: `embed-english-v3.0`, `embed-multilingual-v3.0`, `embed-multilingual-light-v3.0`, `embed-english-v2.0`, `embed-english-light-v2.0` and `embed-multilingual-v2.0`. Embedchain supports all these models. Below you can find YAML config for all: - - - -```yaml embed-english-v3.0.yaml -embedder: - provider: cohere - config: - model: 'embed-english-v3.0' - vector_dimension: 1024 -``` - -```yaml embed-multilingual-v3.0.yaml -embedder: - provider: cohere - config: - model: 'embed-multilingual-v3.0' - vector_dimension: 1024 -``` - -```yaml embed-multilingual-light-v3.0.yaml -embedder: - provider: cohere - config: - model: 'embed-multilingual-light-v3.0' - vector_dimension: 384 -``` - -```yaml embed-english-v2.0.yaml -embedder: - provider: cohere - config: - model: 'embed-english-v2.0' - vector_dimension: 4096 -``` - -```yaml embed-english-light-v2.0.yaml -embedder: - provider: cohere - config: - model: 'embed-english-light-v2.0' - vector_dimension: 1024 -``` - -```yaml embed-multilingual-v2.0.yaml -embedder: - provider: cohere - config: - model: 'embed-multilingual-v2.0' - vector_dimension: 768 -``` - - - -## Ollama - -Ollama enables the use of embedding models, allowing you to generate high-quality embeddings directly on your local machine. Make sure to install [Ollama](https://ollama.com/download) and keep it running before using the embedding model. - -You can find the list of models at [Ollama Embedding Models](https://ollama.com/blog/embedding-models). - -Below is an example of how to use embedding model Ollama: - - - -```python main.py -import os -from embedchain import App - -# load embedding model configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -embedder: - provider: ollama - config: - model: 'all-minilm:latest' -``` - - - -## Clarifai - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[clarifai]' -``` - -set the `CLARIFAI_PAT` as environment variable which you can find in the [security page](https://clarifai.com/settings/security). Optionally you can also pass the PAT key as parameters in LLM/Embedder class. - -Now you are all set with exploring Embedchain. - - - -```python main.py -import os -from embedchain import App - -os.environ["CLARIFAI_PAT"] = "XXX" - -# load llm and embedder configuration from config.yaml file -app = App.from_config(config_path="config.yaml") - -#Now let's add some data. -app.add("https://www.forbes.com/profile/elon-musk") - -#Query the app -response = app.query("what college degrees does elon musk have?") -``` -Head to [Clarifai Platform](https://clarifai.com/explore/models?page=1&perPage=24&filterData=%5B%7B%22field%22%3A%22output_fields%22%2C%22value%22%3A%5B%22embeddings%22%5D%7D%5D) to explore all the State of the Art embedding models available to use. -For passing LLM model inference parameters use `model_kwargs` argument in the config file. Also you can use `api_key` argument to pass `CLARIFAI_PAT` in the config. - -```yaml config.yaml -llm: - provider: clarifai - config: - model: "https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct" - model_kwargs: - temperature: 0.5 - max_tokens: 1000 -embedder: - provider: clarifai - config: - model: "https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15" -``` - \ No newline at end of file diff --git a/embedchain/docs/components/evaluation.mdx b/embedchain/docs/components/evaluation.mdx deleted file mode 100644 index c1143d2ec..000000000 --- a/embedchain/docs/components/evaluation.mdx +++ /dev/null @@ -1,275 +0,0 @@ ---- -title: 🔬 Evaluation ---- - -## Overview - -We provide out-of-the-box evaluation metrics for your RAG application. You can use them to evaluate your RAG applications and compare against different settings of your production RAG application. - -Currently, we provide support for following evaluation metrics: - - - - - - - - -## Quickstart - -Here is a basic example of running evaluation: - -```python example.py -from embedchain import App - -app = App() - -# Add data sources -app.add("https://www.forbes.com/profile/elon-musk") - -# Run evaluation -app.evaluate(["What is the net worth of Elon Musk?", "How many companies Elon Musk owns?"]) -# {'answer_relevancy': 0.9987286412340826, 'groundedness': 1.0, 'context_relevancy': 0.3571428571428571} -``` - -Under the hood, Embedchain does the following: - -1. Runs semantic search in the vector database and fetches context -2. LLM call with question, context to fetch the answer -3. Run evaluation on following metrics: `context relevancy`, `groundedness`, and `answer relevancy` and return result - -## Advanced Usage - -We use OpenAI's `gpt-4` model as default LLM model for automatic evaluation. Hence, we require you to set `OPENAI_API_KEY` as an environment variable. - -### Step-1: Create dataset - -In order to evaluate your RAG application, you have to setup a dataset. A data point in the dataset consists of `questions`, `contexts`, `answer`. Here is an example of how to create a dataset for evaluation: - -```python -from embedchain.utils.eval import EvalData - -data = [ - { - "question": "What is the net worth of Elon Musk?", - "contexts": [ - "Elon Musk PROFILEElon MuskCEO, ...", - "a Twitter poll on whether the journalists' ...", - "2016 and run by Jared Birchall.[335]...", - ], - "answer": "As of the information provided, Elon Musk's net worth is $241.6 billion.", - }, - { - "question": "which companies does Elon Musk own?", - "contexts": [ - "of December 2023[update], ...", - "ThielCofounderView ProfileTeslaHolds ...", - "Elon Musk PROFILEElon MuskCEO, ...", - ], - "answer": "Elon Musk owns several companies, including Tesla, SpaceX, Neuralink, and The Boring Company.", - }, -] - -dataset = [] - -for d in data: - eval_data = EvalData(question=d["question"], contexts=d["contexts"], answer=d["answer"]) - dataset.append(eval_data) -``` - -### Step-2: Run evaluation - -Once you have created your dataset, you can run evaluation on the dataset by picking the metric you want to run evaluation on. - -For example, you can run evaluation on context relevancy metric using the following code: - -```python -from embedchain.evaluation.metrics import ContextRelevance -metric = ContextRelevance() -score = metric.evaluate(dataset) -print(score) -``` - -You can choose a different metric or write your own to run evaluation on. You can check the following links: - -- [Context Relevancy](#context_relevancy) -- [Answer relenvancy](#answer_relevancy) -- [Groundedness](#groundedness) -- [Build your own metric](#custom_metric) - -## Metrics - -### Context Relevancy - -Context relevancy is a metric to determine "how relevant the context is to the question". We use OpenAI's `gpt-4` model to determine the relevancy of the context. We achieve this by prompting the model with the question and the context and asking it to return relevant sentences from the context. We then use the following formula to determine the score: - -``` -context_relevance_score = num_relevant_sentences_in_context / num_of_sentences_in_context -``` - -#### Examples - -You can run the context relevancy evaluation with the following simple code: - -```python -from embedchain.evaluation.metrics import ContextRelevance - -metric = ContextRelevance() -score = metric.evaluate(dataset) # 'dataset' is definted in the create dataset section -print(score) -# 0.27975528364849833 -``` - -In the above example, we used sensible defaults for the evaluation. However, you can also configure the evaluation metric as per your needs using the `ContextRelevanceConfig` class. - -Here is a more advanced example of how to pass a custom evaluation config for evaluating on context relevance metric: - -```python -from embedchain.config.evaluation.base import ContextRelevanceConfig -from embedchain.evaluation.metrics import ContextRelevance - -eval_config = ContextRelevanceConfig(model="gpt-4", api_key="sk-xxx", language="en") -metric = ContextRelevance(config=eval_config) -metric.evaluate(dataset) -``` - -#### `ContextRelevanceConfig` - - - The model to use for the evaluation. Defaults to `gpt-4`. We only support openai's models for now. - - - The openai api key to use for the evaluation. Defaults to `None`. If not provided, we will use the `OPENAI_API_KEY` environment variable. - - - The language of the dataset being evaluated. We need this to determine the understand the context provided in the dataset. Defaults to `en`. - - - The prompt to extract the relevant sentences from the context. Defaults to `CONTEXT_RELEVANCY_PROMPT`, which can be found at `embedchain.config.evaluation.base` path. - - - -### Answer Relevancy - -Answer relevancy is a metric to determine how relevant the answer is to the question. We prompt the model with the answer and asking it to generate questions from the answer. We then use the cosine similarity between the generated questions and the original question to determine the score. - -``` -answer_relevancy_score = mean(cosine_similarity(generated_questions, original_question)) -``` - -#### Examples - -You can run the answer relevancy evaluation with the following simple code: - -```python -from embedchain.evaluation.metrics import AnswerRelevance - -metric = AnswerRelevance() -score = metric.evaluate(dataset) -print(score) -# 0.9505334177461916 -``` - -In the above example, we used sensible defaults for the evaluation. However, you can also configure the evaluation metric as per your needs using the `AnswerRelevanceConfig` class. Here is a more advanced example where you can provide your own evaluation config: - -```python -from embedchain.config.evaluation.base import AnswerRelevanceConfig -from embedchain.evaluation.metrics import AnswerRelevance - -eval_config = AnswerRelevanceConfig( - model='gpt-4', - embedder="text-embedding-ada-002", - api_key="sk-xxx", - num_gen_questions=2 -) -metric = AnswerRelevance(config=eval_config) -score = metric.evaluate(dataset) -``` - -#### `AnswerRelevanceConfig` - - - The model to use for the evaluation. Defaults to `gpt-4`. We only support openai's models for now. - - - The embedder to use for embedding the text. Defaults to `text-embedding-ada-002`. We only support openai's embedders for now. - - - The openai api key to use for the evaluation. Defaults to `None`. If not provided, we will use the `OPENAI_API_KEY` environment variable. - - - The number of questions to generate for each answer. We use the generated questions to compare the similarity with the original question to determine the score. Defaults to `1`. - - - The prompt to extract the `num_gen_questions` number of questions from the provided answer. Defaults to `ANSWER_RELEVANCY_PROMPT`, which can be found at `embedchain.config.evaluation.base` path. - - -## Groundedness - -Groundedness is a metric to determine how grounded the answer is to the context. We use OpenAI's `gpt-4` model to determine the groundedness of the answer. We achieve this by prompting the model with the answer and asking it to generate claims from the answer. We then again prompt the model with the context and the generated claims to determine the verdict on the claims. We then use the following formula to determine the score: - -``` -groundedness_score = (sum of all verdicts) / (total # of claims) -``` - -You can run the groundedness evaluation with the following simple code: - -```python -from embedchain.evaluation.metrics import Groundedness -metric = Groundedness() -score = metric.evaluate(dataset) # dataset from above -print(score) -# 1.0 -``` - -In the above example, we used sensible defaults for the evaluation. However, you can also configure the evaluation metric as per your needs using the `GroundednessConfig` class. Here is a more advanced example where you can configure the evaluation config: - -```python -from embedchain.config.evaluation.base import GroundednessConfig -from embedchain.evaluation.metrics import Groundedness - -eval_config = GroundednessConfig(model='gpt-4', api_key="sk-xxx") -metric = Groundedness(config=eval_config) -score = metric.evaluate(dataset) -``` - - -#### `GroundednessConfig` - - - The model to use for the evaluation. Defaults to `gpt-4`. We only support openai's models for now. - - - The openai api key to use for the evaluation. Defaults to `None`. If not provided, we will use the `OPENAI_API_KEY` environment variable. - - - The prompt to extract the claims from the provided answer. Defaults to `GROUNDEDNESS_ANSWER_CLAIMS_PROMPT`, which can be found at `embedchain.config.evaluation.base` path. - - - The prompt to get verdicts on the claims from the answer from the given context. Defaults to `GROUNDEDNESS_CLAIMS_INFERENCE_PROMPT`, which can be found at `embedchain.config.evaluation.base` path. - - -## Custom - -You can also create your own evaluation metric by extending the `BaseMetric` class. You can find the source code for the existing metrics at `embedchain.evaluation.metrics` path. - - -You must provide the `name` of your custom metric in the `__init__` method of your class. This name will be used to identify your metric in the evaluation report. - - -```python -from typing import Optional - -from embedchain.config.base_config import BaseConfig -from embedchain.evaluation.metrics import BaseMetric -from embedchain.utils.eval import EvalData - -class MyCustomMetric(BaseMetric): - def __init__(self, config: Optional[BaseConfig] = None): - super().__init__(name="my_custom_metric") - - def evaluate(self, dataset: list[EvalData]): - score = 0.0 - # write your evaluation logic here - return score -``` diff --git a/embedchain/docs/components/introduction.mdx b/embedchain/docs/components/introduction.mdx deleted file mode 100644 index 3f9122b5d..000000000 --- a/embedchain/docs/components/introduction.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: 🧩 Introduction ---- - -## Overview - -You can configure following components - -* [Data Source](/components/data-sources/overview) -* [LLM](/components/llms) -* [Embedding Model](/components/embedding-models) -* [Vector Database](/components/vector-databases) -* [Evaluation](/components/evaluation) diff --git a/embedchain/docs/components/llms.mdx b/embedchain/docs/components/llms.mdx deleted file mode 100644 index 183b8cd3f..000000000 --- a/embedchain/docs/components/llms.mdx +++ /dev/null @@ -1,899 +0,0 @@ ---- -title: 🤖 Large language models (LLMs) ---- - -## Overview - -Embedchain comes with built-in support for various popular large language models. We handle the complexity of integrating these models for you, allowing you to easily customize your language model interactions through a user-friendly interface. - - - - - - - - - - - - - - - - - - - - - - -## OpenAI - -To use OpenAI LLM models, you have to set the `OPENAI_API_KEY` environment variable. You can obtain the OpenAI API key from the [OpenAI Platform](https://platform.openai.com/account/api-keys). - -Once you have obtained the key, you can use it like this: - -```python -import os -from embedchain import App - -os.environ['OPENAI_API_KEY'] = 'xxx' - -app = App() -app.add("https://en.wikipedia.org/wiki/OpenAI") -app.query("What is OpenAI?") -``` - -If you are looking to configure the different parameters of the LLM, you can do so by loading the app using a [yaml config](https://github.com/embedchain/embedchain/blob/main/configs/chroma.yaml) file. - - - -```python main.py -import os -from embedchain import App - -os.environ['OPENAI_API_KEY'] = 'xxx' - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false -``` - - -### Function Calling -Embedchain supports OpenAI [Function calling](https://platform.openai.com/docs/guides/function-calling) with a single function. It accepts inputs in accordance with the [Langchain interface](https://python.langchain.com/docs/modules/model_io/chat/function_calling#legacy-args-functions-and-function_call). - - - ```python - from pydantic import BaseModel - - class multiply(BaseModel): - """Multiply two integers together.""" - - a: int = Field(..., description="First integer") - b: int = Field(..., description="Second integer") - ``` - - - - ```python - def multiply(a: int, b: int) -> int: - """Multiply two integers together. - - Args: - a: First integer - b: Second integer - """ - return a * b - ``` - - - ```python - multiply = { - "type": "function", - "function": { - "name": "multiply", - "description": "Multiply two integers together.", - "parameters": { - "type": "object", - "properties": { - "a": { - "description": "First integer", - "type": "integer" - }, - "b": { - "description": "Second integer", - "type": "integer" - } - }, - "required": [ - "a", - "b" - ] - } - } - } - ``` - - -With any of the previous inputs, the OpenAI LLM can be queried to provide the appropriate arguments for the function. - -```python -import os -from embedchain import App -from embedchain.llm.openai import OpenAILlm - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -llm = OpenAILlm(tools=multiply) -app = App(llm=llm) - -result = app.query("What is the result of 125 multiplied by fifteen?") -``` - -## Google AI - -To use Google AI model, you have to set the `GOOGLE_API_KEY` environment variable. You can obtain the Google API key from the [Google Maker Suite](https://makersuite.google.com/app/apikey) - - -```python main.py -import os -from embedchain import App - -os.environ["GOOGLE_API_KEY"] = "xxx" - -app = App.from_config(config_path="config.yaml") - -app.add("https://www.forbes.com/profile/elon-musk") - -response = app.query("What is the net worth of Elon Musk?") -if app.llm.config.stream: # if stream is enabled, response is a generator - for chunk in response: - print(chunk) -else: - print(response) -``` - -```yaml config.yaml -llm: - provider: google - config: - model: gemini-pro - max_tokens: 1000 - temperature: 0.5 - top_p: 1 - stream: false - -embedder: - provider: google - config: - model: 'models/embedding-001' - task_type: "retrieval_document" - title: "Embeddings for Embedchain" -``` - - -## Azure OpenAI - -To use Azure OpenAI model, you have to set some of the azure openai related environment variables as given in the code block below: - - - -```python main.py -import os -from embedchain import App - -os.environ["OPENAI_API_TYPE"] = "azure" -os.environ["AZURE_OPENAI_ENDPOINT"] = "https://xxx.openai.azure.com/" -os.environ["AZURE_OPENAI_KEY"] = "xxx" -os.environ["OPENAI_API_VERSION"] = "xxx" - -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: azure_openai - config: - model: gpt-4o-mini - deployment_name: your_llm_deployment_name - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: azure_openai - config: - model: text-embedding-ada-002 - deployment_name: you_embedding_model_deployment_name -``` - - -You can find the list of models and deployment name on the [Azure OpenAI Platform](https://oai.azure.com/portal). - -## Anthropic - -To use anthropic's model, please set the `ANTHROPIC_API_KEY` which you find on their [Account Settings Page](https://console.anthropic.com/account/keys). - - - -```python main.py -import os -from embedchain import App - -os.environ["ANTHROPIC_API_KEY"] = "xxx" - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: anthropic - config: - model: 'claude-instant-1' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false -``` - - - -## Cohere - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[cohere]' -``` - -Set the `COHERE_API_KEY` as environment variable which you can find on their [Account settings page](https://dashboard.cohere.com/api-keys). - -Once you have the API key, you are all set to use it with Embedchain. - - - -```python main.py -import os -from embedchain import App - -os.environ["COHERE_API_KEY"] = "xxx" - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: cohere - config: - model: large - temperature: 0.5 - max_tokens: 1000 - top_p: 1 -``` - - - -## Together - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[together]' -``` - -Set the `TOGETHER_API_KEY` as environment variable which you can find on their [Account settings page](https://api.together.xyz/settings/api-keys). - -Once you have the API key, you are all set to use it with Embedchain. - - - -```python main.py -import os -from embedchain import App - -os.environ["TOGETHER_API_KEY"] = "xxx" - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: together - config: - model: togethercomputer/RedPajama-INCITE-7B-Base - temperature: 0.5 - max_tokens: 1000 - top_p: 1 -``` - - - -## Ollama - -Setup Ollama using https://github.com/jmorganca/ollama - - - -```python main.py -import os -os.environ["OLLAMA_HOST"] = "http://127.0.0.1:11434" -from embedchain import App - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: ollama - config: - model: 'llama2' - temperature: 0.5 - top_p: 1 - stream: true - base_url: 'http://localhost:11434' -embedder: - provider: ollama - config: - model: znbang/bge:small-en-v1.5-q8_0 - base_url: http://localhost:11434 - -``` - - - - -## vLLM - -Setup vLLM by following instructions given in [their docs](https://docs.vllm.ai/en/latest/getting_started/installation.html). - - - -```python main.py -import os -from embedchain import App - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: vllm - config: - model: 'meta-llama/Llama-2-70b-hf' - temperature: 0.5 - top_p: 1 - top_k: 10 - stream: true - trust_remote_code: true -``` - - - -## Clarifai - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[clarifai]' -``` - -set the `CLARIFAI_PAT` as environment variable which you can find in the [security page](https://clarifai.com/settings/security). Optionally you can also pass the PAT key as parameters in LLM/Embedder class. - -Now you are all set with exploring Embedchain. - - - -```python main.py -import os -from embedchain import App - -os.environ["CLARIFAI_PAT"] = "XXX" - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") - -#Now let's add some data. -app.add("https://www.forbes.com/profile/elon-musk") - -#Query the app -response = app.query("what college degrees does elon musk have?") -``` -Head to [Clarifai Platform](https://clarifai.com/explore/models?page=1&perPage=24&filterData=%5B%7B%22field%22%3A%22use_cases%22%2C%22value%22%3A%5B%22llm%22%5D%7D%5D) to browse various State-of-the-Art LLM models for your use case. -For passing model inference parameters use `model_kwargs` argument in the config file. Also you can use `api_key` argument to pass `CLARIFAI_PAT` in the config. - -```yaml config.yaml -llm: - provider: clarifai - config: - model: "https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct" - model_kwargs: - temperature: 0.5 - max_tokens: 1000 -embedder: - provider: clarifai - config: - model: "https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15" -``` - - - -## GPT4ALL - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[opensource]' -``` - -GPT4all is a free-to-use, locally running, privacy-aware chatbot. No GPU or internet required. You can use this with Embedchain using the following code: - - - -```python main.py -from embedchain import App - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: gpt4all -``` - - - -## JinaChat - -First, set `JINACHAT_API_KEY` in environment variable which you can obtain from [their platform](https://chat.jina.ai/api). - -Once you have the key, load the app using the config yaml file: - - - -```python main.py -import os -from embedchain import App - -os.environ["JINACHAT_API_KEY"] = "xxx" -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: jina - config: - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false -``` - - - -## Hugging Face - - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[huggingface-hub]' -``` - -First, set `HUGGINGFACE_ACCESS_TOKEN` in environment variable which you can obtain from [their platform](https://huggingface.co/settings/tokens). - -You can load the LLMs from Hugging Face using three ways: - -- [Hugging Face Hub](#hugging-face-hub) -- [Hugging Face Local Pipelines](#hugging-face-local-pipelines) -- [Hugging Face Inference Endpoint](#hugging-face-inference-endpoint) - -### Hugging Face Hub - -To load the model from Hugging Face Hub, use the following code: - - - -```python main.py -import os -from embedchain import App - -os.environ["HUGGINGFACE_ACCESS_TOKEN"] = "xxx" - -config = { - "app": {"config": {"id": "my-app"}}, - "llm": { - "provider": "huggingface", - "config": { - "model": "bigscience/bloom-1b7", - "top_p": 0.5, - "max_length": 200, - "temperature": 0.1, - }, - }, -} - -app = App.from_config(config=config) -``` - - -### Hugging Face Local Pipelines - -If you want to load the locally downloaded model from Hugging Face, you can do so by following the code provided below: - - -```python main.py -from embedchain import App - -config = { - "app": {"config": {"id": "my-app"}}, - "llm": { - "provider": "huggingface", - "config": { - "model": "Trendyol/Trendyol-LLM-7b-chat-v0.1", - "local": True, # Necessary if you want to run model locally - "top_p": 0.5, - "max_tokens": 1000, - "temperature": 0.1, - }, - } -} -app = App.from_config(config=config) -``` - - -### Hugging Face Inference Endpoint - -You can also use [Hugging Face Inference Endpoints](https://huggingface.co/docs/inference-endpoints/index#-inference-endpoints) to access custom endpoints. First, set the `HUGGINGFACE_ACCESS_TOKEN` as above. - -Then, load the app using the config yaml file: - - - -```python main.py -from embedchain import App - -config = { - "app": {"config": {"id": "my-app"}}, - "llm": { - "provider": "huggingface", - "config": { - "endpoint": "https://api-inference.huggingface.co/models/gpt2", - "model_params": {"temprature": 0.1, "max_new_tokens": 100} - }, - }, -} -app = App.from_config(config=config) - -``` - - -Currently only supports `text-generation` and `text2text-generation` for now [[ref](https://api.python.langchain.com/en/latest/llms/langchain_community.llms.huggingface_endpoint.HuggingFaceEndpoint.html?highlight=huggingfaceendpoint#)]. - -See langchain's [hugging face endpoint](https://python.langchain.com/docs/integrations/chat/huggingface#huggingfaceendpoint) for more information. - -## Llama2 - -Llama2 is integrated through [Replicate](https://replicate.com/). Set `REPLICATE_API_TOKEN` in environment variable which you can obtain from [their platform](https://replicate.com/account/api-tokens). - -Once you have the token, load the app using the config yaml file: - - - -```python main.py -import os -from embedchain import App - -os.environ["REPLICATE_API_TOKEN"] = "xxx" - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: llama2 - config: - model: 'a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5' - temperature: 0.5 - max_tokens: 1000 - top_p: 0.5 - stream: false -``` - - -## Vertex AI - -Setup Google Cloud Platform application credentials by following the instruction on [GCP](https://cloud.google.com/docs/authentication/external/set-up-adc). Once setup is done, use the following code to create an app using VertexAI as provider: - - - -```python main.py -from embedchain import App - -# load llm configuration from config.yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: vertexai - config: - model: 'chat-bison' - temperature: 0.5 - top_p: 0.5 -``` - - - -## Mistral AI - -Obtain the Mistral AI api key from their [console](https://console.mistral.ai/). - - - - ```python main.py -os.environ["MISTRAL_API_KEY"] = "xxx" - -app = App.from_config(config_path="config.yaml") - -app.add("https://www.forbes.com/profile/elon-musk") - -response = app.query("what is the net worth of Elon Musk?") -# As of January 16, 2024, Elon Musk's net worth is $225.4 billion. - -response = app.chat("which companies does elon own?") -# Elon Musk owns Tesla, SpaceX, Boring Company, Twitter, and X. - -response = app.chat("what question did I ask you already?") -# You have asked me several times already which companies Elon Musk owns, specifically Tesla, SpaceX, Boring Company, Twitter, and X. -``` - -```yaml config.yaml -llm: - provider: mistralai - config: - model: mistral-tiny - temperature: 0.5 - max_tokens: 1000 - top_p: 1 -embedder: - provider: mistralai - config: - model: mistral-embed -``` - - - -## AWS Bedrock - -### Setup -- Before using the AWS Bedrock LLM, make sure you have the appropriate model access from [Bedrock Console](https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess). -- You will also need to authenticate the `boto3` client by using a method in the [AWS documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials) -- You can optionally export an `AWS_REGION` - - -### Usage - - - -```python main.py -import os -from embedchain import App - -os.environ["AWS_REGION"] = "us-west-2" - -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -llm: - provider: aws_bedrock - config: - model: amazon.titan-text-express-v1 - # check notes below for model_kwargs - model_kwargs: - temperature: 0.5 - topP: 1 - maxTokenCount: 1000 -``` - - -
- - The model arguments are different for each providers. Please refer to the [AWS Bedrock Documentation](https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/providers) to find the appropriate arguments for your model. - - -
- -## Groq - -[Groq](https://groq.com/) is the creator of the world's first Language Processing Unit (LPU), providing exceptional speed performance for AI workloads running on their LPU Inference Engine. - - -### Usage - -In order to use LLMs from Groq, go to their [platform](https://console.groq.com/keys) and get the API key. - -Set the API key as `GROQ_API_KEY` environment variable or pass in your app configuration to use the model as given below in the example. - - - -```python main.py -import os -from embedchain import App - -# Set your API key here or pass as the environment variable -groq_api_key = "gsk_xxxx" - -config = { - "llm": { - "provider": "groq", - "config": { - "model": "mixtral-8x7b-32768", - "api_key": groq_api_key, - "stream": True - } - } -} - -app = App.from_config(config=config) -# Add your data source here -app.add("https://docs.embedchain.ai/sitemap.xml", data_type="sitemap") -app.query("Write a poem about Embedchain") - -# In the realm of data, vast and wide, -# Embedchain stands with knowledge as its guide. -# A platform open, for all to try, -# Building bots that can truly fly. - -# With REST API, data in reach, -# Deployment a breeze, as easy as a speech. -# Updating data sources, anytime, anyday, -# Embedchain's power, never sway. - -# A knowledge base, an assistant so grand, -# Connecting to platforms, near and far. -# Discord, WhatsApp, Slack, and more, -# Embedchain's potential, never a bore. -``` - - -## NVIDIA AI - -[NVIDIA AI Foundation Endpoints](https://www.nvidia.com/en-us/ai-data-science/foundation-models/) let you quickly use NVIDIA's AI models, such as Mixtral 8x7B, Llama 2 etc, through our API. These models are available in the [NVIDIA NGC catalog](https://catalog.ngc.nvidia.com/ai-foundation-models), fully optimized and ready to use on NVIDIA's AI platform. They are designed for high speed and easy customization, ensuring smooth performance on any accelerated setup. - - -### Usage - -In order to use LLMs from NVIDIA AI, create an account on [NVIDIA NGC Service](https://catalog.ngc.nvidia.com/). - -Generate an API key from their dashboard. Set the API key as `NVIDIA_API_KEY` environment variable. Note that the `NVIDIA_API_KEY` will start with `nvapi-`. - -Below is an example of how to use LLM model and embedding model from NVIDIA AI: - - - -```python main.py -import os -from embedchain import App - -os.environ['NVIDIA_API_KEY'] = 'nvapi-xxxx' - -config = { - "app": { - "config": { - "id": "my-app", - }, - }, - "llm": { - "provider": "nvidia", - "config": { - "model": "nemotron_steerlm_8b", - }, - }, - "embedder": { - "provider": "nvidia", - "config": { - "model": "nvolveqa_40k", - "vector_dimension": 1024, - }, - }, -} - -app = App.from_config(config=config) - -app.add("https://www.forbes.com/profile/elon-musk") -answer = app.query("What is the net worth of Elon Musk today?") -# Answer: The net worth of Elon Musk is subject to fluctuations based on the market value of his holdings in various companies. -# As of March 1, 2024, his net worth is estimated to be approximately $210 billion. However, this figure can change rapidly due to stock market fluctuations and other factors. -# Additionally, his net worth may include other assets such as real estate and art, which are not reflected in his stock portfolio. -``` - - -## Token Usage - -You can get the cost of the query by setting `token_usage` to `True` in the config file. This will return the token details: `prompt_tokens`, `completion_tokens`, `total_tokens`, `total_cost`, `cost_currency`. -The list of paid LLMs that support token usage are: -- OpenAI -- Vertex AI -- Anthropic -- Cohere -- Together -- Groq -- Mistral AI -- NVIDIA AI - -Here is an example of how to use token usage: - - -```python main.py -os.environ["OPENAI_API_KEY"] = "xxx" - -app = App.from_config(config_path="config.yaml") - -app.add("https://www.forbes.com/profile/elon-musk") - -response = app.query("what is the net worth of Elon Musk?") -# {'answer': 'Elon Musk's net worth is $209.9 billion as of 6/9/24.', -# 'usage': {'prompt_tokens': 1228, -# 'completion_tokens': 21, -# 'total_tokens': 1249, -# 'total_cost': 0.001884, -# 'cost_currency': 'USD'} -# } - - -response = app.chat("Which companies did Elon Musk found?") -# {'answer': 'Elon Musk founded six companies, including Tesla, which is an electric car maker, SpaceX, a rocket producer, and the Boring Company, a tunneling startup.', -# 'usage': {'prompt_tokens': 1616, -# 'completion_tokens': 34, -# 'total_tokens': 1650, -# 'total_cost': 0.002492, -# 'cost_currency': 'USD'} -# } -``` - -```yaml config.yaml -llm: - provider: openai - config: - model: gpt-4o-mini - temperature: 0.5 - max_tokens: 1000 - token_usage: true -``` - - -If a model is missing and you'd like to add it to `model_prices_and_context_window.json`, please feel free to open a PR. - -
- - diff --git a/embedchain/docs/components/retrieval-methods.mdx b/embedchain/docs/components/retrieval-methods.mdx deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/docs/components/vector-databases.mdx b/embedchain/docs/components/vector-databases.mdx deleted file mode 100644 index c889e1054..000000000 --- a/embedchain/docs/components/vector-databases.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: 🗄️ Vector databases ---- - -## Overview - -Utilizing a vector database alongside Embedchain is a seamless process. All you need to do is configure it within the YAML configuration file. We've provided examples for each supported database below: - - - - - - - - - - - - - diff --git a/embedchain/docs/components/vector-databases/chromadb.mdx b/embedchain/docs/components/vector-databases/chromadb.mdx deleted file mode 100644 index 783dfe890..000000000 --- a/embedchain/docs/components/vector-databases/chromadb.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: ChromaDB ---- - - - -```python main.py -from embedchain import App - -# load chroma configuration from yaml file -app = App.from_config(config_path="config1.yaml") -``` - -```yaml config1.yaml -vectordb: - provider: chroma - config: - collection_name: 'my-collection' - dir: db - allow_reset: true -``` - -```yaml config2.yaml -vectordb: - provider: chroma - config: - collection_name: 'my-collection' - host: localhost - port: 5200 - allow_reset: true -``` - - - - diff --git a/embedchain/docs/components/vector-databases/elasticsearch.mdx b/embedchain/docs/components/vector-databases/elasticsearch.mdx deleted file mode 100644 index 0a354e65f..000000000 --- a/embedchain/docs/components/vector-databases/elasticsearch.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Elasticsearch ---- - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[elasticsearch]' -``` - - -You can configure the Elasticsearch connection by providing either `es_url` or `cloud_id`. If you are using the Elasticsearch Service on Elastic Cloud, you can find the `cloud_id` on the [Elastic Cloud dashboard](https://cloud.elastic.co/deployments). - - -You can authorize the connection to Elasticsearch by providing either `basic_auth`, `api_key`, or `bearer_auth`. - - - -```python main.py -from embedchain import App - -# load elasticsearch configuration from yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -vectordb: - provider: elasticsearch - config: - collection_name: 'es-index' - cloud_id: 'deployment-name:xxxx' - basic_auth: - - elastic - - - verify_certs: false -``` - - - diff --git a/embedchain/docs/components/vector-databases/lancedb.mdx b/embedchain/docs/components/vector-databases/lancedb.mdx deleted file mode 100644 index 97af57dfe..000000000 --- a/embedchain/docs/components/vector-databases/lancedb.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: LanceDB ---- - -## Install Embedchain with LanceDB - -Install Embedchain, LanceDB and related dependencies using the following command: - -```bash -pip install "embedchain[lancedb]" -``` - -LanceDB is a developer-friendly, open source database for AI. From hyper scalable vector search and advanced retrieval for RAG, to streaming training data and interactive exploration of large scale AI datasets. -In order to use LanceDB as vector database, not need to set any key for local use. - -### With OPENAI - - -```python main.py -import os -from embedchain import App - -# set OPENAI_API_KEY as env variable -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -# create Embedchain App and set config -app = App.from_config(config={ - "vectordb": { - "provider": "lancedb", - "config": { - "collection_name": "lancedb-index" - } - } - } -) - -# add data source and start query in -app.add("https://www.forbes.com/profile/elon-musk") - -# query continuously -while(True): - question = input("Enter question: ") - if question in ['q', 'exit', 'quit']: - break - answer = app.query(question) - print(answer) -``` - - - -### With Local LLM - - -```python main.py -from embedchain import Pipeline as App - -# config for Embedchain App -config = { - 'llm': { - 'provider': 'huggingface', - 'config': { - 'model': 'mistralai/Mistral-7B-v0.1', - 'temperature': 0.1, - 'max_tokens': 250, - 'top_p': 0.1, - 'stream': True - } - }, - 'embedder': { - 'provider': 'huggingface', - 'config': { - 'model': 'sentence-transformers/all-mpnet-base-v2' - } - }, - 'vectordb': { - 'provider': 'lancedb', - 'config': { - 'collection_name': 'lancedb-index' - } - } -} - -app = App.from_config(config=config) - -# add data source and start query in -app.add("https://www.tesla.com/ns_videos/2022-tesla-impact-report.pdf") - -# query continuously -while(True): - question = input("Enter question: ") - if question in ['q', 'exit', 'quit']: - break - answer = app.query(question) - print(answer) -``` - - - - - \ No newline at end of file diff --git a/embedchain/docs/components/vector-databases/opensearch.mdx b/embedchain/docs/components/vector-databases/opensearch.mdx deleted file mode 100644 index 8f6866977..000000000 --- a/embedchain/docs/components/vector-databases/opensearch.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: OpenSearch ---- - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[opensearch]' -``` - - - -```python main.py -from embedchain import App - -# load opensearch configuration from yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -vectordb: - provider: opensearch - config: - collection_name: 'my-app' - opensearch_url: 'https://localhost:9200' - http_auth: - - admin - - admin - vector_dimension: 1536 - use_ssl: false - verify_certs: false -``` - - - - diff --git a/embedchain/docs/components/vector-databases/pinecone.mdx b/embedchain/docs/components/vector-databases/pinecone.mdx deleted file mode 100644 index d21ebfeac..000000000 --- a/embedchain/docs/components/vector-databases/pinecone.mdx +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Pinecone ---- - -## Overview - -Install pinecone related dependencies using the following command: - -```bash -pip install --upgrade 'pinecone-client pinecone-text' -``` - -In order to use Pinecone as vector database, set the environment variable `PINECONE_API_KEY` which you can find on [Pinecone dashboard](https://app.pinecone.io/). - - - -```python main.py -from embedchain import App - -# Load pinecone configuration from yaml file -app = App.from_config(config_path="pod_config.yaml") -# Or -app = App.from_config(config_path="serverless_config.yaml") -``` - -```yaml pod_config.yaml -vectordb: - provider: pinecone - config: - metric: cosine - vector_dimension: 1536 - index_name: my-pinecone-index - pod_config: - environment: gcp-starter - metadata_config: - indexed: - - "url" - - "hash" -``` - -```yaml serverless_config.yaml -vectordb: - provider: pinecone - config: - metric: cosine - vector_dimension: 1536 - index_name: my-pinecone-index - serverless_config: - cloud: aws - region: us-west-2 -``` - - - -
- -You can find more information about Pinecone configuration [here](https://docs.pinecone.io/docs/manage-indexes#create-a-pod-based-index). -You can also optionally provide `index_name` as a config param in yaml file to specify the index name. If not provided, the index name will be `{collection_name}-{vector_dimension}`. - - -## Usage - -### Hybrid search - -Here is an example of how you can do hybrid search using Pinecone as a vector database through Embedchain. - -```python -import os - -from embedchain import App - -config = { - 'app': { - "config": { - "id": "ec-docs-hybrid-search" - } - }, - 'vectordb': { - 'provider': 'pinecone', - 'config': { - 'metric': 'dotproduct', - 'vector_dimension': 1536, - 'index_name': 'my-index', - 'serverless_config': { - 'cloud': 'aws', - 'region': 'us-west-2' - }, - 'hybrid_search': True, # Remember to set this for hybrid search - } - } -} - -# Initialize app -app = App.from_config(config=config) - -# Add documents -app.add("/path/to/file.pdf", data_type="pdf_file", namespace="my-namespace") - -# Query -app.query("", namespace="my-namespace") - -# Chat -app.chat("", namespace="my-namespace") -``` - -Under the hood, Embedchain fetches the relevant chunks from the documents you added by doing hybrid search on the pinecone index. -If you have questions on how pinecone hybrid search works, please refer to their [offical documentation here](https://docs.pinecone.io/docs/hybrid-search). - - diff --git a/embedchain/docs/components/vector-databases/qdrant.mdx b/embedchain/docs/components/vector-databases/qdrant.mdx deleted file mode 100644 index cadb42e92..000000000 --- a/embedchain/docs/components/vector-databases/qdrant.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Qdrant ---- - -In order to use Qdrant as a vector database, set the environment variables `QDRANT_URL` and `QDRANT_API_KEY` which you can find on [Qdrant Dashboard](https://cloud.qdrant.io/). - - -```python main.py -from embedchain import App - -# load qdrant configuration from yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -vectordb: - provider: qdrant - config: - collection_name: my_qdrant_index -``` - - - diff --git a/embedchain/docs/components/vector-databases/weaviate.mdx b/embedchain/docs/components/vector-databases/weaviate.mdx deleted file mode 100644 index e5b1d5eda..000000000 --- a/embedchain/docs/components/vector-databases/weaviate.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Weaviate ---- - - -In order to use Weaviate as a vector database, set the environment variables `WEAVIATE_ENDPOINT` and `WEAVIATE_API_KEY` which you can find on [Weaviate dashboard](https://console.weaviate.cloud/dashboard). - - -```python main.py -from embedchain import App - -# load weaviate configuration from yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -vectordb: - provider: weaviate - config: - collection_name: my_weaviate_index -``` - - - diff --git a/embedchain/docs/components/vector-databases/zilliz.mdx b/embedchain/docs/components/vector-databases/zilliz.mdx deleted file mode 100644 index 55c0dbaa7..000000000 --- a/embedchain/docs/components/vector-databases/zilliz.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Zilliz ---- - -Install related dependencies using the following command: - -```bash -pip install --upgrade 'embedchain[milvus]' -``` - -Set the Zilliz environment variables `ZILLIZ_CLOUD_URI` and `ZILLIZ_CLOUD_TOKEN` which you can find it on their [cloud platform](https://cloud.zilliz.com/). - - - -```python main.py -import os -from embedchain import App - -os.environ['ZILLIZ_CLOUD_URI'] = 'https://xxx.zillizcloud.com' -os.environ['ZILLIZ_CLOUD_TOKEN'] = 'xxx' - -# load zilliz configuration from yaml file -app = App.from_config(config_path="config.yaml") -``` - -```yaml config.yaml -vectordb: - provider: zilliz - config: - collection_name: 'zilliz_app' - uri: https://xxxx.api.gcp-region.zillizcloud.com - token: xxx - vector_dim: 1536 - metric_type: L2 -``` - - - - diff --git a/embedchain/docs/contribution/dev.mdx b/embedchain/docs/contribution/dev.mdx deleted file mode 100644 index 3ce71c25c..000000000 --- a/embedchain/docs/contribution/dev.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: '👨‍💻 Development' -description: 'Contribute to Embedchain framework development' ---- - -Thank you for your interest in contributing to the EmbedChain project! We welcome your ideas and contributions to help improve the project. Please follow the instructions below to get started: - -1. **Fork the repository**: Click on the "Fork" button at the top right corner of this repository page. This will create a copy of the repository in your own GitHub account. - -2. **Install the required dependencies**: Ensure that you have the necessary dependencies installed in your Python environment. You can do this by running the following command: - -```bash -make install -``` - -3. **Make changes in the code**: Create a new branch in your forked repository and make your desired changes in the codebase. -4. **Format code**: Before creating a pull request, it's important to ensure that your code follows our formatting guidelines. Run the following commands to format the code: - -```bash -make lint format -``` - -5. **Create a pull request**: When you are ready to contribute your changes, submit a pull request to the EmbedChain repository. Provide a clear and descriptive title for your pull request, along with a detailed description of the changes you have made. - -## Team - -### Authors - -- Taranjeet Singh ([@taranjeetio](https://twitter.com/taranjeetio)) -- Deshraj Yadav ([@deshrajdry](https://twitter.com/deshrajdry)) - -### Citation - -If you utilize this repository, please consider citing it with: - -``` -@misc{embedchain, - author = {Taranjeet Singh, Deshraj Yadav}, - title = {Embechain: The Open Source RAG Framework}, - year = {2023}, - publisher = {GitHub}, - journal = {GitHub repository}, - howpublished = {\url{https://github.com/embedchain/embedchain}}, -} -``` diff --git a/embedchain/docs/contribution/docs.mdx b/embedchain/docs/contribution/docs.mdx deleted file mode 100644 index 7aa846ec5..000000000 --- a/embedchain/docs/contribution/docs.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: '📝 Documentation' -description: 'Contribute to Embedchain docs' ---- - - - **Prerequisite** You should have installed Node.js (version 18.10.0 or - higher). - - -Step 1. Install Mintlify on your OS: - - - -```bash npm -npm i -g mintlify -``` - -```bash yarn -yarn global add mintlify -``` - - - -Step 2. Go to the `docs/` directory (where you can find `mint.json`) and run the following command: - -```bash -mintlify dev -``` - -The documentation website is now available at `http://localhost:3000`. - -### Custom Ports - -Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333: - -```bash -mintlify dev --port 3333 -``` - -You will see an error like this if you try to run Mintlify in a port that's already taken: - -```md -Error: listen EADDRINUSE: address already in use :::3000 -``` - -## Mintlify Versions - -Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production. - - - -```bash npm -npm i -g mintlify@latest -``` - -```bash yarn -yarn global upgrade mintlify -``` - - diff --git a/embedchain/docs/contribution/guidelines.mdx b/embedchain/docs/contribution/guidelines.mdx deleted file mode 100644 index 3c5d557eb..000000000 --- a/embedchain/docs/contribution/guidelines.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: '📋 Guidelines' -url: https://github.com/mem0ai/mem0/blob/main/embedchain/CONTRIBUTING.md ---- \ No newline at end of file diff --git a/embedchain/docs/contribution/python.mdx b/embedchain/docs/contribution/python.mdx deleted file mode 100644 index 47bc84c27..000000000 --- a/embedchain/docs/contribution/python.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: '🐍 Python' -url: https://github.com/embedchain/embedchain ---- \ No newline at end of file diff --git a/embedchain/docs/deployment/fly_io.mdx b/embedchain/docs/deployment/fly_io.mdx deleted file mode 100644 index ed8992915..000000000 --- a/embedchain/docs/deployment/fly_io.mdx +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: 'Fly.io' -description: 'Deploy your RAG application to fly.io platform' ---- - -Embedchain has a nice and simple abstraction on top of the [Fly.io](https://fly.io/) tools to let developers deploy RAG application to fly.io platform seamlessly. - -Follow the instructions given below to deploy your first application quickly: - - -## Step-1: Install flyctl command line - - -```bash OSX -brew install flyctl -``` - -```bash Linux -curl -L https://fly.io/install.sh | sh -``` - -```bash Windows -pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex" -``` - - -Once you have installed the fly.io cli tool, signup/login to their platform using the following command: - - -```bash Sign up -fly auth signup -``` - -```bash Sign in -fly auth login -``` - - -In case you run into issues, refer to official [fly.io docs](https://fly.io/docs/hands-on/install-flyctl/). - -## Step-2: Create RAG app - -We provide a command line utility called `ec` in embedchain that inherits the template for `fly.io` platform and help you deploy the app. Follow the instructions to create a fly.io app using the template provided: - -```bash Install embedchain -pip install embedchain -``` - -```bash Create application -mkdir my-rag-app -ec create --template=fly.io -``` - -This will generate a directory structure like this: - -```bash -├── Dockerfile -├── app.py -├── fly.toml -├── .env -├── .env.example -├── embedchain.json -└── requirements.txt -``` - -Feel free to edit the files as required. -- `Dockerfile`: Defines the steps to setup the application -- `app.py`: Contains API app code -- `fly.toml`: fly.io config file -- `.env`: Contains environment variables for production -- `.env.example`: Contains dummy environment variables (can ignore this file) -- `embedchain.json`: Contains embedchain specific configuration for deployment (you don't need to configure this) -- `requirements.txt`: Contains python dependencies for your application - -## Step-3: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -## Step-4: Deploy to fly.io - -You can deploy to fly.io using the following command: -```bash Deploy app -ec deploy -``` - -Once this step finished, it will provide you with the deployment endpoint where you can access the app live. It will look something like this (Swagger docs): - -You can also check the logs, monitor app status etc on their dashboard by running command `fly dashboard`. - - - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/deployment/gradio_app.mdx b/embedchain/docs/deployment/gradio_app.mdx deleted file mode 100644 index 6c79aa208..000000000 --- a/embedchain/docs/deployment/gradio_app.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: 'Gradio.app' -description: 'Deploy your RAG application to gradio.app platform' ---- - -Embedchain offers a Streamlit template to facilitate the development of RAG chatbot applications in just three easy steps. - -Follow the instructions given below to deploy your first application quickly: - -## Step-1: Create RAG app - -We provide a command line utility called `ec` in embedchain that inherits the template for `gradio.app` platform and help you deploy the app. Follow the instructions to create a gradio.app app using the template provided: - -```bash Install embedchain -pip install embedchain -``` - -```bash Create application -mkdir my-rag-app -ec create --template=gradio.app -``` - -This will generate a directory structure like this: - -```bash -├── app.py -├── embedchain.json -└── requirements.txt -``` - -Feel free to edit the files as required. -- `app.py`: Contains API app code -- `embedchain.json`: Contains embedchain specific configuration for deployment (you don't need to configure this) -- `requirements.txt`: Contains python dependencies for your application - -## Step-2: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -## Step-3: Deploy to gradio.app - -```bash Deploy to gradio.app -ec deploy -``` - -This will run `gradio deploy` which will prompt you questions and deploy your app directly to huggingface spaces. - -gradio app - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/deployment/huggingface_spaces.mdx b/embedchain/docs/deployment/huggingface_spaces.mdx deleted file mode 100644 index 5b8811e41..000000000 --- a/embedchain/docs/deployment/huggingface_spaces.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: 'Huggingface.co' -description: 'Deploy your RAG application to huggingface.co platform' ---- - -With Embedchain, you can directly host your apps in just three steps to huggingface spaces where you can view and deploy your app to the world. - -We support two types of deployment to huggingface spaces: - - - - Streamlit.io - - - Gradio.app - - - -## Using streamlit.io - -### Step 1: Create a new RAG app - -Create a new RAG app using the following command: - -```bash -mkdir my-rag-app -ec create --template=hf/streamlit.io # inside my-rag-app directory -``` - -When you run this for the first time, you'll be asked to login to huggingface.co. Once you login, you'll need to create a **write** token. You can create a write token by going to [huggingface.co settings](https://huggingface.co/settings/token). Once you create a token, you'll be asked to enter the token in the terminal. - -This will also create an `embedchain.json` file in your app directory. Add a `name` key into the `embedchain.json` file. This will be the "repo-name" of your app in huggingface spaces. - -```json embedchain.json -{ - "name": "my-rag-app", - "provider": "hf/streamlit.io" -} -``` - -### Step-2: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -### Step-3: Deploy to huggingface spaces - -```bash Deploy to huggingface spaces -ec deploy -``` - -This will deploy your app to huggingface spaces. You can view your app at `https://huggingface.co/spaces//my-rag-app`. This will get prompted in the terminal once the app is deployed. - -## Using gradio.app - -Similar to streamlit.io, you can deploy your app to gradio.app in just three steps. - -### Step 1: Create a new RAG app - -Create a new RAG app using the following command: - -```bash -mkdir my-rag-app -ec create --template=hf/gradio.app # inside my-rag-app directory -``` - -When you run this for the first time, you'll be asked to login to huggingface.co. Once you login, you'll need to create a **write** token. You can create a write token by going to [huggingface.co settings](https://huggingface.co/settings/token). Once you create a token, you'll be asked to enter the token in the terminal. - -This will also create an `embedchain.json` file in your app directory. Add a `name` key into the `embedchain.json` file. This will be the "repo-name" of your app in huggingface spaces. - -```json embedchain.json -{ - "name": "my-rag-app", - "provider": "hf/gradio.app" -} -``` - -### Step-2: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -### Step-3: Deploy to huggingface spaces - -```bash Deploy to huggingface spaces -ec deploy -``` - -This will deploy your app to huggingface spaces. You can view your app at `https://huggingface.co/spaces//my-rag-app`. This will get prompted in the terminal once the app is deployed. - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/deployment/modal_com.mdx b/embedchain/docs/deployment/modal_com.mdx deleted file mode 100644 index e82d367b6..000000000 --- a/embedchain/docs/deployment/modal_com.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: 'Modal.com' -description: 'Deploy your RAG application to modal.com platform' ---- - -Embedchain has a nice and simple abstraction on top of the [Modal.com](https://modal.com/) tools to let developers deploy RAG application to modal.com platform seamlessly. - -Follow the instructions given below to deploy your first application quickly: - - -## Step-1 Create RAG application: - -We provide a command line utility called `ec` in embedchain that inherits the template for `modal.com` platform and help you deploy the app. Follow the instructions to create a modal.com app using the template provided: - - -```bash Create application -pip install embedchain[modal] -mkdir my-rag-app -ec create --template=modal.com -``` - -This `create` command will open a browser window and ask you to login to your modal.com account and will generate a directory structure like this: - -```bash -├── app.py -├── .env -├── .env.example -├── embedchain.json -└── requirements.txt -``` - -Feel free to edit the files as required. -- `app.py`: Contains API app code -- `.env`: Contains environment variables for production -- `.env.example`: Contains dummy environment variables (can ignore this file) -- `embedchain.json`: Contains embedchain specific configuration for deployment (you don't need to configure this) -- `requirements.txt`: Contains python dependencies for your FastAPI application - -## Step-2: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -## Step-3: Deploy to modal.com - -You can deploy to modal.com using the following command: -```bash Deploy app -ec deploy -``` - -Once this step finished, it will provide you with the deployment endpoint where you can access the app live. It will look something like this (Swagger docs): - - - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/deployment/railway.mdx b/embedchain/docs/deployment/railway.mdx deleted file mode 100644 index ef8a60ab8..000000000 --- a/embedchain/docs/deployment/railway.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: 'Railway.app' -description: 'Deploy your RAG application to railway.app' ---- - -It's easy to host your Embedchain-powered apps and APIs on railway. - -Follow the instructions given below to deploy your first application quickly: - -## Step-1: Create RAG app - -```bash Install embedchain -pip install embedchain -``` - - -**Create a full stack app using Embedchain CLI** - -To use your hosted embedchain RAG app, you can easily set up a FastAPI server that can be used anywhere. -To easily set up a FastAPI server, check out [Get started with Full stack](https://docs.embedchain.ai/get-started/full-stack) page. - -Hosting this server on railway is super easy! - - - -## Step-2: Set up your project - -### With Docker - -You can create a `Dockerfile` in the root of the project, with all the instructions. However, this method is sometimes slower in deployment. - -### Without Docker - -By default, Railway uses Python 3.7. Embedchain requires the python version to be >3.9 in order to install. - -To fix this, create a `.python-version` file in the root directory of your project and specify the correct version - -```bash .python-version -3.10 -``` - -You also need to create a `requirements.txt` file to specify the requirements. - -```bash requirements.txt -python-dotenv -embedchain -fastapi==0.108.0 -uvicorn==0.25.0 -embedchain -beautifulsoup4 -sentence-transformers -``` - -## Step-3: Deploy to Railway 🚀 - -1. Go to https://railway.app and create an account. -2. Create a project by clicking on the "Start a new project" button - -### With Github - -Select `Empty Project` or `Deploy from Github Repo`. - -You should be all set! - -### Without Github - -You can also use the railway CLI to deploy your apps from the terminal, if you don't want to connect a git repository. - -To do this, just run this command in your terminal - -```bash Install and set up railway CLI -npm i -g @railway/cli -railway login -railway link [projectID] -``` - -Finally, run `railway up` to deploy your app. -```bash Deploy -railway up -``` - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/deployment/render_com.mdx b/embedchain/docs/deployment/render_com.mdx deleted file mode 100644 index 81ba7f6df..000000000 --- a/embedchain/docs/deployment/render_com.mdx +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: 'Render.com' -description: 'Deploy your RAG application to render.com platform' ---- - -Embedchain has a nice and simple abstraction on top of the [render.com](https://render.com/) tools to let developers deploy RAG application to render.com platform seamlessly. - -Follow the instructions given below to deploy your first application quickly: - -## Step-1: Install `render` command line - - -```bash OSX -brew tap render-oss/render -brew install render -``` - -```bash Linux -# Make sure you have deno installed -> https://docs.render.com/docs/cli#from-source-unsupported-operating-systems -git clone https://github.com/render-oss/render-cli -cd render-cli -make deps -deno task run -deno compile -``` - -```bash Windows -choco install rendercli -``` - - -In case you run into issues, refer to official [render.com docs](https://docs.render.com/docs/cli). - -## Step-2 Create RAG application: - -We provide a command line utility called `ec` in embedchain that inherits the template for `render.com` platform and help you deploy the app. Follow the instructions to create a render.com app using the template provided: - - -```bash Create application -pip install embedchain -mkdir my-rag-app -ec create --template=render.com -``` - -This `create` command will open a browser window and ask you to login to your render.com account and will generate a directory structure like this: - -```bash -├── app.py -├── .env -├── render.yaml -├── embedchain.json -└── requirements.txt -``` - -Feel free to edit the files as required. -- `app.py`: Contains API app code -- `.env`: Contains environment variables for production -- `render.yaml`: Contains render.com specific configuration for deployment (configure this according to your needs, follow [this](https://docs.render.com/docs/blueprint-spec) for more info) -- `embedchain.json`: Contains embedchain specific configuration for deployment (you don't need to configure this) -- `requirements.txt`: Contains python dependencies for your application - -## Step-3: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -## Step-4: Deploy to render.com - -Before deploying to render.com, you only have to set up one thing. - -In the render.yaml file, make sure to modify the repo key by inserting the URL of your Git repository where your application will be hosted. You can create a repository from [GitHub](https://github.com) or [GitLab](https://gitlab.com/users/sign_in). - -After that, you're ready to deploy on render.com. - -```bash Deploy app -ec deploy -``` - -When you run this, it should open up your render dashboard and you can see the app being deployed. You can find your hosted link over there only. - -You can also check the logs, monitor app status etc on their dashboard by running command `render dashboard`. - - - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/deployment/streamlit_io.mdx b/embedchain/docs/deployment/streamlit_io.mdx deleted file mode 100644 index 93dde7400..000000000 --- a/embedchain/docs/deployment/streamlit_io.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: 'Streamlit.io' -description: 'Deploy your RAG application to streamlit.io platform' ---- - -Embedchain offers a Streamlit template to facilitate the development of RAG chatbot applications in just three easy steps. - -Follow the instructions given below to deploy your first application quickly: - -## Step-1: Create RAG app - -We provide a command line utility called `ec` in embedchain that inherits the template for `streamlit.io` platform and help you deploy the app. Follow the instructions to create a streamlit.io app using the template provided: - -```bash Install embedchain -pip install embedchain -``` - -```bash Create application -mkdir my-rag-app -ec create --template=streamlit.io -``` - -This will generate a directory structure like this: - -```bash -├── .streamlit -│ └── secrets.toml -├── app.py -├── embedchain.json -└── requirements.txt -``` - -Feel free to edit the files as required. -- `app.py`: Contains API app code -- `.streamlit/secrets.toml`: Contains secrets for your application -- `embedchain.json`: Contains embedchain specific configuration for deployment (you don't need to configure this) -- `requirements.txt`: Contains python dependencies for your application - -Add your `OPENAI_API_KEY` in `.streamlit/secrets.toml` file to run and deploy the app. - -## Step-2: Test app locally - -You can run the app locally by simply doing: - -```bash Run locally -pip install -r requirements.txt -ec dev -``` - -## Step-3: Deploy to streamlit.io - -![Streamlit App deploy button](https://github.com/embedchain/embedchain/assets/73601258/90658e28-29e5-4ceb-9659-37ff8b861a29) - -Use the deploy button from the streamlit website to deploy your app. - -You can refer this [guide](https://docs.streamlit.io/streamlit-community-cloud/deploy-your-app) if you run into any problems. - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/development.mdx b/embedchain/docs/development.mdx deleted file mode 100644 index 878300893..000000000 --- a/embedchain/docs/development.mdx +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: 'Development' -description: 'Learn how to preview changes locally' ---- - - - **Prerequisite** You should have installed Node.js (version 18.10.0 or - higher). - - -Step 1. Install Mintlify on your OS: - - - -```bash npm -npm i -g mintlify -``` - -```bash yarn -yarn global add mintlify -``` - - - -Step 2. Go to the docs are located (where you can find `mint.json`) and run the following command: - -```bash -mintlify dev -``` - -The documentation website is now available at `http://localhost:3000`. - -### Custom Ports - -Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333: - -```bash -mintlify dev --port 3333 -``` - -You will see an error like this if you try to run Mintlify in a port that's already taken: - -```md -Error: listen EADDRINUSE: address already in use :::3000 -``` - -## Mintlify Versions - -Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production. - - - -```bash npm -npm i -g mintlify@latest -``` - -```bash yarn -yarn global upgrade mintlify -``` - - - -## Deployment - - - Unlimited editors available under the [Startup - Plan](https://mintlify.com/pricing) - - -You should see the following if the deploy successfully went through: - - - - - -## Troubleshooting - -Here's how to solve some common problems when working with the CLI. - - - - Update to Node v18. Run `mintlify install` and try again. - - -Go to the `C:/Users/Username/.mintlify/` directory and remove the `mint` -folder. Then Open the Git Bash in this location and run `git clone -https://github.com/mintlify/mint.git`. - -Repeat step 3. - - - - Try navigating to the root of your device and delete the ~/.mintlify folder. - Then run `mintlify dev` again. - - - -Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/command-line) diff --git a/embedchain/docs/examples/chat-with-PDF.mdx b/embedchain/docs/examples/chat-with-PDF.mdx deleted file mode 100644 index ad8fb9a5b..000000000 --- a/embedchain/docs/examples/chat-with-PDF.mdx +++ /dev/null @@ -1,32 +0,0 @@ -### Embedchain Chat with PDF App - -You can easily create and deploy your own `chat-pdf` App using Embedchain. - -Here are few simple steps for you to create and deploy your app: - -1. Fork the embedchain repo from [Github](https://github.com/embedchain/embedchain). - - -If you run into problems with forking, please refer to [github docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) for forking a repo. - - -2. Navigate to `chat-pdf` example app from your forked repo: - -```bash -cd /examples/chat-pdf -``` - -3. Run your app in development environment with simple commands - -```bash -pip install -r requirements.txt -ec dev -``` - -Feel free to improve our simple `chat-pdf` streamlit app and create pull request to showcase your app [here](https://docs.embedchain.ai/examples/showcase) - -4. You can easily deploy your app using Streamlit interface - -Connect your Github account with Streamlit and refer this [guide](https://docs.streamlit.io/streamlit-community-cloud/deploy-your-app) to deploy your app. - -You can also use the deploy button from your streamlit website you see when running `ec dev` command. diff --git a/embedchain/docs/examples/community/showcase.mdx b/embedchain/docs/examples/community/showcase.mdx deleted file mode 100644 index d8b511919..000000000 --- a/embedchain/docs/examples/community/showcase.mdx +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: '🎪 Community showcase' ---- - -Embedchain community has been super active in creating demos on top of Embedchain. On this page, we showcase all the apps, blogs, videos, and tutorials created by the community. ❤️ - -## Apps - -### Open Source - -- [My GSoC23 bot- Streamlit chat](https://github.com/lucifertrj/EmbedChain_GSoC23_BOT) by Tarun Jain -- [Discord Bot for LLM chat](https://github.com/Reidond/discord_bots_playground/tree/c8b0c36541e4b393782ee506804c4b6962426dd6/python/chat-channel-bot) by Reidond -- [EmbedChain-Streamlit-Docker App](https://github.com/amjadraza/embedchain-streamlit-app) by amjadraza -- [Harry Potter Philosphers Stone Bot](https://github.com/vinayak-kempawad/Harry_Potter_Philosphers_Stone_Bot/) by Vinayak Kempawad, ([LinkedIn post](https://www.linkedin.com/feed/update/urn:li:activity:7080907532155686912/)) -- [LLM bot trained on own messages](https://github.com/Harin329/harinBot) by Hao Wu - -### Closed Source - -- [Taobot.io](https://taobot.io) - chatbot & knowledgebase hybrid by [cachho](https://github.com/cachho) -- [Create Instant ChatBot 🤖 using embedchain](https://databutton.com/v/h3e680h9) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1674704745154641920/)) -- [JOBO 🤖 — The AI-driven sidekick to craft your resume](https://try-jobo.com/) by Enrico Willemse, ([LinkedIn Post](https://www.linkedin.com/posts/enrico-willemse_jobai-gptfun-embedchain-activity-7090340080879374336-ueLB/)) -- [Explore Your Knowledge Base: Interactive chats over various forms of documents](https://chatdocs.dkedar.com/) by Kedar Dabhadkar, ([LinkedIn Post](https://www.linkedin.com/posts/dkedar7_machinelearning-llmops-activity-7092524836639424513-2O3L/)) -- [Chatbot trained on 1000+ videos of Ester hicks the co-author behind the famous book Secret](https://ask-abraham.thoughtseed.repl.co) by Mohan Kumar - - -## Templates - -### Replit -- [Embedchain Chat Bot](https://replit.com/@taranjeet1/Embedchain-Chat-Bot) by taranjeetio -- [Embedchain Memory Chat Bot Template](https://replit.com/@taranjeetio/Embedchain-Memory-Chat-Bot-Template) by taranjeetio -- [Chatbot app to demonstrate question-answering using retrieved information](https://replit.com/@AllisonMorrell/EmbedChainlitPublic) by Allison Morrell, ([LinkedIn Post](https://www.linkedin.com/posts/allison-morrell-2889275a_retrievalbot-screenshots-activity-7080339991754649600-wihZ/)) - -## Posts - -### Blogs - -- [Customer Service LINE Bot](https://www.evanlin.com/langchain-embedchain/) by Evan Lin -- [Chatbot in Under 5 mins using Embedchain](https://medium.com/@ayush.wattal/chatbot-in-under-5-mins-using-embedchain-a4f161fcf9c5) by Ayush Wattal -- [Understanding what the LLM framework embedchain does](https://zenn.dev/hijikix/articles/4bc8d60156a436) by Daisuke Hashimoto -- [In bed with GPT and Node.js](https://dev.to/worldlinetech/in-bed-with-gpt-and-nodejs-4kh2) by Raphaël Semeteys, ([LinkedIn Post](https://www.linkedin.com/posts/raphaelsemeteys_in-bed-with-gpt-and-nodejs-activity-7088113552326029313-nn87/)) -- [Using Embedchain — A powerful LangChain Python wrapper to build Chat Bots even faster!⚡](https://medium.com/@avra42/using-embedchain-a-powerful-langchain-python-wrapper-to-build-chat-bots-even-faster-35c12994a360) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1686767751560310784/)) -- [What is the Embedchain library?](https://jahaniwww.com/%da%a9%d8%aa%d8%a7%d8%a8%d8%ae%d8%a7%d9%86%d9%87-embedchain/) by Ali Jahani, ([LinkedIn Post](https://www.linkedin.com/posts/ajahani_aepaetaeqaexaggahyaeu-aetaexaesabraeaaeqaepaeu-activity-7097605202135904256-ppU-/)) -- [LangChain is Nice, But Have You Tried EmbedChain ?](https://medium.com/thoughts-on-machine-learning/langchain-is-nice-but-have-you-tried-embedchain-215a34421cde) by FS Ndzomga, ([Tweet](https://twitter.com/ndzfs/status/1695583640372035951/)) -- [Simplest Method to Build a Custom Chatbot with GPT-3.5 (via Embedchain)](https://www.ainewsletter.today/p/simplest-method-to-build-a-custom) by Arjun, ([Tweet](https://twitter.com/aiguy_arjun/status/1696393808467091758/)) - -### LinkedIn - -- [What is embedchain](https://www.linkedin.com/posts/activity-7079393104423698432-wRyi/) by Rithesh Sreenivasan -- [Building a chatbot with EmbedChain](https://www.linkedin.com/posts/activity-7078434598984060928-Zdso/) by Lior Sinclair -- [Making chatbot without vs with embedchain](https://www.linkedin.com/posts/kalyanksnlp_llms-chatbots-langchain-activity-7077453416221863936-7N1L/) by Kalyan KS -- [EmbedChain - very intuitive, first you index your data and then query!](https://www.linkedin.com/posts/shubhamsaboo_embedchain-a-framework-to-easily-create-activity-7079535460699557888-ad1X/) by Shubham Saboo -- [EmbedChain - Harnessing power of LLM](https://www.linkedin.com/posts/uditsaini_chatbotrevolution-llmpoweredbots-embedchainframework-activity-7077520356827181056-FjTK/) by Udit S. -- [AI assistant for ABBYY Vantage](https://www.linkedin.com/posts/maximevermeir_llm-github-abbyy-activity-7081658972071424000-fXfZ/) by Maxime V. -- [About embedchain](https://www.linkedin.com/feed/update/urn:li:activity:7080984218914189312/) by Morris Lee -- [How to use Embedchain](https://www.linkedin.com/posts/nehaabansal_github-embedchainembedchain-framework-activity-7085830340136595456-kbW5/) by Neha Bansal -- [Youtube/Webpage summary for Energy Study](https://www.linkedin.com/posts/bar%C4%B1%C5%9F-sanl%C4%B1-34b82715_enerji-python-activity-7082735341563977730-Js0U/) by Barış Sanlı, ([Tweet](https://twitter.com/barissanli/status/1676968784979193857/)) -- [Demo: How to use Embedchain? (Contains Collab Notebook link)](https://www.linkedin.com/posts/liorsinclair_embedchain-is-getting-a-lot-of-traction-because-activity-7103044695995424768-RckT/) by Lior Sinclair - -### Twitter - -- [What is embedchain](https://twitter.com/AlphaSignalAI/status/1672668574450847745) by Lior -- [Building a chatbot with Embedchain](https://twitter.com/Saboo_Shubham_/status/1673537044419686401) by Shubham Saboo -- [Chatbot docker image behind an API with yaml configs with Embedchain](https://twitter.com/tricalt/status/1678411430192730113/) by Vasilije -- [Build AI powered PDF chatbot with just five lines of Python code with Embedchain!](https://twitter.com/Saboo_Shubham_/status/1676627104866156544/) by Shubham Saboo -- [Chatbot against a youtube video using embedchain](https://twitter.com/smaameri/status/1675201443043704834/) by Sami Maameri -- [Highlights of EmbedChain](https://twitter.com/carl_AIwarts/status/1673542204328120321/) by carl_AIwarts -- [Build Llama-2 chatbot in less than 5 minutes](https://twitter.com/Saboo_Shubham_/status/1682168956918833152/) by Shubham Saboo -- [All cool features of embedchain](https://twitter.com/DhravyaShah/status/1683497882438217728/) by Dhravya Shah, ([LinkedIn Post](https://www.linkedin.com/posts/dhravyashah_what-if-i-tell-you-that-you-can-make-an-ai-activity-7089459599287726080-ZIYm/)) -- [Read paid Medium articles for Free using embedchain](https://twitter.com/kumarkaushal_/status/1688952961622585344) by Kaushal Kumar - -## Videos - -- [Embedchain in one shot](https://www.youtube.com/watch?v=vIhDh7H73Ww&t=82s) by AI with Tarun -- [embedChain Create LLM powered bots over any dataset Python Demo Tesla Neurallink Chatbot Example](https://www.youtube.com/watch?v=bJqAn22a6Gc) by Rithesh Sreenivasan -- [Embedchain - NEW 🔥 Langchain BABY to build LLM Bots](https://www.youtube.com/watch?v=qj_GNQ06I8o) by 1littlecoder -- [EmbedChain -- NEW!: Build LLM-Powered Bots with Any Dataset](https://www.youtube.com/watch?v=XmaBezzGHu4) by DataInsightEdge -- [Chat With Your PDFs in less than 10 lines of code! EMBEDCHAIN tutorial](https://www.youtube.com/watch?v=1ugkcsAcw44) by Phani Reddy -- [How To Create A Custom Knowledge AI Powered Bot | Install + How To Use](https://www.youtube.com/watch?v=VfCrIiAst-c) by The Ai Solopreneur -- [Build Custom Chatbot in 6 min with this Framework [Beginner Friendly]](https://www.youtube.com/watch?v=-8HxOpaFySM) by Maya Akim -- [embedchain-streamlit-app](https://www.youtube.com/watch?v=3-9GVd-3v74) by Amjad Raza -- [🤖CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN - a LangChain wrapper, in few lines of code !](https://www.youtube.com/watch?v=Mp7zJe4TIdM) by Avra -- [Building resource-driven LLM-powered bots with Embedchain](https://www.youtube.com/watch?v=IVfcAgxTO4I) by BugBytes -- [embedchain-streamlit-demo](https://www.youtube.com/watch?v=yJAWB13FhYQ) by Amjad Raza -- [Embedchain - create your own AI chatbots using open source models](https://www.youtube.com/shorts/O3rJWKwSrWE) by Dhravya Shah -- [AI ChatBot in 5 lines Python Code](https://www.youtube.com/watch?v=zjWvLJLksv8) by Data Engineering -- [Interview with Karl Marx](https://www.youtube.com/watch?v=5Y4Tscwj1xk) by Alexander Ray Williams -- [Vlog where we try to build a bot based on our content on the internet](https://www.youtube.com/watch?v=I2w8CWM3bx4) by DV, ([Tweet](https://twitter.com/dvcoolster/status/1688387017544261632)) -- [CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN|STREAMLIT with MEMORY |All OPENSOURCE](https://www.youtube.com/watch?v=TqQIHWoWTDQ&pp=ygUKZW1iZWRjaGFpbg%3D%3D) by DataInsightEdge -- [Build POWERFUL LLM Bots EASILY with Your Own Data - Embedchain - Langchain 2.0? (Tutorial)](https://www.youtube.com/watch?v=jE24Y_GasE8) by WorldofAI, ([Tweet](https://twitter.com/intheworldofai/status/1696229166922780737)) -- [Embedchain: An AI knowledge base assistant for customizing enterprise private data, which can be connected to discord, whatsapp, slack, tele and other terminals (with gradio to build a request interface) in Chinese](https://www.youtube.com/watch?v=5RZzCJRk-d0) by AIGC LINK -- [Embedchain Introduction](https://www.youtube.com/watch?v=Jet9zAqyggI) by Fahd Mirza - -## Mentions - -### Github repos - -- [Awesome-LLM](https://github.com/Hannibal046/Awesome-LLM) -- [awesome-chatgpt-api](https://github.com/reorx/awesome-chatgpt-api) -- [awesome-langchain](https://github.com/kyrolabs/awesome-langchain) -- [Awesome-Prompt-Engineering](https://github.com/promptslab/Awesome-Prompt-Engineering) -- [awesome-chatgpt](https://github.com/eon01/awesome-chatgpt) -- [Awesome-LLMOps](https://github.com/tensorchord/Awesome-LLMOps) -- [awesome-generative-ai](https://github.com/filipecalegario/awesome-generative-ai) -- [awesome-gpt](https://github.com/formulahendry/awesome-gpt) -- [awesome-ChatGPT-repositories](https://github.com/taishi-i/awesome-ChatGPT-repositories) -- [awesome-gpt-prompt-engineering](https://github.com/snwfdhmp/awesome-gpt-prompt-engineering) -- [awesome-chatgpt](https://github.com/awesome-chatgpt/awesome-chatgpt) -- [awesome-llm-and-aigc](https://github.com/sjinzh/awesome-llm-and-aigc) -- [awesome-compbio-chatgpt](https://github.com/csbl-br/awesome-compbio-chatgpt) -- [Awesome-LLM4Tool](https://github.com/OpenGVLab/Awesome-LLM4Tool) - -## Meetups - -- [Dash and ChatGPT: Future of AI-enabled apps 30/08/23](https://go.plotly.com/dash-chatgpt) -- [Pie & AI: Bangalore - Build end-to-end LLM app using Embedchain 01/09/23](https://www.eventbrite.com/e/pie-ai-bangalore-build-end-to-end-llm-app-using-embedchain-tickets-698045722547) diff --git a/embedchain/docs/examples/discord_bot.mdx b/embedchain/docs/examples/discord_bot.mdx deleted file mode 100644 index 247f3c634..000000000 --- a/embedchain/docs/examples/discord_bot.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: "🤖 Discord Bot" ---- - -### 🔑 Keys Setup - -- Set your `OPENAI_API_KEY` in your variables.env file. -- Go to [https://discord.com/developers/applications/](https://discord.com/developers/applications/) and click on `New Application`. -- Enter the name for your bot, accept the terms and click on `Create`. On the resulting page, enter the details of your bot as you like. -- On the left sidebar, click on `Bot`. Under the heading `Privileged Gateway Intents`, toggle all 3 options to ON position. Save your changes. -- Now click on `Reset Token` and copy the token value. Set it as `DISCORD_BOT_TOKEN` in .env file. -- On the left sidebar, click on `OAuth2` and go to `General`. -- Set `Authorization Method` to `In-app Authorization`. Under `Scopes` select `bot`. -- Under `Bot Permissions` allow the following and then click on `Save Changes`. - -```text -Send Messages (under Text Permissions) -``` - -- Now under `OAuth2` and go to `URL Generator`. Under `Scopes` select `bot`. -- Under `Bot Permissions` set the same permissions as above. -- Now scroll down and copy the `Generated URL`. Paste it in a browser window and select the Server where you want to add the bot. -- Click on `Continue` and authorize the bot. -- 🎉 The bot has been successfully added to your server. But it's still offline. - -### Take the bot online - - - - ```bash - docker run --name discord-bot -e OPENAI_API_KEY=sk-xxx -e DISCORD_BOT_TOKEN=xxx -p 8080:8080 embedchain/discord-bot:latest - ``` - - - ```bash - pip install --upgrade "embedchain[discord]" - - python -m embedchain.bots.discord - - # or if you prefer to see the question and not only the answer, run it with - python -m embedchain.bots.discord --include-question - ``` - - - -### 🚀 Usage Instructions - -- Go to the server where you have added your bot. - ![Slash commands interaction with bot](https://github.com/embedchain/embedchain/assets/73601258/bf1414e3-d408-4863-b0d2-ef382a76467e) -- You can add data sources to the bot using the slash command: - -```text -/ec add -``` - -- You can ask your queries from the bot using the slash command: - -```text -/ec query -``` - -- You can chat with the bot using the slash command: - -```text -/ec chat -``` - -📝 Note: To use the bot privately, you can message the bot directly by right clicking the bot and selecting `Message`. - -🎉 Happy Chatting! 🎉 diff --git a/embedchain/docs/examples/nextjs-assistant.mdx b/embedchain/docs/examples/nextjs-assistant.mdx deleted file mode 100644 index 86f82fb4f..000000000 --- a/embedchain/docs/examples/nextjs-assistant.mdx +++ /dev/null @@ -1,124 +0,0 @@ -Fork the Embedchain repo on [Github](https://github.com/embedchain/embedchain) to create your own NextJS discord and slack bot powered by Embedchain. - -If you run into problems with forking, please refer to [github docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) for forking a repo. - -We will work from the `examples/nextjs` folder so change your current working directory by running the command - `cd /examples/nextjs` - -# Installation - -First, lets start by install all the required packages and dependencies. - -- Install all the required python packages by running ```pip install -r requirements.txt``` - -- We will use [Fly.io](https://fly.io/) to deploy our embedchain app, discord and slack bot. Follow the step one to install [Fly.io CLI](https://docs.embedchain.ai/deployment/fly_io#step-1-install-flyctl-command-line) - -# Developement - -## Embedchain App - -First, we need an Embedchain app powered with the knowledge of NextJS. We have already created an embedchain app using FastAPI in `ec_app` folder for you. Feel free to ingest data of your choice to power the App. - - -Navigate to `ec_app` folder and create `.env` file in this folder and set your OpenAI API key as shown in `.env.example` file. If you want to use other open-source models, feel free to use the app config in `app.py`. More details for using custom configuration for Embedchain app is [available here](https://docs.embedchain.ai/api-reference/advanced/configuration). - - -Before running the ec commands to develope the app, open `fly.toml` file and update the `name` variable to something unique. This is important as `fly.io` requires users to provide a globally unique deployment app names. - -Now, we need to launch this application with fly.io. You can see your app on [fly.io dashboard](https://fly.io/dashboard). Run the following command to launch your app on fly.io: -```bash -fly launch --no-deploy -``` - -To run the app in development, run the following command: - -```bash -ec dev -``` - -Run `ec deploy` to deploy your app on Fly.io. Once you deploy your app, save the endpoint on which our discord and slack bot will send requests. - - -## Discord bot - -For discord bot, you will need to create the bot on discord developer portal and get the discord bot token and your discord bot name. - -While keeping in mind the following note, create the discord bot by following the instructions from our [discord bot docs](https://docs.embedchain.ai/examples/discord_bot) and get discord bot token. - - -You do not need to set `OPENAI_API_KEY` to run this discord bot. Follow the remaining instructions to create a discord bot app. We recommend you to give the following sets of bot permissions to run the discord bot without errors: - -``` -(General Permissions) -Read Message/View Channels - -(Text Permissions) -Send Messages -Create Public Thread -Create Private Thread -Send Messages in Thread -Manage Threads -Embed Links -Read Message History -``` - - -Once you have your discord bot token and discord app name. Navigate to `nextjs_discord` folder and create `.env` file and define your discord bot token, discord bot name and endpoint of your embedchain app as shown in `.env.example` file. - -To run the app in development: - -```bash -python app.py -``` - -Before deploying the app, open `fly.toml` file and update the `name` variable to something unique. This is important as `fly.io` requires users to provide a globally unique deployment app names. - -Now, we need to launch this application with fly.io. You can see your app on [fly.io dashboard](https://fly.io/dashboard). Run the following command to launch your app on fly.io: -```bash -fly launch --no-deploy -``` - -Run `ec deploy` to deploy your app on Fly.io. Once you deploy your app, your discord bot will be live! - - -## Slack bot - -For Slack bot, you will need to create the bot on slack developer portal and get the slack bot token and slack app token. - -### Setup - -- Create a workspace on Slack if you don't have one already by clicking [here](https://slack.com/intl/en-in/). -- Create a new App on your Slack account by going [here](https://api.slack.com/apps). -- Select `From Scratch`, then enter the Bot Name and select your workspace. -- Go to `App Credentials` section on the `Basic Information` tab from the left sidebar, create your app token and save it in your `.env` file as `SLACK_APP_TOKEN`. -- Go to `Socket Mode` tab from the left sidebar and enable the socket mode to listen to slack message from your workspace. -- (Optional) Under the `App Home` tab you can change your App display name and default name. -- Navigate to `Event Subscription` tab, and enable the event subscription so that we can listen to slack events. -- Once you enable the event subscription, you will need to subscribe to bot events to authorize the bot to listen to app mention events of the bot. Do that by tapping on `Add Bot User Event` button and select `app_mention`. -- On the left Sidebar, go to `OAuth and Permissions` and add the following scopes under `Bot Token Scopes`: -```text -app_mentions:read -channels:history -channels:read -chat:write -emoji:read -reactions:write -reactions:read -``` -- Now select the option `Install to Workspace` and after it's done, copy the `Bot User OAuth Token` and set it in your `.env` file as `SLACK_BOT_TOKEN`. - -Once you have your slack bot token and slack app token. Navigate to `nextjs_slack` folder and create `.env` file and define your slack bot token, slack app token and endpoint of your embedchain app as shown in `.env.example` file. - -To run the app in development: - -```bash -python app.py -``` - -Before deploying the app, open `fly.toml` file and update the `name` variable to something unique. This is important as `fly.io` requires users to provide a globally unique deployment app names. - -Now, we need to launch this application with fly.io. You can see your app on [fly.io dashboard](https://fly.io/dashboard). Run the following command to launch your app on fly.io: -```bash -fly launch --no-deploy -``` - -Run `ec deploy` to deploy your app on Fly.io. Once you deploy your app, your slack bot will be live! diff --git a/embedchain/docs/examples/notebooks-and-replits.mdx b/embedchain/docs/examples/notebooks-and-replits.mdx deleted file mode 100644 index 2da7208a4..000000000 --- a/embedchain/docs/examples/notebooks-and-replits.mdx +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Notebooks & Replits ---- - -# Explore awesome apps - -Check out the remarkable work accomplished using [Embedchain](https://app.embedchain.ai/custom-gpts/). - -## Collection of Google colab notebook and Replit links for users - -Get started with Embedchain by trying out the examples below. You can run the examples in your browser using Google Colab or Replit. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LLMGoogle ColabReplit
OpenAIOpen In ColabTry with Replit Badge
AnthropicOpen In ColabTry with Replit Badge
Azure OpenAIOpen In ColabTry with Replit Badge
VertexAIOpen In ColabTry with Replit Badge
CohereOpen In ColabTry with Replit Badge
TogetherOpen In Colab
OllamaOpen In Colab
Hugging FaceOpen In ColabTry with Replit Badge
JinaChatOpen In ColabTry with Replit Badge
GPT4AllOpen In ColabTry with Replit Badge
Llama2Open In ColabTry with Replit Badge
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Embedding modelGoogle ColabReplit
OpenAIOpen In ColabTry with Replit Badge
VertexAIOpen In ColabTry with Replit Badge
GPT4AllOpen In ColabTry with Replit Badge
Hugging FaceOpen In ColabTry with Replit Badge
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vector DBGoogle ColabReplit
ChromaDBOpen In ColabTry with Replit Badge
ElasticsearchOpen In ColabTry with Replit Badge
OpensearchOpen In ColabTry with Replit Badge
PineconeOpen In ColabTry with Replit Badge
\ No newline at end of file diff --git a/embedchain/docs/examples/openai-assistant.mdx b/embedchain/docs/examples/openai-assistant.mdx deleted file mode 100644 index ffd312fa7..000000000 --- a/embedchain/docs/examples/openai-assistant.mdx +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: 'OpenAI Assistant' ---- - -OpenAI Logo - -Embedchain now supports [OpenAI Assistants API](https://platform.openai.com/docs/assistants/overview) which allows you to build AI assistants within your own applications. An Assistant has instructions and can leverage models, tools, and knowledge to respond to user queries. - -At a high level, an integration of the Assistants API has the following flow: - -1. Create an Assistant in the API by defining custom instructions and picking a model -2. Create a Thread when a user starts a conversation -3. Add Messages to the Thread as the user ask questions -4. Run the Assistant on the Thread to trigger responses. This automatically calls the relevant tools. - -Creating an OpenAI Assistant using Embedchain is very simple 3 step process. - -## Step 1: Create OpenAI Assistant - -Make sure that you have `OPENAI_API_KEY` set in the environment variable. - -```python Initialize -from embedchain.store.assistants import OpenAIAssistant - -assistant = OpenAIAssistant( - name="OpenAI DevDay Assistant", - instructions="You are an organizer of OpenAI DevDay", -) -``` - -If you want to use the existing assistant, you can do something like this: - -```python Initialize -# Load an assistant and create a new thread -assistant = OpenAIAssistant(assistant_id="asst_xxx") - -# Load a specific thread for an assistant -assistant = OpenAIAssistant(assistant_id="asst_xxx", thread_id="thread_xxx") -``` - -## Step-2: Add data to thread - -You can add any custom data source that is supported by Embedchain. Else, you can directly pass the file path on your local system and Embedchain propagates it to OpenAI Assistant. -```python Add data -assistant.add("/path/to/file.pdf") -assistant.add("https://www.youtube.com/watch?v=U9mJuUkhUzk") -assistant.add("https://openai.com/blog/new-models-and-developer-products-announced-at-devday") -``` - -## Step-3: Chat with your Assistant -```python Chat -assistant.chat("How much OpenAI credits were offered to attendees during OpenAI DevDay?") -# Response: 'Every attendee of OpenAI DevDay 2023 was offered $500 in OpenAI credits.' -``` - -You can try it out yourself using the following Google Colab notebook: - - - Open in Colab - diff --git a/embedchain/docs/examples/opensource-assistant.mdx b/embedchain/docs/examples/opensource-assistant.mdx deleted file mode 100644 index f4dcaa521..000000000 --- a/embedchain/docs/examples/opensource-assistant.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: 'Open-Source AI Assistant' ---- - -Embedchain also provides support for creating Open-Source AI Assistants (similar to [OpenAI Assistants API](https://platform.openai.com/docs/assistants/overview)) which allows you to build AI assistants within your own applications using any LLM (OpenAI or otherwise). An Assistant has instructions and can leverage models, tools, and knowledge to respond to user queries. - -At a high level, the Open-Source AI Assistants API has the following flow: - -1. Create an AI Assistant by picking a model -2. Create a Thread when a user starts a conversation -3. Add Messages to the Thread as the user ask questions -4. Run the Assistant on the Thread to trigger responses. This automatically calls the relevant tools. - -Creating an Open-Source AI Assistant is a simple 3 step process. - -## Step 1: Instantiate AI Assistant - -```python Initialize -from embedchain.store.assistants import AIAssistant - -assistant = AIAssistant( - name="My Assistant", - data_sources=[{"source": "https://www.youtube.com/watch?v=U9mJuUkhUzk"}]) -``` - -If you want to use the existing assistant, you can do something like this: - -```python Initialize -# Load an assistant and create a new thread -assistant = AIAssistant(assistant_id="asst_xxx") - -# Load a specific thread for an assistant -assistant = AIAssistant(assistant_id="asst_xxx", thread_id="thread_xxx") -``` - -## Step-2: Add data to thread - -You can add any custom data source that is supported by Embedchain. Else, you can directly pass the file path on your local system and Embedchain propagates it to OpenAI Assistant. - -```python Add data -assistant.add("/path/to/file.pdf") -assistant.add("https://www.youtube.com/watch?v=U9mJuUkhUzk") -assistant.add("https://openai.com/blog/new-models-and-developer-products-announced-at-devday") -``` - -## Step-3: Chat with your AI Assistant - -```python Chat -assistant.chat("How much OpenAI credits were offered to attendees during OpenAI DevDay?") -# Response: 'Every attendee of OpenAI DevDay 2023 was offered $500 in OpenAI credits.' -``` diff --git a/embedchain/docs/examples/poe_bot.mdx b/embedchain/docs/examples/poe_bot.mdx deleted file mode 100644 index 58e831f22..000000000 --- a/embedchain/docs/examples/poe_bot.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: '🔮 Poe Bot' ---- - -### 🚀 Getting started - -1. Install embedchain python package: - -```bash -pip install fastapi-poe==0.0.16 -``` - -2. Create a free account on [Poe](https://www.poe.com?utm_source=embedchain). -3. Click "Create Bot" button on top left. -4. Give it a handle and an optional description. -5. Select `Use API`. -6. Under `API URL` enter your server or ngrok address. You can use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible. -7. Copy your api key and paste it in `.env` as `POE_API_KEY`. -8. You will need to set `OPENAI_API_KEY` for generating embeddings and using LLM. Copy your OpenAI API key from [here](https://platform.openai.com/account/api-keys) and paste it in `.env` as `OPENAI_API_KEY`. -9. Now create your bot using the following code snippet. - -```bash -# make sure that you have set OPENAI_API_KEY and POE_API_KEY in .env file -from embedchain.bots import PoeBot - -poe_bot = PoeBot() - -# add as many data sources as you want -poe_bot.add("https://en.wikipedia.org/wiki/Adam_D%27Angelo") -poe_bot.add("https://www.youtube.com/watch?v=pJQVAqmKua8") - -# start the bot -# this start the poe bot server on port 8080 by default -poe_bot.start() -``` - -10. You can paste the above in a file called `your_script.py` and then simply do - -```bash -python your_script.py -``` - -Now your bot will start running at port `8080` by default. - -11. You can refer the [Supported Data formats](https://docs.embedchain.ai/advanced/data_types) section to refer the supported data types in embedchain. - -12. Click `Run check` to make sure your machine can be reached. -13. Make sure your bot is private if that's what you want. -14. Click `Create bot` at the bottom to finally create the bot -15. Now your bot is created. - -### 💬 How to use - -- To ask the bot questions, just type your query in the Poe interface: -```text - -``` - -- If you wish to add more data source to the bot, simply update your script and add as many `.add` as you like. You need to restart the server. diff --git a/embedchain/docs/examples/rest-api/add-data.mdx b/embedchain/docs/examples/rest-api/add-data.mdx deleted file mode 100644 index 05ed37968..000000000 --- a/embedchain/docs/examples/rest-api/add-data.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -openapi: post /{app_id}/add ---- - - - -```bash Request -curl --request POST \ - --url http://localhost:8080/{app_id}/add \ - -d "source=https://www.forbes.com/profile/elon-musk" \ - -d "data_type=web_page" -``` - - - - - -```json Response -{ "response": "fec7fe91e6b2d732938a2ec2e32bfe3f" } -``` - - diff --git a/embedchain/docs/examples/rest-api/chat.mdx b/embedchain/docs/examples/rest-api/chat.mdx deleted file mode 100644 index 2571bf716..000000000 --- a/embedchain/docs/examples/rest-api/chat.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: post /{app_id}/chat ---- \ No newline at end of file diff --git a/embedchain/docs/examples/rest-api/check-status.mdx b/embedchain/docs/examples/rest-api/check-status.mdx deleted file mode 100644 index 0893cba4c..000000000 --- a/embedchain/docs/examples/rest-api/check-status.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -openapi: get /ping ---- - - - -```bash Request - curl --request GET \ - --url http://localhost:8080/ping -``` - - - - - -```json Response -{ "ping": "pong" } -``` - - diff --git a/embedchain/docs/examples/rest-api/create.mdx b/embedchain/docs/examples/rest-api/create.mdx deleted file mode 100644 index 35863cea5..000000000 --- a/embedchain/docs/examples/rest-api/create.mdx +++ /dev/null @@ -1,96 +0,0 @@ ---- -openapi: post /create ---- - - - -```bash Request -curl --request POST \ - --url http://localhost:8080/create?app_id=app1 \ - -F "config=@/path/to/config.yaml" -``` - - - - - -```json Response -{ "response": "App created successfully. App ID: app1" } -``` - - - -By default we will use the opensource **gpt4all** model to get started. You can also specify your own config by uploading a config YAML file. - -For example, create a `config.yaml` file (adjust according to your requirements): - -```yaml -app: - config: - id: "default-app" - -llm: - provider: openai - config: - model: "gpt-4o-mini" - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - prompt: | - Use the following pieces of context to answer the query at the end. - If you don't know the answer, just say that you don't know, don't try to make up an answer. - - $context - - Query: $query - - Helpful Answer: - -vectordb: - provider: chroma - config: - collection_name: "rest-api-app" - dir: db - allow_reset: true - -embedder: - provider: openai - config: - model: "text-embedding-ada-002" -``` - -To learn more about custom configurations, check out the [custom configurations docs](https://docs.embedchain.ai/advanced/configuration). To explore more examples of config yamls for embedchain, visit [embedchain/configs](https://github.com/embedchain/embedchain/tree/main/configs). - -Now, you can upload this config file in the request body. - -For example, - -```bash Request -curl --request POST \ - --url http://localhost:8080/create?app_id=my-app \ - -F "config=@/path/to/config.yaml" -``` - -**Note:** To use custom models, an **API key** might be required. Refer to the table below to determine the necessary API key for your provider. - -| Keys | Providers | -| -------------------------- | ------------------------------ | -| `OPENAI_API_KEY ` | OpenAI, Azure OpenAI, Jina etc | -| `OPENAI_API_TYPE` | Azure OpenAI | -| `OPENAI_API_BASE` | Azure OpenAI | -| `OPENAI_API_VERSION` | Azure OpenAI | -| `COHERE_API_KEY` | Cohere | -| `TOGETHER_API_KEY` | Together | -| `ANTHROPIC_API_KEY` | Anthropic | -| `JINACHAT_API_KEY` | Jina | -| `HUGGINGFACE_ACCESS_TOKEN` | Huggingface | -| `REPLICATE_API_TOKEN` | LLAMA2 | - -To add env variables, you can simply run the docker command with the `-e` flag. - -For example, - -```bash -docker run --name embedchain -p 8080:8080 -e OPENAI_API_KEY= embedchain/rest-api:latest -``` \ No newline at end of file diff --git a/embedchain/docs/examples/rest-api/delete.mdx b/embedchain/docs/examples/rest-api/delete.mdx deleted file mode 100644 index 3aada3398..000000000 --- a/embedchain/docs/examples/rest-api/delete.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -openapi: delete /{app_id}/delete ---- - - - - -```bash Request - curl --request DELETE \ - --url http://localhost:8080/{app_id}/delete -``` - - - - - -```json Response -{ "response": "App with id {app_id} deleted successfully." } -``` - - diff --git a/embedchain/docs/examples/rest-api/deploy.mdx b/embedchain/docs/examples/rest-api/deploy.mdx deleted file mode 100644 index b72f91da0..000000000 --- a/embedchain/docs/examples/rest-api/deploy.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -openapi: post /{app_id}/deploy ---- - - - - -```bash Request -curl --request POST \ - --url http://localhost:8080/{app_id}/deploy \ - -d "api_key=ec-xxxx" -``` - - - - - -```json Response -{ "response": "App deployed successfully." } -``` - - diff --git a/embedchain/docs/examples/rest-api/get-all-apps.mdx b/embedchain/docs/examples/rest-api/get-all-apps.mdx deleted file mode 100644 index 6f603f9a6..000000000 --- a/embedchain/docs/examples/rest-api/get-all-apps.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -openapi: get /apps ---- - - - -```bash Request -curl --request GET \ - --url http://localhost:8080/apps -``` - - - - - -```json Response -{ - "results": [ - { - "config": "config1.yaml", - "id": 1, - "app_id": "app1" - }, - { - "config": "config2.yaml", - "id": 2, - "app_id": "app2" - } - ] -} -``` - - diff --git a/embedchain/docs/examples/rest-api/get-data.mdx b/embedchain/docs/examples/rest-api/get-data.mdx deleted file mode 100644 index 0c960e6cb..000000000 --- a/embedchain/docs/examples/rest-api/get-data.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -openapi: get /{app_id}/data ---- - - - -```bash Request -curl --request GET \ - --url http://localhost:8080/{app_id}/data -``` - - - - - -```json Response -{ - "results": [ - { - "data_type": "web_page", - "data_value": "https://www.forbes.com/profile/elon-musk/", - "metadata": "null" - } - ] -} -``` - - diff --git a/embedchain/docs/examples/rest-api/getting-started.mdx b/embedchain/docs/examples/rest-api/getting-started.mdx deleted file mode 100644 index 5501792b6..000000000 --- a/embedchain/docs/examples/rest-api/getting-started.mdx +++ /dev/null @@ -1,294 +0,0 @@ ---- -title: "🌍 Getting Started" ---- - -## Quickstart - -To use Embedchain as a REST API service, run the following command: - -```bash -docker run --name embedchain -p 8080:8080 embedchain/rest-api:latest -``` - -Navigate to [http://localhost:8080/docs](http://localhost:8080/docs) to interact with the API. There is a full-fledged Swagger docs playground with all the information about the API endpoints. - -![Swagger Docs Screenshot](https://github.com/embedchain/embedchain/assets/73601258/299d81e5-a0df-407c-afc2-6fa2c4286844) - -## ⚡ Steps to get started - - - - - - ```bash - curl --request POST "http://localhost:8080/create?app_id=my-app" \ - -H "accept: application/json" - ``` - - - ```python - import requests - - url = "http://localhost:8080/create?app_id=my-app" - - payload={} - - response = requests.request("POST", url, data=payload) - - print(response) - ``` - - - ```javascript - const data = fetch("http://localhost:8080/create?app_id=my-app", { - method: "POST", - }).then((res) => res.json()); - - console.log(data); - ``` - - - ```go - package main - - import ( - "fmt" - "net/http" - "io/ioutil" - ) - - func main() { - - url := "http://localhost:8080/create?app_id=my-app" - - payload := strings.NewReader("") - - req, _ := http.NewRequest("POST", url, payload) - - req.Header.Add("Content-Type", "application/json") - - res, _ := http.DefaultClient.Do(req) - - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - fmt.Println(res) - fmt.Println(string(body)) - - } - ``` - - - - - - - - ```bash - curl --request POST \ - --url http://localhost:8080/my-app/add \ - -d "source=https://www.forbes.com/profile/elon-musk" \ - -d "data_type=web_page" - ``` - - - ```python - import requests - - url = "http://localhost:8080/my-app/add" - - payload = "source=https://www.forbes.com/profile/elon-musk&data_type=web_page" - headers = {} - - response = requests.request("POST", url, headers=headers, data=payload) - - print(response) - ``` - - - ```javascript - const data = fetch("http://localhost:8080/my-app/add", { - method: "POST", - body: "source=https://www.forbes.com/profile/elon-musk&data_type=web_page", - }).then((res) => res.json()); - - console.log(data); - ``` - - - ```go - package main - - import ( - "fmt" - "strings" - "net/http" - "io/ioutil" - ) - - func main() { - - url := "http://localhost:8080/my-app/add" - - payload := strings.NewReader("source=https://www.forbes.com/profile/elon-musk&data_type=web_page") - - req, _ := http.NewRequest("POST", url, payload) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, _ := http.DefaultClient.Do(req) - - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - fmt.Println(res) - fmt.Println(string(body)) - - } - ``` - - - - - - - - ```bash - curl --request POST \ - --url http://localhost:8080/my-app/query \ - -d "query=Who is Elon Musk?" - ``` - - - ```python - import requests - - url = "http://localhost:8080/my-app/query" - - payload = "query=Who is Elon Musk?" - headers = {} - - response = requests.request("POST", url, headers=headers, data=payload) - - print(response) - ``` - - - ```javascript - const data = fetch("http://localhost:8080/my-app/query", { - method: "POST", - body: "query=Who is Elon Musk?", - }).then((res) => res.json()); - - console.log(data); - ``` - - - ```go - package main - - import ( - "fmt" - "strings" - "net/http" - "io/ioutil" - ) - - func main() { - - url := "http://localhost:8080/my-app/query" - - payload := strings.NewReader("query=Who is Elon Musk?") - - req, _ := http.NewRequest("POST", url, payload) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, _ := http.DefaultClient.Do(req) - - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - fmt.Println(res) - fmt.Println(string(body)) - - } - ``` - - - - - - - - ```bash - curl --request POST \ - --url http://localhost:8080/my-app/deploy \ - -d "api_key=ec-xxxx" - ``` - - - ```python - import requests - - url = "http://localhost:8080/my-app/deploy" - - payload = "api_key=ec-xxxx" - - response = requests.request("POST", url, data=payload) - - print(response) - ``` - - - ```javascript - const data = fetch("http://localhost:8080/my-app/deploy", { - method: "POST", - body: "api_key=ec-xxxx", - }).then((res) => res.json()); - - console.log(data); - ``` - - - ```go - package main - - import ( - "fmt" - "strings" - "net/http" - "io/ioutil" - ) - - func main() { - - url := "http://localhost:8080/my-app/deploy" - - payload := strings.NewReader("api_key=ec-xxxx") - - req, _ := http.NewRequest("POST", url, payload) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, _ := http.DefaultClient.Do(req) - - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - fmt.Println(res) - fmt.Println(string(body)) - - } - ``` - - - - - - -And you're ready! 🎉 - -If you run into issues, please feel free to contact us using below links: - - diff --git a/embedchain/docs/examples/rest-api/query.mdx b/embedchain/docs/examples/rest-api/query.mdx deleted file mode 100644 index 2d647e505..000000000 --- a/embedchain/docs/examples/rest-api/query.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -openapi: post /{app_id}/query ---- - - - -```bash Request -curl --request POST \ - --url http://localhost:8080/{app_id}/query \ - -d "query=who is Elon Musk?" -``` - - - - - -```json Response -{ "response": "Net worth of Elon Musk is $218 Billion." } -``` - - diff --git a/embedchain/docs/examples/showcase.mdx b/embedchain/docs/examples/showcase.mdx deleted file mode 100644 index d614c3b00..000000000 --- a/embedchain/docs/examples/showcase.mdx +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: '🎪 Community showcase' ---- - -Embedchain community has been super active in creating demos on top of Embedchain. On this page, we showcase all the apps, blogs, videos, and tutorials created by the community. ❤️ - -## Apps - -### Open Source - -- [My GSoC23 bot- Streamlit chat](https://github.com/lucifertrj/EmbedChain_GSoC23_BOT) by Tarun Jain -- [Discord Bot for LLM chat](https://github.com/Reidond/discord_bots_playground/tree/c8b0c36541e4b393782ee506804c4b6962426dd6/python/chat-channel-bot) by Reidond -- [EmbedChain-Streamlit-Docker App](https://github.com/amjadraza/embedchain-streamlit-app) by amjadraza -- [Harry Potter Philosphers Stone Bot](https://github.com/vinayak-kempawad/Harry_Potter_Philosphers_Stone_Bot/) by Vinayak Kempawad, ([LinkedIn post](https://www.linkedin.com/feed/update/urn:li:activity:7080907532155686912/)) -- [LLM bot trained on own messages](https://github.com/Harin329/harinBot) by Hao Wu - -### Closed Source - -- [Taobot.io](https://taobot.io) - chatbot & knowledgebase hybrid by [cachho](https://github.com/cachho) -- [Create Instant ChatBot 🤖 using embedchain](https://databutton.com/v/h3e680h9) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1674704745154641920/)) -- [JOBO 🤖 — The AI-driven sidekick to craft your resume](https://try-jobo.com/) by Enrico Willemse, ([LinkedIn Post](https://www.linkedin.com/posts/enrico-willemse_jobai-gptfun-embedchain-activity-7090340080879374336-ueLB/)) -- [Explore Your Knowledge Base: Interactive chats over various forms of documents](https://chatdocs.dkedar.com/) by Kedar Dabhadkar, ([LinkedIn Post](https://www.linkedin.com/posts/dkedar7_machinelearning-llmops-activity-7092524836639424513-2O3L/)) -- [Chatbot trained on 1000+ videos of Ester hicks the co-author behind the famous book Secret](https://askabraham.tokenofme.io/) by Mohan Kumar - - -## Templates - -### Replit -- [Embedchain Chat Bot](https://replit.com/@taranjeet1/Embedchain-Chat-Bot) by taranjeetio -- [Embedchain Memory Chat Bot Template](https://replit.com/@taranjeetio/Embedchain-Memory-Chat-Bot-Template) by taranjeetio -- [Chatbot app to demonstrate question-answering using retrieved information](https://replit.com/@AllisonMorrell/EmbedChainlitPublic) by Allison Morrell, ([LinkedIn Post](https://www.linkedin.com/posts/allison-morrell-2889275a_retrievalbot-screenshots-activity-7080339991754649600-wihZ/)) - -## Posts - -### Blogs - -- [Customer Service LINE Bot](https://www.evanlin.com/langchain-embedchain/) by Evan Lin -- [Chatbot in Under 5 mins using Embedchain](https://medium.com/@ayush.wattal/chatbot-in-under-5-mins-using-embedchain-a4f161fcf9c5) by Ayush Wattal -- [Understanding what the LLM framework embedchain does](https://zenn.dev/hijikix/articles/4bc8d60156a436) by Daisuke Hashimoto -- [In bed with GPT and Node.js](https://dev.to/worldlinetech/in-bed-with-gpt-and-nodejs-4kh2) by Raphaël Semeteys, ([LinkedIn Post](https://www.linkedin.com/posts/raphaelsemeteys_in-bed-with-gpt-and-nodejs-activity-7088113552326029313-nn87/)) -- [Using Embedchain — A powerful LangChain Python wrapper to build Chat Bots even faster!⚡](https://medium.com/@avra42/using-embedchain-a-powerful-langchain-python-wrapper-to-build-chat-bots-even-faster-35c12994a360) by Avra, ([Tweet](https://twitter.com/Avra_b/status/1686767751560310784/)) -- [What is the Embedchain library?](https://jahaniwww.com/%da%a9%d8%aa%d8%a7%d8%a8%d8%ae%d8%a7%d9%86%d9%87-embedchain/) by Ali Jahani, ([LinkedIn Post](https://www.linkedin.com/posts/ajahani_aepaetaeqaexaggahyaeu-aetaexaesabraeaaeqaepaeu-activity-7097605202135904256-ppU-/)) -- [LangChain is Nice, But Have You Tried EmbedChain ?](https://medium.com/thoughts-on-machine-learning/langchain-is-nice-but-have-you-tried-embedchain-215a34421cde) by FS Ndzomga, ([Tweet](https://twitter.com/ndzfs/status/1695583640372035951/)) -- [Simplest Method to Build a Custom Chatbot with GPT-3.5 (via Embedchain)](https://www.ainewsletter.today/p/simplest-method-to-build-a-custom) by Arjun, ([Tweet](https://twitter.com/aiguy_arjun/status/1696393808467091758/)) - -### LinkedIn - -- [What is embedchain](https://www.linkedin.com/posts/activity-7079393104423698432-wRyi/) by Rithesh Sreenivasan -- [Building a chatbot with EmbedChain](https://www.linkedin.com/posts/activity-7078434598984060928-Zdso/) by Lior Sinclair -- [Making chatbot without vs with embedchain](https://www.linkedin.com/posts/kalyanksnlp_llms-chatbots-langchain-activity-7077453416221863936-7N1L/) by Kalyan KS -- [EmbedChain - very intuitive, first you index your data and then query!](https://www.linkedin.com/posts/shubhamsaboo_embedchain-a-framework-to-easily-create-activity-7079535460699557888-ad1X/) by Shubham Saboo -- [EmbedChain - Harnessing power of LLM](https://www.linkedin.com/posts/uditsaini_chatbotrevolution-llmpoweredbots-embedchainframework-activity-7077520356827181056-FjTK/) by Udit S. -- [AI assistant for ABBYY Vantage](https://www.linkedin.com/posts/maximevermeir_llm-github-abbyy-activity-7081658972071424000-fXfZ/) by Maxime V. -- [About embedchain](https://www.linkedin.com/feed/update/urn:li:activity:7080984218914189312/) by Morris Lee -- [How to use Embedchain](https://www.linkedin.com/posts/nehaabansal_github-embedchainembedchain-framework-activity-7085830340136595456-kbW5/) by Neha Bansal -- [Youtube/Webpage summary for Energy Study](https://www.linkedin.com/posts/bar%C4%B1%C5%9F-sanl%C4%B1-34b82715_enerji-python-activity-7082735341563977730-Js0U/) by Barış Sanlı, ([Tweet](https://twitter.com/barissanli/status/1676968784979193857/)) -- [Demo: How to use Embedchain? (Contains Collab Notebook link)](https://www.linkedin.com/posts/liorsinclair_embedchain-is-getting-a-lot-of-traction-because-activity-7103044695995424768-RckT/) by Lior Sinclair - -### Twitter - -- [What is embedchain](https://twitter.com/AlphaSignalAI/status/1672668574450847745) by Lior -- [Building a chatbot with Embedchain](https://twitter.com/Saboo_Shubham_/status/1673537044419686401) by Shubham Saboo -- [Chatbot docker image behind an API with yaml configs with Embedchain](https://twitter.com/tricalt/status/1678411430192730113/) by Vasilije -- [Build AI powered PDF chatbot with just five lines of Python code with Embedchain!](https://twitter.com/Saboo_Shubham_/status/1676627104866156544/) by Shubham Saboo -- [Chatbot against a youtube video using embedchain](https://twitter.com/smaameri/status/1675201443043704834/) by Sami Maameri -- [Highlights of EmbedChain](https://twitter.com/carl_AIwarts/status/1673542204328120321/) by carl_AIwarts -- [Build Llama-2 chatbot in less than 5 minutes](https://twitter.com/Saboo_Shubham_/status/1682168956918833152/) by Shubham Saboo -- [All cool features of embedchain](https://twitter.com/DhravyaShah/status/1683497882438217728/) by Dhravya Shah, ([LinkedIn Post](https://www.linkedin.com/posts/dhravyashah_what-if-i-tell-you-that-you-can-make-an-ai-activity-7089459599287726080-ZIYm/)) -- [Read paid Medium articles for Free using embedchain](https://twitter.com/kumarkaushal_/status/1688952961622585344) by Kaushal Kumar - -## Videos - -- [Embedchain in one shot](https://www.youtube.com/watch?v=vIhDh7H73Ww&t=82s) by AI with Tarun -- [embedChain Create LLM powered bots over any dataset Python Demo Tesla Neurallink Chatbot Example](https://www.youtube.com/watch?v=bJqAn22a6Gc) by Rithesh Sreenivasan -- [Embedchain - NEW 🔥 Langchain BABY to build LLM Bots](https://www.youtube.com/watch?v=qj_GNQ06I8o) by 1littlecoder -- [EmbedChain -- NEW!: Build LLM-Powered Bots with Any Dataset](https://www.youtube.com/watch?v=XmaBezzGHu4) by DataInsightEdge -- [Chat With Your PDFs in less than 10 lines of code! EMBEDCHAIN tutorial](https://www.youtube.com/watch?v=1ugkcsAcw44) by Phani Reddy -- [How To Create A Custom Knowledge AI Powered Bot | Install + How To Use](https://www.youtube.com/watch?v=VfCrIiAst-c) by The Ai Solopreneur -- [Build Custom Chatbot in 6 min with this Framework [Beginner Friendly]](https://www.youtube.com/watch?v=-8HxOpaFySM) by Maya Akim -- [embedchain-streamlit-app](https://www.youtube.com/watch?v=3-9GVd-3v74) by Amjad Raza -- [🤖CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN - a LangChain wrapper, in few lines of code !](https://www.youtube.com/watch?v=Mp7zJe4TIdM) by Avra -- [Building resource-driven LLM-powered bots with Embedchain](https://www.youtube.com/watch?v=IVfcAgxTO4I) by BugBytes -- [embedchain-streamlit-demo](https://www.youtube.com/watch?v=yJAWB13FhYQ) by Amjad Raza -- [Embedchain - create your own AI chatbots using open source models](https://www.youtube.com/shorts/O3rJWKwSrWE) by Dhravya Shah -- [AI ChatBot in 5 lines Python Code](https://www.youtube.com/watch?v=zjWvLJLksv8) by Data Engineering -- [Interview with Karl Marx](https://www.youtube.com/watch?v=5Y4Tscwj1xk) by Alexander Ray Williams -- [Vlog where we try to build a bot based on our content on the internet](https://www.youtube.com/watch?v=I2w8CWM3bx4) by DV, ([Tweet](https://twitter.com/dvcoolster/status/1688387017544261632)) -- [CHAT with ANY ONLINE RESOURCES using EMBEDCHAIN|STREAMLIT with MEMORY |All OPENSOURCE](https://www.youtube.com/watch?v=TqQIHWoWTDQ&pp=ygUKZW1iZWRjaGFpbg%3D%3D) by DataInsightEdge -- [Build POWERFUL LLM Bots EASILY with Your Own Data - Embedchain - Langchain 2.0? (Tutorial)](https://www.youtube.com/watch?v=jE24Y_GasE8) by WorldofAI, ([Tweet](https://twitter.com/intheworldofai/status/1696229166922780737)) -- [Embedchain: An AI knowledge base assistant for customizing enterprise private data, which can be connected to discord, whatsapp, slack, tele and other terminals (with gradio to build a request interface) in Chinese](https://www.youtube.com/watch?v=5RZzCJRk-d0) by AIGC LINK -- [Embedchain Introduction](https://www.youtube.com/watch?v=Jet9zAqyggI) by Fahd Mirza - -## Mentions - -### Github repos - -- [Awesome-LLM](https://github.com/Hannibal046/Awesome-LLM) -- [awesome-chatgpt-api](https://github.com/reorx/awesome-chatgpt-api) -- [awesome-langchain](https://github.com/kyrolabs/awesome-langchain) -- [Awesome-Prompt-Engineering](https://github.com/promptslab/Awesome-Prompt-Engineering) -- [awesome-chatgpt](https://github.com/eon01/awesome-chatgpt) -- [Awesome-LLMOps](https://github.com/tensorchord/Awesome-LLMOps) -- [awesome-generative-ai](https://github.com/filipecalegario/awesome-generative-ai) -- [awesome-gpt](https://github.com/formulahendry/awesome-gpt) -- [awesome-ChatGPT-repositories](https://github.com/taishi-i/awesome-ChatGPT-repositories) -- [awesome-gpt-prompt-engineering](https://github.com/snwfdhmp/awesome-gpt-prompt-engineering) -- [awesome-chatgpt](https://github.com/awesome-chatgpt/awesome-chatgpt) -- [awesome-llm-and-aigc](https://github.com/sjinzh/awesome-llm-and-aigc) -- [awesome-compbio-chatgpt](https://github.com/csbl-br/awesome-compbio-chatgpt) -- [Awesome-LLM4Tool](https://github.com/OpenGVLab/Awesome-LLM4Tool) - -## Meetups - -- [Dash and ChatGPT: Future of AI-enabled apps 30/08/23](https://go.plotly.com/dash-chatgpt) -- [Pie & AI: Bangalore - Build end-to-end LLM app using Embedchain 01/09/23](https://www.eventbrite.com/e/pie-ai-bangalore-build-end-to-end-llm-app-using-embedchain-tickets-698045722547) diff --git a/embedchain/docs/examples/slack-AI.mdx b/embedchain/docs/examples/slack-AI.mdx deleted file mode 100644 index 7efaba279..000000000 --- a/embedchain/docs/examples/slack-AI.mdx +++ /dev/null @@ -1,67 +0,0 @@ -[Embedchain Examples Repo](https://github.com/embedchain/examples) contains code on how to build your own Slack AI to chat with the unstructured data lying in your slack channels. - -![Slack AI Demo](/images/slack-ai.png) - -## Getting started - -Create a Slack AI involves 3 steps - -* Create slack user -* Set environment variables -* Run the app locally - -### Step 1: Create Slack user token - -Follow the steps given below to fetch your slack user token to get data through Slack APIs: - -1. Create a workspace on Slack if you don’t have one already by clicking [here](https://slack.com/intl/en-in/). -2. Create a new App on your Slack account by going [here](https://api.slack.com/apps). -3. Select `From Scratch`, then enter the App Name and select your workspace. -4. Navigate to `OAuth & Permissions` tab from the left sidebar and go to the `scopes` section. Add the following scopes under `User Token Scopes`: - - ``` - # Following scopes are needed for reading channel history - channels:history - channels:read - - # Following scopes are needed to fetch list of channels from slack - groups:read - mpim:read - im:read - ``` - -5. Click on the `Install to Workspace` button under `OAuth Tokens for Your Workspace` section in the same page and install the app in your slack workspace. -6. After installing the app you will see the `User OAuth Token`, save that token as you will need to configure it as `SLACK_USER_TOKEN` for this demo. - -### Step 2: Set environment variables - -Navigate to `api` folder and set your `HUGGINGFACE_ACCESS_TOKEN` and `SLACK_USER_TOKEN` in `.env.example` file. Then rename the `.env.example` file to `.env`. - - - -By default, we use `Mixtral` model from Hugging Face. However, if you prefer to use OpenAI model, then set `OPENAI_API_KEY` instead of `HUGGINGFACE_ACCESS_TOKEN` along with `SLACK_USER_TOKEN` in `.env` file, and update the code in `api/utils/app.py` file to use OpenAI model instead of Hugging Face model. - - -### Step 3: Run app locally - -Follow the instructions given below to run app locally based on your development setup (with docker or without docker): - -#### With docker - -```bash -docker-compose build -ec start --docker -``` - -#### Without docker - -```bash -ec install-reqs -ec start -``` - -Finally, you will have the Slack AI frontend running on http://localhost:3000. You can also access the REST APIs on http://localhost:8000. - -## Credits - -This demo was built using the Embedchain's [full stack demo template](https://docs.embedchain.ai/get-started/full-stack). Follow the instructions [given here](https://docs.embedchain.ai/get-started/full-stack) to create your own full stack RAG application. diff --git a/embedchain/docs/examples/slack_bot.mdx b/embedchain/docs/examples/slack_bot.mdx deleted file mode 100644 index 034c821d2..000000000 --- a/embedchain/docs/examples/slack_bot.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: '💼 Slack Bot' ---- - -### 🖼️ Setup - -1. Create a workspace on Slack if you don't have one already by clicking [here](https://slack.com/intl/en-in/). -2. Create a new App on your Slack account by going [here](https://api.slack.com/apps). -3. Select `From Scratch`, then enter the Bot Name and select your workspace. -4. On the left Sidebar, go to `OAuth and Permissions` and add the following scopes under `Bot Token Scopes`: -```text -app_mentions:read -channels:history -channels:read -chat:write -``` -5. Now select the option `Install to Workspace` and after it's done, copy the `Bot User OAuth Token` and set it in your secrets as `SLACK_BOT_TOKEN`. -6. Run your bot now, - - - ```bash - docker run --name slack-bot -e OPENAI_API_KEY=sk-xxx -e SLACK_BOT_TOKEN=xxx -p 8000:8000 embedchain/slack-bot - ``` - - - ```bash - pip install --upgrade "embedchain[slack]" - python3 -m embedchain.bots.slack --port 8000 - ``` - - -7. Expose your bot to the internet. You can use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible. -8. On the Slack API website go to `Event Subscriptions` on the left Sidebar and turn on `Enable Events`. -9. In `Request URL`, enter your server or ngrok address. -10. After it gets verified, click on `Subscribe to bot events`, add `message.channels` Bot User Event and click on `Save Changes`. -11. Now go to your workspace, right click on the bot name in the sidebar, click `view app details`, then `add this app to a channel`. - -### 🚀 Usage Instructions - -- Go to the channel where you have added your bot. -- To add data sources to the bot, use the command: -```text -add -``` -- To ask queries from the bot, use the command: -```text -query -``` - -🎉 Happy Chatting! 🎉 diff --git a/embedchain/docs/examples/telegram_bot.mdx b/embedchain/docs/examples/telegram_bot.mdx deleted file mode 100644 index 14f17e90c..000000000 --- a/embedchain/docs/examples/telegram_bot.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "📱 Telegram Bot" ---- - -### 🖼️ Template Setup - -- Open the Telegram app and search for the `BotFather` user. -- Start a chat with BotFather and use the `/newbot` command to create a new bot. -- Follow the instructions to choose a name and username for your bot. -- Once the bot is created, BotFather will provide you with a unique token for your bot. - - - - ```bash - docker run --name telegram-bot -e OPENAI_API_KEY=sk-xxx -e TELEGRAM_BOT_TOKEN=xxx -p 8000:8000 embedchain/telegram-bot - ``` - - - If you wish to use **Docker**, you would need to host your bot on a server. - You can use [ngrok](https://ngrok.com/) to expose your localhost to the - internet and then set the webhook using the ngrok URL. - - - - - - Fork **[this](https://replit.com/@taranjeetio/EC-Telegram-Bot-Template?v=1#README.md)** replit template. - - - - Set your `OPENAI_API_KEY` in Secrets. - - Set the unique token as `TELEGRAM_BOT_TOKEN` in Secrets. - - - - - -- Click on `Run` in the replit container and a URL will get generated for your bot. -- Now set your webhook by running the following link in your browser: - -```url -https://api.telegram.org/bot/setWebhook?url= -``` - -- When you get a successful response in your browser, your bot is ready to be used. - -### 🚀 Usage Instructions - -- Open your bot by searching for it using the bot name or bot username. -- Click on `Start` or type `/start` and follow the on screen instructions. - -🎉 Happy Chatting! 🎉 diff --git a/embedchain/docs/examples/whatsapp_bot.mdx b/embedchain/docs/examples/whatsapp_bot.mdx deleted file mode 100644 index 16a8c504a..000000000 --- a/embedchain/docs/examples/whatsapp_bot.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: '💬 WhatsApp Bot' ---- - -### 🚀 Getting started - -1. Install embedchain python package: - -```bash -pip install --upgrade embedchain -``` - -2. Launch your WhatsApp bot: - - - - ```bash - docker run --name whatsapp-bot -e OPENAI_API_KEY=sk-xxx -p 8000:8000 embedchain/whatsapp-bot - ``` - - - ```bash - python -m embedchain.bots.whatsapp --port 5000 - ``` - - - - -If your bot needs to be accessible online, use your machine's public IP or DNS. Otherwise, employ a proxy server like [ngrok](https://ngrok.com/) to make your local bot accessible. - -3. Create a free account on [Twilio](https://www.twilio.com/try-twilio) - - Set up a WhatsApp Sandbox in your Twilio dashboard. Access it via the left sidebar: `Messaging > Try it out > Send a WhatsApp Message`. - - Follow on-screen instructions to link a phone number for chatting with your bot - - Copy your bot's public URL, add /chat at the end, and paste it in Twilio's WhatsApp Sandbox settings under "When a message comes in". Save the settings. - -- Copy your bot's public url, append `/chat` at the end and paste it under `When a message comes in` under the `Sandbox settings` for Whatsapp in Twilio. Save your settings. - -### 💬 How to use - -- To connect a new number or reconnect an old one in the Sandbox, follow Twilio's instructions. -- To include data sources, use this command: -```text -add -``` - -- To ask the bot questions, just type your query: -```text - -``` - -### Example - -Here is an example of Elon Musk WhatsApp Bot that we created: - - diff --git a/embedchain/docs/favicon.png b/embedchain/docs/favicon.png deleted file mode 100644 index 35494d9ea..000000000 Binary files a/embedchain/docs/favicon.png and /dev/null differ diff --git a/embedchain/docs/get-started/deployment.mdx b/embedchain/docs/get-started/deployment.mdx deleted file mode 100644 index 87e72dcbb..000000000 --- a/embedchain/docs/get-started/deployment.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: 'Overview' -description: 'Deploy your RAG application to production' ---- - -After successfully setting up and testing your RAG app locally, the next step is to deploy it to a hosting service to make it accessible to a wider audience. Embedchain provides integration with different cloud providers so that you can seamlessly deploy your RAG applications to production without having to worry about going through the cloud provider instructions. Embedchain does all the heavy lifting for you. - - - - - - - - - - - -## Seeking help? - -If you run into issues with deployment, please feel free to reach out to us via any of the following methods: - - diff --git a/embedchain/docs/get-started/faq.mdx b/embedchain/docs/get-started/faq.mdx deleted file mode 100644 index 3acae2671..000000000 --- a/embedchain/docs/get-started/faq.mdx +++ /dev/null @@ -1,191 +0,0 @@ ---- -title: ❓ FAQs -description: 'Collections of all the frequently asked questions' ---- - - -Yes, it does. Please refer to the [OpenAI Assistant docs page](/examples/openai-assistant). - - -Use the model provided on huggingface: `mistralai/Mistral-7B-v0.1` - -```python main.py -import os -from embedchain import App - -os.environ["HUGGINGFACE_ACCESS_TOKEN"] = "hf_your_token" - -app = App.from_config("huggingface.yaml") -``` -```yaml huggingface.yaml -llm: - provider: huggingface - config: - model: 'mistralai/Mistral-7B-v0.1' - temperature: 0.5 - max_tokens: 1000 - top_p: 0.5 - stream: false - -embedder: - provider: huggingface - config: - model: 'sentence-transformers/all-mpnet-base-v2' -``` - - - -Use the model `gpt-4-turbo` provided my openai. - - -```python main.py -import os -from embedchain import App - -os.environ['OPENAI_API_KEY'] = 'xxx' - -# load llm configuration from gpt4_turbo.yaml file -app = App.from_config(config_path="gpt4_turbo.yaml") -``` - -```yaml gpt4_turbo.yaml -llm: - provider: openai - config: - model: 'gpt-4-turbo' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false -``` - - - - - -```python main.py -import os -from embedchain import App - -os.environ['OPENAI_API_KEY'] = 'xxx' - -# load llm configuration from gpt4.yaml file -app = App.from_config(config_path="gpt4.yaml") -``` - -```yaml gpt4.yaml -llm: - provider: openai - config: - model: 'gpt-4' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false -``` - - - - - - -```python main.py -from embedchain import App - -# load llm configuration from opensource.yaml file -app = App.from_config(config_path="opensource.yaml") -``` - -```yaml opensource.yaml -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: gpt4all - config: - model: 'all-MiniLM-L6-v2' -``` - - - - -You can achieve this by setting `stream` to `true` in the config file. - - -```yaml openai.yaml -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: true -``` - -```python main.py -import os -from embedchain import App - -os.environ['OPENAI_API_KEY'] = 'sk-xxx' - -app = App.from_config(config_path="openai.yaml") - -app.add("https://www.forbes.com/profile/elon-musk") - -response = app.query("What is the net worth of Elon Musk?") -# response will be streamed in stdout as it is generated. -``` - - - - - Set up the app by adding an `id` in the config file. This keeps the data for future use. You can include this `id` in the yaml config or input it directly in `config` dict. - ```python app1.py - import os - from embedchain import App - - os.environ['OPENAI_API_KEY'] = 'sk-xxx' - - app1 = App.from_config(config={ - "app": { - "config": { - "id": "your-app-id", - } - } - }) - - app1.add("https://www.forbes.com/profile/elon-musk") - - response = app1.query("What is the net worth of Elon Musk?") - ``` - ```python app2.py - import os - from embedchain import App - - os.environ['OPENAI_API_KEY'] = 'sk-xxx' - - app2 = App.from_config(config={ - "app": { - "config": { - # this will persist and load data from app1 session - "id": "your-app-id", - } - } - }) - - response = app2.query("What is the net worth of Elon Musk?") - ``` - - - -#### Still have questions? -If docs aren't sufficient, please feel free to reach out to us using one of the following methods: - - diff --git a/embedchain/docs/get-started/full-stack.mdx b/embedchain/docs/get-started/full-stack.mdx deleted file mode 100644 index cd45a0b9e..000000000 --- a/embedchain/docs/get-started/full-stack.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: '💻 Full stack' ---- - -Get started with full-stack RAG applications using Embedchain's easy-to-use CLI tool. Set up everything with just a few commands, whether you prefer Docker or not. - -## Prerequisites - -Choose your setup method: - -* [Without docker](#without-docker) -* [With Docker](#with-docker) - -### Without Docker - -Ensure these are installed: - -- Embedchain python package (`pip install embedchain`) -- [Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) - -### With Docker - -Install Docker from [Docker's official website](https://docs.docker.com/engine/install/). - -## Quick Start Guide - -### Install the package - -Before proceeding, make sure you have the Embedchain package installed. - -```bash -pip install embedchain -U -``` - -### Setting Up - -For the purpose of the demo, you have to set `OPENAI_API_KEY` to start with but you can choose any llm by changing the configuration easily. - -### Installation Commands - - - -```bash without docker -ec create-app my-app -cd my-app -ec start -``` - -```bash with docker -ec create-app my-app --docker -cd my-app -ec start --docker -``` - - - -### What Happens Next? - -1. Embedchain fetches a full stack template (FastAPI backend, Next.JS frontend). -2. Installs required components. -3. Launches both frontend and backend servers. - -### See It In Action - -Open http://localhost:3000 to view the chat UI. - -![full stack example](/images/fullstack.png) - -### Admin Panel - -Check out the Embedchain admin panel to see the document chunks for your RAG application. - -![full stack chunks](/images/fullstack-chunks.png) - -### API Server - -If you want to access the API server, you can do so at http://localhost:8000/docs. - -![API Server](/images/fullstack-api-server.png) - -You can customize the UI and code as per your requirements. diff --git a/embedchain/docs/get-started/integrations.mdx b/embedchain/docs/get-started/integrations.mdx deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/docs/get-started/introduction.mdx b/embedchain/docs/get-started/introduction.mdx deleted file mode 100644 index fc7ce22ed..000000000 --- a/embedchain/docs/get-started/introduction.mdx +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: 📚 Introduction ---- - -## What is Embedchain? - -Embedchain is an Open Source Framework that makes it easy to create and deploy personalized AI apps. At its core, Embedchain follows the design principle of being *"Conventional but Configurable"* to serve both software engineers and machine learning engineers. - -Embedchain streamlines the creation of personalized LLM applications, offering a seamless process for managing various types of unstructured data. It efficiently segments data into manageable chunks, generates relevant embeddings, and stores them in a vector database for optimized retrieval. With a suite of diverse APIs, it enables users to extract contextual information, find precise answers, or engage in interactive chat conversations, all tailored to their own data. - -## Who is Embedchain for? - -Embedchain is designed for a diverse range of users, from AI professionals like Data Scientists and Machine Learning Engineers to those just starting their AI journey, including college students, independent developers, and hobbyists. Essentially, it's for anyone with an interest in AI, regardless of their expertise level. - -Our APIs are user-friendly yet adaptable, enabling beginners to effortlessly create LLM-powered applications with as few as 4 lines of code. At the same time, we offer extensive customization options for every aspect of building a personalized AI application. This includes the choice of LLMs, vector databases, loaders and chunkers, retrieval strategies, re-ranking, and more. - -Our platform's clear and well-structured abstraction layers ensure that users can tailor the system to meet their specific needs, whether they're crafting a simple project or a complex, nuanced AI application. - -## Why Use Embedchain? - -Developing a personalized AI application for production use presents numerous complexities, such as: - -- Integrating and indexing data from diverse sources. -- Determining optimal data chunking methods for each source. -- Synchronizing the RAG pipeline with regularly updated data sources. -- Implementing efficient data storage in a vector store. -- Deciding whether to include metadata with document chunks. -- Handling permission management. -- Configuring Large Language Models (LLMs). -- Selecting effective prompts. -- Choosing suitable retrieval strategies. -- Assessing the performance of your RAG pipeline. -- Deploying the pipeline into a production environment, among other concerns. - -Embedchain is designed to simplify these tasks, offering conventional yet customizable APIs. Our solution handles the intricate processes of loading, chunking, indexing, and retrieving data. This enables you to concentrate on aspects that are crucial for your specific use case or business objectives, ensuring a smoother and more focused development process. - -## How it works? - -Embedchain makes it easy to add data to your RAG pipeline with these straightforward steps: - -1. **Automatic Data Handling**: It automatically recognizes the data type and loads it. -2. **Efficient Data Processing**: The system creates embeddings for key parts of your data. -3. **Flexible Data Storage**: You get to choose where to store this processed data in a vector database. - -When a user asks a question, whether for chatting, searching, or querying, Embedchain simplifies the response process: - -1. **Query Processing**: It turns the user's question into embeddings. -2. **Document Retrieval**: These embeddings are then used to find related documents in the database. -3. **Answer Generation**: The related documents are used by the LLM to craft a precise answer. - -With Embedchain, you don’t have to worry about the complexities of building a personalized AI application. It offers an easy-to-use interface for developing applications with any kind of data. - -## Getting started - -Checkout our [quickstart guide](/get-started/quickstart) to start your first AI application. - -## Support - -Feel free to reach out to us if you have ideas, feedback or questions that we can help out with. - - - -## Contribute - -- [GitHub](https://github.com/embedchain/embedchain) -- [Contribution docs](/contribution/dev) diff --git a/embedchain/docs/get-started/quickstart.mdx b/embedchain/docs/get-started/quickstart.mdx deleted file mode 100644 index 04d270191..000000000 --- a/embedchain/docs/get-started/quickstart.mdx +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: '⚡ Quickstart' -description: '💡 Create an AI app on your own data in a minute' ---- - -## Installation - -First install the Python package: - -```bash -pip install embedchain -``` - -Once you have installed the package, depending upon your preference you can either use: - - - - This includes Open source LLMs like Mistral, Llama, etc.
- Free to use, and runs locally on your machine. -
- - This includes paid LLMs like GPT 4, Claude, etc.
- Cost money and are accessible via an API. -
-
- -## Open Source Models - -This section gives a quickstart example of using Mistral as the Open source LLM and Sentence transformers as the Open source embedding model. These models are free and run mostly on your local machine. - -We are using Mistral hosted at Hugging Face, so will you need a Hugging Face token to run this example. Its *free* and you can create one [here](https://huggingface.co/docs/hub/security-tokens). - - -```python huggingface_demo.py -import os -# Replace this with your HF token -os.environ["HUGGINGFACE_ACCESS_TOKEN"] = "hf_xxxx" - -from embedchain import App - -config = { - 'llm': { - 'provider': 'huggingface', - 'config': { - 'model': 'mistralai/Mistral-7B-Instruct-v0.2', - 'top_p': 0.5 - } - }, - 'embedder': { - 'provider': 'huggingface', - 'config': { - 'model': 'sentence-transformers/all-mpnet-base-v2' - } - } -} -app = App.from_config(config=config) -app.add("https://www.forbes.com/profile/elon-musk") -app.add("https://en.wikipedia.org/wiki/Elon_Musk") -app.query("What is the net worth of Elon Musk today?") -# Answer: The net worth of Elon Musk today is $258.7 billion. -``` - - -## Paid Models - -In this section, we will use both LLM and embedding model from OpenAI. - -```python openai_demo.py -import os -from embedchain import App - -# Replace this with your OpenAI key -os.environ["OPENAI_API_KEY"] = "sk-xxxx" - -app = App() -app.add("https://www.forbes.com/profile/elon-musk") -app.add("https://en.wikipedia.org/wiki/Elon_Musk") -app.query("What is the net worth of Elon Musk today?") -# Answer: The net worth of Elon Musk today is $258.7 billion. -``` - -# Next Steps - -Now that you have created your first app, you can follow any of the links: - -* [Introduction](/get-started/introduction) -* [Customization](/components/introduction) -* [Use cases](/use-cases/introduction) -* [Deployment](/get-started/deployment) diff --git a/embedchain/docs/images/checks-passed.png b/embedchain/docs/images/checks-passed.png deleted file mode 100644 index 3303c7736..000000000 Binary files a/embedchain/docs/images/checks-passed.png and /dev/null differ diff --git a/embedchain/docs/images/cover.gif b/embedchain/docs/images/cover.gif deleted file mode 100644 index efcc88243..000000000 Binary files a/embedchain/docs/images/cover.gif and /dev/null differ diff --git a/embedchain/docs/images/fly_io.png b/embedchain/docs/images/fly_io.png deleted file mode 100644 index 11a211afd..000000000 Binary files a/embedchain/docs/images/fly_io.png and /dev/null differ diff --git a/embedchain/docs/images/fullstack-api-server.png b/embedchain/docs/images/fullstack-api-server.png deleted file mode 100644 index 8b4ef2ac9..000000000 Binary files a/embedchain/docs/images/fullstack-api-server.png and /dev/null differ diff --git a/embedchain/docs/images/fullstack-chunks.png b/embedchain/docs/images/fullstack-chunks.png deleted file mode 100644 index ba4505ba7..000000000 Binary files a/embedchain/docs/images/fullstack-chunks.png and /dev/null differ diff --git a/embedchain/docs/images/fullstack.png b/embedchain/docs/images/fullstack.png deleted file mode 100644 index ba73bc067..000000000 Binary files a/embedchain/docs/images/fullstack.png and /dev/null differ diff --git a/embedchain/docs/images/gradio_app.png b/embedchain/docs/images/gradio_app.png deleted file mode 100644 index c5ed3cf4a..000000000 Binary files a/embedchain/docs/images/gradio_app.png and /dev/null differ diff --git a/embedchain/docs/images/helicone-embedchain.png b/embedchain/docs/images/helicone-embedchain.png deleted file mode 100644 index 05f61d73c..000000000 Binary files a/embedchain/docs/images/helicone-embedchain.png and /dev/null differ diff --git a/embedchain/docs/images/langsmith.png b/embedchain/docs/images/langsmith.png deleted file mode 100644 index 5d5ff5422..000000000 Binary files a/embedchain/docs/images/langsmith.png and /dev/null differ diff --git a/embedchain/docs/images/og.png b/embedchain/docs/images/og.png deleted file mode 100644 index 7a89999d3..000000000 Binary files a/embedchain/docs/images/og.png and /dev/null differ diff --git a/embedchain/docs/images/slack-ai.png b/embedchain/docs/images/slack-ai.png deleted file mode 100644 index cb2f137de..000000000 Binary files a/embedchain/docs/images/slack-ai.png and /dev/null differ diff --git a/embedchain/docs/images/whatsapp.jpg b/embedchain/docs/images/whatsapp.jpg deleted file mode 100644 index 6f28ba200..000000000 Binary files a/embedchain/docs/images/whatsapp.jpg and /dev/null differ diff --git a/embedchain/docs/integration/chainlit.mdx b/embedchain/docs/integration/chainlit.mdx deleted file mode 100644 index 6a28f309a..000000000 --- a/embedchain/docs/integration/chainlit.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: '⛓️ Chainlit' -description: 'Integrate with Chainlit to create LLM chat apps' ---- - -In this example, we will learn how to use Chainlit and Embedchain together. - -![chainlit-demo](https://github.com/embedchain/embedchain/assets/73601258/d6635624-5cdb-485b-bfbd-3b7c8f18bfff) - -## Setup - -First, install the required packages: - -```bash -pip install embedchain chainlit -``` - -## Create a Chainlit app - -Create a new file called `app.py` and add the following code: - -```python -import chainlit as cl -from embedchain import App - -import os - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -@cl.on_chat_start -async def on_chat_start(): - app = App.from_config(config={ - 'app': { - 'config': { - 'name': 'chainlit-app' - } - }, - 'llm': { - 'config': { - 'stream': True, - } - } - }) - # import your data here - app.add("https://www.forbes.com/profile/elon-musk/") - app.collect_metrics = False - cl.user_session.set("app", app) - - -@cl.on_message -async def on_message(message: cl.Message): - app = cl.user_session.get("app") - msg = cl.Message(content="") - for chunk in await cl.make_async(app.chat)(message.content): - await msg.stream_token(chunk) - - await msg.send() -``` - -## Run the app - -``` -chainlit run app.py -``` - -## Try it out - -Open the app in your browser and start chatting with it! diff --git a/embedchain/docs/integration/helicone.mdx b/embedchain/docs/integration/helicone.mdx deleted file mode 100644 index a5a33445a..000000000 --- a/embedchain/docs/integration/helicone.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "🧊 Helicone" -description: "Implement Helicone, the open-source LLM observability platform, with Embedchain. Monitor, debug, and optimize your AI applications effortlessly." -"twitter:title": "Helicone LLM Observability for Embedchain" ---- - -Get started with [Helicone](https://www.helicone.ai/), the open-source LLM observability platform for developers to monitor, debug, and optimize their applications. - -To use Helicone, you need to do the following steps. - -## Integration Steps - - - - Log into [Helicone](https://www.helicone.ai) or create an account. Once you have an account, you - can generate an [API key](https://helicone.ai/developer). - - - Make sure to generate a [write only API key](helicone-headers/helicone-auth). - - - - -You can configure your base_url and OpenAI API key in your codebase - - -```python main.py -import os -from embedchain import App - -# Modify the base path and add a Helicone URL -os.environ["OPENAI_API_BASE"] = "https://oai.helicone.ai/{YOUR_HELICONE_API_KEY}/v1" -# Add your OpenAI API Key -os.environ["OPENAI_API_KEY"] = "{YOUR_OPENAI_API_KEY}" - -app = App() - -# Add data to your app -app.add("https://en.wikipedia.org/wiki/Elon_Musk") - -# Query your app -print(app.query("How many companies did Elon found? Which companies?")) -``` - - - - - Embedchain requests - - - -Check out [Helicone](https://www.helicone.ai) to see more use cases! diff --git a/embedchain/docs/integration/langsmith.mdx b/embedchain/docs/integration/langsmith.mdx deleted file mode 100644 index 8a200be5b..000000000 --- a/embedchain/docs/integration/langsmith.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: '🛠️ LangSmith' -description: 'Integrate with Langsmith to debug and monitor your LLM app' ---- - -Embedchain now supports integration with [LangSmith](https://www.langchain.com/langsmith). - -To use LangSmith, you need to do the following steps. - -1. Have an account on LangSmith and keep the environment variables in handy -2. Set the environment variables in your app so that embedchain has context about it. -3. Just use embedchain and everything will be logged to LangSmith, so that you can better test and monitor your application. - -Let's cover each step in detail. - - -* First make sure that you have created a LangSmith account and have all the necessary variables handy. LangSmith has a [good documentation](https://docs.smith.langchain.com/) on how to get started with their service. - -* Once you have setup the account, we will need the following environment variables - -```bash -# Setting environment variable for LangChain Tracing V2 integration. -export LANGCHAIN_TRACING_V2=true - -# Setting the API endpoint for LangChain. -export LANGCHAIN_ENDPOINT=https://api.smith.langchain.com - -# Replace '' with your LangChain API key. -export LANGCHAIN_API_KEY= - -# Replace '' with your LangChain project name, or it defaults to "default". -export LANGCHAIN_PROJECT= # if not specified, defaults to "default" -``` - -If you are using Python, you can use the following code to set environment variables - -```python -import os - -# Setting environment variable for LangChain Tracing V2 integration. -os.environ['LANGCHAIN_TRACING_V2'] = 'true' - -# Setting the API endpoint for LangChain. -os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com' - -# Replace '' with your LangChain API key. -os.environ['LANGCHAIN_API_KEY'] = '' - -# Replace '' with your LangChain project name. -os.environ['LANGCHAIN_PROJECT'] = '' -``` - -* Now create an app using Embedchain and everything will be automatically visible in the LangSmith - - -```python -from embedchain import App - -# Initialize EmbedChain application. -app = App() - -# Add data to your app -app.add("https://en.wikipedia.org/wiki/Elon_Musk") - -# Query your app -app.query("How many companies did Elon found?") -``` - -* Now the entire log for this will be visible in langsmith. - - diff --git a/embedchain/docs/integration/openlit.mdx b/embedchain/docs/integration/openlit.mdx deleted file mode 100644 index 22036919e..000000000 --- a/embedchain/docs/integration/openlit.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: '🔭 OpenLIT' -description: 'OpenTelemetry-native Observability and Evals for LLMs & GPUs' ---- - -Embedchain now supports integration with [OpenLIT](https://github.com/openlit/openlit). - -## Getting Started - -### 1. Set environment variables -```bash -# Setting environment variable for OpenTelemetry destination and authetication. -export OTEL_EXPORTER_OTLP_ENDPOINT = "YOUR_OTEL_ENDPOINT" -export OTEL_EXPORTER_OTLP_HEADERS = "YOUR_OTEL_ENDPOINT_AUTH" -``` - -### 2. Install the OpenLIT SDK -Open your terminal and run: - -```shell -pip install openlit -``` - -### 3. Setup Your Application for Monitoring -Now create an app using Embedchain and initialize OpenTelemetry monitoring - -```python -from embedchain import App -import OpenLIT - -# Initialize OpenLIT Auto Instrumentation for monitoring. -openlit.init() - -# Initialize EmbedChain application. -app = App() - -# Add data to your app -app.add("https://en.wikipedia.org/wiki/Elon_Musk") - -# Query your app -app.query("How many companies did Elon found?") -``` - -### 4. Visualize - -Once you've set up data collection with OpenLIT, you can visualize and analyze this information to better understand your application's performance: - -- **Using OpenLIT UI:** Connect to OpenLIT's UI to start exploring performance metrics. Visit the OpenLIT [Quickstart Guide](https://docs.openlit.io/latest/quickstart) for step-by-step details. - -- **Integrate with existing Observability Tools:** If you use tools like Grafana or DataDog, you can integrate the data collected by OpenLIT. For instructions on setting up these connections, check the OpenLIT [Connections Guide](https://docs.openlit.io/latest/connections/intro). diff --git a/embedchain/docs/integration/streamlit-mistral.mdx b/embedchain/docs/integration/streamlit-mistral.mdx deleted file mode 100644 index d7b795755..000000000 --- a/embedchain/docs/integration/streamlit-mistral.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: '🚀 Streamlit' -description: 'Integrate with Streamlit to plug and play with any LLM' ---- - -In this example, we will learn how to use `mistralai/Mixtral-8x7B-Instruct-v0.1` and Embedchain together with Streamlit to build a simple RAG chatbot. - -![Streamlit + Embedchain Demo](https://github.com/embedchain/embedchain/assets/73601258/052f7378-797c-41cf-ac81-f004d0d44dd1) - -## Setup - -Install Embedchain and Streamlit. -```bash -pip install embedchain streamlit -``` - - - ```python - import os - from embedchain import App - import streamlit as st - - with st.sidebar: - huggingface_access_token = st.text_input("Hugging face Token", key="chatbot_api_key", type="password") - "[Get Hugging Face Access Token](https://huggingface.co/settings/tokens)" - "[View the source code](https://github.com/embedchain/examples/mistral-streamlit)" - - - st.title("💬 Chatbot") - st.caption("🚀 An Embedchain app powered by Mistral!") - if "messages" not in st.session_state: - st.session_state.messages = [ - { - "role": "assistant", - "content": """ - Hi! I'm a chatbot. I can answer questions and learn new things!\n - Ask me anything and if you want me to learn something do `/add `.\n - I can learn mostly everything. :) - """, - } - ] - - for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.markdown(message["content"]) - - if prompt := st.chat_input("Ask me anything!"): - if not st.session_state.chatbot_api_key: - st.error("Please enter your Hugging Face Access Token") - st.stop() - - os.environ["HUGGINGFACE_ACCESS_TOKEN"] = st.session_state.chatbot_api_key - app = App.from_config(config_path="config.yaml") - - if prompt.startswith("/add"): - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - prompt = prompt.replace("/add", "").strip() - with st.chat_message("assistant"): - message_placeholder = st.empty() - message_placeholder.markdown("Adding to knowledge base...") - app.add(prompt) - message_placeholder.markdown(f"Added {prompt} to knowledge base!") - st.session_state.messages.append({"role": "assistant", "content": f"Added {prompt} to knowledge base!"}) - st.stop() - - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - - with st.chat_message("assistant"): - msg_placeholder = st.empty() - msg_placeholder.markdown("Thinking...") - full_response = "" - - for response in app.chat(prompt): - msg_placeholder.empty() - full_response += response - - msg_placeholder.markdown(full_response) - st.session_state.messages.append({"role": "assistant", "content": full_response}) - ``` - - - ```yaml - app: - config: - name: 'mistral-streamlit-app' - - llm: - provider: huggingface - config: - model: 'mistralai/Mixtral-8x7B-Instruct-v0.1' - temperature: 0.1 - max_tokens: 250 - top_p: 0.1 - stream: true - - embedder: - provider: huggingface - config: - model: 'sentence-transformers/all-mpnet-base-v2' - ``` - - - -## To run it locally, - -```bash -streamlit run app.py -``` diff --git a/embedchain/docs/logo/dark-rt.svg b/embedchain/docs/logo/dark-rt.svg deleted file mode 100644 index 83eb7fc69..000000000 --- a/embedchain/docs/logo/dark-rt.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/embedchain/docs/logo/dark.svg b/embedchain/docs/logo/dark.svg deleted file mode 100644 index cbd502094..000000000 --- a/embedchain/docs/logo/dark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/embedchain/docs/logo/light-rt.svg b/embedchain/docs/logo/light-rt.svg deleted file mode 100644 index f204d17e6..000000000 --- a/embedchain/docs/logo/light-rt.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/embedchain/docs/logo/light.svg b/embedchain/docs/logo/light.svg deleted file mode 100644 index cbd502094..000000000 --- a/embedchain/docs/logo/light.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/embedchain/docs/mint.json b/embedchain/docs/mint.json deleted file mode 100644 index a3d4aec8d..000000000 --- a/embedchain/docs/mint.json +++ /dev/null @@ -1,277 +0,0 @@ -{ - "$schema": "https://mintlify.com/schema.json", - "name": "Embedchain", - "logo": { - "dark": "/logo/dark-rt.svg", - "light": "/logo/light-rt.svg", - "href": "https://github.com/embedchain/embedchain" - }, - "favicon": "/favicon.png", - "colors": { - "primary": "#3B2FC9", - "light": "#6673FF", - "dark": "#3B2FC9", - "background": { - "dark": "#0f1117", - "light": "#fff" - } - }, - "modeToggle": { - "default": "dark" - }, - "openapi": ["/rest-api.json"], - "metadata": { - "og:image": "/images/og.png", - "twitter:site": "@embedchain" - }, - "tabs": [ - { - "name": "Examples", - "url": "examples" - }, - { - "name": "API Reference", - "url": "api-reference" - } - ], - "anchors": [ - { - "name": "Talk to founders", - "icon": "calendar", - "url": "https://cal.com/taranjeetio/ec" - } - ], - "topbarLinks": [ - { - "name": "GitHub", - "url": "https://github.com/embedchain/embedchain" - } - ], - "topbarCtaButton": { - "name": "Join our slack", - "url": "https://embedchain.ai/slack" - }, - "primaryTab": { - "name": "📘 Documentation" - }, - "navigation": [ - { - "group": "Get Started", - "pages": [ - "get-started/quickstart", - "get-started/introduction", - "get-started/faq", - "get-started/full-stack", - { - "group": "🔗 Integrations", - "pages": [ - "integration/langsmith", - "integration/chainlit", - "integration/streamlit-mistral", - "integration/openlit", - "integration/helicone" - ] - } - ] - }, - { - "group": "Use cases", - "pages": [ - "use-cases/introduction", - "use-cases/chatbots", - "use-cases/question-answering", - "use-cases/semantic-search" - ] - }, - { - "group": "Components", - "pages": [ - "components/introduction", - { - "group": "🗂️ Data sources", - "pages": [ - "components/data-sources/overview", - { - "group": "Data types", - "pages": [ - "components/data-sources/pdf-file", - "components/data-sources/csv", - "components/data-sources/json", - "components/data-sources/text", - "components/data-sources/directory", - "components/data-sources/web-page", - "components/data-sources/youtube-channel", - "components/data-sources/youtube-video", - "components/data-sources/docs-site", - "components/data-sources/mdx", - "components/data-sources/docx", - "components/data-sources/notion", - "components/data-sources/sitemap", - "components/data-sources/xml", - "components/data-sources/qna", - "components/data-sources/openapi", - "components/data-sources/gmail", - "components/data-sources/github", - "components/data-sources/postgres", - "components/data-sources/mysql", - "components/data-sources/slack", - "components/data-sources/discord", - "components/data-sources/discourse", - "components/data-sources/substack", - "components/data-sources/beehiiv", - "components/data-sources/directory", - "components/data-sources/dropbox", - "components/data-sources/image", - "components/data-sources/audio", - "components/data-sources/custom" - ] - }, - "components/data-sources/data-type-handling" - ] - }, - { - "group": "🗄️ Vector databases", - "pages": [ - "components/vector-databases/chromadb", - "components/vector-databases/elasticsearch", - "components/vector-databases/pinecone", - "components/vector-databases/opensearch", - "components/vector-databases/qdrant", - "components/vector-databases/weaviate", - "components/vector-databases/zilliz" - ] - }, - "components/llms", - "components/embedding-models", - "components/evaluation" - ] - }, - { - "group": "Deployment", - "pages": [ - "get-started/deployment", - "deployment/fly_io", - "deployment/modal_com", - "deployment/render_com", - "deployment/railway", - "deployment/streamlit_io", - "deployment/gradio_app", - "deployment/huggingface_spaces" - ] - }, - { - "group": "Community", - "pages": ["community/connect-with-us"] - }, - { - "group": "Examples", - "pages": [ - "examples/chat-with-PDF", - "examples/notebooks-and-replits", - { - "group": "REST API Service", - "pages": [ - "examples/rest-api/getting-started", - "examples/rest-api/create", - "examples/rest-api/get-all-apps", - "examples/rest-api/add-data", - "examples/rest-api/get-data", - "examples/rest-api/query", - "examples/rest-api/deploy", - "examples/rest-api/delete", - "examples/rest-api/check-status" - ] - }, - "examples/openai-assistant", - "examples/opensource-assistant", - "examples/nextjs-assistant", - "examples/slack-AI" - ] - }, - { - "group": "Chatbots", - "pages": [ - "examples/discord_bot", - "examples/slack_bot", - "examples/telegram_bot", - "examples/whatsapp_bot", - "examples/poe_bot" - ] - }, - { - "group": "Showcase", - "pages": ["examples/showcase"] - }, - { - "group": "API Reference", - "pages": [ - "api-reference/app/overview", - { - "group": "App methods", - "pages": [ - "api-reference/app/add", - "api-reference/app/query", - "api-reference/app/chat", - "api-reference/app/search", - "api-reference/app/get", - "api-reference/app/evaluate", - "api-reference/app/deploy", - "api-reference/app/reset", - "api-reference/app/delete" - ] - }, - "api-reference/store/openai-assistant", - "api-reference/store/ai-assistants", - "api-reference/advanced/configuration" - ] - }, - { - "group": "Contributing", - "pages": [ - "contribution/guidelines", - "contribution/dev", - "contribution/docs", - "contribution/python" - ] - }, - { - "group": "Product", - "pages": ["product/release-notes"] - } - ], - "footerSocials": { - "website": "https://embedchain.ai", - "github": "https://github.com/embedchain/embedchain", - "slack": "https://embedchain.ai/slack", - "discord": "https://discord.gg/6PzXDgEjG5", - "twitter": "https://twitter.com/embedchain", - "linkedin": "https://www.linkedin.com/company/embedchain" - }, - "isWhiteLabeled": true, - "analytics": { - "posthog": { - "apiKey": "phc_PHQDA5KwztijnSojsxJ2c1DuJd52QCzJzT2xnSGvjN2", - "apiHost": "https://app.embedchain.ai/ingest" - }, - "ga4": { - "measurementId": "G-4QK7FJE6T3" - } - }, - "feedback": { - "suggestEdit": true, - "raiseIssue": true, - "thumbsRating": true - }, - "search": { - "prompt": "✨ Search embedchain docs..." - }, - "api": { - "baseUrl": "http://localhost:8080" - }, - "redirects": [ - { - "source": "/changelog/command-line", - "destination": "/get-started/introduction" - } - ] -} diff --git a/embedchain/docs/product/release-notes.mdx b/embedchain/docs/product/release-notes.mdx deleted file mode 100644 index 02bcf977b..000000000 --- a/embedchain/docs/product/release-notes.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: ' 📜 Release Notes' -url: https://github.com/embedchain/embedchain/releases ---- \ No newline at end of file diff --git a/embedchain/docs/rest-api.json b/embedchain/docs/rest-api.json deleted file mode 100644 index 087d7e06c..000000000 --- a/embedchain/docs/rest-api.json +++ /dev/null @@ -1,427 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Embedchain REST API", - "description": "This is the REST API for Embedchain.", - "license": { - "name": "Apache 2.0", - "url": "https://github.com/embedchain/embedchain/blob/main/LICENSE" - }, - "version": "0.0.1" - }, - "paths": { - "/ping": { - "get": { - "tags": ["Utility"], - "summary": "Check status", - "description": "Endpoint to check the status of the API", - "operationId": "check_status_ping_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/apps": { - "get": { - "tags": ["Apps"], - "summary": "Get all apps", - "description": "Get all applications", - "operationId": "get_all_apps_apps_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/create": { - "post": { - "tags": ["Apps"], - "summary": "Create app", - "description": "Create a new app using App ID", - "operationId": "create_app_using_default_config_create_post", - "parameters": [ - { - "name": "app_id", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_app_using_default_config_create_post" - } - ], - "title": "Body" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DefaultResponse" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/{app_id}/data": { - "get": { - "tags": ["Apps"], - "summary": "Get data", - "description": "Get all data sources for an app", - "operationId": "get_datasources_associated_with_app_id__app_id__data_get", - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/{app_id}/add": { - "post": { - "tags": ["Apps"], - "summary": "Add data", - "description": "Add a data source to an app.", - "operationId": "add_datasource_to_an_app__app_id__add_post", - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/SourceApp" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DefaultResponse" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/{app_id}/query": { - "post": { - "tags": ["Apps"], - "summary": "Query app", - "description": "Query an app", - "operationId": "query_an_app__app_id__query_post", - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/QueryApp" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DefaultResponse" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/{app_id}/chat": { - "post": { - "tags": ["Apps"], - "summary": "Chat", - "description": "Chat with an app.\n\napp_id: The ID of the app. Use \"default\" for the default app.\n\nmessage: The message that you want to send to the app.", - "operationId": "chat_with_an_app__app_id__chat_post", - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/MessageApp" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DefaultResponse" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/{app_id}/deploy": { - "post": { - "tags": ["Apps"], - "summary": "Deploy app", - "description": "Deploy an existing app.", - "operationId": "deploy_app__app_id__deploy_post", - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DeployAppRequest" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DefaultResponse" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/{app_id}/delete": { - "delete": { - "tags": ["Apps"], - "summary": "Delete app", - "description": "Delete an existing app", - "operationId": "delete_app__app_id__delete_delete", - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "App Id" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DefaultResponse" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Body_create_app_using_default_config_create_post": { - "properties": { - "config": { "type": "string", "format": "binary", "title": "Config" } - }, - "type": "object", - "title": "Body_create_app_using_default_config_create_post" - }, - "DefaultResponse": { - "properties": { "response": { "type": "string", "title": "Response" } }, - "type": "object", - "required": ["response"], - "title": "DefaultResponse" - }, - "DeployAppRequest": { - "properties": { - "api_key": { - "type": "string", - "title": "Api Key", - "description": "The Embedchain API key for app deployments. You get the api key on the Embedchain platform by visiting [https://app.embedchain.ai](https://app.embedchain.ai)", - "default": "" - } - }, - "type": "object", - "title": "DeployAppRequest", - "example":{ - "api_key":"ec-xxx" - } - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { "$ref": "#/components/schemas/ValidationError" }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, - "MessageApp": { - "properties": { - "message": { - "type": "string", - "title": "Message", - "description": "The message that you want to send to the App.", - "default": "" - } - }, - "type": "object", - "title": "MessageApp" - }, - "QueryApp": { - "properties": { - "query": { - "type": "string", - "title": "Query", - "description": "The query that you want to ask the App.", - "default": "" - } - }, - "type": "object", - "title": "QueryApp", - "example":{ - "query":"Who is Elon Musk?" - } - }, - "SourceApp": { - "properties": { - "source": { - "type": "string", - "title": "Source", - "description": "The source that you want to add to the App.", - "default": "" - }, - "data_type": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Data Type", - "description": "The type of data to add, remove it if you want Embedchain to detect it automatically.", - "default": "" - } - }, - "type": "object", - "title": "SourceApp", - "example":{ - "source":"https://en.wikipedia.org/wiki/Elon_Musk" - } - }, - "ValidationError": { - "properties": { - "loc": { - "items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] }, - "type": "array", - "title": "Location" - }, - "msg": { "type": "string", "title": "Message" }, - "type": { "type": "string", "title": "Error Type" } - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError" - } - } - } - } diff --git a/embedchain/docs/support/get-help.mdx b/embedchain/docs/support/get-help.mdx deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/docs/use-cases/chatbots.mdx b/embedchain/docs/use-cases/chatbots.mdx deleted file mode 100644 index d11e45257..000000000 --- a/embedchain/docs/use-cases/chatbots.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: '🤖 Chatbots' ---- - -Chatbots, especially those powered by Large Language Models (LLMs), have a wide range of use cases, significantly enhancing various aspects of business, education, and personal assistance. Here are some key applications: - -- **Customer Service**: Automating responses to common queries and providing 24/7 support. -- **Education**: Offering personalized tutoring and learning assistance. -- **E-commerce**: Assisting in product discovery, recommendations, and transactions. -- **Content Management**: Aiding in writing, summarizing, and organizing content. -- **Data Analysis**: Extracting insights from large datasets. -- **Language Translation**: Providing real-time multilingual support. -- **Mental Health**: Offering preliminary mental health support and conversation. -- **Entertainment**: Engaging users with games, quizzes, and humorous chats. -- **Accessibility Aid**: Enhancing information and service access for individuals with disabilities. - -Embedchain provides the right set of tools to create chatbots for the above use cases. Refer to the following examples of chatbots on and you can built on top of these examples: - - - - Build a tailored GPT chatbot suited for your specific needs. - - - Enhance your Slack workspace with a specialized bot. - - - Create an engaging bot for your Discord server. - - - Develop a handy assistant for Telegram users. - - - Design a WhatsApp bot for efficient communication. - - - Explore advanced bot interactions with Poe Bot. - - diff --git a/embedchain/docs/use-cases/introduction.mdx b/embedchain/docs/use-cases/introduction.mdx deleted file mode 100644 index e908ba64d..000000000 --- a/embedchain/docs/use-cases/introduction.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: 🧱 Introduction ---- - -## Overview - -You can use embedchain to create the following usecases: - -* [Chatbots](/use-cases/chatbots) -* [Question Answering](/use-cases/question-answering) -* [Semantic Search](/use-cases/semantic-search) \ No newline at end of file diff --git a/embedchain/docs/use-cases/question-answering.mdx b/embedchain/docs/use-cases/question-answering.mdx deleted file mode 100644 index f538419b5..000000000 --- a/embedchain/docs/use-cases/question-answering.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: '❓ Question Answering' ---- - -Utilizing large language models (LLMs) for question answering is a transformative application, bringing significant benefits to various real-world situations. Embedchain extensively supports tasks related to question answering, including summarization, content creation, language translation, and data analysis. The versatility of question answering with LLMs enables solutions for numerous practical applications such as: - -- **Educational Aid**: Enhancing learning experiences and aiding with homework -- **Customer Support**: Addressing and resolving customer queries efficiently -- **Research Assistance**: Facilitating academic and professional research endeavors -- **Healthcare Information**: Providing fundamental medical knowledge -- **Technical Support**: Resolving technology-related inquiries -- **Legal Information**: Offering basic legal advice and information -- **Business Insights**: Delivering market analysis and strategic business advice -- **Language Learning** Assistance: Aiding in understanding and translating languages -- **Travel Guidance**: Supplying information on travel and hospitality -- **Content Development**: Assisting authors and creators with research and idea generation - -## Example: Build a Q&A System with Embedchain for Next.JS - -Quickly create a RAG pipeline to answer queries about the [Next.JS Framework](https://nextjs.org/) using Embedchain tools. - -### Step 1: Set Up Your RAG Pipeline - -First, let's create your RAG pipeline. Open your Python environment and enter: - -```python Create pipeline -from embedchain import App -app = App() -``` - -This initializes your application. - -### Step 2: Populate Your Pipeline with Data - -Now, let's add data to your pipeline. We'll include the Next.JS website and its documentation: - -```python Ingest data sources -# Add Next.JS Website and docs -app.add("https://nextjs.org/sitemap.xml", data_type="sitemap") - -# Add Next.JS Forum data -app.add("https://nextjs-forum.com/sitemap.xml", data_type="sitemap") -``` - -This step incorporates over **15K pages** from the Next.JS website and forum into your pipeline. For more data source options, check the [Embedchain data sources overview](/components/data-sources/overview). - -### Step 3: Local Testing of Your Pipeline - -Test the pipeline on your local machine: - -```python Query App -app.query("Summarize the features of Next.js 14?") -``` - -Run this query to see how your pipeline responds with information about Next.js 14. - -### (Optional) Step 4: Deploying Your RAG Pipeline - -Want to go live? Deploy your pipeline with these options: - -- Deploy on the Embedchain Platform -- Self-host on your preferred cloud provider - -For detailed deployment instructions, follow these guides: - -- [Deploying on Embedchain Platform](/get-started/deployment#deploy-on-embedchain-platform) -- [Self-hosting Guide](/get-started/deployment#self-hosting) - -## Need help? - -If you are looking to configure the RAG pipeline further, feel free to checkout the [API reference](/api-reference/pipeline/query). - -In case you run into issues, feel free to contact us via any of the following methods: - - diff --git a/embedchain/docs/use-cases/semantic-search.mdx b/embedchain/docs/use-cases/semantic-search.mdx deleted file mode 100644 index f506e5dd1..000000000 --- a/embedchain/docs/use-cases/semantic-search.mdx +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: '🔍 Semantic Search' ---- - -Semantic searching, which involves understanding the intent and contextual meaning behind search queries, is yet another popular use-case of RAG. It has several popular use cases across various domains: - -- **Information Retrieval**: Enhances search accuracy in databases and websites -- **E-commerce**: Improves product discovery in online shopping -- **Customer Support**: Powers smarter chatbots for effective responses -- **Content Discovery**: Aids in finding relevant media content -- **Knowledge Management**: Streamlines document and data retrieval in enterprises -- **Healthcare**: Facilitates medical research and literature search -- **Legal Research**: Assists in legal document and case law search -- **Academic Research**: Aids in academic paper discovery -- **Language Processing**: Enables multilingual search capabilities - -Embedchain offers a simple yet customizable `search()` API that you can use for semantic search. See the example in the next section to know more. - -## Example: Semantic Search over Next.JS Website + Forum - -### Step 1: Set Up Your RAG Pipeline - -First, let's create your RAG pipeline. Open your Python environment and enter: - -```python Create pipeline -from embedchain import App -app = App() -``` - -This initializes your application. - -### Step 2: Populate Your Pipeline with Data - -Now, let's add data to your pipeline. We'll include the Next.JS website and its documentation: - -```python Ingest data sources -# Add Next.JS Website and docs -app.add("https://nextjs.org/sitemap.xml", data_type="sitemap") - -# Add Next.JS Forum data -app.add("https://nextjs-forum.com/sitemap.xml", data_type="sitemap") -``` - -This step incorporates over **15K pages** from the Next.JS website and forum into your pipeline. For more data source options, check the [Embedchain data sources overview](/components/data-sources/overview). - -### Step 3: Local Testing of Your Pipeline - -Test the pipeline on your local machine: - -```python Search App -app.search("Summarize the features of Next.js 14?") -[ - { - 'context': 'Next.js 14 | Next.jsBack to BlogThursday, October 26th 2023Next.js 14Posted byLee Robinson@leeerobTim Neutkens@timneutkensAs we announced at Next.js Conf, Next.js 14 is our most focused release with: Turbopack: 5,000 tests passing for App & Pages Router 53% faster local server startup 94% faster code updates with Fast Refresh Server Actions (Stable): Progressively enhanced mutations Integrated with caching & revalidating Simple function calls, or works natively with forms Partial Prerendering', - 'metadata': { - 'source': 'https://nextjs.org/blog/next-14', - 'document_id': '6c8d1a7b-ea34-4927-8823-daa29dcfc5af--b83edb69b8fc7e442ff8ca311b48510e6c80bf00caa806b3a6acb34e1bcdd5d5' - } - }, - { - 'context': 'Next.js 13.3 | Next.jsBack to BlogThursday, April 6th 2023Next.js 13.3Posted byDelba de Oliveira@delba_oliveiraTim Neutkens@timneutkensNext.js 13.3 adds popular community-requested features, including: File-Based Metadata API: Dynamically generate sitemaps, robots, favicons, and more. Dynamic Open Graph Images: Generate OG images using JSX, HTML, and CSS. Static Export for App Router: Static / Single-Page Application (SPA) support for Server Components. Parallel Routes and Interception: Advanced', - 'metadata': { - 'source': 'https://nextjs.org/blog/next-13-3', - 'document_id': '6c8d1a7b-ea34-4927-8823-daa29dcfc5af--b83edb69b8fc7e442ff8ca311b48510e6c80bf00caa806b3a6acb34e1bcdd5d5' - } - }, - { - 'context': 'Upgrading: Version 14 | Next.js MenuUsing App RouterFeatures available in /appApp Router.UpgradingVersion 14Version 14 Upgrading from 13 to 14 To update to Next.js version 14, run the following command using your preferred package manager: Terminalnpm i next@latest react@latest react-dom@latest eslint-config-next@latest Terminalyarn add next@latest react@latest react-dom@latest eslint-config-next@latest Terminalpnpm up next react react-dom eslint-config-next -latest Terminalbun add next@latest', - 'metadata': { - 'source': 'https://nextjs.org/docs/app/building-your-application/upgrading/version-14', - 'document_id': '6c8d1a7b-ea34-4927-8823-daa29dcfc5af--b83edb69b8fc7e442ff8ca311b48510e6c80bf00caa806b3a6acb34e1bcdd5d5' - } - } -] -``` -The `source` key contains the url of the document that yielded that document chunk. - -If you are interested in configuring the search further, refer to our [API documentation](/api-reference/pipeline/search). - -### (Optional) Step 4: Deploying Your RAG Pipeline - -Want to go live? Deploy your pipeline with these options: - -- Deploy on the Embedchain Platform -- Self-host on your preferred cloud provider - -For detailed deployment instructions, follow these guides: - -- [Deploying on Embedchain Platform](/get-started/deployment#deploy-on-embedchain-platform) -- [Self-hosting Guide](/get-started/deployment#self-hosting) - ----- - -This guide will help you swiftly set up a semantic search pipeline with Embedchain, making it easier to access and analyze specific information from large data sources. - - -## Need help? - -In case you run into issues, feel free to contact us via any of the following methods: - - diff --git a/embedchain/embedchain/__init__.py b/embedchain/embedchain/__init__.py deleted file mode 100644 index b59aed77d..000000000 --- a/embedchain/embedchain/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib.metadata - -__version__ = importlib.metadata.version(__package__ or __name__) - -from embedchain.app import App # noqa: F401 -from embedchain.client import Client # noqa: F401 -from embedchain.pipeline import Pipeline # noqa: F401 - -# Setup the user directory if doesn't exist already -Client.setup() diff --git a/embedchain/embedchain/alembic.ini b/embedchain/embedchain/alembic.ini deleted file mode 100644 index 53023ad8d..000000000 --- a/embedchain/embedchain/alembic.ini +++ /dev/null @@ -1,116 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = embedchain:migrations - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary -# hooks = ruff -# ruff.type = exec -# ruff.executable = %(here)s/.venv/bin/ruff -# ruff.options = --fix REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = WARN -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/embedchain/embedchain/app.py b/embedchain/embedchain/app.py deleted file mode 100644 index b4d051607..000000000 --- a/embedchain/embedchain/app.py +++ /dev/null @@ -1,517 +0,0 @@ -import ast -import concurrent.futures -import json -import logging -import os -from typing import Any, Optional, Union - -import requests -import yaml -from tqdm import tqdm - -from embedchain.cache import ( - Config, - ExactMatchEvaluation, - SearchDistanceEvaluation, - cache, - gptcache_data_manager, - gptcache_pre_function, -) -from embedchain.client import Client -from embedchain.config import AppConfig, CacheConfig, ChunkerConfig, Mem0Config -from embedchain.core.db.database import get_session -from embedchain.core.db.models import DataSource -from embedchain.embedchain import EmbedChain -from embedchain.embedder.base import BaseEmbedder -from embedchain.embedder.openai import OpenAIEmbedder -from embedchain.evaluation.base import BaseMetric -from embedchain.evaluation.metrics import ( - AnswerRelevance, - ContextRelevance, - Groundedness, -) -from embedchain.factory import EmbedderFactory, LlmFactory, VectorDBFactory -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm -from embedchain.llm.openai import OpenAILlm -from embedchain.telemetry.posthog import AnonymousTelemetry -from embedchain.utils.evaluation import EvalData, EvalMetric -from embedchain.utils.misc import validate_config -from embedchain.vectordb.base import BaseVectorDB -from embedchain.vectordb.chroma import ChromaDB -from mem0 import Memory - -logger = logging.getLogger(__name__) - - -@register_deserializable -class App(EmbedChain): - """ - EmbedChain App lets you create a LLM powered app for your unstructured - data by defining your chosen data source, embedding model, - and vector database. - """ - - def __init__( - self, - id: str = None, - name: str = None, - config: AppConfig = None, - db: BaseVectorDB = None, - embedding_model: BaseEmbedder = None, - llm: BaseLlm = None, - config_data: dict = None, - auto_deploy: bool = False, - chunker: ChunkerConfig = None, - cache_config: CacheConfig = None, - memory_config: Mem0Config = None, - log_level: int = logging.WARN, - ): - """ - Initialize a new `App` instance. - - :param config: Configuration for the pipeline, defaults to None - :type config: AppConfig, optional - :param db: The database to use for storing and retrieving embeddings, defaults to None - :type db: BaseVectorDB, optional - :param embedding_model: The embedding model used to calculate embeddings, defaults to None - :type embedding_model: BaseEmbedder, optional - :param llm: The LLM model used to calculate embeddings, defaults to None - :type llm: BaseLlm, optional - :param config_data: Config dictionary, defaults to None - :type config_data: dict, optional - :param auto_deploy: Whether to deploy the pipeline automatically, defaults to False - :type auto_deploy: bool, optional - :raises Exception: If an error occurs while creating the pipeline - """ - if id and config_data: - raise Exception("Cannot provide both id and config. Please provide only one of them.") - - if id and name: - raise Exception("Cannot provide both id and name. Please provide only one of them.") - - if name and config: - raise Exception("Cannot provide both name and config. Please provide only one of them.") - - self.auto_deploy = auto_deploy - # Store the dict config as an attribute to be able to send it - self.config_data = config_data if (config_data and validate_config(config_data)) else None - self.client = None - # pipeline_id from the backend - self.id = None - self.chunker = ChunkerConfig(**chunker) if chunker else None - self.cache_config = cache_config - self.memory_config = memory_config - - self.config = config or AppConfig() - self.name = self.config.name - self.config.id = self.local_id = "default-app-id" if self.config.id is None else self.config.id - - if id is not None: - # Init client first since user is trying to fetch the pipeline - # details from the platform - self._init_client() - pipeline_details = self._get_pipeline(id) - self.config.id = self.local_id = pipeline_details["metadata"]["local_id"] - self.id = id - - if name is not None: - self.name = name - - self.embedding_model = embedding_model or OpenAIEmbedder() - self.db = db or ChromaDB() - self.llm = llm or OpenAILlm() - self._init_db() - - # Session for the metadata db - self.db_session = get_session() - - # If cache_config is provided, initializing the cache ... - if self.cache_config is not None: - self._init_cache() - - # If memory_config is provided, initializing the memory ... - self.mem0_memory = None - if self.memory_config is not None: - self.mem0_memory = Memory() - - # Send anonymous telemetry - self._telemetry_props = {"class": self.__class__.__name__} - self.telemetry = AnonymousTelemetry(enabled=self.config.collect_metrics) - self.telemetry.capture(event_name="init", properties=self._telemetry_props) - - self.user_asks = [] - if self.auto_deploy: - self.deploy() - - def _init_db(self): - """ - Initialize the database. - """ - self.db._set_embedder(self.embedding_model) - self.db._initialize() - self.db.set_collection_name(self.db.config.collection_name) - - def _init_cache(self): - if self.cache_config.similarity_eval_config.strategy == "exact": - similarity_eval_func = ExactMatchEvaluation() - else: - similarity_eval_func = SearchDistanceEvaluation( - max_distance=self.cache_config.similarity_eval_config.max_distance, - positive=self.cache_config.similarity_eval_config.positive, - ) - - cache.init( - pre_embedding_func=gptcache_pre_function, - embedding_func=self.embedding_model.to_embeddings, - data_manager=gptcache_data_manager(vector_dimension=self.embedding_model.vector_dimension), - similarity_evaluation=similarity_eval_func, - config=Config(**self.cache_config.init_config.as_dict()), - ) - - def _init_client(self): - """ - Initialize the client. - """ - config = Client.load_config() - if config.get("api_key"): - self.client = Client() - else: - api_key = input( - "🔑 Enter your Embedchain API key. You can find the API key at https://app.embedchain.ai/settings/keys/ \n" # noqa: E501 - ) - self.client = Client(api_key=api_key) - - def _get_pipeline(self, id): - """ - Get existing pipeline - """ - print("🛠️ Fetching pipeline details from the platform...") - url = f"{self.client.host}/api/v1/pipelines/{id}/cli/" - r = requests.get( - url, - headers={"Authorization": f"Token {self.client.api_key}"}, - ) - if r.status_code == 404: - raise Exception(f"❌ Pipeline with id {id} not found!") - - print( - f"🎉 Pipeline loaded successfully! Pipeline url: https://app.embedchain.ai/pipelines/{r.json()['id']}\n" # noqa: E501 - ) - return r.json() - - def _create_pipeline(self): - """ - Create a pipeline on the platform. - """ - print("🛠️ Creating pipeline on the platform...") - # self.config_data is a dict. Pass it inside the key 'yaml_config' to the backend - payload = { - "yaml_config": json.dumps(self.config_data), - "name": self.name, - "local_id": self.local_id, - } - url = f"{self.client.host}/api/v1/pipelines/cli/create/" - r = requests.post( - url, - json=payload, - headers={"Authorization": f"Token {self.client.api_key}"}, - ) - if r.status_code not in [200, 201]: - raise Exception(f"❌ Error occurred while creating pipeline. API response: {r.text}") - - if r.status_code == 200: - print( - f"🎉🎉🎉 Existing pipeline found! View your pipeline: https://app.embedchain.ai/pipelines/{r.json()['id']}\n" # noqa: E501 - ) # noqa: E501 - elif r.status_code == 201: - print( - f"🎉🎉🎉 Pipeline created successfully! View your pipeline: https://app.embedchain.ai/pipelines/{r.json()['id']}\n" # noqa: E501 - ) - return r.json() - - def _get_presigned_url(self, data_type, data_value): - payload = {"data_type": data_type, "data_value": data_value} - r = requests.post( - f"{self.client.host}/api/v1/pipelines/{self.id}/cli/presigned_url/", - json=payload, - headers={"Authorization": f"Token {self.client.api_key}"}, - ) - r.raise_for_status() - return r.json() - - def _upload_file_to_presigned_url(self, presigned_url, file_path): - try: - with open(file_path, "rb") as file: - response = requests.put(presigned_url, data=file) - response.raise_for_status() - return response.status_code == 200 - except Exception as e: - logger.exception(f"Error occurred during file upload: {str(e)}") - print("❌ Error occurred during file upload!") - return False - - def _upload_data_to_pipeline(self, data_type, data_value, metadata=None): - payload = { - "data_type": data_type, - "data_value": data_value, - "metadata": metadata, - } - try: - self._send_api_request(f"/api/v1/pipelines/{self.id}/cli/add/", payload) - # print the local file path if user tries to upload a local file - printed_value = metadata.get("file_path") if metadata.get("file_path") else data_value - print(f"✅ Data of type: {data_type}, value: {printed_value} added successfully.") - except Exception as e: - print(f"❌ Error occurred during data upload for type {data_type}!. Error: {str(e)}") - - def _send_api_request(self, endpoint, payload): - url = f"{self.client.host}{endpoint}" - headers = {"Authorization": f"Token {self.client.api_key}"} - response = requests.post(url, json=payload, headers=headers) - response.raise_for_status() - return response - - def _process_and_upload_data(self, data_hash, data_type, data_value): - if os.path.isabs(data_value): - presigned_url_data = self._get_presigned_url(data_type, data_value) - presigned_url = presigned_url_data["presigned_url"] - s3_key = presigned_url_data["s3_key"] - if self._upload_file_to_presigned_url(presigned_url, file_path=data_value): - metadata = {"file_path": data_value, "s3_key": s3_key} - data_value = presigned_url - else: - logger.error(f"File upload failed for hash: {data_hash}") - return False - else: - if data_type == "qna_pair": - data_value = list(ast.literal_eval(data_value)) - metadata = {} - - try: - self._upload_data_to_pipeline(data_type, data_value, metadata) - self._mark_data_as_uploaded(data_hash) - return True - except Exception: - print(f"❌ Error occurred during data upload for hash {data_hash}!") - return False - - def _mark_data_as_uploaded(self, data_hash): - self.db_session.query(DataSource).filter_by(hash=data_hash, app_id=self.local_id).update({"is_uploaded": 1}) - - def get_data_sources(self): - data_sources = self.db_session.query(DataSource).filter_by(app_id=self.local_id).all() - results = [] - for row in data_sources: - results.append({"data_type": row.type, "data_value": row.value, "metadata": row.meta_data}) - return results - - def deploy(self): - if self.client is None: - self._init_client() - - pipeline_data = self._create_pipeline() - self.id = pipeline_data["id"] - - results = self.db_session.query(DataSource).filter_by(app_id=self.local_id, is_uploaded=0).all() - if len(results) > 0: - print("🛠️ Adding data to your pipeline...") - for result in results: - data_hash, data_type, data_value = result.hash, result.data_type, result.data_value - self._process_and_upload_data(data_hash, data_type, data_value) - - # Send anonymous telemetry - self.telemetry.capture(event_name="deploy", properties=self._telemetry_props) - - @classmethod - def from_config( - cls, - config_path: Optional[str] = None, - config: Optional[dict[str, Any]] = None, - auto_deploy: bool = False, - yaml_path: Optional[str] = None, - ): - """ - Instantiate a App object from a configuration. - - :param config_path: Path to the YAML or JSON configuration file. - :type config_path: Optional[str] - :param config: A dictionary containing the configuration. - :type config: Optional[dict[str, Any]] - :param auto_deploy: Whether to deploy the app automatically, defaults to False - :type auto_deploy: bool, optional - :param yaml_path: (Deprecated) Path to the YAML configuration file. Use config_path instead. - :type yaml_path: Optional[str] - :return: An instance of the App class. - :rtype: App - """ - # Backward compatibility for yaml_path - if yaml_path and not config_path: - config_path = yaml_path - - if config_path and config: - raise ValueError("Please provide only one of config_path or config.") - - config_data = None - - if config_path: - file_extension = os.path.splitext(config_path)[1] - with open(config_path, "r", encoding="UTF-8") as file: - if file_extension in [".yaml", ".yml"]: - config_data = yaml.safe_load(file) - elif file_extension == ".json": - config_data = json.load(file) - else: - raise ValueError("config_path must be a path to a YAML or JSON file.") - elif config and isinstance(config, dict): - config_data = config - else: - logger.error( - "Please provide either a config file path (YAML or JSON) or a config dictionary. Falling back to defaults because no config is provided.", # noqa: E501 - ) - config_data = {} - - # Validate the config - validate_config(config_data) - - app_config_data = config_data.get("app", {}).get("config", {}) - vector_db_config_data = config_data.get("vectordb", {}) - embedding_model_config_data = config_data.get("embedding_model", config_data.get("embedder", {})) - memory_config_data = config_data.get("memory", {}) - llm_config_data = config_data.get("llm", {}) - chunker_config_data = config_data.get("chunker", {}) - cache_config_data = config_data.get("cache", None) - - app_config = AppConfig(**app_config_data) - memory_config = Mem0Config(**memory_config_data) if memory_config_data else None - - vector_db_provider = vector_db_config_data.get("provider", "chroma") - vector_db = VectorDBFactory.create(vector_db_provider, vector_db_config_data.get("config", {})) - - if llm_config_data: - llm_provider = llm_config_data.get("provider", "openai") - llm = LlmFactory.create(llm_provider, llm_config_data.get("config", {})) - else: - llm = None - - embedding_model_provider = embedding_model_config_data.get("provider", "openai") - embedding_model = EmbedderFactory.create( - embedding_model_provider, embedding_model_config_data.get("config", {}) - ) - - if cache_config_data is not None: - cache_config = CacheConfig.from_config(cache_config_data) - else: - cache_config = None - - return cls( - config=app_config, - llm=llm, - db=vector_db, - embedding_model=embedding_model, - config_data=config_data, - auto_deploy=auto_deploy, - chunker=chunker_config_data, - cache_config=cache_config, - memory_config=memory_config, - ) - - def _eval(self, dataset: list[EvalData], metric: Union[BaseMetric, str]): - """ - Evaluate the app on a dataset for a given metric. - """ - metric_str = metric.name if isinstance(metric, BaseMetric) else metric - eval_class_map = { - EvalMetric.CONTEXT_RELEVANCY.value: ContextRelevance, - EvalMetric.ANSWER_RELEVANCY.value: AnswerRelevance, - EvalMetric.GROUNDEDNESS.value: Groundedness, - } - - if metric_str in eval_class_map: - return eval_class_map[metric_str]().evaluate(dataset) - - # Handle the case for custom metrics - if isinstance(metric, BaseMetric): - return metric.evaluate(dataset) - else: - raise ValueError(f"Invalid metric: {metric}") - - def evaluate( - self, - questions: Union[str, list[str]], - metrics: Optional[list[Union[BaseMetric, str]]] = None, - num_workers: int = 4, - ): - """ - Evaluate the app on a question. - - param: questions: A question or a list of questions to evaluate. - type: questions: Union[str, list[str]] - param: metrics: A list of metrics to evaluate. Defaults to all metrics. - type: metrics: Optional[list[Union[BaseMetric, str]]] - param: num_workers: Number of workers to use for parallel processing. - type: num_workers: int - return: A dictionary containing the evaluation results. - rtype: dict - """ - if "OPENAI_API_KEY" not in os.environ: - raise ValueError("Please set the OPENAI_API_KEY environment variable with permission to use `gpt4` model.") - - queries, answers, contexts = [], [], [] - if isinstance(questions, list): - with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: - future_to_data = {executor.submit(self.query, q, citations=True): q for q in questions} - for future in tqdm( - concurrent.futures.as_completed(future_to_data), - total=len(future_to_data), - desc="Getting answer and contexts for questions", - ): - question = future_to_data[future] - queries.append(question) - answer, context = future.result() - answers.append(answer) - contexts.append(list(map(lambda x: x[0], context))) - else: - answer, context = self.query(questions, citations=True) - queries = [questions] - answers = [answer] - contexts = [list(map(lambda x: x[0], context))] - - metrics = metrics or [ - EvalMetric.CONTEXT_RELEVANCY.value, - EvalMetric.ANSWER_RELEVANCY.value, - EvalMetric.GROUNDEDNESS.value, - ] - - logger.info(f"Collecting data from {len(queries)} questions for evaluation...") - dataset = [] - for q, a, c in zip(queries, answers, contexts): - dataset.append(EvalData(question=q, answer=a, contexts=c)) - - logger.info(f"Evaluating {len(dataset)} data points...") - result = {} - with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: - future_to_metric = {executor.submit(self._eval, dataset, metric): metric for metric in metrics} - for future in tqdm( - concurrent.futures.as_completed(future_to_metric), - total=len(future_to_metric), - desc="Evaluating metrics", - ): - metric = future_to_metric[future] - if isinstance(metric, BaseMetric): - result[metric.name] = future.result() - else: - result[metric] = future.result() - - if self.config.collect_metrics: - telemetry_props = self._telemetry_props - metrics_names = [] - for metric in metrics: - if isinstance(metric, BaseMetric): - metrics_names.append(metric.name) - else: - metrics_names.append(metric) - telemetry_props["metrics"] = metrics_names - self.telemetry.capture(event_name="evaluate", properties=telemetry_props) - - return result diff --git a/embedchain/embedchain/bots/__init__.py b/embedchain/embedchain/bots/__init__.py deleted file mode 100644 index 34cef58f2..000000000 --- a/embedchain/embedchain/bots/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from embedchain.bots.poe import PoeBot # noqa: F401 -from embedchain.bots.whatsapp import WhatsAppBot # noqa: F401 - -# TODO: fix discord import -# from embedchain.bots.discord import DiscordBot diff --git a/embedchain/embedchain/bots/base.py b/embedchain/embedchain/bots/base.py deleted file mode 100644 index 4a817cc4c..000000000 --- a/embedchain/embedchain/bots/base.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Any - -from embedchain import App -from embedchain.config import AddConfig, AppConfig, BaseLlmConfig -from embedchain.embedder.openai import OpenAIEmbedder -from embedchain.helpers.json_serializable import ( - JSONSerializable, - register_deserializable, -) -from embedchain.llm.openai import OpenAILlm -from embedchain.vectordb.chroma import ChromaDB - - -@register_deserializable -class BaseBot(JSONSerializable): - def __init__(self): - self.app = App(config=AppConfig(), llm=OpenAILlm(), db=ChromaDB(), embedding_model=OpenAIEmbedder()) - - def add(self, data: Any, config: AddConfig = None): - """ - Add data to the bot (to the vector database). - Auto-dectects type only, so some data types might not be usable. - - :param data: data to embed - :type data: Any - :param config: configuration class instance, defaults to None - :type config: AddConfig, optional - """ - config = config if config else AddConfig() - self.app.add(data, config=config) - - def query(self, query: str, config: BaseLlmConfig = None) -> str: - """ - Query the bot - - :param query: the user query - :type query: str - :param config: configuration class instance, defaults to None - :type config: BaseLlmConfig, optional - :return: Answer - :rtype: str - """ - config = config - return self.app.query(query, config=config) - - def start(self): - """Start the bot's functionality.""" - raise NotImplementedError("Subclasses must implement the start method.") diff --git a/embedchain/embedchain/bots/discord.py b/embedchain/embedchain/bots/discord.py deleted file mode 100644 index a288cab6d..000000000 --- a/embedchain/embedchain/bots/discord.py +++ /dev/null @@ -1,128 +0,0 @@ -import argparse -import logging -import os - -from embedchain.helpers.json_serializable import register_deserializable - -from .base import BaseBot - -try: - import discord - from discord import app_commands - from discord.ext import commands -except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Discord are not installed." "Please install with `pip install discord==2.3.2`" - ) from None - - -logger = logging.getLogger(__name__) - -intents = discord.Intents.default() -intents.message_content = True -client = discord.Client(intents=intents) -tree = app_commands.CommandTree(client) - -# Invite link example -# https://discord.com/api/oauth2/authorize?client_id={DISCORD_CLIENT_ID}&permissions=2048&scope=bot - - -@register_deserializable -class DiscordBot(BaseBot): - def __init__(self, *args, **kwargs): - BaseBot.__init__(self, *args, **kwargs) - - def add_data(self, message): - data = message.split(" ")[-1] - try: - self.add(data) - response = f"Added data from: {data}" - except Exception: - logger.exception(f"Failed to add data {data}.") - response = "Some error occurred while adding data." - return response - - def ask_bot(self, message): - try: - response = self.query(message) - except Exception: - logger.exception(f"Failed to query {message}.") - response = "An error occurred. Please try again!" - return response - - def start(self): - client.run(os.environ["DISCORD_BOT_TOKEN"]) - - -# @tree decorator cannot be used in a class. A global discord_bot is used as a workaround. - - -@tree.command(name="question", description="ask embedchain") -async def query_command(interaction: discord.Interaction, question: str): - await interaction.response.defer() - member = client.guilds[0].get_member(client.user.id) - logger.info(f"User: {member}, Query: {question}") - try: - answer = discord_bot.ask_bot(question) - if args.include_question: - response = f"> {question}\n\n{answer}" - else: - response = answer - await interaction.followup.send(response) - except Exception as e: - await interaction.followup.send("An error occurred. Please try again!") - logger.error("Error occurred during 'query' command:", e) - - -@tree.command(name="add", description="add new content to the embedchain database") -async def add_command(interaction: discord.Interaction, url_or_text: str): - await interaction.response.defer() - member = client.guilds[0].get_member(client.user.id) - logger.info(f"User: {member}, Add: {url_or_text}") - try: - response = discord_bot.add_data(url_or_text) - await interaction.followup.send(response) - except Exception as e: - await interaction.followup.send("An error occurred. Please try again!") - logger.error("Error occurred during 'add' command:", e) - - -@tree.command(name="ping", description="Simple ping pong command") -async def ping(interaction: discord.Interaction): - await interaction.response.send_message("Pong", ephemeral=True) - - -@tree.error -async def on_app_command_error(interaction: discord.Interaction, error: discord.app_commands.AppCommandError) -> None: - if isinstance(error, commands.CommandNotFound): - await interaction.followup.send("Invalid command. Please refer to the documentation for correct syntax.") - else: - logger.error("Error occurred during command execution:", error) - - -@client.event -async def on_ready(): - # TODO: Sync in admin command, to not hit rate limits. - # This might be overkill for most users, and it would require to set a guild or user id, where sync is allowed. - await tree.sync() - logger.debug("Command tree synced") - logger.info(f"Logged in as {client.user.name}") - - -def start_command(): - parser = argparse.ArgumentParser(description="EmbedChain DiscordBot command line interface") - parser.add_argument( - "--include-question", - help="include question in query reply, otherwise it is hidden behind the slash command.", - action="store_true", - ) - global args - args = parser.parse_args() - - global discord_bot - discord_bot = DiscordBot() - discord_bot.start() - - -if __name__ == "__main__": - start_command() diff --git a/embedchain/embedchain/bots/poe.py b/embedchain/embedchain/bots/poe.py deleted file mode 100644 index 25c1bba5e..000000000 --- a/embedchain/embedchain/bots/poe.py +++ /dev/null @@ -1,87 +0,0 @@ -import argparse -import logging -import os -from typing import Optional - -from embedchain.helpers.json_serializable import register_deserializable - -from .base import BaseBot - -try: - from fastapi_poe import PoeBot, run -except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Poe are not installed." "Please install with `pip install fastapi-poe==0.0.16`" - ) from None - - -def start_command(): - parser = argparse.ArgumentParser(description="EmbedChain PoeBot command line interface") - # parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind") - parser.add_argument("--port", default=8080, type=int, help="Port to bind") - parser.add_argument("--api-key", type=str, help="Poe API key") - # parser.add_argument( - # "--history-length", - # default=5, - # type=int, - # help="Set the max size of the chat history. Multiplies cost, but improves conversation awareness.", - # ) - args = parser.parse_args() - - # FIXME: Arguments are automatically loaded by Poebot's ArgumentParser which causes it to fail. - # the port argument here is also just for show, it actually works because poe has the same argument. - - run(PoeBot(), api_key=args.api_key or os.environ.get("POE_API_KEY")) - - -@register_deserializable -class PoeBot(BaseBot, PoeBot): - def __init__(self): - self.history_length = 5 - super().__init__() - - async def get_response(self, query): - last_message = query.query[-1].content - try: - history = ( - [f"{m.role}: {m.content}" for m in query.query[-(self.history_length + 1) : -1]] - if len(query.query) > 0 - else None - ) - except Exception as e: - logging.error(f"Error when processing the chat history. Message is being sent without history. Error: {e}") - answer = self.handle_message(last_message, history) - yield self.text_event(answer) - - def handle_message(self, message, history: Optional[list[str]] = None): - if message.startswith("/add "): - response = self.add_data(message) - else: - response = self.ask_bot(message, history) - return response - - # def add_data(self, message): - # data = message.split(" ")[-1] - # try: - # self.add(data) - # response = f"Added data from: {data}" - # except Exception: - # logging.exception(f"Failed to add data {data}.") - # response = "Some error occurred while adding data." - # return response - - def ask_bot(self, message, history: list[str]): - try: - self.app.llm.set_history(history=history) - response = self.query(message) - except Exception: - logging.exception(f"Failed to query {message}.") - response = "An error occurred. Please try again!" - return response - - def start(self): - start_command() - - -if __name__ == "__main__": - start_command() diff --git a/embedchain/embedchain/bots/slack.py b/embedchain/embedchain/bots/slack.py deleted file mode 100644 index be23fddd9..000000000 --- a/embedchain/embedchain/bots/slack.py +++ /dev/null @@ -1,101 +0,0 @@ -import argparse -import logging -import os -import signal -import sys - -from embedchain import App -from embedchain.helpers.json_serializable import register_deserializable - -from .base import BaseBot - -try: - from flask import Flask, request - from slack_sdk import WebClient -except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Slack are not installed." - "Please install with `pip install slack-sdk==3.21.3 flask==2.3.3`" - ) from None - - -logger = logging.getLogger(__name__) - -SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN") - - -@register_deserializable -class SlackBot(BaseBot): - def __init__(self): - self.client = WebClient(token=SLACK_BOT_TOKEN) - self.chat_bot = App() - self.recent_message = {"ts": 0, "channel": ""} - super().__init__() - - def handle_message(self, event_data): - message = event_data.get("event") - if message and "text" in message and message.get("subtype") != "bot_message": - text: str = message["text"] - if float(message.get("ts")) > float(self.recent_message["ts"]): - self.recent_message["ts"] = message["ts"] - self.recent_message["channel"] = message["channel"] - if text.startswith("query"): - _, question = text.split(" ", 1) - try: - response = self.chat_bot.chat(question) - self.send_slack_message(message["channel"], response) - logger.info("Query answered successfully!") - except Exception as e: - self.send_slack_message(message["channel"], "An error occurred. Please try again!") - logger.error("Error occurred during 'query' command:", e) - elif text.startswith("add"): - _, data_type, url_or_text = text.split(" ", 2) - if url_or_text.startswith("<") and url_or_text.endswith(">"): - url_or_text = url_or_text[1:-1] - try: - self.chat_bot.add(url_or_text, data_type) - self.send_slack_message(message["channel"], f"Added {data_type} : {url_or_text}") - except ValueError as e: - self.send_slack_message(message["channel"], f"Error: {str(e)}") - logger.error("Error occurred during 'add' command:", e) - except Exception as e: - self.send_slack_message(message["channel"], f"Failed to add {data_type} : {url_or_text}") - logger.error("Error occurred during 'add' command:", e) - - def send_slack_message(self, channel, message): - response = self.client.chat_postMessage(channel=channel, text=message) - return response - - def start(self, host="0.0.0.0", port=5000, debug=True): - app = Flask(__name__) - - def signal_handler(sig, frame): - logger.info("\nGracefully shutting down the SlackBot...") - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - - @app.route("/", methods=["POST"]) - def chat(): - # Check if the request is a verification request - if request.json.get("challenge"): - return str(request.json.get("challenge")) - - response = self.handle_message(request.json) - return str(response) - - app.run(host=host, port=port, debug=debug) - - -def start_command(): - parser = argparse.ArgumentParser(description="EmbedChain SlackBot command line interface") - parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind") - parser.add_argument("--port", default=5000, type=int, help="Port to bind") - args = parser.parse_args() - - slack_bot = SlackBot() - slack_bot.start(host=args.host, port=args.port) - - -if __name__ == "__main__": - start_command() diff --git a/embedchain/embedchain/bots/whatsapp.py b/embedchain/embedchain/bots/whatsapp.py deleted file mode 100644 index bec926bbe..000000000 --- a/embedchain/embedchain/bots/whatsapp.py +++ /dev/null @@ -1,83 +0,0 @@ -import argparse -import importlib -import logging -import signal -import sys - -from embedchain.helpers.json_serializable import register_deserializable - -from .base import BaseBot - -logger = logging.getLogger(__name__) - - -@register_deserializable -class WhatsAppBot(BaseBot): - def __init__(self): - try: - self.flask = importlib.import_module("flask") - self.twilio = importlib.import_module("twilio") - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for WhatsApp are not installed. " - "Please install with `pip install twilio==8.5.0 flask==2.3.3`" - ) from None - super().__init__() - - def handle_message(self, message): - if message.startswith("add "): - response = self.add_data(message) - else: - response = self.ask_bot(message) - return response - - def add_data(self, message): - data = message.split(" ")[-1] - try: - self.add(data) - response = f"Added data from: {data}" - except Exception: - logger.exception(f"Failed to add data {data}.") - response = "Some error occurred while adding data." - return response - - def ask_bot(self, message): - try: - response = self.query(message) - except Exception: - logger.exception(f"Failed to query {message}.") - response = "An error occurred. Please try again!" - return response - - def start(self, host="0.0.0.0", port=5000, debug=True): - app = self.flask.Flask(__name__) - - def signal_handler(sig, frame): - logger.info("\nGracefully shutting down the WhatsAppBot...") - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - - @app.route("/chat", methods=["POST"]) - def chat(): - incoming_message = self.flask.request.values.get("Body", "").lower() - response = self.handle_message(incoming_message) - twilio_response = self.twilio.twiml.messaging_response.MessagingResponse() - twilio_response.message(response) - return str(twilio_response) - - app.run(host=host, port=port, debug=debug) - - -def start_command(): - parser = argparse.ArgumentParser(description="EmbedChain WhatsAppBot command line interface") - parser.add_argument("--host", default="0.0.0.0", help="Host IP to bind") - parser.add_argument("--port", default=5000, type=int, help="Port to bind") - args = parser.parse_args() - - whatsapp_bot = WhatsAppBot() - whatsapp_bot.start(host=args.host, port=args.port) - - -if __name__ == "__main__": - start_command() diff --git a/embedchain/embedchain/cache.py b/embedchain/embedchain/cache.py deleted file mode 100644 index 765141c3c..000000000 --- a/embedchain/embedchain/cache.py +++ /dev/null @@ -1,46 +0,0 @@ -import logging -import os # noqa: F401 -from typing import Any - -from gptcache import cache # noqa: F401 -from gptcache.adapter.adapter import adapt # noqa: F401 -from gptcache.config import Config # noqa: F401 -from gptcache.manager import get_data_manager -from gptcache.manager.scalar_data.base import Answer -from gptcache.manager.scalar_data.base import DataType as CacheDataType -from gptcache.session import Session -from gptcache.similarity_evaluation.distance import ( # noqa: F401 - SearchDistanceEvaluation, -) -from gptcache.similarity_evaluation.exact_match import ( # noqa: F401 - ExactMatchEvaluation, -) - -logger = logging.getLogger(__name__) - - -def gptcache_pre_function(data: dict[str, Any], **params: dict[str, Any]): - return data["input_query"] - - -def gptcache_data_manager(vector_dimension): - return get_data_manager(cache_base="sqlite", vector_base="chromadb", max_size=1000, eviction="LRU") - - -def gptcache_data_convert(cache_data): - logger.info("[Cache] Cache hit, returning cache data...") - return cache_data - - -def gptcache_update_cache_callback(llm_data, update_cache_func, *args, **kwargs): - logger.info("[Cache] Cache missed, updating cache...") - update_cache_func(Answer(llm_data, CacheDataType.STR)) - return llm_data - - -def _gptcache_session_hit_func(cur_session_id: str, cache_session_ids: list, cache_questions: list, cache_answer: str): - return cur_session_id in cache_session_ids - - -def get_gptcache_session(session_id: str): - return Session(name=session_id, check_hit_func=_gptcache_session_hit_func) diff --git a/embedchain/embedchain/chunkers/__init__.py b/embedchain/embedchain/chunkers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/chunkers/audio.py b/embedchain/embedchain/chunkers/audio.py deleted file mode 100644 index 0aebda32e..000000000 --- a/embedchain/embedchain/chunkers/audio.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class AudioChunker(BaseChunker): - """Chunker for audio.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/base_chunker.py b/embedchain/embedchain/chunkers/base_chunker.py deleted file mode 100644 index 1f04a7d3f..000000000 --- a/embedchain/embedchain/chunkers/base_chunker.py +++ /dev/null @@ -1,94 +0,0 @@ -import hashlib -import logging -from typing import Any, Optional - -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import JSONSerializable -from embedchain.models.data_type import DataType - -logger = logging.getLogger(__name__) - - -class BaseChunker(JSONSerializable): - def __init__(self, text_splitter): - """Initialize the chunker.""" - self.text_splitter = text_splitter - self.data_type = None - - def create_chunks( - self, - loader, - src, - app_id=None, - config: Optional[ChunkerConfig] = None, - **kwargs: Optional[dict[str, Any]], - ): - """ - Loads data and chunks it. - - :param loader: The loader whose `load_data` method is used to create - the raw data. - :param src: The data to be handled by the loader. Can be a URL for - remote sources or local content for local loaders. - :param app_id: App id used to generate the doc_id. - """ - documents = [] - chunk_ids = [] - id_map = {} - min_chunk_size = config.min_chunk_size if config is not None else 1 - logger.info(f"Skipping chunks smaller than {min_chunk_size} characters") - data_result = loader.load_data(src, **kwargs) - data_records = data_result["data"] - doc_id = data_result["doc_id"] - # Prefix app_id in the document id if app_id is not None to - # distinguish between different documents stored in the same - # elasticsearch or opensearch index - doc_id = f"{app_id}--{doc_id}" if app_id is not None else doc_id - metadatas = [] - for data in data_records: - content = data["content"] - - metadata = data["meta_data"] - # add data type to meta data to allow query using data type - metadata["data_type"] = self.data_type.value - metadata["doc_id"] = doc_id - - # TODO: Currently defaulting to the src as the url. This is done intentianally since some - # of the data types like 'gmail' loader doesn't have the url in the meta data. - url = metadata.get("url", src) - - chunks = self.get_chunks(content) - for chunk in chunks: - chunk_id = hashlib.sha256((chunk + url).encode()).hexdigest() - chunk_id = f"{app_id}--{chunk_id}" if app_id is not None else chunk_id - if id_map.get(chunk_id) is None and len(chunk) >= min_chunk_size: - id_map[chunk_id] = True - chunk_ids.append(chunk_id) - documents.append(chunk) - metadatas.append(metadata) - return { - "documents": documents, - "ids": chunk_ids, - "metadatas": metadatas, - "doc_id": doc_id, - } - - def get_chunks(self, content): - """ - Returns chunks using text splitter instance. - - Override in child class if custom logic. - """ - return self.text_splitter.split_text(content) - - def set_data_type(self, data_type: DataType): - """ - set the data type of chunker - """ - self.data_type = data_type - - # TODO: This should be done during initialization. This means it has to be done in the child classes. - - @staticmethod - def get_word_count(documents) -> int: - return sum(len(document.split(" ")) for document in documents) diff --git a/embedchain/embedchain/chunkers/beehiiv.py b/embedchain/embedchain/chunkers/beehiiv.py deleted file mode 100644 index 7c130d542..000000000 --- a/embedchain/embedchain/chunkers/beehiiv.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class BeehiivChunker(BaseChunker): - """Chunker for Beehiiv.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/common_chunker.py b/embedchain/embedchain/chunkers/common_chunker.py deleted file mode 100644 index 53676d400..000000000 --- a/embedchain/embedchain/chunkers/common_chunker.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class CommonChunker(BaseChunker): - """Common chunker for all loaders.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=2000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/discourse.py b/embedchain/embedchain/chunkers/discourse.py deleted file mode 100644 index 14898bf01..000000000 --- a/embedchain/embedchain/chunkers/discourse.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class DiscourseChunker(BaseChunker): - """Chunker for discourse.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/docs_site.py b/embedchain/embedchain/chunkers/docs_site.py deleted file mode 100644 index d51dc8ee2..000000000 --- a/embedchain/embedchain/chunkers/docs_site.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class DocsSiteChunker(BaseChunker): - """Chunker for code docs site.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=500, chunk_overlap=50, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/docx_file.py b/embedchain/embedchain/chunkers/docx_file.py deleted file mode 100644 index 1452349e8..000000000 --- a/embedchain/embedchain/chunkers/docx_file.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class DocxFileChunker(BaseChunker): - """Chunker for .docx file.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/excel_file.py b/embedchain/embedchain/chunkers/excel_file.py deleted file mode 100644 index 7de00a52f..000000000 --- a/embedchain/embedchain/chunkers/excel_file.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class ExcelFileChunker(BaseChunker): - """Chunker for Excel file.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/gmail.py b/embedchain/embedchain/chunkers/gmail.py deleted file mode 100644 index 6b804f546..000000000 --- a/embedchain/embedchain/chunkers/gmail.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class GmailChunker(BaseChunker): - """Chunker for gmail.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/google_drive.py b/embedchain/embedchain/chunkers/google_drive.py deleted file mode 100644 index 8440325b5..000000000 --- a/embedchain/embedchain/chunkers/google_drive.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class GoogleDriveChunker(BaseChunker): - """Chunker for google drive folder.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/image.py b/embedchain/embedchain/chunkers/image.py deleted file mode 100644 index d29a84f4d..000000000 --- a/embedchain/embedchain/chunkers/image.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class ImageChunker(BaseChunker): - """Chunker for Images.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=2000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/json.py b/embedchain/embedchain/chunkers/json.py deleted file mode 100644 index ebc525419..000000000 --- a/embedchain/embedchain/chunkers/json.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class JSONChunker(BaseChunker): - """Chunker for json.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/mdx.py b/embedchain/embedchain/chunkers/mdx.py deleted file mode 100644 index 1c277dda7..000000000 --- a/embedchain/embedchain/chunkers/mdx.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class MdxChunker(BaseChunker): - """Chunker for mdx files.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/mysql.py b/embedchain/embedchain/chunkers/mysql.py deleted file mode 100644 index 2b1c11ace..000000000 --- a/embedchain/embedchain/chunkers/mysql.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class MySQLChunker(BaseChunker): - """Chunker for json.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/notion.py b/embedchain/embedchain/chunkers/notion.py deleted file mode 100644 index 190d59b57..000000000 --- a/embedchain/embedchain/chunkers/notion.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class NotionChunker(BaseChunker): - """Chunker for notion.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/openapi.py b/embedchain/embedchain/chunkers/openapi.py deleted file mode 100644 index fbe7b708b..000000000 --- a/embedchain/embedchain/chunkers/openapi.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig - - -class OpenAPIChunker(BaseChunker): - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/pdf_file.py b/embedchain/embedchain/chunkers/pdf_file.py deleted file mode 100644 index 56bae064e..000000000 --- a/embedchain/embedchain/chunkers/pdf_file.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class PdfFileChunker(BaseChunker): - """Chunker for PDF file.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/postgres.py b/embedchain/embedchain/chunkers/postgres.py deleted file mode 100644 index 7c6859bd0..000000000 --- a/embedchain/embedchain/chunkers/postgres.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class PostgresChunker(BaseChunker): - """Chunker for postgres.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/qna_pair.py b/embedchain/embedchain/chunkers/qna_pair.py deleted file mode 100644 index c0d8277b1..000000000 --- a/embedchain/embedchain/chunkers/qna_pair.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class QnaPairChunker(BaseChunker): - """Chunker for QnA pair.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/rss_feed.py b/embedchain/embedchain/chunkers/rss_feed.py deleted file mode 100644 index 1767f9edd..000000000 --- a/embedchain/embedchain/chunkers/rss_feed.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class RSSFeedChunker(BaseChunker): - """Chunker for RSS Feed.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=2000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/sitemap.py b/embedchain/embedchain/chunkers/sitemap.py deleted file mode 100644 index 64e773742..000000000 --- a/embedchain/embedchain/chunkers/sitemap.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class SitemapChunker(BaseChunker): - """Chunker for sitemap.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=500, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/slack.py b/embedchain/embedchain/chunkers/slack.py deleted file mode 100644 index 595682beb..000000000 --- a/embedchain/embedchain/chunkers/slack.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class SlackChunker(BaseChunker): - """Chunker for postgres.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/substack.py b/embedchain/embedchain/chunkers/substack.py deleted file mode 100644 index 92cacd6cb..000000000 --- a/embedchain/embedchain/chunkers/substack.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class SubstackChunker(BaseChunker): - """Chunker for Substack.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/table.py b/embedchain/embedchain/chunkers/table.py deleted file mode 100644 index 567ed6541..000000000 --- a/embedchain/embedchain/chunkers/table.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig - - -class TableChunker(BaseChunker): - """Chunker for tables, for instance csv, google sheets or databases.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/text.py b/embedchain/embedchain/chunkers/text.py deleted file mode 100644 index f33d60c46..000000000 --- a/embedchain/embedchain/chunkers/text.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class TextChunker(BaseChunker): - """Chunker for text.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=300, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/unstructured_file.py b/embedchain/embedchain/chunkers/unstructured_file.py deleted file mode 100644 index d55f23ef0..000000000 --- a/embedchain/embedchain/chunkers/unstructured_file.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class UnstructuredFileChunker(BaseChunker): - """Chunker for Unstructured file.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=1000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/web_page.py b/embedchain/embedchain/chunkers/web_page.py deleted file mode 100644 index 5ef7f40df..000000000 --- a/embedchain/embedchain/chunkers/web_page.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class WebPageChunker(BaseChunker): - """Chunker for web page.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=2000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/xml.py b/embedchain/embedchain/chunkers/xml.py deleted file mode 100644 index c1bab0a77..000000000 --- a/embedchain/embedchain/chunkers/xml.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class XmlChunker(BaseChunker): - """Chunker for XML files.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=500, chunk_overlap=50, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/chunkers/youtube_video.py b/embedchain/embedchain/chunkers/youtube_video.py deleted file mode 100644 index bde0a8f78..000000000 --- a/embedchain/embedchain/chunkers/youtube_video.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from langchain.text_splitter import RecursiveCharacterTextSplitter - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class YoutubeVideoChunker(BaseChunker): - """Chunker for Youtube video.""" - - def __init__(self, config: Optional[ChunkerConfig] = None): - if config is None: - config = ChunkerConfig(chunk_size=2000, chunk_overlap=0, length_function=len) - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=config.chunk_size, - chunk_overlap=config.chunk_overlap, - length_function=config.length_function, - ) - super().__init__(text_splitter) diff --git a/embedchain/embedchain/cli.py b/embedchain/embedchain/cli.py deleted file mode 100644 index e4f0401d5..000000000 --- a/embedchain/embedchain/cli.py +++ /dev/null @@ -1,335 +0,0 @@ -import json -import os -import shutil -import signal -import subprocess -import sys -import tempfile -import time -import zipfile -from pathlib import Path - -import click -import requests -from rich.console import Console - -from embedchain.telemetry.posthog import AnonymousTelemetry -from embedchain.utils.cli import ( - deploy_fly, - deploy_gradio_app, - deploy_hf_spaces, - deploy_modal, - deploy_render, - deploy_streamlit, - get_pkg_path_from_name, - setup_fly_io_app, - setup_gradio_app, - setup_hf_app, - setup_modal_com_app, - setup_render_com_app, - setup_streamlit_io_app, -) - -console = Console() -api_process = None -ui_process = None - -anonymous_telemetry = AnonymousTelemetry() - - -def signal_handler(sig, frame): - """Signal handler to catch termination signals and kill server processes.""" - global api_process, ui_process - console.print("\n🛑 [bold yellow]Stopping servers...[/bold yellow]") - if api_process: - api_process.terminate() - console.print("🛑 [bold yellow]API server stopped.[/bold yellow]") - if ui_process: - ui_process.terminate() - console.print("🛑 [bold yellow]UI server stopped.[/bold yellow]") - sys.exit(0) - - -@click.group() -def cli(): - pass - - -@cli.command() -@click.argument("app_name") -@click.option("--docker", is_flag=True, help="Use docker to create the app.") -@click.pass_context -def create_app(ctx, app_name, docker): - if Path(app_name).exists(): - console.print( - f"❌ [red]Directory '{app_name}' already exists. Try using a new directory name, or remove it.[/red]" - ) - return - - os.makedirs(app_name) - os.chdir(app_name) - - # Step 1: Download the zip file - zip_url = "http://github.com/embedchain/ec-admin/archive/main.zip" - console.print(f"Creating a new embedchain app in [green]{Path().resolve()}[/green]\n") - try: - response = requests.get(zip_url) - response.raise_for_status() - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(response.content) - zip_file_path = tmp_file.name - console.print("✅ [bold green]Fetched template successfully.[/bold green]") - except requests.RequestException as e: - console.print(f"❌ [bold red]Failed to download zip file: {e}[/bold red]") - anonymous_telemetry.capture(event_name="ec_create_app", properties={"success": False}) - return - - # Step 2: Extract the zip file - try: - with zipfile.ZipFile(zip_file_path, "r") as zip_ref: - # Get the name of the root directory inside the zip file - root_dir = Path(zip_ref.namelist()[0]) - for member in zip_ref.infolist(): - # Build the path to extract the file to, skipping the root directory - target_file = Path(member.filename).relative_to(root_dir) - source_file = zip_ref.open(member, "r") - if member.is_dir(): - # Create directory if it doesn't exist - os.makedirs(target_file, exist_ok=True) - else: - with open(target_file, "wb") as file: - # Write the file - shutil.copyfileobj(source_file, file) - console.print("✅ [bold green]Extracted zip file successfully.[/bold green]") - anonymous_telemetry.capture(event_name="ec_create_app", properties={"success": True}) - except zipfile.BadZipFile: - console.print("❌ [bold red]Error in extracting zip file. The file might be corrupted.[/bold red]") - anonymous_telemetry.capture(event_name="ec_create_app", properties={"success": False}) - return - - if docker: - subprocess.run(["docker-compose", "build"], check=True) - else: - ctx.invoke(install_reqs) - - -@cli.command() -def install_reqs(): - try: - console.print("Installing python requirements...\n") - time.sleep(2) - os.chdir("api") - subprocess.run(["pip", "install", "-r", "requirements.txt"], check=True) - os.chdir("..") - console.print("\n ✅ [bold green]Installed API requirements successfully.[/bold green]\n") - except Exception as e: - console.print(f"❌ [bold red]Failed to install API requirements: {e}[/bold red]") - anonymous_telemetry.capture(event_name="ec_install_reqs", properties={"success": False}) - return - - try: - os.chdir("ui") - subprocess.run(["yarn"], check=True) - console.print("\n✅ [bold green]Successfully installed frontend requirements.[/bold green]") - anonymous_telemetry.capture(event_name="ec_install_reqs", properties={"success": True}) - except Exception as e: - console.print(f"❌ [bold red]Failed to install frontend requirements. Error: {e}[/bold red]") - anonymous_telemetry.capture(event_name="ec_install_reqs", properties={"success": False}) - - -@cli.command() -@click.option("--docker", is_flag=True, help="Run inside docker.") -def start(docker): - if docker: - subprocess.run(["docker-compose", "up"], check=True) - return - - # Set up signal handling - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - # Step 1: Start the API server - try: - os.chdir("api") - api_process = subprocess.Popen(["python", "-m", "main"], stdout=None, stderr=None) - os.chdir("..") - console.print("✅ [bold green]API server started successfully.[/bold green]") - except Exception as e: - console.print(f"❌ [bold red]Failed to start the API server: {e}[/bold red]") - anonymous_telemetry.capture(event_name="ec_start", properties={"success": False}) - return - - # Sleep for 2 seconds to give the user time to read the message - time.sleep(2) - - # Step 2: Install UI requirements and start the UI server - try: - os.chdir("ui") - subprocess.run(["yarn"], check=True) - ui_process = subprocess.Popen(["yarn", "dev"]) - console.print("✅ [bold green]UI server started successfully.[/bold green]") - anonymous_telemetry.capture(event_name="ec_start", properties={"success": True}) - except Exception as e: - console.print(f"❌ [bold red]Failed to start the UI server: {e}[/bold red]") - anonymous_telemetry.capture(event_name="ec_start", properties={"success": False}) - - # Keep the script running until it receives a kill signal - try: - api_process.wait() - ui_process.wait() - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]Stopping server...[/bold yellow]") - - -@cli.command() -@click.option("--template", default="fly.io", help="The template to use.") -@click.argument("extra_args", nargs=-1, type=click.UNPROCESSED) -def create(template, extra_args): - anonymous_telemetry.capture(event_name="ec_create", properties={"template_used": template}) - template_dir = template - if "/" in template_dir: - template_dir = template.split("/")[1] - src_path = get_pkg_path_from_name(template_dir) - shutil.copytree(src_path, os.getcwd(), dirs_exist_ok=True) - console.print(f"✅ [bold green]Successfully created app from template '{template}'.[/bold green]") - - if template == "fly.io": - setup_fly_io_app(extra_args) - elif template == "modal.com": - setup_modal_com_app(extra_args) - elif template == "render.com": - setup_render_com_app() - elif template == "streamlit.io": - setup_streamlit_io_app() - elif template == "gradio.app": - setup_gradio_app() - elif template == "hf/gradio.app" or template == "hf/streamlit.io": - setup_hf_app() - else: - raise ValueError(f"Unknown template '{template}'.") - - embedchain_config = {"provider": template} - with open("embedchain.json", "w") as file: - json.dump(embedchain_config, file, indent=4) - console.print( - f"🎉 [green]All done! Successfully created `embedchain.json` with '{template}' as provider.[/green]" - ) - - -def run_dev_fly_io(debug, host, port): - uvicorn_command = ["uvicorn", "app:app"] - - if debug: - uvicorn_command.append("--reload") - - uvicorn_command.extend(["--host", host, "--port", str(port)]) - - try: - console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]") - subprocess.run(uvicorn_command, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") - - -def run_dev_modal_com(): - modal_run_cmd = ["modal", "serve", "app"] - try: - console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(modal_run_cmd)}[/bold cyan]") - subprocess.run(modal_run_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") - - -def run_dev_streamlit_io(): - streamlit_run_cmd = ["streamlit", "run", "app.py"] - try: - console.print(f"🚀 [bold cyan]Running Streamlit app with command: {' '.join(streamlit_run_cmd)}[/bold cyan]") - subprocess.run(streamlit_run_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]Streamlit server stopped[/bold yellow]") - - -def run_dev_render_com(debug, host, port): - uvicorn_command = ["uvicorn", "app:app"] - - if debug: - uvicorn_command.append("--reload") - - uvicorn_command.extend(["--host", host, "--port", str(port)]) - - try: - console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]") - subprocess.run(uvicorn_command, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") - - -def run_dev_gradio(): - gradio_run_cmd = ["gradio", "app.py"] - try: - console.print(f"🚀 [bold cyan]Running Gradio app with command: {' '.join(gradio_run_cmd)}[/bold cyan]") - subprocess.run(gradio_run_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]Gradio server stopped[/bold yellow]") - - -@cli.command() -@click.option("--debug", is_flag=True, help="Enable or disable debug mode.") -@click.option("--host", default="127.0.0.1", help="The host address to run the FastAPI app on.") -@click.option("--port", default=8000, help="The port to run the FastAPI app on.") -def dev(debug, host, port): - template = "" - with open("embedchain.json", "r") as file: - embedchain_config = json.load(file) - template = embedchain_config["provider"] - - anonymous_telemetry.capture(event_name="ec_dev", properties={"template_used": template}) - if template == "fly.io": - run_dev_fly_io(debug, host, port) - elif template == "modal.com": - run_dev_modal_com() - elif template == "render.com": - run_dev_render_com(debug, host, port) - elif template == "streamlit.io" or template == "hf/streamlit.io": - run_dev_streamlit_io() - elif template == "gradio.app" or template == "hf/gradio.app": - run_dev_gradio() - else: - raise ValueError(f"Unknown template '{template}'.") - - -@cli.command() -def deploy(): - # Check for platform-specific files - template = "" - ec_app_name = "" - with open("embedchain.json", "r") as file: - embedchain_config = json.load(file) - ec_app_name = embedchain_config["name"] if "name" in embedchain_config else None - template = embedchain_config["provider"] - - anonymous_telemetry.capture(event_name="ec_deploy", properties={"template_used": template}) - if template == "fly.io": - deploy_fly() - elif template == "modal.com": - deploy_modal() - elif template == "render.com": - deploy_render() - elif template == "streamlit.io": - deploy_streamlit() - elif template == "gradio.app": - deploy_gradio_app() - elif template.startswith("hf/"): - deploy_hf_spaces(ec_app_name) - else: - console.print("❌ [bold red]No recognized deployment platform found.[/bold red]") diff --git a/embedchain/embedchain/client.py b/embedchain/embedchain/client.py deleted file mode 100644 index 7e8fcddbb..000000000 --- a/embedchain/embedchain/client.py +++ /dev/null @@ -1,103 +0,0 @@ -import json -import logging -import os -import uuid - -import requests - -from embedchain.constants import CONFIG_DIR, CONFIG_FILE - -logger = logging.getLogger(__name__) - - -class Client: - def __init__(self, api_key=None, host="https://apiv2.embedchain.ai"): - self.config_data = self.load_config() - self.host = host - - if api_key: - if self.check(api_key): - self.api_key = api_key - self.save() - else: - raise ValueError( - "Invalid API key provided. You can find your API key on https://app.embedchain.ai/settings/keys." - ) - else: - if "api_key" in self.config_data: - self.api_key = self.config_data["api_key"] - logger.info("API key loaded successfully!") - else: - raise ValueError( - "You are not logged in. Please obtain an API key from https://app.embedchain.ai/settings/keys/" - ) - - @classmethod - def setup(cls): - """ - Loads the user id from the config file if it exists, otherwise generates a new - one and saves it to the config file. - - :return: user id - :rtype: str - """ - os.makedirs(CONFIG_DIR, exist_ok=True) - - if os.path.exists(CONFIG_FILE): - with open(CONFIG_FILE, "r") as f: - data = json.load(f) - if "user_id" in data: - return data["user_id"] - - u_id = str(uuid.uuid4()) - with open(CONFIG_FILE, "w") as f: - json.dump({"user_id": u_id}, f) - - @classmethod - def load_config(cls): - if not os.path.exists(CONFIG_FILE): - cls.setup() - - with open(CONFIG_FILE, "r") as config_file: - return json.load(config_file) - - def save(self): - self.config_data["api_key"] = self.api_key - with open(CONFIG_FILE, "w") as config_file: - json.dump(self.config_data, config_file, indent=4) - - logger.info("API key saved successfully!") - - def clear(self): - if "api_key" in self.config_data: - del self.config_data["api_key"] - with open(CONFIG_FILE, "w") as config_file: - json.dump(self.config_data, config_file, indent=4) - self.api_key = None - logger.info("API key deleted successfully!") - else: - logger.warning("API key not found in the configuration file.") - - def update(self, api_key): - if self.check(api_key): - self.api_key = api_key - self.save() - logger.info("API key updated successfully!") - else: - logger.warning("Invalid API key provided. API key not updated.") - - def check(self, api_key): - validation_url = f"{self.host}/api/v1/accounts/api_keys/validate/" - response = requests.post(validation_url, headers={"Authorization": f"Token {api_key}"}) - if response.status_code == 200: - return True - else: - logger.warning(f"Response from API: {response.text}") - logger.warning("Invalid API key. Unable to validate.") - return False - - def get(self): - return self.api_key - - def __str__(self): - return self.api_key diff --git a/embedchain/embedchain/config/__init__.py b/embedchain/embedchain/config/__init__.py deleted file mode 100644 index 768408b78..000000000 --- a/embedchain/embedchain/config/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# flake8: noqa: F401 - -from .add_config import AddConfig, ChunkerConfig -from .app_config import AppConfig -from .base_config import BaseConfig -from .cache_config import CacheConfig -from .embedder.base import BaseEmbedderConfig -from .embedder.base import BaseEmbedderConfig as EmbedderConfig -from .embedder.ollama import OllamaEmbedderConfig -from .llm.base import BaseLlmConfig -from .mem0_config import Mem0Config -from .vector_db.chroma import ChromaDbConfig -from .vector_db.elasticsearch import ElasticsearchDBConfig -from .vector_db.opensearch import OpenSearchDBConfig -from .vector_db.zilliz import ZillizDBConfig diff --git a/embedchain/embedchain/config/add_config.py b/embedchain/embedchain/config/add_config.py deleted file mode 100644 index 56686e8ec..000000000 --- a/embedchain/embedchain/config/add_config.py +++ /dev/null @@ -1,79 +0,0 @@ -import builtins -import logging -from collections.abc import Callable -from importlib import import_module -from typing import Optional - -from embedchain.config.base_config import BaseConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class ChunkerConfig(BaseConfig): - """ - Config for the chunker used in `add` method - """ - - def __init__( - self, - chunk_size: Optional[int] = 2000, - chunk_overlap: Optional[int] = 0, - length_function: Optional[Callable[[str], int]] = None, - min_chunk_size: Optional[int] = 0, - ): - self.chunk_size = chunk_size - self.chunk_overlap = chunk_overlap - self.min_chunk_size = min_chunk_size - if self.min_chunk_size >= self.chunk_size: - raise ValueError(f"min_chunk_size {min_chunk_size} should be less than chunk_size {chunk_size}") - if self.min_chunk_size < self.chunk_overlap: - logging.warning( - f"min_chunk_size {min_chunk_size} should be greater than chunk_overlap {chunk_overlap}, otherwise it is redundant." # noqa:E501 - ) - - if isinstance(length_function, str): - self.length_function = self.load_func(length_function) - else: - self.length_function = length_function if length_function else len - - @staticmethod - def load_func(dotpath: str): - if "." not in dotpath: - return getattr(builtins, dotpath) - else: - module_, func = dotpath.rsplit(".", maxsplit=1) - m = import_module(module_) - return getattr(m, func) - - -@register_deserializable -class LoaderConfig(BaseConfig): - """ - Config for the loader used in `add` method - """ - - def __init__(self): - pass - - -@register_deserializable -class AddConfig(BaseConfig): - """ - Config for the `add` method. - """ - - def __init__( - self, - chunker: Optional[ChunkerConfig] = None, - loader: Optional[LoaderConfig] = None, - ): - """ - Initializes a configuration class instance for the `add` method. - - :param chunker: Chunker config, defaults to None - :type chunker: Optional[ChunkerConfig], optional - :param loader: Loader config, defaults to None - :type loader: Optional[LoaderConfig], optional - """ - self.loader = loader - self.chunker = chunker diff --git a/embedchain/embedchain/config/app_config.py b/embedchain/embedchain/config/app_config.py deleted file mode 100644 index f3b571b7f..000000000 --- a/embedchain/embedchain/config/app_config.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Optional - -from embedchain.helpers.json_serializable import register_deserializable - -from .base_app_config import BaseAppConfig - - -@register_deserializable -class AppConfig(BaseAppConfig): - """ - Config to initialize an embedchain custom `App` instance, with extra config options. - """ - - def __init__( - self, - log_level: str = "WARNING", - id: Optional[str] = None, - name: Optional[str] = None, - collect_metrics: Optional[bool] = True, - **kwargs, - ): - """ - Initializes a configuration class instance for an App. This is the simplest form of an embedchain app. - Most of the configuration is done in the `App` class itself. - - :param log_level: Debug level ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], defaults to "WARNING" - :type log_level: str, optional - :param id: ID of the app. Document metadata will have this id., defaults to None - :type id: Optional[str], optional - :param collect_metrics: Send anonymous telemetry to improve embedchain, defaults to True - :type collect_metrics: Optional[bool], optional - """ - self.name = name - super().__init__(log_level=log_level, id=id, collect_metrics=collect_metrics, **kwargs) diff --git a/embedchain/embedchain/config/base_app_config.py b/embedchain/embedchain/config/base_app_config.py deleted file mode 100644 index 781ca024a..000000000 --- a/embedchain/embedchain/config/base_app_config.py +++ /dev/null @@ -1,58 +0,0 @@ -import logging -from typing import Optional - -from embedchain.config.base_config import BaseConfig -from embedchain.helpers.json_serializable import JSONSerializable -from embedchain.vectordb.base import BaseVectorDB - -logger = logging.getLogger(__name__) - - -class BaseAppConfig(BaseConfig, JSONSerializable): - """ - Parent config to initialize an instance of `App`. - """ - - def __init__( - self, - log_level: str = "WARNING", - db: Optional[BaseVectorDB] = None, - id: Optional[str] = None, - collect_metrics: bool = True, - collection_name: Optional[str] = None, - ): - """ - Initializes a configuration class instance for an App. - Most of the configuration is done in the `App` class itself. - - :param log_level: Debug level ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], defaults to "WARNING" - :type log_level: str, optional - :param db: A database class. It is recommended to set this directly in the `App` class, not this config, - defaults to None - :type db: Optional[BaseVectorDB], optional - :param id: ID of the app. Document metadata will have this id., defaults to None - :type id: Optional[str], optional - :param collect_metrics: Send anonymous telemetry to improve embedchain, defaults to True - :type collect_metrics: Optional[bool], optional - :param collection_name: Default collection name. It's recommended to use app.db.set_collection_name() instead, - defaults to None - :type collection_name: Optional[str], optional - """ - self.id = id - self.collect_metrics = True if (collect_metrics is True or collect_metrics is None) else False - self.collection_name = collection_name - - if db: - self._db = db - logger.warning( - "DEPRECATION WARNING: Please supply the database as the second parameter during app init. " - "Such as `app(config=config, db=db)`." - ) - - if collection_name: - logger.warning("DEPRECATION WARNING: Please supply the collection name to the database config.") - return - - def _setup_logging(self, log_level): - logger.basicConfig(format="%(asctime)s [%(name)s] [%(levelname)s] %(message)s", level=log_level) - self.logger = logger.getLogger(__name__) diff --git a/embedchain/embedchain/config/base_config.py b/embedchain/embedchain/config/base_config.py deleted file mode 100644 index bf7869f41..000000000 --- a/embedchain/embedchain/config/base_config.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any - -from embedchain.helpers.json_serializable import JSONSerializable - - -class BaseConfig(JSONSerializable): - """ - Base config. - """ - - def __init__(self): - """Initializes a configuration class for a class.""" - pass - - def as_dict(self) -> dict[str, Any]: - """Return config object as a dict - - :return: config object as dict - :rtype: dict[str, Any] - """ - return vars(self) diff --git a/embedchain/embedchain/config/cache_config.py b/embedchain/embedchain/config/cache_config.py deleted file mode 100644 index ef8bd1fb3..000000000 --- a/embedchain/embedchain/config/cache_config.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Any, Optional - -from embedchain.config.base_config import BaseConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class CacheSimilarityEvalConfig(BaseConfig): - """ - This is the evaluator to compare two embeddings according to their distance computed in embedding retrieval stage. - In the retrieval stage, `search_result` is the distance used for approximate nearest neighbor search and have been - put into `cache_dict`. `max_distance` is used to bound this distance to make it between [0-`max_distance`]. - `positive` is used to indicate this distance is directly proportional to the similarity of two entities. - If `positive` is set `False`, `max_distance` will be used to subtract this distance to get the final score. - - :param max_distance: the bound of maximum distance. - :type max_distance: float - :param positive: if the larger distance indicates more similar of two entities, It is True. Otherwise, it is False. - :type positive: bool - """ - - def __init__( - self, - strategy: Optional[str] = "distance", - max_distance: Optional[float] = 1.0, - positive: Optional[bool] = False, - ): - self.strategy = strategy - self.max_distance = max_distance - self.positive = positive - - @staticmethod - def from_config(config: Optional[dict[str, Any]]): - if config is None: - return CacheSimilarityEvalConfig() - else: - return CacheSimilarityEvalConfig( - strategy=config.get("strategy", "distance"), - max_distance=config.get("max_distance", 1.0), - positive=config.get("positive", False), - ) - - -@register_deserializable -class CacheInitConfig(BaseConfig): - """ - This is a cache init config. Used to initialize a cache. - - :param similarity_threshold: a threshold ranged from 0 to 1 to filter search results with similarity score higher \ - than the threshold. When it is 0, there is no hits. When it is 1, all search results will be returned as hits. - :type similarity_threshold: float - :param auto_flush: it will be automatically flushed every time xx pieces of data are added, default to 20 - :type auto_flush: int - """ - - def __init__( - self, - similarity_threshold: Optional[float] = 0.8, - auto_flush: Optional[int] = 20, - ): - if similarity_threshold < 0 or similarity_threshold > 1: - raise ValueError(f"similarity_threshold {similarity_threshold} should be between 0 and 1") - - self.similarity_threshold = similarity_threshold - self.auto_flush = auto_flush - - @staticmethod - def from_config(config: Optional[dict[str, Any]]): - if config is None: - return CacheInitConfig() - else: - return CacheInitConfig( - similarity_threshold=config.get("similarity_threshold", 0.8), - auto_flush=config.get("auto_flush", 20), - ) - - -@register_deserializable -class CacheConfig(BaseConfig): - def __init__( - self, - similarity_eval_config: Optional[CacheSimilarityEvalConfig] = CacheSimilarityEvalConfig(), - init_config: Optional[CacheInitConfig] = CacheInitConfig(), - ): - self.similarity_eval_config = similarity_eval_config - self.init_config = init_config - - @staticmethod - def from_config(config: Optional[dict[str, Any]]): - if config is None: - return CacheConfig() - else: - return CacheConfig( - similarity_eval_config=CacheSimilarityEvalConfig.from_config(config.get("similarity_evaluation", {})), - init_config=CacheInitConfig.from_config(config.get("init_config", {})), - ) diff --git a/embedchain/embedchain/config/embedder/__init__.py b/embedchain/embedchain/config/embedder/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/config/embedder/aws_bedrock.py b/embedchain/embedchain/config/embedder/aws_bedrock.py deleted file mode 100644 index f0bd0c538..000000000 --- a/embedchain/embedchain/config/embedder/aws_bedrock.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, Dict, Optional - -from embedchain.config.embedder.base import BaseEmbedderConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class AWSBedrockEmbedderConfig(BaseEmbedderConfig): - def __init__( - self, - model: Optional[str] = None, - deployment_name: Optional[str] = None, - vector_dimension: Optional[int] = None, - task_type: Optional[str] = None, - title: Optional[str] = None, - model_kwargs: Optional[Dict[str, Any]] = None, - ): - super().__init__(model, deployment_name, vector_dimension) - self.task_type = task_type or "retrieval_document" - self.title = title or "Embeddings for Embedchain" - self.model_kwargs = model_kwargs or {} diff --git a/embedchain/embedchain/config/embedder/base.py b/embedchain/embedchain/config/embedder/base.py deleted file mode 100644 index 56c4070d0..000000000 --- a/embedchain/embedchain/config/embedder/base.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Any, Dict, Optional, Union - -import httpx - -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class BaseEmbedderConfig: - def __init__( - self, - model: Optional[str] = None, - deployment_name: Optional[str] = None, - vector_dimension: Optional[int] = None, - endpoint: Optional[str] = None, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - model_kwargs: Optional[Dict[str, Any]] = None, - http_client_proxies: Optional[Union[Dict, str]] = None, - http_async_client_proxies: Optional[Union[Dict, str]] = None, - ): - """ - Initialize a new instance of an embedder config class. - - :param model: model name of the llm embedding model (not applicable to all providers), defaults to None - :type model: Optional[str], optional - :param deployment_name: deployment name for llm embedding model, defaults to None - :type deployment_name: Optional[str], optional - :param vector_dimension: vector dimension of the embedding model, defaults to None - :type vector_dimension: Optional[int], optional - :param endpoint: endpoint for the embedding model, defaults to None - :type endpoint: Optional[str], optional - :param api_key: hugginface api key, defaults to None - :type api_key: Optional[str], optional - :param api_base: huggingface api base, defaults to None - :type api_base: Optional[str], optional - :param model_kwargs: key-value arguments for the embedding model, defaults a dict inside init. - :type model_kwargs: Optional[Dict[str, Any]], defaults a dict inside init. - :param http_client_proxies: The proxy server settings used to create self.http_client, defaults to None - :type http_client_proxies: Optional[Dict | str], optional - :param http_async_client_proxies: The proxy server settings for async calls used to create - self.http_async_client, defaults to None - :type http_async_client_proxies: Optional[Dict | str], optional - """ - self.model = model - self.deployment_name = deployment_name - self.vector_dimension = vector_dimension - self.endpoint = endpoint - self.api_key = api_key - self.api_base = api_base - self.model_kwargs = model_kwargs or {} - self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None - self.http_async_client = ( - httpx.AsyncClient(proxies=http_async_client_proxies) if http_async_client_proxies else None - ) diff --git a/embedchain/embedchain/config/embedder/google.py b/embedchain/embedchain/config/embedder/google.py deleted file mode 100644 index 7cf5a9011..000000000 --- a/embedchain/embedchain/config/embedder/google.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Optional - -from embedchain.config.embedder.base import BaseEmbedderConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class GoogleAIEmbedderConfig(BaseEmbedderConfig): - def __init__( - self, - model: Optional[str] = None, - deployment_name: Optional[str] = None, - vector_dimension: Optional[int] = None, - task_type: Optional[str] = None, - title: Optional[str] = None, - ): - super().__init__(model, deployment_name, vector_dimension) - self.task_type = task_type or "retrieval_document" - self.title = title or "Embeddings for Embedchain" diff --git a/embedchain/embedchain/config/embedder/ollama.py b/embedchain/embedchain/config/embedder/ollama.py deleted file mode 100644 index f680328f9..000000000 --- a/embedchain/embedchain/config/embedder/ollama.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Optional - -from embedchain.config.embedder.base import BaseEmbedderConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class OllamaEmbedderConfig(BaseEmbedderConfig): - def __init__( - self, - model: Optional[str] = None, - base_url: Optional[str] = None, - vector_dimension: Optional[int] = None, - ): - super().__init__(model=model, vector_dimension=vector_dimension) - self.base_url = base_url or "http://localhost:11434" diff --git a/embedchain/embedchain/config/evaluation/__init__.py b/embedchain/embedchain/config/evaluation/__init__.py deleted file mode 100644 index 67e78dade..000000000 --- a/embedchain/embedchain/config/evaluation/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .base import ( # noqa: F401 - AnswerRelevanceConfig, - ContextRelevanceConfig, - GroundednessConfig, -) diff --git a/embedchain/embedchain/config/evaluation/base.py b/embedchain/embedchain/config/evaluation/base.py deleted file mode 100644 index 5c44d3f83..000000000 --- a/embedchain/embedchain/config/evaluation/base.py +++ /dev/null @@ -1,92 +0,0 @@ -from typing import Optional - -from embedchain.config.base_config import BaseConfig - -ANSWER_RELEVANCY_PROMPT = """ -Please provide $num_gen_questions questions from the provided answer. -You must provide the complete question, if are not able to provide the complete question, return empty string (""). -Please only provide one question per line without numbers or bullets to distinguish them. -You must only provide the questions and no other text. - -$answer -""" # noqa:E501 - - -CONTEXT_RELEVANCY_PROMPT = """ -Please extract relevant sentences from the provided context that is required to answer the given question. -If no relevant sentences are found, or if you believe the question cannot be answered from the given context, return the empty string (""). -While extracting candidate sentences you're not allowed to make any changes to sentences from given context or make up any sentences. -You must only provide sentences from the given context and nothing else. - -Context: $context -Question: $question -""" # noqa:E501 - -GROUNDEDNESS_ANSWER_CLAIMS_PROMPT = """ -Please provide one or more statements from each sentence of the provided answer. -You must provide the symantically equivalent statements for each sentence of the answer. -You must provide the complete statement, if are not able to provide the complete statement, return empty string (""). -Please only provide one statement per line WITHOUT numbers or bullets. -If the question provided is not being answered in the provided answer, return empty string (""). -You must only provide the statements and no other text. - -$question -$answer -""" # noqa:E501 - -GROUNDEDNESS_CLAIMS_INFERENCE_PROMPT = """ -Given the context and the provided claim statements, please provide a verdict for each claim statement whether it can be completely inferred from the given context or not. -Use only "1" (yes), "0" (no) and "-1" (null) for "yes", "no" or "null" respectively. -You must provide one verdict per line, ONLY WITH "1", "0" or "-1" as per your verdict to the given statement and nothing else. -You must provide the verdicts in the same order as the claim statements. - -Contexts: -$context - -Claim statements: -$claim_statements -""" # noqa:E501 - - -class GroundednessConfig(BaseConfig): - def __init__( - self, - model: str = "gpt-4", - api_key: Optional[str] = None, - answer_claims_prompt: str = GROUNDEDNESS_ANSWER_CLAIMS_PROMPT, - claims_inference_prompt: str = GROUNDEDNESS_CLAIMS_INFERENCE_PROMPT, - ): - self.model = model - self.api_key = api_key - self.answer_claims_prompt = answer_claims_prompt - self.claims_inference_prompt = claims_inference_prompt - - -class AnswerRelevanceConfig(BaseConfig): - def __init__( - self, - model: str = "gpt-4", - embedder: str = "text-embedding-ada-002", - api_key: Optional[str] = None, - num_gen_questions: int = 1, - prompt: str = ANSWER_RELEVANCY_PROMPT, - ): - self.model = model - self.embedder = embedder - self.api_key = api_key - self.num_gen_questions = num_gen_questions - self.prompt = prompt - - -class ContextRelevanceConfig(BaseConfig): - def __init__( - self, - model: str = "gpt-4", - api_key: Optional[str] = None, - language: str = "en", - prompt: str = CONTEXT_RELEVANCY_PROMPT, - ): - self.model = model - self.api_key = api_key - self.language = language - self.prompt = prompt diff --git a/embedchain/embedchain/config/llm/__init__.py b/embedchain/embedchain/config/llm/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/config/llm/base.py b/embedchain/embedchain/config/llm/base.py deleted file mode 100644 index 693d09c5b..000000000 --- a/embedchain/embedchain/config/llm/base.py +++ /dev/null @@ -1,276 +0,0 @@ -import json -import logging -import re -from pathlib import Path -from string import Template -from typing import Any, Dict, Mapping, Optional, Union - -import httpx - -from embedchain.config.base_config import BaseConfig -from embedchain.helpers.json_serializable import register_deserializable - -logger = logging.getLogger(__name__) - -DEFAULT_PROMPT = """ -You are a Q&A expert system. Your responses must always be rooted in the context provided for each query. Here are some guidelines to follow: - -1. Refrain from explicitly mentioning the context provided in your response. -2. The context should silently guide your answers without being directly acknowledged. -3. Do not use phrases such as 'According to the context provided', 'Based on the context, ...' etc. - -Context information: ----------------------- -$context ----------------------- - -Query: $query -Answer: -""" # noqa:E501 - -DEFAULT_PROMPT_WITH_HISTORY = """ -You are a Q&A expert system. Your responses must always be rooted in the context provided for each query. You are also provided with the conversation history with the user. Make sure to use relevant context from conversation history as needed. - -Here are some guidelines to follow: - -1. Refrain from explicitly mentioning the context provided in your response. -2. The context should silently guide your answers without being directly acknowledged. -3. Do not use phrases such as 'According to the context provided', 'Based on the context, ...' etc. - -Context information: ----------------------- -$context ----------------------- - -Conversation history: ----------------------- -$history ----------------------- - -Query: $query -Answer: -""" # noqa:E501 - -DEFAULT_PROMPT_WITH_MEM0_MEMORY = """ -You are an expert at answering questions based on provided memories. You are also provided with the context and conversation history of the user. Make sure to use relevant context from conversation history and context as needed. - -Here are some guidelines to follow: -1. Refrain from explicitly mentioning the context provided in your response. -2. Take into consideration the conversation history and context provided. -3. Do not use phrases such as 'According to the context provided', 'Based on the context, ...' etc. - -Striclty return the query exactly as it is if it is not a question or if no relevant information is found. - -Context information: ----------------------- -$context ----------------------- - -Conversation history: ----------------------- -$history ----------------------- - -Memories/Preferences: ----------------------- -$memories ----------------------- - -Query: $query -Answer: -""" # noqa:E501 - -DOCS_SITE_DEFAULT_PROMPT = """ -You are an expert AI assistant for developer support product. Your responses must always be rooted in the context provided for each query. Wherever possible, give complete code snippet. Dont make up any code snippet on your own. - -Here are some guidelines to follow: - -1. Refrain from explicitly mentioning the context provided in your response. -2. The context should silently guide your answers without being directly acknowledged. -3. Do not use phrases such as 'According to the context provided', 'Based on the context, ...' etc. - -Context information: ----------------------- -$context ----------------------- - -Query: $query -Answer: -""" # noqa:E501 - -DEFAULT_PROMPT_TEMPLATE = Template(DEFAULT_PROMPT) -DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE = Template(DEFAULT_PROMPT_WITH_HISTORY) -DEFAULT_PROMPT_WITH_MEM0_MEMORY_TEMPLATE = Template(DEFAULT_PROMPT_WITH_MEM0_MEMORY) -DOCS_SITE_PROMPT_TEMPLATE = Template(DOCS_SITE_DEFAULT_PROMPT) -query_re = re.compile(r"\$\{*query\}*") -context_re = re.compile(r"\$\{*context\}*") -history_re = re.compile(r"\$\{*history\}*") - - -@register_deserializable -class BaseLlmConfig(BaseConfig): - """ - Config for the `query` method. - """ - - def __init__( - self, - number_documents: int = 3, - template: Optional[Template] = None, - prompt: Optional[Template] = None, - model: Optional[str] = None, - temperature: float = 0, - max_tokens: int = 1000, - top_p: float = 1, - stream: bool = False, - online: bool = False, - token_usage: bool = False, - deployment_name: Optional[str] = None, - system_prompt: Optional[str] = None, - where: dict[str, Any] = None, - query_type: Optional[str] = None, - callbacks: Optional[list] = None, - api_key: Optional[str] = None, - base_url: Optional[str] = None, - endpoint: Optional[str] = None, - model_kwargs: Optional[dict[str, Any]] = None, - http_client_proxies: Optional[Union[Dict, str]] = None, - http_async_client_proxies: Optional[Union[Dict, str]] = None, - local: Optional[bool] = False, - default_headers: Optional[Mapping[str, str]] = None, - api_version: Optional[str] = None, - ): - """ - Initializes a configuration class instance for the LLM. - - Takes the place of the former `QueryConfig` or `ChatConfig`. - - :param number_documents: Number of documents to pull from the database as - context, defaults to 1 - :type number_documents: int, optional - :param template: The `Template` instance to use as a template for - prompt, defaults to None (deprecated) - :type template: Optional[Template], optional - :param prompt: The `Template` instance to use as a template for - prompt, defaults to None - :type prompt: Optional[Template], optional - :param model: Controls the OpenAI model used, defaults to None - :type model: Optional[str], optional - :param temperature: Controls the randomness of the model's output. - Higher values (closer to 1) make output more random, lower values make it more deterministic, defaults to 0 - :type temperature: float, optional - :param max_tokens: Controls how many tokens are generated, defaults to 1000 - :type max_tokens: int, optional - :param top_p: Controls the diversity of words. Higher values (closer to 1) make word selection more diverse, - defaults to 1 - :type top_p: float, optional - :param stream: Control if response is streamed back to user, defaults to False - :type stream: bool, optional - :param online: Controls whether to use internet for answering query, defaults to False - :type online: bool, optional - :param token_usage: Controls whether to return token usage in response, defaults to False - :type token_usage: bool, optional - :param deployment_name: t.b.a., defaults to None - :type deployment_name: Optional[str], optional - :param system_prompt: System prompt string, defaults to None - :type system_prompt: Optional[str], optional - :param where: A dictionary of key-value pairs to filter the database results., defaults to None - :type where: dict[str, Any], optional - :param api_key: The api key of the custom endpoint, defaults to None - :type api_key: Optional[str], optional - :param endpoint: The api url of the custom endpoint, defaults to None - :type endpoint: Optional[str], optional - :param model_kwargs: A dictionary of key-value pairs to pass to the model, defaults to None - :type model_kwargs: Optional[Dict[str, Any]], optional - :param callbacks: Langchain callback functions to use, defaults to None - :type callbacks: Optional[list], optional - :param query_type: The type of query to use, defaults to None - :type query_type: Optional[str], optional - :param http_client_proxies: The proxy server settings used to create self.http_client, defaults to None - :type http_client_proxies: Optional[Dict | str], optional - :param http_async_client_proxies: The proxy server settings for async calls used to create - self.http_async_client, defaults to None - :type http_async_client_proxies: Optional[Dict | str], optional - :param local: If True, the model will be run locally, defaults to False (for huggingface provider) - :type local: Optional[bool], optional - :param default_headers: Set additional HTTP headers to be sent with requests to OpenAI - :type default_headers: Optional[Mapping[str, str]], optional - :raises ValueError: If the template is not valid as template should - contain $context and $query (and optionally $history) - :raises ValueError: Stream is not boolean - """ - if template is not None: - logger.warning( - "The `template` argument is deprecated and will be removed in a future version. " - + "Please use `prompt` instead." - ) - if prompt is None: - prompt = template - - if prompt is None: - prompt = DEFAULT_PROMPT_TEMPLATE - - self.number_documents = number_documents - self.temperature = temperature - self.max_tokens = max_tokens - self.model = model - self.top_p = top_p - self.online = online - self.token_usage = token_usage - self.deployment_name = deployment_name - self.system_prompt = system_prompt - self.query_type = query_type - self.callbacks = callbacks - self.api_key = api_key - self.base_url = base_url - self.endpoint = endpoint - self.model_kwargs = model_kwargs - self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None - self.http_async_client = ( - httpx.AsyncClient(proxies=http_async_client_proxies) if http_async_client_proxies else None - ) - self.local = local - self.default_headers = default_headers - self.online = online - self.api_version = api_version - - if token_usage: - f = Path(__file__).resolve().parent.parent / "model_prices_and_context_window.json" - self.model_pricing_map = json.load(f.open()) - - if isinstance(prompt, str): - prompt = Template(prompt) - - if self.validate_prompt(prompt): - self.prompt = prompt - else: - raise ValueError("The 'prompt' should have 'query' and 'context' keys and potentially 'history' (if used).") - - if not isinstance(stream, bool): - raise ValueError("`stream` should be bool") - self.stream = stream - self.where = where - - @staticmethod - def validate_prompt(prompt: Template) -> Optional[re.Match[str]]: - """ - validate the prompt - - :param prompt: the prompt to validate - :type prompt: Template - :return: valid (true) or invalid (false) - :rtype: Optional[re.Match[str]] - """ - return re.search(query_re, prompt.template) and re.search(context_re, prompt.template) - - @staticmethod - def _validate_prompt_history(prompt: Template) -> Optional[re.Match[str]]: - """ - validate the prompt with history - - :param prompt: the prompt to validate - :type prompt: Template - :return: valid (true) or invalid (false) - :rtype: Optional[re.Match[str]] - """ - return re.search(history_re, prompt.template) diff --git a/embedchain/embedchain/config/mem0_config.py b/embedchain/embedchain/config/mem0_config.py deleted file mode 100644 index 924ba8744..000000000 --- a/embedchain/embedchain/config/mem0_config.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, Optional - -from embedchain.config.base_config import BaseConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class Mem0Config(BaseConfig): - def __init__(self, api_key: str, top_k: Optional[int] = 10): - self.api_key = api_key - self.top_k = top_k - - @staticmethod - def from_config(config: Optional[dict[str, Any]]): - if config is None: - return Mem0Config() - else: - return Mem0Config( - api_key=config.get("api_key", ""), - init_config=config.get("top_k", 10), - ) diff --git a/embedchain/embedchain/config/model_prices_and_context_window.json b/embedchain/embedchain/config/model_prices_and_context_window.json deleted file mode 100644 index c68f90394..000000000 --- a/embedchain/embedchain/config/model_prices_and_context_window.json +++ /dev/null @@ -1,824 +0,0 @@ -{ - "openai/gpt-4": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00003, - "output_cost_per_token": 0.00006 - }, - "openai/gpt-4o": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000005, - "output_cost_per_token": 0.000015 - }, - "openai/gpt-4o-mini": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000015, - "output_cost_per_token": 0.00000060 - }, - "openai/gpt-4o-mini-2024-07-18": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000015, - "output_cost_per_token": 0.00000060 - }, - "openai/gpt-4o-2024-05-13": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000005, - "output_cost_per_token": 0.000015 - }, - "openai/gpt-4-turbo-preview": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "openai/gpt-4-0314": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00003, - "output_cost_per_token": 0.00006 - }, - "openai/gpt-4-0613": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00003, - "output_cost_per_token": 0.00006 - }, - "openai/gpt-4-32k": { - "max_tokens": 4096, - "max_input_tokens": 32768, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00006, - "output_cost_per_token": 0.00012 - }, - "openai/gpt-4-32k-0314": { - "max_tokens": 4096, - "max_input_tokens": 32768, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00006, - "output_cost_per_token": 0.00012 - }, - "openai/gpt-4-32k-0613": { - "max_tokens": 4096, - "max_input_tokens": 32768, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00006, - "output_cost_per_token": 0.00012 - }, - "openai/gpt-4-turbo": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "openai/gpt-4-turbo-2024-04-09": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "openai/gpt-4-1106-preview": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "openai/gpt-4-0125-preview": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "openai/gpt-3.5-turbo": { - "max_tokens": 4097, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "openai/gpt-3.5-turbo-0301": { - "max_tokens": 4097, - "max_input_tokens": 4097, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "openai/gpt-3.5-turbo-0613": { - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "openai/gpt-3.5-turbo-1106": { - "max_tokens": 16385, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000010, - "output_cost_per_token": 0.0000020 - }, - "openai/gpt-3.5-turbo-0125": { - "max_tokens": 16385, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000005, - "output_cost_per_token": 0.0000015 - }, - "openai/gpt-3.5-turbo-16k": { - "max_tokens": 16385, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000004 - }, - "openai/gpt-3.5-turbo-16k-0613": { - "max_tokens": 16385, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000004 - }, - "openai/text-embedding-3-large": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "output_vector_size": 3072, - "input_cost_per_token": 0.00000013, - "output_cost_per_token": 0.000000 - }, - "openai/text-embedding-3-small": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "output_vector_size": 1536, - "input_cost_per_token": 0.00000002, - "output_cost_per_token": 0.000000 - }, - "openai/text-embedding-ada-002": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "output_vector_size": 1536, - "input_cost_per_token": 0.0000001, - "output_cost_per_token": 0.000000 - }, - "openai/text-embedding-ada-002-v2": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "input_cost_per_token": 0.0000001, - "output_cost_per_token": 0.000000 - }, - "openai/babbage-002": { - "max_tokens": 16384, - "max_input_tokens": 16384, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000004, - "output_cost_per_token": 0.0000004 - }, - "openai/davinci-002": { - "max_tokens": 16384, - "max_input_tokens": 16384, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000002, - "output_cost_per_token": 0.000002 - }, - "openai/gpt-3.5-turbo-instruct": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "openai/gpt-3.5-turbo-instruct-0914": { - "max_tokens": 4097, - "max_input_tokens": 8192, - "max_output_tokens": 4097, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "azure/gpt-4o": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000005, - "output_cost_per_token": 0.000015 - }, - "azure/gpt-4o-mini": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000015, - "output_cost_per_token": 0.00000060 - }, - "azure/gpt-4-turbo-2024-04-09": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "azure/gpt-4-0125-preview": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "azure/gpt-4-1106-preview": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "azure/gpt-4-0613": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00003, - "output_cost_per_token": 0.00006 - }, - "azure/gpt-4-32k-0613": { - "max_tokens": 4096, - "max_input_tokens": 32768, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00006, - "output_cost_per_token": 0.00012 - }, - "azure/gpt-4-32k": { - "max_tokens": 4096, - "max_input_tokens": 32768, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00006, - "output_cost_per_token": 0.00012 - }, - "azure/gpt-4": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00003, - "output_cost_per_token": 0.00006 - }, - "azure/gpt-4-turbo": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "azure/gpt-4-turbo-vision-preview": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003 - }, - "azure/gpt-3.5-turbo-16k-0613": { - "max_tokens": 4096, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000004 - }, - "azure/gpt-3.5-turbo-1106": { - "max_tokens": 4096, - "max_input_tokens": 16384, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "azure/gpt-3.5-turbo-0125": { - "max_tokens": 4096, - "max_input_tokens": 16384, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000005, - "output_cost_per_token": 0.0000015 - }, - "azure/gpt-3.5-turbo-16k": { - "max_tokens": 4096, - "max_input_tokens": 16385, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000004 - }, - "azure/gpt-3.5-turbo": { - "max_tokens": 4096, - "max_input_tokens": 4097, - "max_output_tokens": 4096, - "input_cost_per_token": 0.0000005, - "output_cost_per_token": 0.0000015 - }, - "azure/gpt-3.5-turbo-instruct-0914": { - "max_tokens": 4097, - "max_input_tokens": 4097, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "azure/gpt-3.5-turbo-instruct": { - "max_tokens": 4097, - "max_input_tokens": 4097, - "input_cost_per_token": 0.0000015, - "output_cost_per_token": 0.000002 - }, - "azure/text-embedding-ada-002": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "input_cost_per_token": 0.0000001, - "output_cost_per_token": 0.000000 - }, - "azure/text-embedding-3-large": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "input_cost_per_token": 0.00000013, - "output_cost_per_token": 0.000000 - }, - "azure/text-embedding-3-small": { - "max_tokens": 8191, - "max_input_tokens": 8191, - "input_cost_per_token": 0.00000002, - "output_cost_per_token": 0.000000 - }, - "mistralai/mistral-tiny": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.00000025 - }, - "mistralai/mistral-small": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000001, - "output_cost_per_token": 0.000003 - }, - "mistralai/mistral-small-latest": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000001, - "output_cost_per_token": 0.000003 - }, - "mistralai/mistral-medium": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.0000027, - "output_cost_per_token": 0.0000081 - }, - "mistralai/mistral-medium-latest": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.0000027, - "output_cost_per_token": 0.0000081 - }, - "mistralai/mistral-medium-2312": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.0000027, - "output_cost_per_token": 0.0000081 - }, - "mistralai/mistral-large-latest": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000004, - "output_cost_per_token": 0.000012 - }, - "mistralai/mistral-large-2402": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000004, - "output_cost_per_token": 0.000012 - }, - "mistralai/open-mistral-7b": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.00000025 - }, - "mistralai/open-mixtral-8x7b": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.0000007, - "output_cost_per_token": 0.0000007 - }, - "mistralai/open-mixtral-8x22b": { - "max_tokens": 8191, - "max_input_tokens": 64000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000002, - "output_cost_per_token": 0.000006 - }, - "mistralai/codestral-latest": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000001, - "output_cost_per_token": 0.000003 - }, - "mistralai/codestral-2405": { - "max_tokens": 8191, - "max_input_tokens": 32000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000001, - "output_cost_per_token": 0.000003 - }, - "mistralai/mistral-embed": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "input_cost_per_token": 0.0000001, - "output_cost_per_token": 0.0 - }, - "groq/llama2-70b-4096": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000070, - "output_cost_per_token": 0.00000080 - }, - "groq/llama3-8b-8192": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000010, - "output_cost_per_token": 0.00000010 - }, - "groq/llama3-70b-8192": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000064, - "output_cost_per_token": 0.00000080 - }, - "groq/mixtral-8x7b-32768": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 0.00000027, - "output_cost_per_token": 0.00000027 - }, - "groq/gemma-7b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000010, - "output_cost_per_token": 0.00000010 - }, - "anthropic/claude-instant-1": { - "max_tokens": 8191, - "max_input_tokens": 100000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.00000163, - "output_cost_per_token": 0.00000551 - }, - "anthropic/claude-instant-1.2": { - "max_tokens": 8191, - "max_input_tokens": 100000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000000163, - "output_cost_per_token": 0.000000551 - }, - "anthropic/claude-2": { - "max_tokens": 8191, - "max_input_tokens": 100000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000008, - "output_cost_per_token": 0.000024 - }, - "anthropic/claude-2.1": { - "max_tokens": 8191, - "max_input_tokens": 200000, - "max_output_tokens": 8191, - "input_cost_per_token": 0.000008, - "output_cost_per_token": 0.000024 - }, - "anthropic/claude-3-haiku-20240307": { - "max_tokens": 4096, - "max_input_tokens": 200000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.00000125 - }, - "anthropic/claude-3-opus-20240229": { - "max_tokens": 4096, - "max_input_tokens": 200000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000075 - }, - "anthropic/claude-3-sonnet-20240229": { - "max_tokens": 4096, - "max_input_tokens": 200000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000015 - }, - "vertexai/chat-bison": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/chat-bison@001": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/chat-bison@002": { - "max_tokens": 4096, - "max_input_tokens": 8192, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/chat-bison-32k": { - "max_tokens": 8192, - "max_input_tokens": 32000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/code-bison": { - "max_tokens": 1024, - "max_input_tokens": 6144, - "max_output_tokens": 1024, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/code-bison@001": { - "max_tokens": 1024, - "max_input_tokens": 6144, - "max_output_tokens": 1024, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/code-gecko@001": { - "max_tokens": 64, - "max_input_tokens": 2048, - "max_output_tokens": 64, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/code-gecko@002": { - "max_tokens": 64, - "max_input_tokens": 2048, - "max_output_tokens": 64, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/code-gecko": { - "max_tokens": 64, - "max_input_tokens": 2048, - "max_output_tokens": 64, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/codechat-bison": { - "max_tokens": 1024, - "max_input_tokens": 6144, - "max_output_tokens": 1024, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/codechat-bison@001": { - "max_tokens": 1024, - "max_input_tokens": 6144, - "max_output_tokens": 1024, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/codechat-bison-32k": { - "max_tokens": 8192, - "max_input_tokens": 32000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125 - }, - "vertexai/gemini-pro": { - "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/gemini-1.0-pro": { - "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/gemini-1.0-pro-001": { - "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/gemini-1.0-pro-002": { - "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/gemini-1.5-pro": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000625, - "output_cost_per_token": 0.000001875 - }, - "vertexai/gemini-1.5-flash-001": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0, - "output_cost_per_token": 0 - }, - "vertexai/gemini-1.5-flash-preview-0514": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0, - "output_cost_per_token": 0 - }, - "vertexai/gemini-1.5-pro-001": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000625, - "output_cost_per_token": 0.000001875 - }, - "vertexai/gemini-1.5-pro-preview-0514": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000625, - "output_cost_per_token": 0.000001875 - }, - "vertexai/gemini-1.5-pro-preview-0215": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000625, - "output_cost_per_token": 0.000001875 - }, - "vertexai/gemini-1.5-pro-preview-0409": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0.000000625, - "output_cost_per_token": 0.000001875 - }, - "vertexai/gemini-experimental": { - "max_tokens": 8192, - "max_input_tokens": 1000000, - "max_output_tokens": 8192, - "input_cost_per_token": 0, - "output_cost_per_token": 0 - }, - "vertexai/gemini-pro-vision": { - "max_tokens": 2048, - "max_input_tokens": 16384, - "max_output_tokens": 2048, - "max_images_per_prompt": 16, - "max_videos_per_prompt": 1, - "max_video_length": 2, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/gemini-1.0-pro-vision": { - "max_tokens": 2048, - "max_input_tokens": 16384, - "max_output_tokens": 2048, - "max_images_per_prompt": 16, - "max_videos_per_prompt": 1, - "max_video_length": 2, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/gemini-1.0-pro-vision-001": { - "max_tokens": 2048, - "max_input_tokens": 16384, - "max_output_tokens": 2048, - "max_images_per_prompt": 16, - "max_videos_per_prompt": 1, - "max_video_length": 2, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.0000005 - }, - "vertexai/claude-3-sonnet@20240229": { - "max_tokens": 4096, - "max_input_tokens": 200000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000015 - }, - "vertexai/claude-3-haiku@20240307": { - "max_tokens": 4096, - "max_input_tokens": 200000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000025, - "output_cost_per_token": 0.00000125 - }, - "vertexai/claude-3-opus@20240229": { - "max_tokens": 4096, - "max_input_tokens": 200000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000075 - }, - "cohere/command-r": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.00000050, - "output_cost_per_token": 0.0000015 - }, - "cohere/command-light": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000015 - }, - "cohere/command-r-plus": { - "max_tokens": 4096, - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000003, - "output_cost_per_token": 0.000015 - }, - "cohere/command-nightly": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000015 - }, - "cohere/command": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000015 - }, - "cohere/command-medium-beta": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000015 - }, - "cohere/command-xlarge-beta": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 0.000015, - "output_cost_per_token": 0.000015 - }, - "together/together-ai-up-to-3b": { - "input_cost_per_token": 0.0000001, - "output_cost_per_token": 0.0000001 - }, - "together/together-ai-3.1b-7b": { - "input_cost_per_token": 0.0000002, - "output_cost_per_token": 0.0000002 - }, - "together/together-ai-7.1b-20b": { - "max_tokens": 1000, - "input_cost_per_token": 0.0000004, - "output_cost_per_token": 0.0000004 - }, - "together/together-ai-20.1b-40b": { - "input_cost_per_token": 0.0000008, - "output_cost_per_token": 0.0000008 - }, - "together/together-ai-40.1b-70b": { - "input_cost_per_token": 0.0000009, - "output_cost_per_token": 0.0000009 - }, - "together/mistralai/Mixtral-8x7B-Instruct-v0.1": { - "input_cost_per_token": 0.0000006, - "output_cost_per_token": 0.0000006 - } -} \ No newline at end of file diff --git a/embedchain/embedchain/config/vector_db/base.py b/embedchain/embedchain/config/vector_db/base.py deleted file mode 100644 index 3252880a9..000000000 --- a/embedchain/embedchain/config/vector_db/base.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Optional - -from embedchain.config.base_config import BaseConfig - - -class BaseVectorDbConfig(BaseConfig): - def __init__( - self, - collection_name: Optional[str] = None, - dir: str = "db", - host: Optional[str] = None, - port: Optional[str] = None, - **kwargs, - ): - """ - Initializes a configuration class instance for the vector database. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param dir: Path to the database directory, where the database is stored, defaults to "db" - :type dir: str, optional - :param host: Database connection remote host. Use this if you run Embedchain as a client, defaults to None - :type host: Optional[str], optional - :param host: Database connection remote port. Use this if you run Embedchain as a client, defaults to None - :type port: Optional[str], optional - :param kwargs: Additional keyword arguments - :type kwargs: dict - """ - self.collection_name = collection_name or "embedchain_store" - self.dir = dir - self.host = host - self.port = port - # Assign additional keyword arguments - if kwargs: - for key, value in kwargs.items(): - setattr(self, key, value) diff --git a/embedchain/embedchain/config/vector_db/chroma.py b/embedchain/embedchain/config/vector_db/chroma.py deleted file mode 100644 index 64220165c..000000000 --- a/embedchain/embedchain/config/vector_db/chroma.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class ChromaDbConfig(BaseVectorDbConfig): - def __init__( - self, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - host: Optional[str] = None, - port: Optional[str] = None, - batch_size: Optional[int] = 100, - allow_reset=False, - chroma_settings: Optional[dict] = None, - ): - """ - Initializes a configuration class instance for ChromaDB. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param dir: Path to the database directory, where the database is stored, defaults to None - :type dir: Optional[str], optional - :param host: Database connection remote host. Use this if you run Embedchain as a client, defaults to None - :type host: Optional[str], optional - :param port: Database connection remote port. Use this if you run Embedchain as a client, defaults to None - :type port: Optional[str], optional - :param batch_size: Number of items to insert in one batch, defaults to 100 - :type batch_size: Optional[int], optional - :param allow_reset: Resets the database. defaults to False - :type allow_reset: bool - :param chroma_settings: Chroma settings dict, defaults to None - :type chroma_settings: Optional[dict], optional - """ - - self.chroma_settings = chroma_settings - self.allow_reset = allow_reset - self.batch_size = batch_size - super().__init__(collection_name=collection_name, dir=dir, host=host, port=port) diff --git a/embedchain/embedchain/config/vector_db/elasticsearch.py b/embedchain/embedchain/config/vector_db/elasticsearch.py deleted file mode 100644 index 5e8ef6b61..000000000 --- a/embedchain/embedchain/config/vector_db/elasticsearch.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -from typing import Optional, Union - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class ElasticsearchDBConfig(BaseVectorDbConfig): - def __init__( - self, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - es_url: Union[str, list[str]] = None, - cloud_id: Optional[str] = None, - batch_size: Optional[int] = 100, - **ES_EXTRA_PARAMS: dict[str, any], - ): - """ - Initializes a configuration class instance for an Elasticsearch client. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param dir: Path to the database directory, where the database is stored, defaults to None - :type dir: Optional[str], optional - :param es_url: elasticsearch url or list of nodes url to be used for connection, defaults to None - :type es_url: Union[str, list[str]], optional - :param cloud_id: cloud id of the elasticsearch cluster, defaults to None - :type cloud_id: Optional[str], optional - :param batch_size: Number of items to insert in one batch, defaults to 100 - :type batch_size: Optional[int], optional - :param ES_EXTRA_PARAMS: extra params dict that can be passed to elasticsearch. - :type ES_EXTRA_PARAMS: dict[str, Any], optional - """ - if es_url and cloud_id: - raise ValueError("Only one of `es_url` and `cloud_id` can be set.") - # self, es_url: Union[str, list[str]] = None, **ES_EXTRA_PARAMS: dict[str, any]): - self.ES_URL = es_url or os.environ.get("ELASTICSEARCH_URL") - self.CLOUD_ID = cloud_id or os.environ.get("ELASTICSEARCH_CLOUD_ID") - if not self.ES_URL and not self.CLOUD_ID: - raise AttributeError( - "Elasticsearch needs a URL or CLOUD_ID attribute, " - "this can either be passed to `ElasticsearchDBConfig` or as `ELASTICSEARCH_URL` or `ELASTICSEARCH_CLOUD_ID` in `.env`" # noqa: E501 - ) - self.ES_EXTRA_PARAMS = ES_EXTRA_PARAMS - # Load API key from .env if it's not explicitly passed. - # Can only set one of 'api_key', 'basic_auth', and 'bearer_auth' - if ( - not self.ES_EXTRA_PARAMS.get("api_key") - and not self.ES_EXTRA_PARAMS.get("basic_auth") - and not self.ES_EXTRA_PARAMS.get("bearer_auth") - ): - self.ES_EXTRA_PARAMS["api_key"] = os.environ.get("ELASTICSEARCH_API_KEY") - - self.batch_size = batch_size - super().__init__(collection_name=collection_name, dir=dir) diff --git a/embedchain/embedchain/config/vector_db/lancedb.py b/embedchain/embedchain/config/vector_db/lancedb.py deleted file mode 100644 index 08b7d0ac7..000000000 --- a/embedchain/embedchain/config/vector_db/lancedb.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class LanceDBConfig(BaseVectorDbConfig): - def __init__( - self, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - host: Optional[str] = None, - port: Optional[str] = None, - allow_reset=True, - ): - """ - Initializes a configuration class instance for LanceDB. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param dir: Path to the database directory, where the database is stored, defaults to None - :type dir: Optional[str], optional - :param host: Database connection remote host. Use this if you run Embedchain as a client, defaults to None - :type host: Optional[str], optional - :param port: Database connection remote port. Use this if you run Embedchain as a client, defaults to None - :type port: Optional[str], optional - :param allow_reset: Resets the database. defaults to False - :type allow_reset: bool - """ - - self.allow_reset = allow_reset - super().__init__(collection_name=collection_name, dir=dir, host=host, port=port) diff --git a/embedchain/embedchain/config/vector_db/opensearch.py b/embedchain/embedchain/config/vector_db/opensearch.py deleted file mode 100644 index 5beeb8cee..000000000 --- a/embedchain/embedchain/config/vector_db/opensearch.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class OpenSearchDBConfig(BaseVectorDbConfig): - def __init__( - self, - opensearch_url: str, - http_auth: tuple[str, str], - vector_dimension: int = 1536, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - batch_size: Optional[int] = 100, - **extra_params: dict[str, any], - ): - """ - Initializes a configuration class instance for an OpenSearch client. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param opensearch_url: URL of the OpenSearch domain - :type opensearch_url: str, Eg, "http://localhost:9200" - :param http_auth: Tuple of username and password - :type http_auth: tuple[str, str], Eg, ("username", "password") - :param vector_dimension: Dimension of the vector, defaults to 1536 (openai embedding model) - :type vector_dimension: int, optional - :param dir: Path to the database directory, where the database is stored, defaults to None - :type dir: Optional[str], optional - :param batch_size: Number of items to insert in one batch, defaults to 100 - :type batch_size: Optional[int], optional - """ - self.opensearch_url = opensearch_url - self.http_auth = http_auth - self.vector_dimension = vector_dimension - self.extra_params = extra_params - self.batch_size = batch_size - - super().__init__(collection_name=collection_name, dir=dir) diff --git a/embedchain/embedchain/config/vector_db/pinecone.py b/embedchain/embedchain/config/vector_db/pinecone.py deleted file mode 100644 index 83248579f..000000000 --- a/embedchain/embedchain/config/vector_db/pinecone.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class PineconeDBConfig(BaseVectorDbConfig): - def __init__( - self, - index_name: Optional[str] = None, - api_key: Optional[str] = None, - vector_dimension: int = 1536, - metric: Optional[str] = "cosine", - pod_config: Optional[dict[str, any]] = None, - serverless_config: Optional[dict[str, any]] = None, - hybrid_search: bool = False, - bm25_encoder: any = None, - batch_size: Optional[int] = 100, - **extra_params: dict[str, any], - ): - self.metric = metric - self.api_key = api_key - self.index_name = index_name - self.vector_dimension = vector_dimension - self.extra_params = extra_params - self.hybrid_search = hybrid_search - self.bm25_encoder = bm25_encoder - self.batch_size = batch_size - if pod_config is None and serverless_config is None: - # If no config is provided, use the default pod spec config - pod_environment = os.environ.get("PINECONE_ENV", "gcp-starter") - self.pod_config = {"environment": pod_environment, "metadata_config": {"indexed": ["*"]}} - else: - self.pod_config = pod_config - self.serverless_config = serverless_config - - if self.pod_config and self.serverless_config: - raise ValueError("Only one of pod_config or serverless_config can be provided.") - - if self.hybrid_search and self.metric != "dotproduct": - raise ValueError( - "Hybrid search is only supported with dotproduct metric in Pinecone. See full docs here: https://docs.pinecone.io/docs/hybrid-search#limitations" - ) # noqa:E501 - - super().__init__(collection_name=self.index_name, dir=None) diff --git a/embedchain/embedchain/config/vector_db/qdrant.py b/embedchain/embedchain/config/vector_db/qdrant.py deleted file mode 100644 index acdeacfff..000000000 --- a/embedchain/embedchain/config/vector_db/qdrant.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class QdrantDBConfig(BaseVectorDbConfig): - """ - Config to initialize a qdrant client. - :param: url. qdrant url or list of nodes url to be used for connection - """ - - def __init__( - self, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - hnsw_config: Optional[dict[str, any]] = None, - quantization_config: Optional[dict[str, any]] = None, - on_disk: Optional[bool] = None, - batch_size: Optional[int] = 10, - **extra_params: dict[str, any], - ): - """ - Initializes a configuration class instance for a qdrant client. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param dir: Path to the database directory, where the database is stored, defaults to None - :type dir: Optional[str], optional - :param hnsw_config: Params for HNSW index - :type hnsw_config: Optional[dict[str, any]], defaults to None - :param quantization_config: Params for quantization, if None - quantization will be disabled - :type quantization_config: Optional[dict[str, any]], defaults to None - :param on_disk: If true - point`s payload will not be stored in memory. - It will be read from the disk every time it is requested. - This setting saves RAM by (slightly) increasing the response time. - Note: those payload values that are involved in filtering and are indexed - remain in RAM. - :type on_disk: bool, optional, defaults to None - :param batch_size: Number of items to insert in one batch, defaults to 10 - :type batch_size: Optional[int], optional - """ - self.hnsw_config = hnsw_config - self.quantization_config = quantization_config - self.on_disk = on_disk - self.batch_size = batch_size - self.extra_params = extra_params - super().__init__(collection_name=collection_name, dir=dir) diff --git a/embedchain/embedchain/config/vector_db/weaviate.py b/embedchain/embedchain/config/vector_db/weaviate.py deleted file mode 100644 index f40c472e7..000000000 --- a/embedchain/embedchain/config/vector_db/weaviate.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class WeaviateDBConfig(BaseVectorDbConfig): - def __init__( - self, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - batch_size: Optional[int] = 100, - **extra_params: dict[str, any], - ): - self.batch_size = batch_size - self.extra_params = extra_params - super().__init__(collection_name=collection_name, dir=dir) diff --git a/embedchain/embedchain/config/vector_db/zilliz.py b/embedchain/embedchain/config/vector_db/zilliz.py deleted file mode 100644 index 268941157..000000000 --- a/embedchain/embedchain/config/vector_db/zilliz.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -from typing import Optional - -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.helpers.json_serializable import register_deserializable - - -@register_deserializable -class ZillizDBConfig(BaseVectorDbConfig): - def __init__( - self, - collection_name: Optional[str] = None, - dir: Optional[str] = None, - uri: Optional[str] = None, - token: Optional[str] = None, - vector_dim: Optional[str] = None, - metric_type: Optional[str] = None, - ): - """ - Initializes a configuration class instance for the vector database. - - :param collection_name: Default name for the collection, defaults to None - :type collection_name: Optional[str], optional - :param dir: Path to the database directory, where the database is stored, defaults to "db" - :type dir: str, optional - :param uri: Cluster endpoint obtained from the Zilliz Console, defaults to None - :type uri: Optional[str], optional - :param token: API Key, if a Serverless Cluster, username:password, if a Dedicated Cluster, defaults to None - :type token: Optional[str], optional - """ - self.uri = uri or os.environ.get("ZILLIZ_CLOUD_URI") - if not self.uri: - raise AttributeError( - "Zilliz needs a URI attribute, " - "this can either be passed to `ZILLIZ_CLOUD_URI` or as `ZILLIZ_CLOUD_URI` in `.env`" - ) - - self.token = token or os.environ.get("ZILLIZ_CLOUD_TOKEN") - if not self.token: - raise AttributeError( - "Zilliz needs a token attribute, " - "this can either be passed to `ZILLIZ_CLOUD_TOKEN` or as `ZILLIZ_CLOUD_TOKEN` in `.env`," - "if having a username and password, pass it in the form 'username:password' to `ZILLIZ_CLOUD_TOKEN`" - ) - - self.metric_type = metric_type if metric_type else "L2" - - self.vector_dim = vector_dim - super().__init__(collection_name=collection_name, dir=dir) diff --git a/embedchain/embedchain/config/vectordb/__init__.py b/embedchain/embedchain/config/vectordb/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/constants.py b/embedchain/embedchain/constants.py deleted file mode 100644 index d3d7b28b3..000000000 --- a/embedchain/embedchain/constants.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from pathlib import Path - -ABS_PATH = os.getcwd() -HOME_DIR = os.environ.get("EMBEDCHAIN_CONFIG_DIR", str(Path.home())) -CONFIG_DIR = os.path.join(HOME_DIR, ".embedchain") -CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json") -SQLITE_PATH = os.path.join(CONFIG_DIR, "embedchain.db") - -# Set the environment variable for the database URI -os.environ.setdefault("EMBEDCHAIN_DB_URI", f"sqlite:///{SQLITE_PATH}") diff --git a/embedchain/embedchain/core/__init__.py b/embedchain/embedchain/core/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/core/db/__init__.py b/embedchain/embedchain/core/db/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/core/db/database.py b/embedchain/embedchain/core/db/database.py deleted file mode 100644 index 0965ca8ff..000000000 --- a/embedchain/embedchain/core/db/database.py +++ /dev/null @@ -1,88 +0,0 @@ -import os - -from alembic import command -from alembic.config import Config -from sqlalchemy import create_engine -from sqlalchemy.engine.base import Engine -from sqlalchemy.orm import Session as SQLAlchemySession -from sqlalchemy.orm import scoped_session, sessionmaker - -from .models import Base - - -class DatabaseManager: - def __init__(self, echo: bool = False): - self.database_uri = os.environ.get("EMBEDCHAIN_DB_URI") - self.echo = echo - self.engine: Engine = None - self._session_factory = None - - def setup_engine(self) -> None: - """Initializes the database engine and session factory.""" - if not self.database_uri: - raise RuntimeError("Database URI is not set. Set the EMBEDCHAIN_DB_URI environment variable.") - connect_args = {} - if self.database_uri.startswith("sqlite"): - connect_args["check_same_thread"] = False - self.engine = create_engine(self.database_uri, echo=self.echo, connect_args=connect_args) - self._session_factory = scoped_session(sessionmaker(bind=self.engine)) - Base.metadata.bind = self.engine - - def init_db(self) -> None: - """Creates all tables defined in the Base metadata.""" - if not self.engine: - raise RuntimeError("Database engine is not initialized. Call setup_engine() first.") - Base.metadata.create_all(self.engine) - - def get_session(self) -> SQLAlchemySession: - """Provides a session for database operations.""" - if not self._session_factory: - raise RuntimeError("Session factory is not initialized. Call setup_engine() first.") - return self._session_factory() - - def close_session(self) -> None: - """Closes the current session.""" - if self._session_factory: - self._session_factory.remove() - - def execute_transaction(self, transaction_block): - """Executes a block of code within a database transaction.""" - session = self.get_session() - try: - transaction_block(session) - session.commit() - except Exception as e: - session.rollback() - raise e - finally: - self.close_session() - - -# Singleton pattern to use throughout the application -database_manager = DatabaseManager() - - -# Convenience functions for backward compatibility and ease of use -def setup_engine(database_uri: str, echo: bool = False) -> None: - database_manager.database_uri = database_uri - database_manager.echo = echo - database_manager.setup_engine() - - -def alembic_upgrade() -> None: - """Upgrades the database to the latest version.""" - alembic_config_path = os.path.join(os.path.dirname(__file__), "..", "..", "alembic.ini") - alembic_cfg = Config(alembic_config_path) - command.upgrade(alembic_cfg, "head") - - -def init_db() -> None: - alembic_upgrade() - - -def get_session() -> SQLAlchemySession: - return database_manager.get_session() - - -def execute_transaction(transaction_block): - database_manager.execute_transaction(transaction_block) diff --git a/embedchain/embedchain/core/db/models.py b/embedchain/embedchain/core/db/models.py deleted file mode 100644 index af77803f7..000000000 --- a/embedchain/embedchain/core/db/models.py +++ /dev/null @@ -1,31 +0,0 @@ -import uuid - -from sqlalchemy import TIMESTAMP, Column, Integer, String, Text, func -from sqlalchemy.orm import declarative_base - -Base = declarative_base() -metadata = Base.metadata - - -class DataSource(Base): - __tablename__ = "ec_data_sources" - - id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) - app_id = Column(Text, index=True) - hash = Column(Text, index=True) - type = Column(Text, index=True) - value = Column(Text) - meta_data = Column(Text, name="metadata") - is_uploaded = Column(Integer, default=0) - - -class ChatHistory(Base): - __tablename__ = "ec_chat_history" - - app_id = Column(String, primary_key=True) - id = Column(String, primary_key=True) - session_id = Column(String, primary_key=True, index=True) - question = Column(Text) - answer = Column(Text) - meta_data = Column(Text, name="metadata") - created_at = Column(TIMESTAMP, default=func.current_timestamp(), index=True) diff --git a/embedchain/embedchain/data_formatter/__init__.py b/embedchain/embedchain/data_formatter/__init__.py deleted file mode 100644 index 047b8e7ca..000000000 --- a/embedchain/embedchain/data_formatter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .data_formatter import DataFormatter # noqa: F401 diff --git a/embedchain/embedchain/data_formatter/data_formatter.py b/embedchain/embedchain/data_formatter/data_formatter.py deleted file mode 100644 index 72923888d..000000000 --- a/embedchain/embedchain/data_formatter/data_formatter.py +++ /dev/null @@ -1,154 +0,0 @@ -from importlib import import_module -from typing import Any, Optional - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config import AddConfig -from embedchain.config.add_config import ChunkerConfig, LoaderConfig -from embedchain.helpers.json_serializable import JSONSerializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.models.data_type import DataType - - -class DataFormatter(JSONSerializable): - """ - DataFormatter is an internal utility class which abstracts the mapping for - loaders and chunkers to the data_type entered by the user in their - .add or .add_local method call - """ - - def __init__( - self, - data_type: DataType, - config: AddConfig, - loader: Optional[BaseLoader] = None, - chunker: Optional[BaseChunker] = None, - ): - """ - Initialize a dataformatter, set data type and chunker based on datatype. - - :param data_type: The type of the data to load and chunk. - :type data_type: DataType - :param config: AddConfig instance with nested loader and chunker config attributes. - :type config: AddConfig - """ - self.loader = self._get_loader(data_type=data_type, config=config.loader, loader=loader) - self.chunker = self._get_chunker(data_type=data_type, config=config.chunker, chunker=chunker) - - @staticmethod - def _lazy_load(module_path: str): - module_path, class_name = module_path.rsplit(".", 1) - module = import_module(module_path) - return getattr(module, class_name) - - def _get_loader( - self, - data_type: DataType, - config: LoaderConfig, - loader: Optional[BaseLoader], - **kwargs: Optional[dict[str, Any]], - ) -> BaseLoader: - """ - Returns the appropriate data loader for the given data type. - - :param data_type: The type of the data to load. - :type data_type: DataType - :param config: Config to initialize the loader with. - :type config: LoaderConfig - :raises ValueError: If an unsupported data type is provided. - :return: The loader for the given data type. - :rtype: BaseLoader - """ - loaders = { - DataType.YOUTUBE_VIDEO: "embedchain.loaders.youtube_video.YoutubeVideoLoader", - DataType.PDF_FILE: "embedchain.loaders.pdf_file.PdfFileLoader", - DataType.WEB_PAGE: "embedchain.loaders.web_page.WebPageLoader", - DataType.QNA_PAIR: "embedchain.loaders.local_qna_pair.LocalQnaPairLoader", - DataType.TEXT: "embedchain.loaders.local_text.LocalTextLoader", - DataType.DOCX: "embedchain.loaders.docx_file.DocxFileLoader", - DataType.SITEMAP: "embedchain.loaders.sitemap.SitemapLoader", - DataType.XML: "embedchain.loaders.xml.XmlLoader", - DataType.DOCS_SITE: "embedchain.loaders.docs_site_loader.DocsSiteLoader", - DataType.CSV: "embedchain.loaders.csv.CsvLoader", - DataType.MDX: "embedchain.loaders.mdx.MdxLoader", - DataType.IMAGE: "embedchain.loaders.image.ImageLoader", - DataType.UNSTRUCTURED: "embedchain.loaders.unstructured_file.UnstructuredLoader", - DataType.JSON: "embedchain.loaders.json.JSONLoader", - DataType.OPENAPI: "embedchain.loaders.openapi.OpenAPILoader", - DataType.GMAIL: "embedchain.loaders.gmail.GmailLoader", - DataType.NOTION: "embedchain.loaders.notion.NotionLoader", - DataType.SUBSTACK: "embedchain.loaders.substack.SubstackLoader", - DataType.YOUTUBE_CHANNEL: "embedchain.loaders.youtube_channel.YoutubeChannelLoader", - DataType.DISCORD: "embedchain.loaders.discord.DiscordLoader", - DataType.RSSFEED: "embedchain.loaders.rss_feed.RSSFeedLoader", - DataType.BEEHIIV: "embedchain.loaders.beehiiv.BeehiivLoader", - DataType.GOOGLE_DRIVE: "embedchain.loaders.google_drive.GoogleDriveLoader", - DataType.DIRECTORY: "embedchain.loaders.directory_loader.DirectoryLoader", - DataType.SLACK: "embedchain.loaders.slack.SlackLoader", - DataType.DROPBOX: "embedchain.loaders.dropbox.DropboxLoader", - DataType.TEXT_FILE: "embedchain.loaders.text_file.TextFileLoader", - DataType.EXCEL_FILE: "embedchain.loaders.excel_file.ExcelFileLoader", - DataType.AUDIO: "embedchain.loaders.audio.AudioLoader", - } - - if data_type == DataType.CUSTOM or loader is not None: - loader_class: type = loader - if loader_class: - return loader_class - elif data_type in loaders: - loader_class: type = self._lazy_load(loaders[data_type]) - return loader_class() - - raise ValueError( - f"Cant find the loader for {data_type}.\ - We recommend to pass the loader to use data_type: {data_type},\ - check `https://docs.embedchain.ai/data-sources/overview`." - ) - - def _get_chunker(self, data_type: DataType, config: ChunkerConfig, chunker: Optional[BaseChunker]) -> BaseChunker: - """Returns the appropriate chunker for the given data type (updated for lazy loading).""" - chunker_classes = { - DataType.YOUTUBE_VIDEO: "embedchain.chunkers.youtube_video.YoutubeVideoChunker", - DataType.PDF_FILE: "embedchain.chunkers.pdf_file.PdfFileChunker", - DataType.WEB_PAGE: "embedchain.chunkers.web_page.WebPageChunker", - DataType.QNA_PAIR: "embedchain.chunkers.qna_pair.QnaPairChunker", - DataType.TEXT: "embedchain.chunkers.text.TextChunker", - DataType.DOCX: "embedchain.chunkers.docx_file.DocxFileChunker", - DataType.SITEMAP: "embedchain.chunkers.sitemap.SitemapChunker", - DataType.XML: "embedchain.chunkers.xml.XmlChunker", - DataType.DOCS_SITE: "embedchain.chunkers.docs_site.DocsSiteChunker", - DataType.CSV: "embedchain.chunkers.table.TableChunker", - DataType.MDX: "embedchain.chunkers.mdx.MdxChunker", - DataType.IMAGE: "embedchain.chunkers.image.ImageChunker", - DataType.UNSTRUCTURED: "embedchain.chunkers.unstructured_file.UnstructuredFileChunker", - DataType.JSON: "embedchain.chunkers.json.JSONChunker", - DataType.OPENAPI: "embedchain.chunkers.openapi.OpenAPIChunker", - DataType.GMAIL: "embedchain.chunkers.gmail.GmailChunker", - DataType.NOTION: "embedchain.chunkers.notion.NotionChunker", - DataType.SUBSTACK: "embedchain.chunkers.substack.SubstackChunker", - DataType.YOUTUBE_CHANNEL: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.DISCORD: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.CUSTOM: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.RSSFEED: "embedchain.chunkers.rss_feed.RSSFeedChunker", - DataType.BEEHIIV: "embedchain.chunkers.beehiiv.BeehiivChunker", - DataType.GOOGLE_DRIVE: "embedchain.chunkers.google_drive.GoogleDriveChunker", - DataType.DIRECTORY: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.SLACK: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.DROPBOX: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.TEXT_FILE: "embedchain.chunkers.common_chunker.CommonChunker", - DataType.EXCEL_FILE: "embedchain.chunkers.excel_file.ExcelFileChunker", - DataType.AUDIO: "embedchain.chunkers.audio.AudioChunker", - } - - if chunker is not None: - return chunker - elif data_type in chunker_classes: - chunker_class = self._lazy_load(chunker_classes[data_type]) - chunker = chunker_class(config) - chunker.set_data_type(data_type) - return chunker - - raise ValueError( - f"Cant find the chunker for {data_type}.\ - We recommend to pass the chunker to use data_type: {data_type},\ - check `https://docs.embedchain.ai/data-sources/overview`." - ) diff --git a/embedchain/embedchain/deployment/fly.io/.dockerignore b/embedchain/embedchain/deployment/fly.io/.dockerignore deleted file mode 100644 index 9f4c740db..000000000 --- a/embedchain/embedchain/deployment/fly.io/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -db/ \ No newline at end of file diff --git a/embedchain/embedchain/deployment/fly.io/.env.example b/embedchain/embedchain/deployment/fly.io/.env.example deleted file mode 100644 index b29363f94..000000000 --- a/embedchain/embedchain/deployment/fly.io/.env.example +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY=sk-xxx \ No newline at end of file diff --git a/embedchain/embedchain/deployment/fly.io/Dockerfile b/embedchain/embedchain/deployment/fly.io/Dockerfile deleted file mode 100644 index 9eac80cee..000000000 --- a/embedchain/embedchain/deployment/fly.io/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt /app/ - -RUN pip install -r requirements.txt - -COPY . /app - -EXPOSE 8080 - -CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/embedchain/embedchain/deployment/fly.io/app.py b/embedchain/embedchain/deployment/fly.io/app.py deleted file mode 100644 index 003543c46..000000000 --- a/embedchain/embedchain/deployment/fly.io/app.py +++ /dev/null @@ -1,56 +0,0 @@ -from dotenv import load_dotenv -from fastapi import FastAPI, responses -from pydantic import BaseModel - -from embedchain import App - -load_dotenv(".env") - -app = FastAPI(title="Embedchain FastAPI App") -embedchain_app = App() - - -class SourceModel(BaseModel): - source: str - - -class QuestionModel(BaseModel): - question: str - - -@app.post("/add") -async def add_source(source_model: SourceModel): - """ - Adds a new source to the EmbedChain app. - Expects a JSON with a "source" key. - """ - source = source_model.source - embedchain_app.add(source) - return {"message": f"Source '{source}' added successfully."} - - -@app.post("/query") -async def handle_query(question_model: QuestionModel): - """ - Handles a query to the EmbedChain app. - Expects a JSON with a "question" key. - """ - question = question_model.question - answer = embedchain_app.query(question) - return {"answer": answer} - - -@app.post("/chat") -async def handle_chat(question_model: QuestionModel): - """ - Handles a chat request to the EmbedChain app. - Expects a JSON with a "question" key. - """ - question = question_model.question - response = embedchain_app.chat(question) - return {"response": response} - - -@app.get("/") -async def root(): - return responses.RedirectResponse(url="/docs") diff --git a/embedchain/embedchain/deployment/fly.io/requirements.txt b/embedchain/embedchain/deployment/fly.io/requirements.txt deleted file mode 100644 index 3a7689298..000000000 --- a/embedchain/embedchain/deployment/fly.io/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi==0.104.0 -uvicorn==0.23.2 -embedchain -beautifulsoup4 \ No newline at end of file diff --git a/embedchain/embedchain/deployment/gradio.app/app.py b/embedchain/embedchain/deployment/gradio.app/app.py deleted file mode 100644 index 24a96a908..000000000 --- a/embedchain/embedchain/deployment/gradio.app/app.py +++ /dev/null @@ -1,18 +0,0 @@ -import os - -import gradio as gr - -from embedchain import App - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - -app = App() - - -def query(message, history): - return app.chat(message) - - -demo = gr.ChatInterface(query) - -demo.launch() diff --git a/embedchain/embedchain/deployment/gradio.app/requirements.txt b/embedchain/embedchain/deployment/gradio.app/requirements.txt deleted file mode 100644 index f8b933480..000000000 --- a/embedchain/embedchain/deployment/gradio.app/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -gradio>=4.14.0 -embedchain diff --git a/embedchain/embedchain/deployment/modal.com/.env.example b/embedchain/embedchain/deployment/modal.com/.env.example deleted file mode 100644 index b29363f94..000000000 --- a/embedchain/embedchain/deployment/modal.com/.env.example +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY=sk-xxx \ No newline at end of file diff --git a/embedchain/embedchain/deployment/modal.com/.gitignore b/embedchain/embedchain/deployment/modal.com/.gitignore deleted file mode 100644 index 4c49bd78f..000000000 --- a/embedchain/embedchain/deployment/modal.com/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.env diff --git a/embedchain/embedchain/deployment/modal.com/app.py b/embedchain/embedchain/deployment/modal.com/app.py deleted file mode 100644 index 1e02aeefb..000000000 --- a/embedchain/embedchain/deployment/modal.com/app.py +++ /dev/null @@ -1,86 +0,0 @@ -from dotenv import load_dotenv -from fastapi import Body, FastAPI, responses -from modal import Image, Secret, Stub, asgi_app - -from embedchain import App - -load_dotenv(".env") - -image = Image.debian_slim().pip_install( - "embedchain", - "lanchain_community==0.2.6", - "youtube-transcript-api==0.6.1", - "pytube==15.0.0", - "beautifulsoup4==4.12.3", - "slack-sdk==3.21.3", - "huggingface_hub==0.23.0", - "gitpython==3.1.38", - "yt_dlp==2023.11.14", - "PyGithub==1.59.1", - "feedparser==6.0.10", - "newspaper3k==0.2.8", - "listparser==0.19", -) - -stub = Stub( - name="embedchain-app", - image=image, - secrets=[Secret.from_dotenv(".env")], -) - -web_app = FastAPI() -embedchain_app = App(name="embedchain-modal-app") - - -@web_app.post("/add") -async def add( - source: str = Body(..., description="Source to be added"), - data_type: str | None = Body(None, description="Type of the data source"), -): - """ - Adds a new source to the EmbedChain app. - Expects a JSON with a "source" and "data_type" key. - "data_type" is optional. - """ - if source and data_type: - embedchain_app.add(source, data_type) - elif source: - embedchain_app.add(source) - else: - return {"message": "No source provided."} - return {"message": f"Source '{source}' added successfully."} - - -@web_app.post("/query") -async def query(question: str = Body(..., description="Question to be answered")): - """ - Handles a query to the EmbedChain app. - Expects a JSON with a "question" key. - """ - if not question: - return {"message": "No question provided."} - answer = embedchain_app.query(question) - return {"answer": answer} - - -@web_app.get("/chat") -async def chat(question: str = Body(..., description="Question to be answered")): - """ - Handles a chat request to the EmbedChain app. - Expects a JSON with a "question" key. - """ - if not question: - return {"message": "No question provided."} - response = embedchain_app.chat(question) - return {"response": response} - - -@web_app.get("/") -async def root(): - return responses.RedirectResponse(url="/docs") - - -@stub.function(image=image) -@asgi_app() -def fastapi_app(): - return web_app diff --git a/embedchain/embedchain/deployment/modal.com/requirements.txt b/embedchain/embedchain/deployment/modal.com/requirements.txt deleted file mode 100644 index 69a3172af..000000000 --- a/embedchain/embedchain/deployment/modal.com/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -modal==0.56.4329 -fastapi==0.104.0 -uvicorn==0.23.2 -embedchain diff --git a/embedchain/embedchain/deployment/render.com/.env.example b/embedchain/embedchain/deployment/render.com/.env.example deleted file mode 100644 index b29363f94..000000000 --- a/embedchain/embedchain/deployment/render.com/.env.example +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY=sk-xxx \ No newline at end of file diff --git a/embedchain/embedchain/deployment/render.com/.gitignore b/embedchain/embedchain/deployment/render.com/.gitignore deleted file mode 100644 index 4c49bd78f..000000000 --- a/embedchain/embedchain/deployment/render.com/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.env diff --git a/embedchain/embedchain/deployment/render.com/app.py b/embedchain/embedchain/deployment/render.com/app.py deleted file mode 100644 index 00d29bf3d..000000000 --- a/embedchain/embedchain/deployment/render.com/app.py +++ /dev/null @@ -1,53 +0,0 @@ -from fastapi import FastAPI, responses -from pydantic import BaseModel - -from embedchain import App - -app = FastAPI(title="Embedchain FastAPI App") -embedchain_app = App() - - -class SourceModel(BaseModel): - source: str - - -class QuestionModel(BaseModel): - question: str - - -@app.post("/add") -async def add_source(source_model: SourceModel): - """ - Adds a new source to the EmbedChain app. - Expects a JSON with a "source" key. - """ - source = source_model.source - embedchain_app.add(source) - return {"message": f"Source '{source}' added successfully."} - - -@app.post("/query") -async def handle_query(question_model: QuestionModel): - """ - Handles a query to the EmbedChain app. - Expects a JSON with a "question" key. - """ - question = question_model.question - answer = embedchain_app.query(question) - return {"answer": answer} - - -@app.post("/chat") -async def handle_chat(question_model: QuestionModel): - """ - Handles a chat request to the EmbedChain app. - Expects a JSON with a "question" key. - """ - question = question_model.question - response = embedchain_app.chat(question) - return {"response": response} - - -@app.get("/") -async def root(): - return responses.RedirectResponse(url="/docs") diff --git a/embedchain/embedchain/deployment/render.com/render.yaml b/embedchain/embedchain/deployment/render.com/render.yaml deleted file mode 100644 index 04ec5048b..000000000 --- a/embedchain/embedchain/deployment/render.com/render.yaml +++ /dev/null @@ -1,16 +0,0 @@ -services: - - type: web - name: ec-render-app - runtime: python - repo: https://github.com// - scaling: - minInstances: 1 - maxInstances: 3 - targetMemoryPercent: 60 # optional if targetCPUPercent is set - targetCPUPercent: 60 # optional if targetMemory is set - buildCommand: pip install -r requirements.txt - startCommand: uvicorn app:app --host 0.0.0.0 - envVars: - - key: OPENAI_API_KEY - value: sk-xxx - autoDeploy: false # optional diff --git a/embedchain/embedchain/deployment/render.com/requirements.txt b/embedchain/embedchain/deployment/render.com/requirements.txt deleted file mode 100644 index 3a7689298..000000000 --- a/embedchain/embedchain/deployment/render.com/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi==0.104.0 -uvicorn==0.23.2 -embedchain -beautifulsoup4 \ No newline at end of file diff --git a/embedchain/embedchain/deployment/streamlit.io/.streamlit/secrets.toml b/embedchain/embedchain/deployment/streamlit.io/.streamlit/secrets.toml deleted file mode 100644 index 1fa8f4495..000000000 --- a/embedchain/embedchain/deployment/streamlit.io/.streamlit/secrets.toml +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY="sk-xxx" diff --git a/embedchain/embedchain/deployment/streamlit.io/app.py b/embedchain/embedchain/deployment/streamlit.io/app.py deleted file mode 100644 index 74a6b0599..000000000 --- a/embedchain/embedchain/deployment/streamlit.io/app.py +++ /dev/null @@ -1,59 +0,0 @@ -import streamlit as st - -from embedchain import App - - -@st.cache_resource -def embedchain_bot(): - return App() - - -st.title("💬 Chatbot") -st.caption("🚀 An Embedchain app powered by OpenAI!") -if "messages" not in st.session_state: - st.session_state.messages = [ - { - "role": "assistant", - "content": """ - Hi! I'm a chatbot. I can answer questions and learn new things!\n - Ask me anything and if you want me to learn something do `/add `.\n - I can learn mostly everything. :) - """, - } - ] - -for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.markdown(message["content"]) - -if prompt := st.chat_input("Ask me anything!"): - app = embedchain_bot() - - if prompt.startswith("/add"): - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - prompt = prompt.replace("/add", "").strip() - with st.chat_message("assistant"): - message_placeholder = st.empty() - message_placeholder.markdown("Adding to knowledge base...") - app.add(prompt) - message_placeholder.markdown(f"Added {prompt} to knowledge base!") - st.session_state.messages.append({"role": "assistant", "content": f"Added {prompt} to knowledge base!"}) - st.stop() - - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - - with st.chat_message("assistant"): - msg_placeholder = st.empty() - msg_placeholder.markdown("Thinking...") - full_response = "" - - for response in app.chat(prompt): - msg_placeholder.empty() - full_response += response - - msg_placeholder.markdown(full_response) - st.session_state.messages.append({"role": "assistant", "content": full_response}) diff --git a/embedchain/embedchain/deployment/streamlit.io/requirements.txt b/embedchain/embedchain/deployment/streamlit.io/requirements.txt deleted file mode 100644 index b864076ae..000000000 --- a/embedchain/embedchain/deployment/streamlit.io/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -streamlit==1.29.0 -embedchain diff --git a/embedchain/embedchain/embedchain.py b/embedchain/embedchain/embedchain.py deleted file mode 100644 index 4a1a4dc09..000000000 --- a/embedchain/embedchain/embedchain.py +++ /dev/null @@ -1,789 +0,0 @@ -import hashlib -import json -import logging -from typing import Any, Optional, Union - -from dotenv import load_dotenv -from langchain.docstore.document import Document - -from embedchain.cache import ( - adapt, - get_gptcache_session, - gptcache_data_convert, - gptcache_update_cache_callback, -) -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config import AddConfig, BaseLlmConfig, ChunkerConfig -from embedchain.config.base_app_config import BaseAppConfig -from embedchain.core.db.models import ChatHistory, DataSource -from embedchain.data_formatter import DataFormatter -from embedchain.embedder.base import BaseEmbedder -from embedchain.helpers.json_serializable import JSONSerializable -from embedchain.llm.base import BaseLlm -from embedchain.loaders.base_loader import BaseLoader -from embedchain.models.data_type import ( - DataType, - DirectDataType, - IndirectDataType, - SpecialDataType, -) -from embedchain.utils.misc import detect_datatype, is_valid_json_string -from embedchain.vectordb.base import BaseVectorDB - -load_dotenv() - -logger = logging.getLogger(__name__) - - -class EmbedChain(JSONSerializable): - def __init__( - self, - config: BaseAppConfig, - llm: BaseLlm, - db: BaseVectorDB = None, - embedder: BaseEmbedder = None, - system_prompt: Optional[str] = None, - ): - """ - Initializes the EmbedChain instance, sets up a vector DB client and - creates a collection. - - :param config: Configuration just for the app, not the db or llm or embedder. - :type config: BaseAppConfig - :param llm: Instance of the LLM you want to use. - :type llm: BaseLlm - :param db: Instance of the Database to use, defaults to None - :type db: BaseVectorDB, optional - :param embedder: instance of the embedder to use, defaults to None - :type embedder: BaseEmbedder, optional - :param system_prompt: System prompt to use in the llm query, defaults to None - :type system_prompt: Optional[str], optional - :raises ValueError: No database or embedder provided. - """ - self.config = config - self.cache_config = None - self.memory_config = None - self.mem0_memory = None - # Llm - self.llm = llm - # Database has support for config assignment for backwards compatibility - if db is None and (not hasattr(self.config, "db") or self.config.db is None): - raise ValueError("App requires Database.") - self.db = db or self.config.db - # Embedder - if embedder is None: - raise ValueError("App requires Embedder.") - self.embedder = embedder - - # Initialize database - self.db._set_embedder(self.embedder) - self.db._initialize() - # Set collection name from app config for backwards compatibility. - if config.collection_name: - self.db.set_collection_name(config.collection_name) - - # Add variables that are "shortcuts" - if system_prompt: - self.llm.config.system_prompt = system_prompt - - # Fetch the history from the database if exists - self.llm.update_history(app_id=self.config.id) - - # Attributes that aren't subclass related. - self.user_asks = [] - - self.chunker: Optional[ChunkerConfig] = None - - @property - def collect_metrics(self): - return self.config.collect_metrics - - @collect_metrics.setter - def collect_metrics(self, value): - if not isinstance(value, bool): - raise ValueError(f"Boolean value expected but got {type(value)}.") - self.config.collect_metrics = value - - @property - def online(self): - return self.llm.config.online - - @online.setter - def online(self, value): - if not isinstance(value, bool): - raise ValueError(f"Boolean value expected but got {type(value)}.") - self.llm.config.online = value - - def add( - self, - source: Any, - data_type: Optional[DataType] = None, - metadata: Optional[dict[str, Any]] = None, - config: Optional[AddConfig] = None, - dry_run=False, - loader: Optional[BaseLoader] = None, - chunker: Optional[BaseChunker] = None, - **kwargs: Optional[dict[str, Any]], - ): - """ - Adds the data from the given URL to the vector db. - Loads the data, chunks it, create embedding for each chunk - and then stores the embedding to vector database. - - :param source: The data to embed, can be a URL, local file or raw content, depending on the data type. - :type source: Any - :param data_type: Automatically detected, but can be forced with this argument. The type of the data to add, - defaults to None - :type data_type: Optional[DataType], optional - :param metadata: Metadata associated with the data source., defaults to None - :type metadata: Optional[dict[str, Any]], optional - :param config: The `AddConfig` instance to use as configuration options., defaults to None - :type config: Optional[AddConfig], optional - :raises ValueError: Invalid data type - :param dry_run: Optional. A dry run displays the chunks to ensure that the loader and chunker work as intended. - defaults to False - :type dry_run: bool - :param loader: The loader to use to load the data, defaults to None - :type loader: BaseLoader, optional - :param chunker: The chunker to use to chunk the data, defaults to None - :type chunker: BaseChunker, optional - :param kwargs: To read more params for the query function - :type kwargs: dict[str, Any] - :return: source_hash, a md5-hash of the source, in hexadecimal representation. - :rtype: str - """ - if config is not None: - pass - elif self.chunker is not None: - config = AddConfig(chunker=self.chunker) - else: - config = AddConfig() - - try: - DataType(source) - logger.warning( - f"""Starting from version v0.0.40, Embedchain can automatically detect the data type. So, in the `add` method, the argument order has changed. You no longer need to specify '{source}' for the `source` argument. So the code snippet will be `.add("{data_type}", "{source}")`""" # noqa #E501 - ) - logger.warning( - "Embedchain is swapping the arguments for you. This functionality might be deprecated in the future, so please adjust your code." # noqa #E501 - ) - source, data_type = data_type, source - except ValueError: - pass - - if data_type: - try: - data_type = DataType(data_type) - except ValueError: - logger.info( - f"Invalid data_type: '{data_type}', using `custom` instead.\n Check docs to pass the valid data type: `https://docs.embedchain.ai/data-sources/overview`" # noqa: E501 - ) - data_type = DataType.CUSTOM - - if not data_type: - data_type = detect_datatype(source) - - # `source_hash` is the md5 hash of the source argument - source_hash = hashlib.md5(str(source).encode("utf-8")).hexdigest() - - self.user_asks.append([source, data_type.value, metadata]) - - data_formatter = DataFormatter(data_type, config, loader, chunker) - documents, metadatas, _ids, new_chunks = self._load_and_embed( - data_formatter.loader, data_formatter.chunker, source, metadata, source_hash, config, dry_run, **kwargs - ) - if data_type in {DataType.DOCS_SITE}: - self.is_docs_site_instance = True - - # Convert the source to a string if it is not already - if not isinstance(source, str): - source = str(source) - - # Insert the data into the 'ec_data_sources' table - self.db_session.add( - DataSource( - hash=source_hash, - app_id=self.config.id, - type=data_type.value, - value=source, - metadata=json.dumps(metadata), - ) - ) - try: - self.db_session.commit() - except Exception as e: - logger.error(f"Error adding data source: {e}") - self.db_session.rollback() - - if dry_run: - data_chunks_info = {"chunks": documents, "metadata": metadatas, "count": len(documents), "type": data_type} - logger.debug(f"Dry run info : {data_chunks_info}") - return data_chunks_info - - # Send anonymous telemetry - if self.config.collect_metrics: - # it's quicker to check the variable twice than to count words when they won't be submitted. - word_count = data_formatter.chunker.get_word_count(documents) - - # Send anonymous telemetry - event_properties = { - **self._telemetry_props, - "data_type": data_type.value, - "word_count": word_count, - "chunks_count": new_chunks, - } - self.telemetry.capture(event_name="add", properties=event_properties) - - return source_hash - - def _get_existing_doc_id(self, chunker: BaseChunker, src: Any): - """ - Get id of existing document for a given source, based on the data type - """ - # Find existing embeddings for the source - # Depending on the data type, existing embeddings are checked for. - if chunker.data_type.value in [item.value for item in DirectDataType]: - # DirectDataTypes can't be updated. - # Think of a text: - # Either it's the same, then it won't change, so it's not an update. - # Or it's different, then it will be added as a new text. - return None - elif chunker.data_type.value in [item.value for item in IndirectDataType]: - # These types have an indirect source reference - # As long as the reference is the same, they can be updated. - where = {"url": src} - if chunker.data_type == DataType.JSON and is_valid_json_string(src): - url = hashlib.sha256((src).encode("utf-8")).hexdigest() - where = {"url": url} - - if self.config.id is not None: - where.update({"app_id": self.config.id}) - - existing_embeddings = self.db.get( - where=where, - limit=1, - ) - if len(existing_embeddings.get("metadatas", [])) > 0: - return existing_embeddings["metadatas"][0]["doc_id"] - else: - return None - elif chunker.data_type.value in [item.value for item in SpecialDataType]: - # These types don't contain indirect references. - # Through custom logic, they can be attributed to a source and be updated. - if chunker.data_type == DataType.QNA_PAIR: - # QNA_PAIRs update the answer if the question already exists. - where = {"question": src[0]} - if self.config.id is not None: - where.update({"app_id": self.config.id}) - - existing_embeddings = self.db.get( - where=where, - limit=1, - ) - if len(existing_embeddings.get("metadatas", [])) > 0: - return existing_embeddings["metadatas"][0]["doc_id"] - else: - return None - else: - raise NotImplementedError( - f"SpecialDataType {chunker.data_type} must have a custom logic to check for existing data" - ) - else: - raise TypeError( - f"{chunker.data_type} is type {type(chunker.data_type)}. " - "When it should be DirectDataType, IndirectDataType or SpecialDataType." - ) - - def _load_and_embed( - self, - loader: BaseLoader, - chunker: BaseChunker, - src: Any, - metadata: Optional[dict[str, Any]] = None, - source_hash: Optional[str] = None, - add_config: Optional[AddConfig] = None, - dry_run=False, - **kwargs: Optional[dict[str, Any]], - ): - """ - Loads the data from the given URL, chunks it, and adds it to database. - - :param loader: The loader to use to load the data. - :type loader: BaseLoader - :param chunker: The chunker to use to chunk the data. - :type chunker: BaseChunker - :param src: The data to be handled by the loader. Can be a URL for - remote sources or local content for local loaders. - :type src: Any - :param metadata: Metadata associated with the data source. - :type metadata: dict[str, Any], optional - :param source_hash: Hexadecimal hash of the source. - :type source_hash: str, optional - :param add_config: The `AddConfig` instance to use as configuration options. - :type add_config: AddConfig, optional - :param dry_run: A dry run returns chunks and doesn't update DB. - :type dry_run: bool, defaults to False - :return: (list) documents (embedded text), (list) metadata, (list) ids, (int) number of chunks - """ - existing_doc_id = self._get_existing_doc_id(chunker=chunker, src=src) - app_id = self.config.id if self.config is not None else None - - # Create chunks - embeddings_data = chunker.create_chunks(loader, src, app_id=app_id, config=add_config.chunker, **kwargs) - # spread chunking results - documents = embeddings_data["documents"] - metadatas = embeddings_data["metadatas"] - ids = embeddings_data["ids"] - new_doc_id = embeddings_data["doc_id"] - - if existing_doc_id and existing_doc_id == new_doc_id: - logger.info("Doc content has not changed. Skipping creating chunks and embeddings") - return [], [], [], 0 - - # this means that doc content has changed. - if existing_doc_id and existing_doc_id != new_doc_id: - logger.info("Doc content has changed. Recomputing chunks and embeddings intelligently.") - self.db.delete({"doc_id": existing_doc_id}) - - # get existing ids, and discard doc if any common id exist. - where = {"url": src} - if chunker.data_type == DataType.JSON and is_valid_json_string(src): - url = hashlib.sha256((src).encode("utf-8")).hexdigest() - where = {"url": url} - - # if data type is qna_pair, we check for question - if chunker.data_type == DataType.QNA_PAIR: - where = {"question": src[0]} - - if self.config.id is not None: - where["app_id"] = self.config.id - - db_result = self.db.get(ids=ids, where=where) # optional filter - existing_ids = set(db_result["ids"]) - if len(existing_ids): - data_dict = {id: (doc, meta) for id, doc, meta in zip(ids, documents, metadatas)} - data_dict = {id: value for id, value in data_dict.items() if id not in existing_ids} - - if not data_dict: - src_copy = src - if len(src_copy) > 50: - src_copy = src[:50] + "..." - logger.info(f"All data from {src_copy} already exists in the database.") - # Make sure to return a matching return type - return [], [], [], 0 - - ids = list(data_dict.keys()) - documents, metadatas = zip(*data_dict.values()) - - # Loop though all metadatas and add extras. - new_metadatas = [] - for m in metadatas: - # Add app id in metadatas so that they can be queried on later - if self.config.id: - m["app_id"] = self.config.id - - # Add hashed source - m["hash"] = source_hash - - # Note: Metadata is the function argument - if metadata: - # Spread whatever is in metadata into the new object. - m.update(metadata) - - new_metadatas.append(m) - metadatas = new_metadatas - - if dry_run: - return list(documents), metadatas, ids, 0 - - # Count before, to calculate a delta in the end. - chunks_before_addition = self.db.count() - - # Filter out empty documents and ensure they meet the API requirements - valid_documents = [doc for doc in documents if doc and isinstance(doc, str)] - - documents = valid_documents - - # Chunk documents into batches of 2048 and handle each batch - # helps wigth large loads of embeddings that hit OpenAI limits - document_batches = [documents[i : i + 2048] for i in range(0, len(documents), 2048)] - metadata_batches = [metadatas[i : i + 2048] for i in range(0, len(metadatas), 2048)] - id_batches = [ids[i : i + 2048] for i in range(0, len(ids), 2048)] - for batch_docs, batch_meta, batch_ids in zip(document_batches, metadata_batches, id_batches): - try: - # Add only valid batches - if batch_docs: - self.db.add(documents=batch_docs, metadatas=batch_meta, ids=batch_ids, **kwargs) - except Exception as e: - logger.info(f"Failed to add batch due to a bad request: {e}") - # Handle the error, e.g., by logging, retrying, or skipping - pass - - count_new_chunks = self.db.count() - chunks_before_addition - logger.info(f"Successfully saved {str(src)[:100]} ({chunker.data_type}). New chunks count: {count_new_chunks}") - - return list(documents), metadatas, ids, count_new_chunks - - @staticmethod - def _format_result(results): - return [ - (Document(page_content=result[0], metadata=result[1] or {}), result[2]) - for result in zip( - results["documents"][0], - results["metadatas"][0], - results["distances"][0], - ) - ] - - def _retrieve_from_database( - self, - input_query: str, - config: Optional[BaseLlmConfig] = None, - where=None, - citations: bool = False, - **kwargs: Optional[dict[str, Any]], - ) -> Union[list[tuple[str, str, str]], list[str]]: - """ - Queries the vector database based on the given input query. - Gets relevant doc based on the query - - :param input_query: The query to use. - :type input_query: str - :param config: The query configuration, defaults to None - :type config: Optional[BaseLlmConfig], optional - :param where: A dictionary of key-value pairs to filter the database results, defaults to None - :type where: _type_, optional - :param citations: A boolean to indicate if db should fetch citation source - :type citations: bool - :return: List of contents of the document that matched your query - :rtype: list[str] - """ - query_config = config or self.llm.config - if where is not None: - where = where - else: - where = {} - if query_config is not None and query_config.where is not None: - where = query_config.where - - if self.config.id is not None: - where.update({"app_id": self.config.id}) - - contexts = self.db.query( - input_query=input_query, - n_results=query_config.number_documents, - where=where, - citations=citations, - **kwargs, - ) - - return contexts - - def query( - self, - input_query: str, - config: BaseLlmConfig = None, - dry_run=False, - where: Optional[dict] = None, - citations: bool = False, - **kwargs: dict[str, Any], - ) -> Union[tuple[str, list[tuple[str, dict]]], str, dict[str, Any]]: - """ - Queries the vector database based on the given input query. - Gets relevant doc based on the query and then passes it to an - LLM as context to get the answer. - - :param input_query: The query to use. - :type input_query: str - :param config: The `BaseLlmConfig` instance to use as configuration options. This is used for one method call. - To persistently use a config, declare it during app init., defaults to None - :type config: BaseLlmConfig, optional - :param dry_run: A dry run does everything except send the resulting prompt to - the LLM. The purpose is to test the prompt, not the response., defaults to False - :type dry_run: bool, optional - :param where: A dictionary of key-value pairs to filter the database results., defaults to None - :type where: dict[str, str], optional - :param citations: A boolean to indicate if db should fetch citation source - :type citations: bool - :param kwargs: To read more params for the query function. Ex. we use citations boolean - param to return context along with the answer - :type kwargs: dict[str, Any] - :return: The answer to the query, with citations if the citation flag is True - or the dry run result - :rtype: str, if citations is False and token_usage is False, otherwise if citations is true then - tuple[str, list[tuple[str,str,str]]] and if token_usage is true then - tuple[str, list[tuple[str,str,str]], dict[str, Any]] - """ - contexts = self._retrieve_from_database( - input_query=input_query, config=config, where=where, citations=citations, **kwargs - ) - if citations and len(contexts) > 0 and isinstance(contexts[0], tuple): - contexts_data_for_llm_query = list(map(lambda x: x[0], contexts)) - else: - contexts_data_for_llm_query = contexts - - if self.cache_config is not None: - logger.info("Cache enabled. Checking cache...") - answer = adapt( - llm_handler=self.llm.query, - cache_data_convert=gptcache_data_convert, - update_cache_callback=gptcache_update_cache_callback, - session=get_gptcache_session(session_id=self.config.id), - input_query=input_query, - contexts=contexts_data_for_llm_query, - config=config, - dry_run=dry_run, - ) - else: - if self.llm.config.token_usage: - answer, token_info = self.llm.query( - input_query=input_query, contexts=contexts_data_for_llm_query, config=config, dry_run=dry_run - ) - else: - answer = self.llm.query( - input_query=input_query, contexts=contexts_data_for_llm_query, config=config, dry_run=dry_run - ) - - # Send anonymous telemetry - if self.config.collect_metrics: - self.telemetry.capture(event_name="query", properties=self._telemetry_props) - - if citations: - if self.llm.config.token_usage: - return {"answer": answer, "contexts": contexts, "usage": token_info} - return answer, contexts - if self.llm.config.token_usage: - return {"answer": answer, "usage": token_info} - - logger.warning( - "Starting from v0.1.125 the return type of query method will be changed to tuple containing `answer`." - ) - return answer - - def chat( - self, - input_query: str, - config: Optional[BaseLlmConfig] = None, - dry_run=False, - session_id: str = "default", - where: Optional[dict[str, str]] = None, - citations: bool = False, - **kwargs: dict[str, Any], - ) -> Union[tuple[str, list[tuple[str, dict]]], str, dict[str, Any]]: - """ - Queries the vector database on the given input query. - Gets relevant doc based on the query and then passes it to an - LLM as context to get the answer. - - Maintains the whole conversation in memory. - - :param input_query: The query to use. - :type input_query: str - :param config: The `BaseLlmConfig` instance to use as configuration options. This is used for one method call. - To persistently use a config, declare it during app init., defaults to None - :type config: BaseLlmConfig, optional - :param dry_run: A dry run does everything except send the resulting prompt to - the LLM. The purpose is to test the prompt, not the response., defaults to False - :type dry_run: bool, optional - :param session_id: The session id to use for chat history, defaults to 'default'. - :type session_id: str, optional - :param where: A dictionary of key-value pairs to filter the database results., defaults to None - :type where: dict[str, str], optional - :param citations: A boolean to indicate if db should fetch citation source - :type citations: bool - :param kwargs: To read more params for the query function. Ex. we use citations boolean - param to return context along with the answer - :type kwargs: dict[str, Any] - :return: The answer to the query, with citations if the citation flag is True - or the dry run result - :rtype: str, if citations is False and token_usage is False, otherwise if citations is true then - tuple[str, list[tuple[str,str,str]]] and if token_usage is true then - tuple[str, list[tuple[str,str,str]], dict[str, Any]] - """ - contexts = self._retrieve_from_database( - input_query=input_query, config=config, where=where, citations=citations, **kwargs - ) - if citations and len(contexts) > 0 and isinstance(contexts[0], tuple): - contexts_data_for_llm_query = list(map(lambda x: x[0], contexts)) - else: - contexts_data_for_llm_query = contexts - - memories = None - if self.mem0_memory: - memories = self.mem0_memory.search( - query=input_query, agent_id=self.config.id, user_id=session_id, limit=self.memory_config.top_k - ) - - # Update the history beforehand so that we can handle multiple chat sessions in the same python session - self.llm.update_history(app_id=self.config.id, session_id=session_id) - - if self.cache_config is not None: - logger.debug("Cache enabled. Checking cache...") - cache_id = f"{session_id}--{self.config.id}" - answer = adapt( - llm_handler=self.llm.chat, - cache_data_convert=gptcache_data_convert, - update_cache_callback=gptcache_update_cache_callback, - session=get_gptcache_session(session_id=cache_id), - input_query=input_query, - contexts=contexts_data_for_llm_query, - config=config, - dry_run=dry_run, - ) - else: - logger.debug("Cache disabled. Running chat without cache.") - if self.llm.config.token_usage: - answer, token_info = self.llm.query( - input_query=input_query, - contexts=contexts_data_for_llm_query, - config=config, - dry_run=dry_run, - memories=memories, - ) - else: - answer = self.llm.query( - input_query=input_query, - contexts=contexts_data_for_llm_query, - config=config, - dry_run=dry_run, - memories=memories, - ) - - # Add to Mem0 memory if enabled - # Adding answer here because it would be much useful than input question itself - if self.mem0_memory: - self.mem0_memory.add(data=answer, agent_id=self.config.id, user_id=session_id) - - # add conversation in memory - self.llm.add_history(self.config.id, input_query, answer, session_id=session_id) - - # Send anonymous telemetry - if self.config.collect_metrics: - self.telemetry.capture(event_name="chat", properties=self._telemetry_props) - - if citations: - if self.llm.config.token_usage: - return {"answer": answer, "contexts": contexts, "usage": token_info} - return answer, contexts - if self.llm.config.token_usage: - return {"answer": answer, "usage": token_info} - - logger.warning( - "Starting from v0.1.125 the return type of query method will be changed to tuple containing `answer`." - ) - return answer - - def search(self, query, num_documents=3, where=None, raw_filter=None, namespace=None): - """ - Search for similar documents related to the query in the vector database. - - Args: - query (str): The query to use. - num_documents (int, optional): Number of similar documents to fetch. Defaults to 3. - where (dict[str, any], optional): Filter criteria for the search. - raw_filter (dict[str, any], optional): Advanced raw filter criteria for the search. - namespace (str, optional): The namespace to search in. Defaults to None. - - Raises: - ValueError: If both `raw_filter` and `where` are used simultaneously. - - Returns: - list[dict]: A list of dictionaries, each containing the 'context' and 'metadata' of a document. - """ - # Send anonymous telemetry - if self.config.collect_metrics: - self.telemetry.capture(event_name="search", properties=self._telemetry_props) - - if raw_filter and where: - raise ValueError("You can't use both `raw_filter` and `where` together.") - - filter_type = "raw_filter" if raw_filter else "where" - filter_criteria = raw_filter if raw_filter else where - - params = { - "input_query": query, - "n_results": num_documents, - "citations": True, - "app_id": self.config.id, - "namespace": namespace, - filter_type: filter_criteria, - } - - return [{"context": c[0], "metadata": c[1]} for c in self.db.query(**params)] - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - Using `app.db.set_collection_name` method is preferred to this. - - :param name: Name of the collection. - :type name: str - """ - self.db.set_collection_name(name) - # Create the collection if it does not exist - self.db._get_or_create_collection(name) - # TODO: Check whether it is necessary to assign to the `self.collection` attribute, - # since the main purpose is the creation. - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - `App` does not have to be reinitialized after using this method. - """ - try: - self.db_session.query(DataSource).filter_by(app_id=self.config.id).delete() - self.db_session.query(ChatHistory).filter_by(app_id=self.config.id).delete() - self.db_session.commit() - except Exception as e: - logger.error(f"Error deleting data sources: {e}") - self.db_session.rollback() - return None - self.db.reset() - self.delete_all_chat_history(app_id=self.config.id) - # Send anonymous telemetry - if self.config.collect_metrics: - self.telemetry.capture(event_name="reset", properties=self._telemetry_props) - - def get_history( - self, - num_rounds: int = 10, - display_format: bool = True, - session_id: Optional[str] = "default", - fetch_all: bool = False, - ): - history = self.llm.memory.get( - app_id=self.config.id, - session_id=session_id, - num_rounds=num_rounds, - display_format=display_format, - fetch_all=fetch_all, - ) - return history - - def delete_session_chat_history(self, session_id: str = "default"): - self.llm.memory.delete(app_id=self.config.id, session_id=session_id) - self.llm.update_history(app_id=self.config.id) - - def delete_all_chat_history(self, app_id: str): - self.llm.memory.delete(app_id=app_id) - self.llm.update_history(app_id=app_id) - - def delete(self, source_id: str): - """ - Deletes the data from the database. - :param source_hash: The hash of the source. - :type source_hash: str - """ - try: - self.db_session.query(DataSource).filter_by(hash=source_id, app_id=self.config.id).delete() - self.db_session.commit() - except Exception as e: - logger.error(f"Error deleting data sources: {e}") - self.db_session.rollback() - return None - self.db.delete(where={"hash": source_id}) - logger.info(f"Successfully deleted {source_id}") - # Send anonymous telemetry - if self.config.collect_metrics: - self.telemetry.capture(event_name="delete", properties=self._telemetry_props) diff --git a/embedchain/embedchain/embedder/__init__.py b/embedchain/embedchain/embedder/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/embedder/aws_bedrock.py b/embedchain/embedchain/embedder/aws_bedrock.py deleted file mode 100644 index 235fc3dab..000000000 --- a/embedchain/embedchain/embedder/aws_bedrock.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional - -try: - from langchain_aws import BedrockEmbeddings -except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for AWSBedrock are not installed." "Please install with `pip install langchain_aws`" - ) from None - -from embedchain.config.embedder.aws_bedrock import AWSBedrockEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class AWSBedrockEmbedder(BaseEmbedder): - def __init__(self, config: Optional[AWSBedrockEmbedderConfig] = None): - super().__init__(config) - - if self.config.model is None or self.config.model == "amazon.titan-embed-text-v2:0": - self.config.model = "amazon.titan-embed-text-v2:0" # Default model if not specified - vector_dimension = self.config.vector_dimension or VectorDimensions.AMAZON_TITAN_V2.value - elif self.config.model == "amazon.titan-embed-text-v1": - vector_dimension = VectorDimensions.AMAZON_TITAN_V1.value - else: - vector_dimension = self.config.vector_dimension - - embeddings = BedrockEmbeddings(model_id=self.config.model, model_kwargs=self.config.model_kwargs) - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - - self.set_embedding_fn(embedding_fn=embedding_fn) - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/azure_openai.py b/embedchain/embedchain/embedder/azure_openai.py deleted file mode 100644 index 71802ad87..000000000 --- a/embedchain/embedchain/embedder/azure_openai.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional - -from langchain_openai import AzureOpenAIEmbeddings - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class AzureOpenAIEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config=config) - - if self.config.model is None: - self.config.model = "text-embedding-ada-002" - - embeddings = AzureOpenAIEmbeddings( - deployment=self.config.deployment_name, - http_client=self.config.http_client, - http_async_client=self.config.http_async_client, - ) - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - - self.set_embedding_fn(embedding_fn=embedding_fn) - vector_dimension = self.config.vector_dimension or VectorDimensions.OPENAI.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/base.py b/embedchain/embedchain/embedder/base.py deleted file mode 100644 index 7f65477bf..000000000 --- a/embedchain/embedchain/embedder/base.py +++ /dev/null @@ -1,90 +0,0 @@ -from collections.abc import Callable -from typing import Any, Optional - -from embedchain.config.embedder.base import BaseEmbedderConfig - -try: - from chromadb.api.types import Embeddable, EmbeddingFunction, Embeddings -except RuntimeError: - from embedchain.utils.misc import use_pysqlite3 - - use_pysqlite3() - from chromadb.api.types import Embeddable, EmbeddingFunction, Embeddings - - -class EmbeddingFunc(EmbeddingFunction): - def __init__(self, embedding_fn: Callable[[list[str]], list[str]]): - self.embedding_fn = embedding_fn - - def __call__(self, input: Embeddable) -> Embeddings: - return self.embedding_fn(input) - - -class BaseEmbedder: - """ - Class that manages everything regarding embeddings. Including embedding function, loaders and chunkers. - - Embedding functions and vector dimensions are set based on the child class you choose. - To manually overwrite you can use this classes `set_...` methods. - """ - - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - """ - Initialize the embedder class. - - :param config: embedder configuration option class, defaults to None - :type config: Optional[BaseEmbedderConfig], optional - """ - if config is None: - self.config = BaseEmbedderConfig() - else: - self.config = config - self.vector_dimension: int - - def set_embedding_fn(self, embedding_fn: Callable[[list[str]], list[str]]): - """ - Set or overwrite the embedding function to be used by the database to store and retrieve documents. - - :param embedding_fn: Function to be used to generate embeddings. - :type embedding_fn: Callable[[list[str]], list[str]] - :raises ValueError: Embedding function is not callable. - """ - if not hasattr(embedding_fn, "__call__"): - raise ValueError("Embedding function is not a function") - self.embedding_fn = embedding_fn - - def set_vector_dimension(self, vector_dimension: int): - """ - Set or overwrite the vector dimension size - - :param vector_dimension: vector dimension size - :type vector_dimension: int - """ - if not isinstance(vector_dimension, int): - raise TypeError("vector dimension must be int") - self.vector_dimension = vector_dimension - - @staticmethod - def _langchain_default_concept(embeddings: Any): - """ - Langchains default function layout for embeddings. - - :param embeddings: Langchain embeddings - :type embeddings: Any - :return: embedding function - :rtype: Callable - """ - - return EmbeddingFunc(embeddings.embed_documents) - - def to_embeddings(self, data: str, **_): - """ - Convert data to embeddings - - :param data: data to convert to embeddings - :type data: str - :return: embeddings - :rtype: list[float] - """ - embeddings = self.embedding_fn([data]) - return embeddings[0] diff --git a/embedchain/embedchain/embedder/clarifai.py b/embedchain/embedchain/embedder/clarifai.py deleted file mode 100644 index 8f0bb2fe4..000000000 --- a/embedchain/embedchain/embedder/clarifai.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -from typing import Optional, Union - -from chromadb import EmbeddingFunction, Embeddings - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder - - -class ClarifaiEmbeddingFunction(EmbeddingFunction): - def __init__(self, config: BaseEmbedderConfig) -> None: - super().__init__() - try: - from clarifai.client.input import Inputs - from clarifai.client.model import Model - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for ClarifaiEmbeddingFunction are not installed." - 'Please install with `pip install --upgrade "embedchain[clarifai]"`' - ) from None - self.config = config - self.api_key = config.api_key or os.getenv("CLARIFAI_PAT") - self.model = config.model - self.model_obj = Model(url=self.model, pat=self.api_key) - self.input_obj = Inputs(pat=self.api_key) - - def __call__(self, input: Union[str, list[str]]) -> Embeddings: - if isinstance(input, str): - input = [input] - - batch_size = 32 - embeddings = [] - try: - for i in range(0, len(input), batch_size): - batch = input[i : i + batch_size] - input_batch = [ - self.input_obj.get_text_input(input_id=str(id), raw_text=inp) for id, inp in enumerate(batch) - ] - response = self.model_obj.predict(input_batch) - embeddings.extend([list(output.data.embeddings[0].vector) for output in response.outputs]) - except Exception as e: - print(f"Predict failed, exception: {e}") - - return embeddings - - -class ClarifaiEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config) - - embedding_func = ClarifaiEmbeddingFunction(config=self.config) - self.set_embedding_fn(embedding_fn=embedding_func) diff --git a/embedchain/embedchain/embedder/cohere.py b/embedchain/embedchain/embedder/cohere.py deleted file mode 100644 index 489ba97f3..000000000 --- a/embedchain/embedchain/embedder/cohere.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Optional - -from langchain_cohere.embeddings import CohereEmbeddings - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class CohereEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config=config) - - embeddings = CohereEmbeddings(model=self.config.model) - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.COHERE.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/google.py b/embedchain/embedchain/embedder/google.py deleted file mode 100644 index c0be83500..000000000 --- a/embedchain/embedchain/embedder/google.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional, Union - -import google.generativeai as genai -from chromadb import EmbeddingFunction, Embeddings - -from embedchain.config.embedder.google import GoogleAIEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class GoogleAIEmbeddingFunction(EmbeddingFunction): - def __init__(self, config: Optional[GoogleAIEmbedderConfig] = None) -> None: - super().__init__() - self.config = config or GoogleAIEmbedderConfig() - - def __call__(self, input: Union[list[str], str]) -> Embeddings: - model = self.config.model - title = self.config.title - task_type = self.config.task_type - if isinstance(input, str): - input_ = [input] - else: - input_ = input - data = genai.embed_content(model=model, content=input_, task_type=task_type, title=title) - embeddings = data["embedding"] - if isinstance(input_, str): - embeddings = [embeddings] - return embeddings - - -class GoogleAIEmbedder(BaseEmbedder): - def __init__(self, config: Optional[GoogleAIEmbedderConfig] = None): - super().__init__(config) - embedding_fn = GoogleAIEmbeddingFunction(config=config) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.GOOGLE_AI.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/gpt4all.py b/embedchain/embedchain/embedder/gpt4all.py deleted file mode 100644 index 83123f499..000000000 --- a/embedchain/embedchain/embedder/gpt4all.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class GPT4AllEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config=config) - - from langchain_community.embeddings import ( - GPT4AllEmbeddings as LangchainGPT4AllEmbeddings, - ) - - model_name = self.config.model or "all-MiniLM-L6-v2-f16.gguf" - gpt4all_kwargs = {'allow_download': 'True'} - embeddings = LangchainGPT4AllEmbeddings(model_name=model_name, gpt4all_kwargs=gpt4all_kwargs) - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.GPT4ALL.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/huggingface.py b/embedchain/embedchain/embedder/huggingface.py deleted file mode 100644 index 062208e77..000000000 --- a/embedchain/embedchain/embedder/huggingface.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -from typing import Optional - -from langchain_community.embeddings import HuggingFaceEmbeddings - -try: - from langchain_huggingface import HuggingFaceEndpointEmbeddings -except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for HuggingFaceHub are not installed." - "Please install with `pip install langchain_huggingface`" - ) from None - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class HuggingFaceEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config=config) - - if self.config.endpoint: - if not self.config.api_key and "HUGGINGFACE_ACCESS_TOKEN" not in os.environ: - raise ValueError( - "Please set the HUGGINGFACE_ACCESS_TOKEN environment variable or pass API Key in the config." - ) - - embeddings = HuggingFaceEndpointEmbeddings( - model=self.config.endpoint, - huggingfacehub_api_token=self.config.api_key or os.getenv("HUGGINGFACE_ACCESS_TOKEN"), - ) - else: - embeddings = HuggingFaceEmbeddings(model_name=self.config.model, model_kwargs=self.config.model_kwargs) - - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.HUGGING_FACE.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/mistralai.py b/embedchain/embedchain/embedder/mistralai.py deleted file mode 100644 index 29db72ae0..000000000 --- a/embedchain/embedchain/embedder/mistralai.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -from typing import Optional, Union - -from chromadb import EmbeddingFunction, Embeddings - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class MistralAIEmbeddingFunction(EmbeddingFunction): - def __init__(self, config: BaseEmbedderConfig) -> None: - super().__init__() - try: - from langchain_mistralai import MistralAIEmbeddings - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for MistralAI are not installed." - 'Please install with `pip install --upgrade "embedchain[mistralai]"`' - ) from None - self.config = config - api_key = self.config.api_key or os.getenv("MISTRAL_API_KEY") - self.client = MistralAIEmbeddings(mistral_api_key=api_key) - self.client.model = self.config.model - - def __call__(self, input: Union[list[str], str]) -> Embeddings: - if isinstance(input, str): - input_ = [input] - else: - input_ = input - response = self.client.embed_documents(input_) - return response - - -class MistralAIEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config) - - if self.config.model is None: - self.config.model = "mistral-embed" - - embedding_fn = MistralAIEmbeddingFunction(config=self.config) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.MISTRAL_AI.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/nvidia.py b/embedchain/embedchain/embedder/nvidia.py deleted file mode 100644 index 5a499037f..000000000 --- a/embedchain/embedchain/embedder/nvidia.py +++ /dev/null @@ -1,28 +0,0 @@ -import logging -import os -from typing import Optional - -from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - -logger = logging.getLogger(__name__) - - -class NvidiaEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - if "NVIDIA_API_KEY" not in os.environ: - raise ValueError("NVIDIA_API_KEY environment variable must be set") - - super().__init__(config=config) - - model = self.config.model or "nvolveqa_40k" - logger.info(f"Using NVIDIA embedding model: {model}") - embedder = NVIDIAEmbeddings(model=model) - embedding_fn = BaseEmbedder._langchain_default_concept(embedder) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.NVIDIA_AI.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/ollama.py b/embedchain/embedchain/embedder/ollama.py deleted file mode 100644 index 9e4ada473..000000000 --- a/embedchain/embedchain/embedder/ollama.py +++ /dev/null @@ -1,32 +0,0 @@ -import logging -from typing import Optional - -try: - from ollama import Client -except ImportError: - raise ImportError("Ollama Embedder requires extra dependencies. Install with `pip install ollama`") from None - -from langchain_community.embeddings import OllamaEmbeddings - -from embedchain.config import OllamaEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - -logger = logging.getLogger(__name__) - - -class OllamaEmbedder(BaseEmbedder): - def __init__(self, config: Optional[OllamaEmbedderConfig] = None): - super().__init__(config=config) - - client = Client(host=config.base_url) - local_models = client.list()["models"] - if not any(model.get("name") == self.config.model for model in local_models): - logger.info(f"Pulling {self.config.model} from Ollama!") - client.pull(self.config.model) - embeddings = OllamaEmbeddings(model=self.config.model, base_url=config.base_url) - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.OLLAMA.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/openai.py b/embedchain/embedchain/embedder/openai.py deleted file mode 100644 index e14a1aa70..000000000 --- a/embedchain/embedchain/embedder/openai.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import warnings -from typing import Optional - -from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class OpenAIEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config=config) - - if self.config.model is None: - self.config.model = "text-embedding-ada-002" - - api_key = self.config.api_key or os.environ["OPENAI_API_KEY"] - api_base = ( - self.config.api_base - or os.environ.get("OPENAI_API_BASE") - or os.getenv("OPENAI_BASE_URL") - or "https://api.openai.com/v1" - ) - if os.environ.get("OPENAI_API_BASE"): - warnings.warn( - "The environment variable 'OPENAI_API_BASE' is deprecated and will be removed in the 0.1.140. " - "Please use 'OPENAI_BASE_URL' instead.", - DeprecationWarning - ) - - if api_key is None and os.getenv("OPENAI_ORGANIZATION") is None: - raise ValueError("OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided") # noqa:E501 - embedding_fn = OpenAIEmbeddingFunction( - api_key=api_key, - api_base=api_base, - organization_id=os.getenv("OPENAI_ORGANIZATION"), - model_name=self.config.model, - ) - self.set_embedding_fn(embedding_fn=embedding_fn) - vector_dimension = self.config.vector_dimension or VectorDimensions.OPENAI.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/embedder/vertexai.py b/embedchain/embedchain/embedder/vertexai.py deleted file mode 100644 index 1f3331dc6..000000000 --- a/embedchain/embedchain/embedder/vertexai.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Optional - -from langchain_google_vertexai import VertexAIEmbeddings - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.models import VectorDimensions - - -class VertexAIEmbedder(BaseEmbedder): - def __init__(self, config: Optional[BaseEmbedderConfig] = None): - super().__init__(config=config) - - embeddings = VertexAIEmbeddings(model_name=config.model) - embedding_fn = BaseEmbedder._langchain_default_concept(embeddings) - self.set_embedding_fn(embedding_fn=embedding_fn) - - vector_dimension = self.config.vector_dimension or VectorDimensions.VERTEX_AI.value - self.set_vector_dimension(vector_dimension=vector_dimension) diff --git a/embedchain/embedchain/evaluation/__init__.py b/embedchain/embedchain/evaluation/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/evaluation/base.py b/embedchain/embedchain/evaluation/base.py deleted file mode 100644 index 4528e7689..000000000 --- a/embedchain/embedchain/evaluation/base.py +++ /dev/null @@ -1,29 +0,0 @@ -from abc import ABC, abstractmethod - -from embedchain.utils.evaluation import EvalData - - -class BaseMetric(ABC): - """Base class for a metric. - - This class provides a common interface for all metrics. - """ - - def __init__(self, name: str = "base_metric"): - """ - Initialize the BaseMetric. - """ - self.name = name - - @abstractmethod - def evaluate(self, dataset: list[EvalData]): - """ - Abstract method to evaluate the dataset. - - This method should be implemented by subclasses to perform the actual - evaluation on the dataset. - - :param dataset: dataset to evaluate - :type dataset: list[EvalData] - """ - raise NotImplementedError() diff --git a/embedchain/embedchain/evaluation/metrics/__init__.py b/embedchain/embedchain/evaluation/metrics/__init__.py deleted file mode 100644 index 95f579005..000000000 --- a/embedchain/embedchain/evaluation/metrics/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .answer_relevancy import AnswerRelevance # noqa: F401 -from .context_relevancy import ContextRelevance # noqa: F401 -from .groundedness import Groundedness # noqa: F401 diff --git a/embedchain/embedchain/evaluation/metrics/answer_relevancy.py b/embedchain/embedchain/evaluation/metrics/answer_relevancy.py deleted file mode 100644 index 3e5c3859e..000000000 --- a/embedchain/embedchain/evaluation/metrics/answer_relevancy.py +++ /dev/null @@ -1,95 +0,0 @@ -import concurrent.futures -import logging -import os -from string import Template -from typing import Optional - -import numpy as np -from openai import OpenAI -from tqdm import tqdm - -from embedchain.config.evaluation.base import AnswerRelevanceConfig -from embedchain.evaluation.base import BaseMetric -from embedchain.utils.evaluation import EvalData, EvalMetric - -logger = logging.getLogger(__name__) - - -class AnswerRelevance(BaseMetric): - """ - Metric for evaluating the relevance of answers. - """ - - def __init__(self, config: Optional[AnswerRelevanceConfig] = AnswerRelevanceConfig()): - super().__init__(name=EvalMetric.ANSWER_RELEVANCY.value) - self.config = config - api_key = self.config.api_key or os.getenv("OPENAI_API_KEY") - if not api_key: - raise ValueError("API key not found. Set 'OPENAI_API_KEY' or pass it in the config.") - self.client = OpenAI(api_key=api_key) - - def _generate_prompt(self, data: EvalData) -> str: - """ - Generates a prompt based on the provided data. - """ - return Template(self.config.prompt).substitute( - num_gen_questions=self.config.num_gen_questions, answer=data.answer - ) - - def _generate_questions(self, prompt: str) -> list[str]: - """ - Generates questions from the prompt. - """ - response = self.client.chat.completions.create( - model=self.config.model, - messages=[{"role": "user", "content": prompt}], - ) - return response.choices[0].message.content.strip().split("\n") - - def _generate_embedding(self, question: str) -> np.ndarray: - """ - Generates the embedding for a question. - """ - response = self.client.embeddings.create( - input=question, - model=self.config.embedder, - ) - return np.array(response.data[0].embedding) - - def _compute_similarity(self, original: np.ndarray, generated: np.ndarray) -> float: - """ - Computes the cosine similarity between two embeddings. - """ - original = original.reshape(1, -1) - norm = np.linalg.norm(original) * np.linalg.norm(generated, axis=1) - return np.dot(generated, original.T).flatten() / norm - - def _compute_score(self, data: EvalData) -> float: - """ - Computes the relevance score for a given data item. - """ - prompt = self._generate_prompt(data) - generated_questions = self._generate_questions(prompt) - original_embedding = self._generate_embedding(data.question) - generated_embeddings = np.array([self._generate_embedding(q) for q in generated_questions]) - similarities = self._compute_similarity(original_embedding, generated_embeddings) - return np.mean(similarities) - - def evaluate(self, dataset: list[EvalData]) -> float: - """ - Evaluates the dataset and returns the average answer relevance score. - """ - results = [] - - with concurrent.futures.ThreadPoolExecutor() as executor: - future_to_data = {executor.submit(self._compute_score, data): data for data in dataset} - for future in tqdm( - concurrent.futures.as_completed(future_to_data), total=len(dataset), desc="Evaluating Answer Relevancy" - ): - data = future_to_data[future] - try: - results.append(future.result()) - except Exception as e: - logger.error(f"Error evaluating answer relevancy for {data}: {e}") - - return np.mean(results) if results else 0.0 diff --git a/embedchain/embedchain/evaluation/metrics/context_relevancy.py b/embedchain/embedchain/evaluation/metrics/context_relevancy.py deleted file mode 100644 index f821713fa..000000000 --- a/embedchain/embedchain/evaluation/metrics/context_relevancy.py +++ /dev/null @@ -1,69 +0,0 @@ -import concurrent.futures -import os -from string import Template -from typing import Optional - -import numpy as np -import pysbd -from openai import OpenAI -from tqdm import tqdm - -from embedchain.config.evaluation.base import ContextRelevanceConfig -from embedchain.evaluation.base import BaseMetric -from embedchain.utils.evaluation import EvalData, EvalMetric - - -class ContextRelevance(BaseMetric): - """ - Metric for evaluating the relevance of context in a dataset. - """ - - def __init__(self, config: Optional[ContextRelevanceConfig] = ContextRelevanceConfig()): - super().__init__(name=EvalMetric.CONTEXT_RELEVANCY.value) - self.config = config - api_key = self.config.api_key or os.getenv("OPENAI_API_KEY") - if not api_key: - raise ValueError("API key not found. Set 'OPENAI_API_KEY' or pass it in the config.") - self.client = OpenAI(api_key=api_key) - self._sbd = pysbd.Segmenter(language=self.config.language, clean=False) - - def _sentence_segmenter(self, text: str) -> list[str]: - """ - Segments the given text into sentences. - """ - return self._sbd.segment(text) - - def _compute_score(self, data: EvalData) -> float: - """ - Computes the context relevance score for a given data item. - """ - original_context = "\n".join(data.contexts) - prompt = Template(self.config.prompt).substitute(context=original_context, question=data.question) - response = self.client.chat.completions.create( - model=self.config.model, messages=[{"role": "user", "content": prompt}] - ) - useful_context = response.choices[0].message.content.strip() - useful_context_sentences = self._sentence_segmenter(useful_context) - original_context_sentences = self._sentence_segmenter(original_context) - - if not original_context_sentences: - return 0.0 - return len(useful_context_sentences) / len(original_context_sentences) - - def evaluate(self, dataset: list[EvalData]) -> float: - """ - Evaluates the dataset and returns the average context relevance score. - """ - scores = [] - - with concurrent.futures.ThreadPoolExecutor() as executor: - futures = [executor.submit(self._compute_score, data) for data in dataset] - for future in tqdm( - concurrent.futures.as_completed(futures), total=len(dataset), desc="Evaluating Context Relevancy" - ): - try: - scores.append(future.result()) - except Exception as e: - print(f"Error during evaluation: {e}") - - return np.mean(scores) if scores else 0.0 diff --git a/embedchain/embedchain/evaluation/metrics/groundedness.py b/embedchain/embedchain/evaluation/metrics/groundedness.py deleted file mode 100644 index 86f3f320e..000000000 --- a/embedchain/embedchain/evaluation/metrics/groundedness.py +++ /dev/null @@ -1,104 +0,0 @@ -import concurrent.futures -import logging -import os -from string import Template -from typing import Optional - -import numpy as np -from openai import OpenAI -from tqdm import tqdm - -from embedchain.config.evaluation.base import GroundednessConfig -from embedchain.evaluation.base import BaseMetric -from embedchain.utils.evaluation import EvalData, EvalMetric - -logger = logging.getLogger(__name__) - - -class Groundedness(BaseMetric): - """ - Metric for groundedness of answer from the given contexts. - """ - - def __init__(self, config: Optional[GroundednessConfig] = None): - super().__init__(name=EvalMetric.GROUNDEDNESS.value) - self.config = config or GroundednessConfig() - api_key = self.config.api_key or os.getenv("OPENAI_API_KEY") - if not api_key: - raise ValueError("Please set the OPENAI_API_KEY environment variable or pass the `api_key` in config.") - self.client = OpenAI(api_key=api_key) - - def _generate_answer_claim_prompt(self, data: EvalData) -> str: - """ - Generate the prompt for the given data. - """ - prompt = Template(self.config.answer_claims_prompt).substitute(question=data.question, answer=data.answer) - return prompt - - def _get_claim_statements(self, prompt: str) -> np.ndarray: - """ - Get claim statements from the answer. - """ - response = self.client.chat.completions.create( - model=self.config.model, - messages=[{"role": "user", "content": f"{prompt}"}], - ) - result = response.choices[0].message.content.strip() - claim_statements = np.array([statement for statement in result.split("\n") if statement]) - return claim_statements - - def _generate_claim_inference_prompt(self, data: EvalData, claim_statements: list[str]) -> str: - """ - Generate the claim inference prompt for the given data and claim statements. - """ - prompt = Template(self.config.claims_inference_prompt).substitute( - context="\n".join(data.contexts), claim_statements="\n".join(claim_statements) - ) - return prompt - - def _get_claim_verdict_scores(self, prompt: str) -> np.ndarray: - """ - Get verdicts for claim statements. - """ - response = self.client.chat.completions.create( - model=self.config.model, - messages=[{"role": "user", "content": f"{prompt}"}], - ) - result = response.choices[0].message.content.strip() - claim_verdicts = result.split("\n") - verdict_score_map = {"1": 1, "0": 0, "-1": np.nan} - verdict_scores = np.array([verdict_score_map[verdict] for verdict in claim_verdicts]) - return verdict_scores - - def _compute_score(self, data: EvalData) -> float: - """ - Compute the groundedness score for a single data point. - """ - answer_claims_prompt = self._generate_answer_claim_prompt(data) - claim_statements = self._get_claim_statements(answer_claims_prompt) - - claim_inference_prompt = self._generate_claim_inference_prompt(data, claim_statements) - verdict_scores = self._get_claim_verdict_scores(claim_inference_prompt) - return np.sum(verdict_scores) / claim_statements.size - - def evaluate(self, dataset: list[EvalData]): - """ - Evaluate the dataset and returns the average groundedness score. - """ - results = [] - - with concurrent.futures.ThreadPoolExecutor() as executor: - future_to_data = {executor.submit(self._compute_score, data): data for data in dataset} - for future in tqdm( - concurrent.futures.as_completed(future_to_data), - total=len(future_to_data), - desc="Evaluating Groundedness", - ): - data = future_to_data[future] - try: - score = future.result() - results.append(score) - except Exception as e: - logger.error(f"Error while evaluating groundedness for data point {data}: {e}") - - return np.mean(results) if results else 0.0 diff --git a/embedchain/embedchain/factory.py b/embedchain/embedchain/factory.py deleted file mode 100644 index 69636286c..000000000 --- a/embedchain/embedchain/factory.py +++ /dev/null @@ -1,122 +0,0 @@ -import importlib - - -def load_class(class_type): - module_path, class_name = class_type.rsplit(".", 1) - module = importlib.import_module(module_path) - return getattr(module, class_name) - - -class LlmFactory: - provider_to_class = { - "anthropic": "embedchain.llm.anthropic.AnthropicLlm", - "azure_openai": "embedchain.llm.azure_openai.AzureOpenAILlm", - "cohere": "embedchain.llm.cohere.CohereLlm", - "together": "embedchain.llm.together.TogetherLlm", - "gpt4all": "embedchain.llm.gpt4all.GPT4ALLLlm", - "ollama": "embedchain.llm.ollama.OllamaLlm", - "huggingface": "embedchain.llm.huggingface.HuggingFaceLlm", - "jina": "embedchain.llm.jina.JinaLlm", - "llama2": "embedchain.llm.llama2.Llama2Llm", - "openai": "embedchain.llm.openai.OpenAILlm", - "vertexai": "embedchain.llm.vertex_ai.VertexAILlm", - "google": "embedchain.llm.google.GoogleLlm", - "aws_bedrock": "embedchain.llm.aws_bedrock.AWSBedrockLlm", - "mistralai": "embedchain.llm.mistralai.MistralAILlm", - "clarifai": "embedchain.llm.clarifai.ClarifaiLlm", - "groq": "embedchain.llm.groq.GroqLlm", - "nvidia": "embedchain.llm.nvidia.NvidiaLlm", - "vllm": "embedchain.llm.vllm.VLLM", - } - provider_to_config_class = { - "embedchain": "embedchain.config.llm.base.BaseLlmConfig", - "openai": "embedchain.config.llm.base.BaseLlmConfig", - "anthropic": "embedchain.config.llm.base.BaseLlmConfig", - } - - @classmethod - def create(cls, provider_name, config_data): - class_type = cls.provider_to_class.get(provider_name) - # Default to embedchain base config if the provider is not in the config map - config_name = "embedchain" if provider_name not in cls.provider_to_config_class else provider_name - config_class_type = cls.provider_to_config_class.get(config_name) - if class_type: - llm_class = load_class(class_type) - llm_config_class = load_class(config_class_type) - return llm_class(config=llm_config_class(**config_data)) - else: - raise ValueError(f"Unsupported Llm provider: {provider_name}") - - -class EmbedderFactory: - provider_to_class = { - "azure_openai": "embedchain.embedder.azure_openai.AzureOpenAIEmbedder", - "gpt4all": "embedchain.embedder.gpt4all.GPT4AllEmbedder", - "huggingface": "embedchain.embedder.huggingface.HuggingFaceEmbedder", - "openai": "embedchain.embedder.openai.OpenAIEmbedder", - "vertexai": "embedchain.embedder.vertexai.VertexAIEmbedder", - "google": "embedchain.embedder.google.GoogleAIEmbedder", - "mistralai": "embedchain.embedder.mistralai.MistralAIEmbedder", - "clarifai": "embedchain.embedder.clarifai.ClarifaiEmbedder", - "nvidia": "embedchain.embedder.nvidia.NvidiaEmbedder", - "cohere": "embedchain.embedder.cohere.CohereEmbedder", - "ollama": "embedchain.embedder.ollama.OllamaEmbedder", - "aws_bedrock": "embedchain.embedder.aws_bedrock.AWSBedrockEmbedder", - } - provider_to_config_class = { - "azure_openai": "embedchain.config.embedder.base.BaseEmbedderConfig", - "google": "embedchain.config.embedder.google.GoogleAIEmbedderConfig", - "gpt4all": "embedchain.config.embedder.base.BaseEmbedderConfig", - "huggingface": "embedchain.config.embedder.base.BaseEmbedderConfig", - "clarifai": "embedchain.config.embedder.base.BaseEmbedderConfig", - "openai": "embedchain.config.embedder.base.BaseEmbedderConfig", - "ollama": "embedchain.config.embedder.ollama.OllamaEmbedderConfig", - "aws_bedrock": "embedchain.config.embedder.aws_bedrock.AWSBedrockEmbedderConfig", - } - - @classmethod - def create(cls, provider_name, config_data): - class_type = cls.provider_to_class.get(provider_name) - # Default to openai config if the provider is not in the config map - config_name = "openai" if provider_name not in cls.provider_to_config_class else provider_name - config_class_type = cls.provider_to_config_class.get(config_name) - if class_type: - embedder_class = load_class(class_type) - embedder_config_class = load_class(config_class_type) - return embedder_class(config=embedder_config_class(**config_data)) - else: - raise ValueError(f"Unsupported Embedder provider: {provider_name}") - - -class VectorDBFactory: - provider_to_class = { - "chroma": "embedchain.vectordb.chroma.ChromaDB", - "elasticsearch": "embedchain.vectordb.elasticsearch.ElasticsearchDB", - "opensearch": "embedchain.vectordb.opensearch.OpenSearchDB", - "lancedb": "embedchain.vectordb.lancedb.LanceDB", - "pinecone": "embedchain.vectordb.pinecone.PineconeDB", - "qdrant": "embedchain.vectordb.qdrant.QdrantDB", - "weaviate": "embedchain.vectordb.weaviate.WeaviateDB", - "zilliz": "embedchain.vectordb.zilliz.ZillizVectorDB", - } - provider_to_config_class = { - "chroma": "embedchain.config.vector_db.chroma.ChromaDbConfig", - "elasticsearch": "embedchain.config.vector_db.elasticsearch.ElasticsearchDBConfig", - "opensearch": "embedchain.config.vector_db.opensearch.OpenSearchDBConfig", - "lancedb": "embedchain.config.vector_db.lancedb.LanceDBConfig", - "pinecone": "embedchain.config.vector_db.pinecone.PineconeDBConfig", - "qdrant": "embedchain.config.vector_db.qdrant.QdrantDBConfig", - "weaviate": "embedchain.config.vector_db.weaviate.WeaviateDBConfig", - "zilliz": "embedchain.config.vector_db.zilliz.ZillizDBConfig", - } - - @classmethod - def create(cls, provider_name, config_data): - class_type = cls.provider_to_class.get(provider_name) - config_class_type = cls.provider_to_config_class.get(provider_name) - if class_type: - embedder_class = load_class(class_type) - embedder_config_class = load_class(config_class_type) - return embedder_class(config=embedder_config_class(**config_data)) - else: - raise ValueError(f"Unsupported Embedder provider: {provider_name}") diff --git a/embedchain/embedchain/helpers/__init__.py b/embedchain/embedchain/helpers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/helpers/callbacks.py b/embedchain/embedchain/helpers/callbacks.py deleted file mode 100644 index 4847e0fea..000000000 --- a/embedchain/embedchain/helpers/callbacks.py +++ /dev/null @@ -1,73 +0,0 @@ -import queue -from typing import Any, Union - -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain.schema import LLMResult - -STOP_ITEM = "[END]" -""" -This is a special item that is used to signal the end of the stream. -""" - - -class StreamingStdOutCallbackHandlerYield(StreamingStdOutCallbackHandler): - """ - This is a callback handler that yields the tokens as they are generated. - For a usage example, see the :func:`generate` function below. - """ - - q: queue.Queue - """ - The queue to write the tokens to as they are generated. - """ - - def __init__(self, q: queue.Queue) -> None: - """ - Initialize the callback handler. - q: The queue to write the tokens to as they are generated. - """ - super().__init__() - self.q = q - - def on_llm_start(self, serialized: dict[str, Any], prompts: list[str], **kwargs: Any) -> None: - """Run when LLM starts running.""" - with self.q.mutex: - self.q.queue.clear() - - def on_llm_new_token(self, token: str, **kwargs: Any) -> None: - """Run on new LLM token. Only available when streaming is enabled.""" - self.q.put(token) - - def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: - """Run when LLM ends running.""" - self.q.put(STOP_ITEM) - - def on_llm_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None: - """Run when LLM errors.""" - self.q.put("%s: %s" % (type(error).__name__, str(error))) - self.q.put(STOP_ITEM) - - -def generate(rq: queue.Queue): - """ - This is a generator that yields the items in the queue until it reaches the stop item. - - Usage example: - ``` - def askQuestion(callback_fn: StreamingStdOutCallbackHandlerYield): - llm = OpenAI(streaming=True, callbacks=[callback_fn]) - return llm.invoke(prompt="Write a poem about a tree.") - - @app.route("/", methods=["GET"]) - def generate_output(): - q = Queue() - callback_fn = StreamingStdOutCallbackHandlerYield(q) - threading.Thread(target=askQuestion, args=(callback_fn,)).start() - return Response(generate(q), mimetype="text/event-stream") - ``` - """ - while True: - result: str = rq.get() - if result == STOP_ITEM or result is None: - break - yield result diff --git a/embedchain/embedchain/helpers/json_serializable.py b/embedchain/embedchain/helpers/json_serializable.py deleted file mode 100644 index 656bb44bc..000000000 --- a/embedchain/embedchain/helpers/json_serializable.py +++ /dev/null @@ -1,198 +0,0 @@ -import json -import logging -from string import Template -from typing import Any, Type, TypeVar, Union - -T = TypeVar("T", bound="JSONSerializable") - -# NOTE: Through inheritance, all of our classes should be children of JSONSerializable. (highest level) -# NOTE: The @register_deserializable decorator should be added to all user facing child classes. (lowest level) - -logger = logging.getLogger(__name__) - - -def register_deserializable(cls: Type[T]) -> Type[T]: - """ - A class decorator to register a class as deserializable. - - When a class is decorated with @register_deserializable, it becomes - a part of the set of classes that the JSONSerializable class can - deserialize. - - Deserialization is in essence loading attributes from a json file. - This decorator is a security measure put in place to make sure that - you don't load attributes that were initially part of another class. - - Example: - @register_deserializable - class ChildClass(JSONSerializable): - def __init__(self, ...): - # initialization logic - - Args: - cls (Type): The class to be registered. - - Returns: - Type: The same class, after registration. - """ - JSONSerializable._register_class_as_deserializable(cls) - return cls - - -class JSONSerializable: - """ - A class to represent a JSON serializable object. - - This class provides methods to serialize and deserialize objects, - as well as to save serialized objects to a file and load them back. - """ - - _deserializable_classes = set() # Contains classes that are whitelisted for deserialization. - - def serialize(self) -> str: - """ - Serialize the object to a JSON-formatted string. - - Returns: - str: A JSON string representation of the object. - """ - try: - return json.dumps(self, default=self._auto_encoder, ensure_ascii=False) - except Exception as e: - logger.error(f"Serialization error: {e}") - return "{}" - - @classmethod - def deserialize(cls, json_str: str) -> Any: - """ - Deserialize a JSON-formatted string to an object. - If it fails, a default class is returned instead. - Note: This *returns* an instance, it's not automatically loaded on the calling class. - - Example: - app = App.deserialize(json_str) - - Args: - json_str (str): A JSON string representation of an object. - - Returns: - Object: The deserialized object. - """ - try: - return json.loads(json_str, object_hook=cls._auto_decoder) - except Exception as e: - logger.error(f"Deserialization error: {e}") - # Return a default instance in case of failure - return cls() - - @staticmethod - def _auto_encoder(obj: Any) -> Union[dict[str, Any], None]: - """ - Automatically encode an object for JSON serialization. - - Args: - obj (Object): The object to be encoded. - - Returns: - dict: A dictionary representation of the object. - """ - if hasattr(obj, "__dict__"): - dct = {} - for key, value in obj.__dict__.items(): - try: - # Recursive: If the value is an instance of a subclass of JSONSerializable, - # serialize it using the JSONSerializable serialize method. - if isinstance(value, JSONSerializable): - serialized_value = value.serialize() - # The value is stored as a serialized string. - dct[key] = json.loads(serialized_value) - # Custom rules (subclass is not json serializable by default) - elif isinstance(value, Template): - dct[key] = {"__type__": "Template", "data": value.template} - # Future custom types we can follow a similar pattern - # elif isinstance(value, SomeOtherType): - # dct[key] = { - # "__type__": "SomeOtherType", - # "data": value.some_method() - # } - # NOTE: Keep in mind that this logic needs to be applied to the decoder too. - else: - json.dumps(value) # Try to serialize the value. - dct[key] = value - except TypeError: - pass # If it fails, simply pass to skip this key-value pair of the dictionary. - - dct["__class__"] = obj.__class__.__name__ - return dct - raise TypeError(f"Object of type {type(obj)} is not JSON serializable") - - @classmethod - def _auto_decoder(cls, dct: dict[str, Any]) -> Any: - """ - Automatically decode a dictionary to an object during JSON deserialization. - - Args: - dct (dict): The dictionary representation of an object. - - Returns: - Object: The decoded object or the original dictionary if decoding is not possible. - """ - class_name = dct.pop("__class__", None) - if class_name: - if not hasattr(cls, "_deserializable_classes"): # Additional safety check - raise AttributeError(f"`{class_name}` has no registry of allowed deserializations.") - if class_name not in {cl.__name__ for cl in cls._deserializable_classes}: - raise KeyError(f"Deserialization of class `{class_name}` is not allowed.") - target_class = next((cl for cl in cls._deserializable_classes if cl.__name__ == class_name), None) - if target_class: - obj = target_class.__new__(target_class) - for key, value in dct.items(): - if isinstance(value, dict) and "__type__" in value: - if value["__type__"] == "Template": - value = Template(value["data"]) - # For future custom types we can follow a similar pattern - # elif value["__type__"] == "SomeOtherType": - # value = SomeOtherType.some_constructor(value["data"]) - default_value = getattr(target_class, key, None) - setattr(obj, key, value or default_value) - return obj - return dct - - def save_to_file(self, filename: str) -> None: - """ - Save the serialized object to a file. - - Args: - filename (str): The path to the file where the object should be saved. - """ - with open(filename, "w", encoding="utf-8") as f: - f.write(self.serialize()) - - @classmethod - def load_from_file(cls, filename: str) -> Any: - """ - Load and deserialize an object from a file. - - Args: - filename (str): The path to the file from which the object should be loaded. - - Returns: - Object: The deserialized object. - """ - with open(filename, "r", encoding="utf-8") as f: - json_str = f.read() - return cls.deserialize(json_str) - - @classmethod - def _register_class_as_deserializable(cls, target_class: Type[T]) -> None: - """ - Register a class as deserializable. This is a classmethod and globally shared. - - This method adds the target class to the set of classes that - can be deserialized. This is a security measure to ensure only - whitelisted classes are deserialized. - - Args: - target_class (Type): The class to be registered. - """ - cls._deserializable_classes.add(target_class) diff --git a/embedchain/embedchain/llm/__init__.py b/embedchain/embedchain/llm/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/llm/anthropic.py b/embedchain/embedchain/llm/anthropic.py deleted file mode 100644 index b5a90a6d5..000000000 --- a/embedchain/embedchain/llm/anthropic.py +++ /dev/null @@ -1,59 +0,0 @@ -import logging -import os -from typing import Any, Optional - -try: - from langchain_anthropic import ChatAnthropic -except ImportError: - raise ImportError("Please install the langchain-anthropic package by running `pip install langchain-anthropic`.") - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - -logger = logging.getLogger(__name__) - - -@register_deserializable -class AnthropicLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if not self.config.api_key and "ANTHROPIC_API_KEY" not in os.environ: - raise ValueError("Please set the ANTHROPIC_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "anthropic/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["input_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["output_tokens"] - response_token_info = { - "prompt_tokens": token_info["input_tokens"], - "completion_tokens": token_info["output_tokens"], - "total_tokens": token_info["input_tokens"] + token_info["output_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - api_key = config.api_key or os.getenv("ANTHROPIC_API_KEY") - chat = ChatAnthropic(anthropic_api_key=api_key, temperature=config.temperature, model_name=config.model) - - if config.max_tokens and config.max_tokens != 1000: - logger.warning("Config option `max_tokens` is not supported by this model.") - - messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt) - - chat_response = chat.invoke(messages) - if config.token_usage: - return chat_response.content, chat_response.response_metadata["token_usage"] - return chat_response.content diff --git a/embedchain/embedchain/llm/aws_bedrock.py b/embedchain/embedchain/llm/aws_bedrock.py deleted file mode 100644 index 7f916268b..000000000 --- a/embedchain/embedchain/llm/aws_bedrock.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -from typing import Optional - -try: - from langchain_aws import BedrockLLM -except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for AWSBedrock are not installed." "Please install with `pip install langchain_aws`" - ) from None - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class AWSBedrockLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config) - - def get_llm_model_answer(self, prompt) -> str: - response = self._get_answer(prompt, self.config) - return response - - def _get_answer(self, prompt: str, config: BaseLlmConfig) -> str: - try: - import boto3 - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for AWSBedrock are not installed." - "Please install with `pip install boto3==1.34.20`." - ) from None - - self.boto_client = boto3.client( - "bedrock-runtime", os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1")) - ) - - kwargs = { - "model_id": config.model or "amazon.titan-text-express-v1", - "client": self.boto_client, - "model_kwargs": config.model_kwargs - or { - "temperature": config.temperature, - }, - } - - if config.stream: - from langchain.callbacks.streaming_stdout import ( - StreamingStdOutCallbackHandler, - ) - - kwargs["streaming"] = True - kwargs["callbacks"] = [StreamingStdOutCallbackHandler()] - - llm = BedrockLLM(**kwargs) - - return llm.invoke(prompt) diff --git a/embedchain/embedchain/llm/azure_openai.py b/embedchain/embedchain/llm/azure_openai.py deleted file mode 100644 index c219270ac..000000000 --- a/embedchain/embedchain/llm/azure_openai.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging -from typing import Optional - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - -logger = logging.getLogger(__name__) - - -@register_deserializable -class AzureOpenAILlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - - def get_llm_model_answer(self, prompt): - return self._get_answer(prompt=prompt, config=self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - from langchain_openai import AzureChatOpenAI - - if not config.deployment_name: - raise ValueError("Deployment name must be provided for Azure OpenAI") - - chat = AzureChatOpenAI( - deployment_name=config.deployment_name, - openai_api_version=str(config.api_version) if config.api_version else "2024-02-01", - model_name=config.model or "gpt-4o-mini", - temperature=config.temperature, - max_tokens=config.max_tokens, - streaming=config.stream, - http_client=config.http_client, - http_async_client=config.http_async_client, - ) - - if config.top_p and config.top_p != 1: - logger.warning("Config option `top_p` is not supported by this model.") - - messages = BaseLlm._get_messages(prompt, system_prompt=config.system_prompt) - - return chat.invoke(messages).content diff --git a/embedchain/embedchain/llm/base.py b/embedchain/embedchain/llm/base.py deleted file mode 100644 index ace4bb79b..000000000 --- a/embedchain/embedchain/llm/base.py +++ /dev/null @@ -1,350 +0,0 @@ -import logging -import os -from collections.abc import Generator -from typing import Any, Optional - -from langchain.schema import BaseMessage as LCBaseMessage - -from embedchain.config import BaseLlmConfig -from embedchain.config.llm.base import ( - DEFAULT_PROMPT, - DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE, - DEFAULT_PROMPT_WITH_MEM0_MEMORY_TEMPLATE, - DOCS_SITE_PROMPT_TEMPLATE, -) -from embedchain.constants import SQLITE_PATH -from embedchain.core.db.database import init_db, setup_engine -from embedchain.helpers.json_serializable import JSONSerializable -from embedchain.memory.base import ChatHistory -from embedchain.memory.message import ChatMessage - -logger = logging.getLogger(__name__) - - -class BaseLlm(JSONSerializable): - def __init__(self, config: Optional[BaseLlmConfig] = None): - """Initialize a base LLM class - - :param config: LLM configuration option class, defaults to None - :type config: Optional[BaseLlmConfig], optional - """ - if config is None: - self.config = BaseLlmConfig() - else: - self.config = config - - # Initialize the metadata db for the app here since llmfactory needs it for initialization of - # the llm memory - setup_engine(database_uri=os.environ.get("EMBEDCHAIN_DB_URI", f"sqlite:///{SQLITE_PATH}")) - init_db() - - self.memory = ChatHistory() - self.is_docs_site_instance = False - self.history: Any = None - - def get_llm_model_answer(self): - """ - Usually implemented by child class - """ - raise NotImplementedError - - def set_history(self, history: Any): - """ - Provide your own history. - Especially interesting for the query method, which does not internally manage conversation history. - - :param history: History to set - :type history: Any - """ - self.history = history - - def update_history(self, app_id: str, session_id: str = "default"): - """Update class history attribute with history in memory (for chat method)""" - chat_history = self.memory.get(app_id=app_id, session_id=session_id, num_rounds=10) - self.set_history([str(history) for history in chat_history]) - - def add_history( - self, - app_id: str, - question: str, - answer: str, - metadata: Optional[dict[str, Any]] = None, - session_id: str = "default", - ): - chat_message = ChatMessage() - chat_message.add_user_message(question, metadata=metadata) - chat_message.add_ai_message(answer, metadata=metadata) - self.memory.add(app_id=app_id, chat_message=chat_message, session_id=session_id) - self.update_history(app_id=app_id, session_id=session_id) - - def _format_history(self) -> str: - """Format history to be used in prompt - - :return: Formatted history - :rtype: str - """ - return "\n".join(self.history) - - def _format_memories(self, memories: list[dict]) -> str: - """Format memories to be used in prompt - - :param memories: Memories to format - :type memories: list[dict] - :return: Formatted memories - :rtype: str - """ - return "\n".join([memory["text"] for memory in memories]) - - def generate_prompt(self, input_query: str, contexts: list[str], **kwargs: dict[str, Any]) -> str: - """ - Generates a prompt based on the given query and context, ready to be - passed to an LLM - - :param input_query: The query to use. - :type input_query: str - :param contexts: List of similar documents to the query used as context. - :type contexts: list[str] - :return: The prompt - :rtype: str - """ - context_string = " | ".join(contexts) - web_search_result = kwargs.get("web_search_result", "") - memories = kwargs.get("memories", None) - if web_search_result: - context_string = self._append_search_and_context(context_string, web_search_result) - - prompt_contains_history = self.config._validate_prompt_history(self.config.prompt) - if prompt_contains_history: - prompt = self.config.prompt.substitute( - context=context_string, query=input_query, history=self._format_history() or "No history" - ) - elif self.history and not prompt_contains_history: - # History is present, but not included in the prompt. - # check if it's the default prompt without history - if ( - not self.config._validate_prompt_history(self.config.prompt) - and self.config.prompt.template == DEFAULT_PROMPT - ): - if memories: - # swap in the template with Mem0 memory template - prompt = DEFAULT_PROMPT_WITH_MEM0_MEMORY_TEMPLATE.substitute( - context=context_string, - query=input_query, - history=self._format_history(), - memories=self._format_memories(memories), - ) - else: - # swap in the template with history - prompt = DEFAULT_PROMPT_WITH_HISTORY_TEMPLATE.substitute( - context=context_string, query=input_query, history=self._format_history() - ) - else: - # If we can't swap in the default, we still proceed but tell users that the history is ignored. - logger.warning( - "Your bot contains a history, but prompt does not include `$history` key. History is ignored." - ) - prompt = self.config.prompt.substitute(context=context_string, query=input_query) - else: - # basic use case, no history. - prompt = self.config.prompt.substitute(context=context_string, query=input_query) - return prompt - - @staticmethod - def _append_search_and_context(context: str, web_search_result: str) -> str: - """Append web search context to existing context - - :param context: Existing context - :type context: str - :param web_search_result: Web search result - :type web_search_result: str - :return: Concatenated web search result - :rtype: str - """ - return f"{context}\nWeb Search Result: {web_search_result}" - - def get_answer_from_llm(self, prompt: str): - """ - Gets an answer based on the given query and context by passing it - to an LLM. - - :param prompt: Gets an answer based on the given query and context by passing it to an LLM. - :type prompt: str - :return: The answer. - :rtype: _type_ - """ - return self.get_llm_model_answer(prompt) - - @staticmethod - def access_search_and_get_results(input_query: str): - """ - Search the internet for additional context - - :param input_query: search query - :type input_query: str - :return: Search results - :rtype: Unknown - """ - try: - from langchain.tools import DuckDuckGoSearchRun - except ImportError: - raise ImportError( - "Searching requires extra dependencies. Install with `pip install duckduckgo-search==6.1.5`" - ) from None - search = DuckDuckGoSearchRun() - logger.info(f"Access search to get answers for {input_query}") - return search.run(input_query) - - @staticmethod - def _stream_response(answer: Any, token_info: Optional[dict[str, Any]] = None) -> Generator[Any, Any, None]: - """Generator to be used as streaming response - - :param answer: Answer chunk from llm - :type answer: Any - :yield: Answer chunk from llm - :rtype: Generator[Any, Any, None] - """ - streamed_answer = "" - for chunk in answer: - streamed_answer = streamed_answer + chunk - yield chunk - logger.info(f"Answer: {streamed_answer}") - if token_info: - logger.info(f"Token Info: {token_info}") - - def query(self, input_query: str, contexts: list[str], config: BaseLlmConfig = None, dry_run=False, memories=None): - """ - Queries the vector database based on the given input query. - Gets relevant doc based on the query and then passes it to an - LLM as context to get the answer. - - :param input_query: The query to use. - :type input_query: str - :param contexts: Embeddings retrieved from the database to be used as context. - :type contexts: list[str] - :param config: The `BaseLlmConfig` instance to use as configuration options. This is used for one method call. - To persistently use a config, declare it during app init., defaults to None - :type config: Optional[BaseLlmConfig], optional - :param dry_run: A dry run does everything except send the resulting prompt to - the LLM. The purpose is to test the prompt, not the response., defaults to False - :type dry_run: bool, optional - :return: The answer to the query or the dry run result - :rtype: str - """ - try: - if config: - # A config instance passed to this method will only be applied temporarily, for one call. - # So we will save the previous config and restore it at the end of the execution. - # For this we use the serializer. - prev_config = self.config.serialize() - self.config = config - - if config is not None and config.query_type == "Images": - return contexts - - if self.is_docs_site_instance: - self.config.prompt = DOCS_SITE_PROMPT_TEMPLATE - self.config.number_documents = 5 - k = {} - if self.config.online: - k["web_search_result"] = self.access_search_and_get_results(input_query) - k["memories"] = memories - prompt = self.generate_prompt(input_query, contexts, **k) - logger.info(f"Prompt: {prompt}") - if dry_run: - return prompt - - if self.config.token_usage: - answer, token_info = self.get_answer_from_llm(prompt) - else: - answer = self.get_answer_from_llm(prompt) - if isinstance(answer, str): - logger.info(f"Answer: {answer}") - if self.config.token_usage: - return answer, token_info - return answer - else: - if self.config.token_usage: - return self._stream_response(answer, token_info) - return self._stream_response(answer) - finally: - if config: - # Restore previous config - self.config: BaseLlmConfig = BaseLlmConfig.deserialize(prev_config) - - def chat( - self, input_query: str, contexts: list[str], config: BaseLlmConfig = None, dry_run=False, session_id: str = None - ): - """ - Queries the vector database on the given input query. - Gets relevant doc based on the query and then passes it to an - LLM as context to get the answer. - - Maintains the whole conversation in memory. - - :param input_query: The query to use. - :type input_query: str - :param contexts: Embeddings retrieved from the database to be used as context. - :type contexts: list[str] - :param config: The `BaseLlmConfig` instance to use as configuration options. This is used for one method call. - To persistently use a config, declare it during app init., defaults to None - :type config: Optional[BaseLlmConfig], optional - :param dry_run: A dry run does everything except send the resulting prompt to - the LLM. The purpose is to test the prompt, not the response., defaults to False - :type dry_run: bool, optional - :param session_id: Session ID to use for the conversation, defaults to None - :type session_id: str, optional - :return: The answer to the query or the dry run result - :rtype: str - """ - try: - if config: - # A config instance passed to this method will only be applied temporarily, for one call. - # So we will save the previous config and restore it at the end of the execution. - # For this we use the serializer. - prev_config = self.config.serialize() - self.config = config - - if self.is_docs_site_instance: - self.config.prompt = DOCS_SITE_PROMPT_TEMPLATE - self.config.number_documents = 5 - k = {} - if self.config.online: - k["web_search_result"] = self.access_search_and_get_results(input_query) - - prompt = self.generate_prompt(input_query, contexts, **k) - logger.info(f"Prompt: {prompt}") - - if dry_run: - return prompt - - answer, token_info = self.get_answer_from_llm(prompt) - if isinstance(answer, str): - logger.info(f"Answer: {answer}") - return answer, token_info - else: - # this is a streamed response and needs to be handled differently. - return self._stream_response(answer, token_info) - finally: - if config: - # Restore previous config - self.config: BaseLlmConfig = BaseLlmConfig.deserialize(prev_config) - - @staticmethod - def _get_messages(prompt: str, system_prompt: Optional[str] = None) -> list[LCBaseMessage]: - """ - Construct a list of langchain messages - - :param prompt: User prompt - :type prompt: str - :param system_prompt: System prompt, defaults to None - :type system_prompt: Optional[str], optional - :return: List of messages - :rtype: list[BaseMessage] - """ - from langchain.schema import HumanMessage, SystemMessage - - messages = [] - if system_prompt: - messages.append(SystemMessage(content=system_prompt)) - messages.append(HumanMessage(content=prompt)) - return messages diff --git a/embedchain/embedchain/llm/clarifai.py b/embedchain/embedchain/llm/clarifai.py deleted file mode 100644 index 6d87d1b15..000000000 --- a/embedchain/embedchain/llm/clarifai.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging -import os -from typing import Optional - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class ClarifaiLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if not self.config.api_key and "CLARIFAI_PAT" not in os.environ: - raise ValueError("Please set the CLARIFAI_PAT environment variable.") - - def get_llm_model_answer(self, prompt): - return self._get_answer(prompt=prompt, config=self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - try: - from clarifai.client.model import Model - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Clarifai are not installed." - "Please install with `pip install clarifai==10.0.1`" - ) from None - - model_name = config.model - logging.info(f"Using clarifai LLM model: {model_name}") - api_key = config.api_key or os.getenv("CLARIFAI_PAT") - model = Model(url=model_name, pat=api_key) - params = config.model_kwargs - - try: - (params := {}) if config.model_kwargs is None else config.model_kwargs - predict_response = model.predict_by_bytes( - bytes(prompt, "utf-8"), - input_type="text", - inference_params=params, - ) - text = predict_response.outputs[0].data.text.raw - return text - - except Exception as e: - logging.error(f"Predict failed, exception: {e}") diff --git a/embedchain/embedchain/llm/cohere.py b/embedchain/embedchain/llm/cohere.py deleted file mode 100644 index 0a9614b9a..000000000 --- a/embedchain/embedchain/llm/cohere.py +++ /dev/null @@ -1,66 +0,0 @@ -import importlib -import os -from typing import Any, Optional - -from langchain_cohere import ChatCohere - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class CohereLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - try: - importlib.import_module("cohere") - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Cohere are not installed." - "Please install with `pip install langchain_cohere==1.16.0`" - ) from None - - super().__init__(config=config) - if not self.config.api_key and "COHERE_API_KEY" not in os.environ: - raise ValueError("Please set the COHERE_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.system_prompt: - raise ValueError("CohereLlm does not support `system_prompt`") - - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "cohere/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["input_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["output_tokens"] - response_token_info = { - "prompt_tokens": token_info["input_tokens"], - "completion_tokens": token_info["output_tokens"], - "total_tokens": token_info["input_tokens"] + token_info["output_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - api_key = config.api_key or os.environ["COHERE_API_KEY"] - kwargs = { - "model_name": config.model or "command-r", - "temperature": config.temperature, - "max_tokens": config.max_tokens, - "together_api_key": api_key, - } - - chat = ChatCohere(**kwargs) - chat_response = chat.invoke(prompt) - if config.token_usage: - return chat_response.content, chat_response.response_metadata["token_count"] - return chat_response.content diff --git a/embedchain/embedchain/llm/google.py b/embedchain/embedchain/llm/google.py deleted file mode 100644 index c0002fa99..000000000 --- a/embedchain/embedchain/llm/google.py +++ /dev/null @@ -1,62 +0,0 @@ -import logging -import os -from collections.abc import Generator -from typing import Any, Optional, Union - -try: - import google.generativeai as genai -except ImportError: - raise ImportError("GoogleLlm requires extra dependencies. Install with `pip install google-generativeai`") from None - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - -logger = logging.getLogger(__name__) - - -@register_deserializable -class GoogleLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config) - if not self.config.api_key and "GOOGLE_API_KEY" not in os.environ: - raise ValueError("Please set the GOOGLE_API_KEY environment variable or pass it in the config.") - - api_key = self.config.api_key or os.getenv("GOOGLE_API_KEY") - genai.configure(api_key=api_key) - - def get_llm_model_answer(self, prompt): - if self.config.system_prompt: - raise ValueError("GoogleLlm does not support `system_prompt`") - response = self._get_answer(prompt) - return response - - def _get_answer(self, prompt: str) -> Union[str, Generator[Any, Any, None]]: - model_name = self.config.model or "gemini-pro" - logger.info(f"Using Google LLM model: {model_name}") - model = genai.GenerativeModel(model_name=model_name) - - generation_config_params = { - "candidate_count": 1, - "max_output_tokens": self.config.max_tokens, - "temperature": self.config.temperature or 0.5, - } - - if 0.0 <= self.config.top_p <= 1.0: - generation_config_params["top_p"] = self.config.top_p - else: - raise ValueError("`top_p` must be > 0.0 and < 1.0") - - generation_config = genai.types.GenerationConfig(**generation_config_params) - - response = model.generate_content( - prompt, - generation_config=generation_config, - stream=self.config.stream, - ) - if self.config.stream: - # TODO: Implement streaming - response.resolve() - return response.text - else: - return response.text diff --git a/embedchain/embedchain/llm/gpt4all.py b/embedchain/embedchain/llm/gpt4all.py deleted file mode 100644 index 76062b08b..000000000 --- a/embedchain/embedchain/llm/gpt4all.py +++ /dev/null @@ -1,67 +0,0 @@ -import os -from collections.abc import Iterable -from pathlib import Path -from typing import Optional, Union - -from langchain.callbacks.stdout import StdOutCallbackHandler -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class GPT4ALLLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if self.config.model is None: - self.config.model = "orca-mini-3b-gguf2-q4_0.gguf" - self.instance = GPT4ALLLlm._get_instance(self.config.model) - self.instance.streaming = self.config.stream - - def get_llm_model_answer(self, prompt): - return self._get_answer(prompt=prompt, config=self.config) - - @staticmethod - def _get_instance(model): - try: - from langchain_community.llms.gpt4all import GPT4All as LangchainGPT4All - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The GPT4All python package is not installed. Please install it with `pip install --upgrade embedchain[opensource]`" # noqa E501 - ) from None - - model_path = Path(model).expanduser() - if os.path.isabs(model_path): - if os.path.exists(model_path): - return LangchainGPT4All(model=str(model_path)) - else: - raise ValueError(f"Model does not exist at {model_path=}") - else: - return LangchainGPT4All(model=model, allow_download=True) - - def _get_answer(self, prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]: - if config.model and config.model != self.config.model: - raise RuntimeError( - "GPT4ALLLlm does not support switching models at runtime. Please create a new app instance." - ) - - messages = [] - if config.system_prompt: - messages.append(config.system_prompt) - messages.append(prompt) - kwargs = { - "temp": config.temperature, - "max_tokens": config.max_tokens, - } - if config.top_p: - kwargs["top_p"] = config.top_p - - callbacks = [StreamingStdOutCallbackHandler()] if config.stream else [StdOutCallbackHandler()] - - response = self.instance.generate(prompts=messages, callbacks=callbacks, **kwargs) - answer = "" - for generations in response.generations: - answer += " ".join(map(lambda generation: generation.text, generations)) - return answer diff --git a/embedchain/embedchain/llm/groq.py b/embedchain/embedchain/llm/groq.py deleted file mode 100644 index 3f18d3da9..000000000 --- a/embedchain/embedchain/llm/groq.py +++ /dev/null @@ -1,67 +0,0 @@ -import os -from typing import Any, Optional - -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain.schema import HumanMessage, SystemMessage - -try: - from langchain_groq import ChatGroq -except ImportError: - raise ImportError("Groq requires extra dependencies. Install with `pip install langchain-groq`") from None - - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class GroqLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if not self.config.api_key and "GROQ_API_KEY" not in os.environ: - raise ValueError("Please set the GROQ_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "groq/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["prompt_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["completion_tokens"] - response_token_info = { - "prompt_tokens": token_info["prompt_tokens"], - "completion_tokens": token_info["completion_tokens"], - "total_tokens": token_info["prompt_tokens"] + token_info["completion_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - def _get_answer(self, prompt: str, config: BaseLlmConfig) -> str: - messages = [] - if config.system_prompt: - messages.append(SystemMessage(content=config.system_prompt)) - messages.append(HumanMessage(content=prompt)) - api_key = config.api_key or os.environ["GROQ_API_KEY"] - kwargs = { - "model_name": config.model or "mixtral-8x7b-32768", - "temperature": config.temperature, - "groq_api_key": api_key, - } - if config.stream: - callbacks = config.callbacks if config.callbacks else [StreamingStdOutCallbackHandler()] - chat = ChatGroq(**kwargs, streaming=config.stream, callbacks=callbacks, api_key=api_key) - else: - chat = ChatGroq(**kwargs) - - chat_response = chat.invoke(prompt) - if self.config.token_usage: - return chat_response.content, chat_response.response_metadata["token_usage"] - return chat_response.content diff --git a/embedchain/embedchain/llm/huggingface.py b/embedchain/embedchain/llm/huggingface.py deleted file mode 100644 index 28767b07b..000000000 --- a/embedchain/embedchain/llm/huggingface.py +++ /dev/null @@ -1,99 +0,0 @@ -import importlib -import logging -import os -from typing import Optional - -from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint -from langchain_community.llms.huggingface_hub import HuggingFaceHub -from langchain_community.llms.huggingface_pipeline import HuggingFacePipeline - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - -logger = logging.getLogger(__name__) - - -@register_deserializable -class HuggingFaceLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - try: - importlib.import_module("huggingface_hub") - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for HuggingFaceHub are not installed." - "Please install with `pip install huggingface-hub==0.23.0`" - ) from None - - super().__init__(config=config) - if not self.config.api_key and "HUGGINGFACE_ACCESS_TOKEN" not in os.environ: - raise ValueError("Please set the HUGGINGFACE_ACCESS_TOKEN environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt): - if self.config.system_prompt: - raise ValueError("HuggingFaceLlm does not support `system_prompt`") - return HuggingFaceLlm._get_answer(prompt=prompt, config=self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - # If the user wants to run the model locally, they can do so by setting the `local` flag to True - if config.model and config.local: - return HuggingFaceLlm._from_pipeline(prompt=prompt, config=config) - elif config.model: - return HuggingFaceLlm._from_model(prompt=prompt, config=config) - elif config.endpoint: - return HuggingFaceLlm._from_endpoint(prompt=prompt, config=config) - else: - raise ValueError("Either `model` or `endpoint` must be set in config") - - @staticmethod - def _from_model(prompt: str, config: BaseLlmConfig) -> str: - model_kwargs = { - "temperature": config.temperature or 0.1, - "max_new_tokens": config.max_tokens, - } - - if 0.0 < config.top_p < 1.0: - model_kwargs["top_p"] = config.top_p - else: - raise ValueError("`top_p` must be > 0.0 and < 1.0") - - model = config.model - api_key = config.api_key or os.getenv("HUGGINGFACE_ACCESS_TOKEN") - logger.info(f"Using HuggingFaceHub with model {model}") - llm = HuggingFaceHub( - huggingfacehub_api_token=api_key, - repo_id=model, - model_kwargs=model_kwargs, - ) - return llm.invoke(prompt) - - @staticmethod - def _from_endpoint(prompt: str, config: BaseLlmConfig) -> str: - api_key = config.api_key or os.getenv("HUGGINGFACE_ACCESS_TOKEN") - llm = HuggingFaceEndpoint( - huggingfacehub_api_token=api_key, - endpoint_url=config.endpoint, - task="text-generation", - model_kwargs=config.model_kwargs, - ) - return llm.invoke(prompt) - - @staticmethod - def _from_pipeline(prompt: str, config: BaseLlmConfig) -> str: - model_kwargs = { - "temperature": config.temperature or 0.1, - "max_new_tokens": config.max_tokens, - } - - if 0.0 < config.top_p < 1.0: - model_kwargs["top_p"] = config.top_p - else: - raise ValueError("`top_p` must be > 0.0 and < 1.0") - - llm = HuggingFacePipeline.from_model_id( - model_id=config.model, - task="text-generation", - pipeline_kwargs=model_kwargs, - ) - return llm.invoke(prompt) diff --git a/embedchain/embedchain/llm/jina.py b/embedchain/embedchain/llm/jina.py deleted file mode 100644 index ac3a0e76f..000000000 --- a/embedchain/embedchain/llm/jina.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -from typing import Optional - -from langchain.schema import HumanMessage, SystemMessage -from langchain_community.chat_models import JinaChat - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class JinaLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if not self.config.api_key and "JINACHAT_API_KEY" not in os.environ: - raise ValueError("Please set the JINACHAT_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt): - response = JinaLlm._get_answer(prompt, self.config) - return response - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - messages = [] - if config.system_prompt: - messages.append(SystemMessage(content=config.system_prompt)) - messages.append(HumanMessage(content=prompt)) - kwargs = { - "temperature": config.temperature, - "max_tokens": config.max_tokens, - "jinachat_api_key": config.api_key or os.environ["JINACHAT_API_KEY"], - "model_kwargs": {}, - } - if config.top_p: - kwargs["model_kwargs"]["top_p"] = config.top_p - if config.stream: - from langchain.callbacks.streaming_stdout import ( - StreamingStdOutCallbackHandler, - ) - - chat = JinaChat(**kwargs, streaming=config.stream, callbacks=[StreamingStdOutCallbackHandler()]) - else: - chat = JinaChat(**kwargs) - return chat(messages).content diff --git a/embedchain/embedchain/llm/llama2.py b/embedchain/embedchain/llm/llama2.py deleted file mode 100644 index 8a82f3f75..000000000 --- a/embedchain/embedchain/llm/llama2.py +++ /dev/null @@ -1,53 +0,0 @@ -import importlib -import os -from typing import Optional - -from langchain_community.llms.replicate import Replicate - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class Llama2Llm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - try: - importlib.import_module("replicate") - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Llama2 are not installed." - 'Please install with `pip install --upgrade "embedchain[llama2]"`' - ) from None - - # Set default config values specific to this llm - if not config: - config = BaseLlmConfig() - # Add variables to this block that have a default value in the parent class - config.max_tokens = 500 - config.temperature = 0.75 - # Add variables that are `none` by default to this block. - if not config.model: - config.model = ( - "a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5" - ) - - super().__init__(config=config) - if not self.config.api_key and "REPLICATE_API_TOKEN" not in os.environ: - raise ValueError("Please set the REPLICATE_API_TOKEN environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt): - # TODO: Move the model and other inputs into config - if self.config.system_prompt: - raise ValueError("Llama2 does not support `system_prompt`") - api_key = self.config.api_key or os.getenv("REPLICATE_API_TOKEN") - llm = Replicate( - model=self.config.model, - replicate_api_token=api_key, - input={ - "temperature": self.config.temperature, - "max_length": self.config.max_tokens, - "top_p": self.config.top_p, - }, - ) - return llm.invoke(prompt) diff --git a/embedchain/embedchain/llm/mistralai.py b/embedchain/embedchain/llm/mistralai.py deleted file mode 100644 index 92af3be17..000000000 --- a/embedchain/embedchain/llm/mistralai.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -from typing import Any, Optional - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class MistralAILlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config) - if not self.config.api_key and "MISTRAL_API_KEY" not in os.environ: - raise ValueError("Please set the MISTRAL_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "mistralai/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["prompt_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["completion_tokens"] - response_token_info = { - "prompt_tokens": token_info["prompt_tokens"], - "completion_tokens": token_info["completion_tokens"], - "total_tokens": token_info["prompt_tokens"] + token_info["completion_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig): - try: - from langchain_core.messages import HumanMessage, SystemMessage - from langchain_mistralai.chat_models import ChatMistralAI - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for MistralAI are not installed." - 'Please install with `pip install --upgrade "embedchain[mistralai]"`' - ) from None - - api_key = config.api_key or os.getenv("MISTRAL_API_KEY") - client = ChatMistralAI(mistral_api_key=api_key) - messages = [] - if config.system_prompt: - messages.append(SystemMessage(content=config.system_prompt)) - messages.append(HumanMessage(content=prompt)) - kwargs = { - "model": config.model or "mistral-tiny", - "temperature": config.temperature, - "max_tokens": config.max_tokens, - "top_p": config.top_p, - } - - # TODO: Add support for streaming - if config.stream: - answer = "" - for chunk in client.stream(**kwargs, input=messages): - answer += chunk.content - return answer - else: - chat_response = client.invoke(**kwargs, input=messages) - if config.token_usage: - return chat_response.content, chat_response.response_metadata["token_usage"] - return chat_response.content diff --git a/embedchain/embedchain/llm/nvidia.py b/embedchain/embedchain/llm/nvidia.py deleted file mode 100644 index 71c045b6a..000000000 --- a/embedchain/embedchain/llm/nvidia.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -from collections.abc import Iterable -from typing import Any, Optional, Union - -from langchain.callbacks.manager import CallbackManager -from langchain.callbacks.stdout import StdOutCallbackHandler -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler - -try: - from langchain_nvidia_ai_endpoints import ChatNVIDIA -except ImportError: - raise ImportError( - "NVIDIA AI endpoints requires extra dependencies. Install with `pip install langchain-nvidia-ai-endpoints`" - ) from None - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class NvidiaLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if not self.config.api_key and "NVIDIA_API_KEY" not in os.environ: - raise ValueError("Please set the NVIDIA_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "nvidia/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["input_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["output_tokens"] - response_token_info = { - "prompt_tokens": token_info["input_tokens"], - "completion_tokens": token_info["output_tokens"], - "total_tokens": token_info["input_tokens"] + token_info["output_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]: - callback_manager = [StreamingStdOutCallbackHandler()] if config.stream else [StdOutCallbackHandler()] - model_kwargs = config.model_kwargs or {} - labels = model_kwargs.get("labels", None) - params = {"model": config.model, "nvidia_api_key": config.api_key or os.getenv("NVIDIA_API_KEY")} - if config.system_prompt: - params["system_prompt"] = config.system_prompt - if config.temperature: - params["temperature"] = config.temperature - if config.top_p: - params["top_p"] = config.top_p - if labels: - params["labels"] = labels - llm = ChatNVIDIA(**params, callback_manager=CallbackManager(callback_manager)) - chat_response = llm.invoke(prompt) if labels is None else llm.invoke(prompt, labels=labels) - if config.token_usage: - return chat_response.content, chat_response.response_metadata["token_usage"] - return chat_response.content diff --git a/embedchain/embedchain/llm/ollama.py b/embedchain/embedchain/llm/ollama.py deleted file mode 100644 index e34ff38e1..000000000 --- a/embedchain/embedchain/llm/ollama.py +++ /dev/null @@ -1,54 +0,0 @@ -import logging -from collections.abc import Iterable -from typing import Optional, Union - -from langchain.callbacks.manager import CallbackManager -from langchain.callbacks.stdout import StdOutCallbackHandler -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain_community.llms.ollama import Ollama - -try: - from ollama import Client -except ImportError: - raise ImportError("Ollama requires extra dependencies. Install with `pip install ollama`") from None - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - -logger = logging.getLogger(__name__) - - -@register_deserializable -class OllamaLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if self.config.model is None: - self.config.model = "llama2" - - client = Client(host=config.base_url) - local_models = client.list()["models"] - if not any(model.get("name") == self.config.model for model in local_models): - logger.info(f"Pulling {self.config.model} from Ollama!") - client.pull(self.config.model) - - def get_llm_model_answer(self, prompt): - return self._get_answer(prompt=prompt, config=self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]: - if config.stream: - callbacks = config.callbacks if config.callbacks else [StreamingStdOutCallbackHandler()] - else: - callbacks = [StdOutCallbackHandler()] - - llm = Ollama( - model=config.model, - system=config.system_prompt, - temperature=config.temperature, - top_p=config.top_p, - callback_manager=CallbackManager(callbacks), - base_url=config.base_url, - ) - - return llm.invoke(prompt) diff --git a/embedchain/embedchain/llm/openai.py b/embedchain/embedchain/llm/openai.py deleted file mode 100644 index ace146118..000000000 --- a/embedchain/embedchain/llm/openai.py +++ /dev/null @@ -1,120 +0,0 @@ -import json -import os -import warnings -from typing import Any, Callable, Dict, Optional, Type, Union - -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain.schema import BaseMessage, HumanMessage, SystemMessage -from langchain_core.tools import BaseTool -from langchain_openai import ChatOpenAI -from pydantic import BaseModel - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class OpenAILlm(BaseLlm): - def __init__( - self, - config: Optional[BaseLlmConfig] = None, - tools: Optional[Union[Dict[str, Any], Type[BaseModel], Callable[..., Any], BaseTool]] = None, - ): - self.tools = tools - super().__init__(config=config) - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "openai/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["prompt_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["completion_tokens"] - response_token_info = { - "prompt_tokens": token_info["prompt_tokens"], - "completion_tokens": token_info["completion_tokens"], - "total_tokens": token_info["prompt_tokens"] + token_info["completion_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - - return self._get_answer(prompt, self.config) - - def _get_answer(self, prompt: str, config: BaseLlmConfig) -> str: - messages = [] - if config.system_prompt: - messages.append(SystemMessage(content=config.system_prompt)) - messages.append(HumanMessage(content=prompt)) - kwargs = { - "model": config.model or "gpt-4o-mini", - "temperature": config.temperature, - "max_tokens": config.max_tokens, - "model_kwargs": config.model_kwargs or {}, - } - api_key = config.api_key or os.environ["OPENAI_API_KEY"] - base_url = ( - config.base_url - or os.getenv("OPENAI_API_BASE") - or os.getenv("OPENAI_BASE_URL") - or "https://api.openai.com/v1" - ) - if os.environ.get("OPENAI_API_BASE"): - warnings.warn( - "The environment variable 'OPENAI_API_BASE' is deprecated and will be removed in the 0.1.140. " - "Please use 'OPENAI_BASE_URL' instead.", - DeprecationWarning - ) - - if config.top_p: - kwargs["top_p"] = config.top_p - if config.default_headers: - kwargs["default_headers"] = config.default_headers - if config.stream: - callbacks = config.callbacks if config.callbacks else [StreamingStdOutCallbackHandler()] - chat = ChatOpenAI( - **kwargs, - streaming=config.stream, - callbacks=callbacks, - api_key=api_key, - base_url=base_url, - http_client=config.http_client, - http_async_client=config.http_async_client, - ) - else: - chat = ChatOpenAI( - **kwargs, - api_key=api_key, - base_url=base_url, - http_client=config.http_client, - http_async_client=config.http_async_client, - ) - if self.tools: - return self._query_function_call(chat, self.tools, messages) - - chat_response = chat.invoke(messages) - if self.config.token_usage: - return chat_response.content, chat_response.response_metadata["token_usage"] - return chat_response.content - - def _query_function_call( - self, - chat: ChatOpenAI, - tools: Optional[Union[Dict[str, Any], Type[BaseModel], Callable[..., Any], BaseTool]], - messages: list[BaseMessage], - ) -> str: - from langchain.output_parsers.openai_tools import JsonOutputToolsParser - from langchain_core.utils.function_calling import convert_to_openai_tool - - openai_tools = [convert_to_openai_tool(tools)] - chat = chat.bind(tools=openai_tools).pipe(JsonOutputToolsParser()) - try: - return json.dumps(chat.invoke(messages)[0]) - except IndexError: - return "Input could not be mapped to the function!" diff --git a/embedchain/embedchain/llm/together.py b/embedchain/embedchain/llm/together.py deleted file mode 100644 index 84443a712..000000000 --- a/embedchain/embedchain/llm/together.py +++ /dev/null @@ -1,71 +0,0 @@ -import importlib -import os -from typing import Any, Optional - -try: - from langchain_together import ChatTogether -except ImportError: - raise ImportError( - "Please install the langchain_together package by running `pip install langchain_together==0.1.3`." - ) - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class TogetherLlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - try: - importlib.import_module("together") - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for Together are not installed." - 'Please install with `pip install --upgrade "embedchain[together]"`' - ) from None - - super().__init__(config=config) - if not self.config.api_key and "TOGETHER_API_KEY" not in os.environ: - raise ValueError("Please set the TOGETHER_API_KEY environment variable or pass it in the config.") - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.system_prompt: - raise ValueError("TogetherLlm does not support `system_prompt`") - - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "together/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["prompt_tokens"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info["completion_tokens"] - response_token_info = { - "prompt_tokens": token_info["prompt_tokens"], - "completion_tokens": token_info["completion_tokens"], - "total_tokens": token_info["prompt_tokens"] + token_info["completion_tokens"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - api_key = config.api_key or os.environ["TOGETHER_API_KEY"] - kwargs = { - "model_name": config.model or "mixtral-8x7b-32768", - "temperature": config.temperature, - "max_tokens": config.max_tokens, - "together_api_key": api_key, - } - - chat = ChatTogether(**kwargs) - chat_response = chat.invoke(prompt) - if config.token_usage: - return chat_response.content, chat_response.response_metadata["token_usage"] - return chat_response.content diff --git a/embedchain/embedchain/llm/vertex_ai.py b/embedchain/embedchain/llm/vertex_ai.py deleted file mode 100644 index 55c31a1ad..000000000 --- a/embedchain/embedchain/llm/vertex_ai.py +++ /dev/null @@ -1,68 +0,0 @@ -import importlib -import logging -from typing import Any, Optional - -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain_google_vertexai import ChatVertexAI - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - -logger = logging.getLogger(__name__) - - -@register_deserializable -class VertexAILlm(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - try: - importlib.import_module("vertexai") - except ModuleNotFoundError: - raise ModuleNotFoundError( - "The required dependencies for VertexAI are not installed." - 'Please install with `pip install --upgrade "embedchain[vertexai]"`' - ) from None - super().__init__(config=config) - - def get_llm_model_answer(self, prompt) -> tuple[str, Optional[dict[str, Any]]]: - if self.config.token_usage: - response, token_info = self._get_answer(prompt, self.config) - model_name = "vertexai/" + self.config.model - if model_name not in self.config.model_pricing_map: - raise ValueError( - f"Model {model_name} not found in `model_prices_and_context_window.json`. \ - You can disable token usage by setting `token_usage` to False." - ) - total_cost = ( - self.config.model_pricing_map[model_name]["input_cost_per_token"] * token_info["prompt_token_count"] - ) + self.config.model_pricing_map[model_name]["output_cost_per_token"] * token_info[ - "candidates_token_count" - ] - response_token_info = { - "prompt_tokens": token_info["prompt_token_count"], - "completion_tokens": token_info["candidates_token_count"], - "total_tokens": token_info["prompt_token_count"] + token_info["candidates_token_count"], - "total_cost": round(total_cost, 10), - "cost_currency": "USD", - } - return response, response_token_info - return self._get_answer(prompt, self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> str: - if config.top_p and config.top_p != 1: - logger.warning("Config option `top_p` is not supported by this model.") - - if config.stream: - callbacks = config.callbacks if config.callbacks else [StreamingStdOutCallbackHandler()] - llm = ChatVertexAI( - temperature=config.temperature, model=config.model, callbacks=callbacks, streaming=config.stream - ) - else: - llm = ChatVertexAI(temperature=config.temperature, model=config.model) - - messages = VertexAILlm._get_messages(prompt) - chat_response = llm.invoke(messages) - if config.token_usage: - return chat_response.content, chat_response.response_metadata["usage_metadata"] - return chat_response.content diff --git a/embedchain/embedchain/llm/vllm.py b/embedchain/embedchain/llm/vllm.py deleted file mode 100644 index 88a8e2ad2..000000000 --- a/embedchain/embedchain/llm/vllm.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Iterable, Optional, Union - -from langchain.callbacks.manager import CallbackManager -from langchain.callbacks.stdout import StdOutCallbackHandler -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain_community.llms import VLLM as BaseVLLM - -from embedchain.config import BaseLlmConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.llm.base import BaseLlm - - -@register_deserializable -class VLLM(BaseLlm): - def __init__(self, config: Optional[BaseLlmConfig] = None): - super().__init__(config=config) - if self.config.model is None: - self.config.model = "mosaicml/mpt-7b" - - def get_llm_model_answer(self, prompt): - return self._get_answer(prompt=prompt, config=self.config) - - @staticmethod - def _get_answer(prompt: str, config: BaseLlmConfig) -> Union[str, Iterable]: - callback_manager = [StreamingStdOutCallbackHandler()] if config.stream else [StdOutCallbackHandler()] - - # Prepare the arguments for BaseVLLM - llm_args = { - "model": config.model, - "temperature": config.temperature, - "top_p": config.top_p, - "callback_manager": CallbackManager(callback_manager), - } - - # Add model_kwargs if they are not None - if config.model_kwargs is not None: - llm_args.update(config.model_kwargs) - - llm = BaseVLLM(**llm_args) - return llm.invoke(prompt) diff --git a/embedchain/embedchain/loaders/__init__.py b/embedchain/embedchain/loaders/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/loaders/audio.py b/embedchain/embedchain/loaders/audio.py deleted file mode 100644 index 6b2b69cf2..000000000 --- a/embedchain/embedchain/loaders/audio.py +++ /dev/null @@ -1,53 +0,0 @@ -import hashlib -import os - -import validators - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - -try: - from deepgram import DeepgramClient, PrerecordedOptions -except ImportError: - raise ImportError( - "Audio file requires extra dependencies. Install with `pip install deepgram-sdk==3.2.7`" - ) from None - - -@register_deserializable -class AudioLoader(BaseLoader): - def __init__(self): - if not os.environ.get("DEEPGRAM_API_KEY"): - raise ValueError("DEEPGRAM_API_KEY is not set") - - DG_KEY = os.environ.get("DEEPGRAM_API_KEY") - self.client = DeepgramClient(DG_KEY) - - def load_data(self, url: str): - """Load data from a audio file or URL.""" - - options = PrerecordedOptions( - model="nova-2", - smart_format=True, - ) - if validators.url(url): - source = {"url": url} - response = self.client.listen.prerecorded.v("1").transcribe_url(source, options) - else: - with open(url, "rb") as audio: - source = {"buffer": audio} - response = self.client.listen.prerecorded.v("1").transcribe_file(source, options) - content = response["results"]["channels"][0]["alternatives"][0]["transcript"] - - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - metadata = {"url": url} - - return { - "doc_id": doc_id, - "data": [ - { - "content": content, - "meta_data": metadata, - } - ], - } diff --git a/embedchain/embedchain/loaders/base_loader.py b/embedchain/embedchain/loaders/base_loader.py deleted file mode 100644 index 9dccfd539..000000000 --- a/embedchain/embedchain/loaders/base_loader.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Any, Optional - -from embedchain.helpers.json_serializable import JSONSerializable - - -class BaseLoader(JSONSerializable): - def __init__(self): - pass - - def load_data(self, url, **kwargs: Optional[dict[str, Any]]): - """ - Implemented by child classes - """ - pass diff --git a/embedchain/embedchain/loaders/beehiiv.py b/embedchain/embedchain/loaders/beehiiv.py deleted file mode 100644 index 12d0fe4a9..000000000 --- a/embedchain/embedchain/loaders/beehiiv.py +++ /dev/null @@ -1,107 +0,0 @@ -import hashlib -import logging -import time -from xml.etree import ElementTree - -import requests - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import is_readable - -logger = logging.getLogger(__name__) - - -@register_deserializable -class BeehiivLoader(BaseLoader): - """ - This loader is used to load data from Beehiiv URLs. - """ - - def load_data(self, url: str): - try: - from bs4 import BeautifulSoup - from bs4.builder import ParserRejectedMarkup - except ImportError: - raise ImportError( - "Beehiiv requires extra dependencies. Install with `pip install beautifulsoup4==4.12.3`" - ) from None - - if not url.endswith("sitemap.xml"): - url = url + "/sitemap.xml" - - output = [] - # we need to set this as a header to avoid 403 - headers = { - "User-Agent": ( - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 " - "Safari/537.36" - ), - } - response = requests.get(url, headers=headers) - try: - response.raise_for_status() - except requests.exceptions.HTTPError as e: - raise ValueError( - f""" - Failed to load {url}: {e}. Please use the root substack URL. For example, https://example.substack.com - """ - ) - - try: - ElementTree.fromstring(response.content) - except ElementTree.ParseError: - raise ValueError( - f""" - Failed to parse {url}. Please use the root substack URL. For example, https://example.substack.com - """ - ) - soup = BeautifulSoup(response.text, "xml") - links = [link.text for link in soup.find_all("loc") if link.parent.name == "url" and "/p/" in link.text] - if len(links) == 0: - links = [link.text for link in soup.find_all("loc") if "/p/" in link.text] - - doc_id = hashlib.sha256((" ".join(links) + url).encode()).hexdigest() - - def serialize_response(soup: BeautifulSoup): - data = {} - - h1_el = soup.find("h1") - if h1_el is not None: - data["title"] = h1_el.text - - description_el = soup.find("meta", {"name": "description"}) - if description_el is not None: - data["description"] = description_el["content"] - - content_el = soup.find("div", {"id": "content-blocks"}) - if content_el is not None: - data["content"] = content_el.text - - return data - - def load_link(link: str): - try: - beehiiv_data = requests.get(link, headers=headers) - beehiiv_data.raise_for_status() - - soup = BeautifulSoup(beehiiv_data.text, "html.parser") - data = serialize_response(soup) - data = str(data) - if is_readable(data): - return data - else: - logger.warning(f"Page is not readable (too many invalid characters): {link}") - except ParserRejectedMarkup as e: - logger.error(f"Failed to parse {link}: {e}") - return None - - for link in links: - data = load_link(link) - if data: - output.append({"content": data, "meta_data": {"url": link}}) - # TODO: allow users to configure this - time.sleep(1.0) # added to avoid rate limiting - - return {"doc_id": doc_id, "data": output} diff --git a/embedchain/embedchain/loaders/csv.py b/embedchain/embedchain/loaders/csv.py deleted file mode 100644 index 2714d5759..000000000 --- a/embedchain/embedchain/loaders/csv.py +++ /dev/null @@ -1,49 +0,0 @@ -import csv -import hashlib -from io import StringIO -from urllib.parse import urlparse - -import requests - -from embedchain.loaders.base_loader import BaseLoader - - -class CsvLoader(BaseLoader): - @staticmethod - def _detect_delimiter(first_line): - delimiters = [",", "\t", ";", "|"] - counts = {delimiter: first_line.count(delimiter) for delimiter in delimiters} - return max(counts, key=counts.get) - - @staticmethod - def _get_file_content(content): - url = urlparse(content) - if all([url.scheme, url.netloc]) and url.scheme not in ["file", "http", "https"]: - raise ValueError("Not a valid URL.") - - if url.scheme in ["http", "https"]: - response = requests.get(content) - response.raise_for_status() - return StringIO(response.text) - elif url.scheme == "file": - path = url.path - return open(path, newline="", encoding="utf-8") # Open the file using the path from the URI - else: - return open(content, newline="", encoding="utf-8") # Treat content as a regular file path - - @staticmethod - def load_data(content): - """Load a csv file with headers. Each line is a document""" - result = [] - lines = [] - with CsvLoader._get_file_content(content) as file: - first_line = file.readline() - delimiter = CsvLoader._detect_delimiter(first_line) - file.seek(0) # Reset the file pointer to the start - reader = csv.DictReader(file, delimiter=delimiter) - for i, row in enumerate(reader): - line = ", ".join([f"{field}: {value}" for field, value in row.items()]) - lines.append(line) - result.append({"content": line, "meta_data": {"url": content, "row": i + 1}}) - doc_id = hashlib.sha256((content + " ".join(lines)).encode()).hexdigest() - return {"doc_id": doc_id, "data": result} diff --git a/embedchain/embedchain/loaders/directory_loader.py b/embedchain/embedchain/loaders/directory_loader.py deleted file mode 100644 index 5903813b5..000000000 --- a/embedchain/embedchain/loaders/directory_loader.py +++ /dev/null @@ -1,63 +0,0 @@ -import hashlib -import logging -from pathlib import Path -from typing import Any, Optional - -from embedchain.config import AddConfig -from embedchain.data_formatter.data_formatter import DataFormatter -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.loaders.text_file import TextFileLoader -from embedchain.utils.misc import detect_datatype - -logger = logging.getLogger(__name__) - - -@register_deserializable -class DirectoryLoader(BaseLoader): - """Load data from a directory.""" - - def __init__(self, config: Optional[dict[str, Any]] = None): - super().__init__() - config = config or {} - self.recursive = config.get("recursive", True) - self.extensions = config.get("extensions", None) - self.errors = [] - - def load_data(self, path: str): - directory_path = Path(path) - if not directory_path.is_dir(): - raise ValueError(f"Invalid path: {path}") - - logger.info(f"Loading data from directory: {path}") - data_list = self._process_directory(directory_path) - doc_id = hashlib.sha256((str(data_list) + str(directory_path)).encode()).hexdigest() - - for error in self.errors: - logger.warning(error) - - return {"doc_id": doc_id, "data": data_list} - - def _process_directory(self, directory_path: Path): - data_list = [] - for file_path in directory_path.rglob("*") if self.recursive else directory_path.glob("*"): - # don't include dotfiles - if file_path.name.startswith("."): - continue - if file_path.is_file() and (not self.extensions or any(file_path.suffix == ext for ext in self.extensions)): - loader = self._predict_loader(file_path) - data_list.extend(loader.load_data(str(file_path))["data"]) - elif file_path.is_dir(): - logger.info(f"Loading data from directory: {file_path}") - return data_list - - def _predict_loader(self, file_path: Path) -> BaseLoader: - try: - data_type = detect_datatype(str(file_path)) - config = AddConfig() - return DataFormatter(data_type=data_type, config=config)._get_loader( - data_type=data_type, config=config.loader, loader=None - ) - except Exception as e: - self.errors.append(f"Error processing {file_path}: {e}") - return TextFileLoader() diff --git a/embedchain/embedchain/loaders/discord.py b/embedchain/embedchain/loaders/discord.py deleted file mode 100644 index 807a3d00c..000000000 --- a/embedchain/embedchain/loaders/discord.py +++ /dev/null @@ -1,152 +0,0 @@ -import hashlib -import logging -import os - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - -logger = logging.getLogger(__name__) - - -@register_deserializable -class DiscordLoader(BaseLoader): - """ - Load data from a Discord Channel ID. - """ - - def __init__(self): - if not os.environ.get("DISCORD_TOKEN"): - raise ValueError("DISCORD_TOKEN is not set") - - self.token = os.environ.get("DISCORD_TOKEN") - - @staticmethod - def _format_message(message): - return { - "message_id": message.id, - "content": message.content, - "author": { - "id": message.author.id, - "name": message.author.name, - "discriminator": message.author.discriminator, - }, - "created_at": message.created_at.isoformat(), - "attachments": [ - { - "id": attachment.id, - "filename": attachment.filename, - "size": attachment.size, - "url": attachment.url, - "proxy_url": attachment.proxy_url, - "height": attachment.height, - "width": attachment.width, - } - for attachment in message.attachments - ], - "embeds": [ - { - "title": embed.title, - "type": embed.type, - "description": embed.description, - "url": embed.url, - "timestamp": embed.timestamp.isoformat(), - "color": embed.color, - "footer": { - "text": embed.footer.text, - "icon_url": embed.footer.icon_url, - "proxy_icon_url": embed.footer.proxy_icon_url, - }, - "image": { - "url": embed.image.url, - "proxy_url": embed.image.proxy_url, - "height": embed.image.height, - "width": embed.image.width, - }, - "thumbnail": { - "url": embed.thumbnail.url, - "proxy_url": embed.thumbnail.proxy_url, - "height": embed.thumbnail.height, - "width": embed.thumbnail.width, - }, - "video": { - "url": embed.video.url, - "height": embed.video.height, - "width": embed.video.width, - }, - "provider": { - "name": embed.provider.name, - "url": embed.provider.url, - }, - "author": { - "name": embed.author.name, - "url": embed.author.url, - "icon_url": embed.author.icon_url, - "proxy_icon_url": embed.author.proxy_icon_url, - }, - "fields": [ - { - "name": field.name, - "value": field.value, - "inline": field.inline, - } - for field in embed.fields - ], - } - for embed in message.embeds - ], - } - - def load_data(self, channel_id: str): - """Load data from a Discord Channel ID.""" - import discord - - messages = [] - - class DiscordClient(discord.Client): - async def on_ready(self) -> None: - logger.info("Logged on as {0}!".format(self.user)) - try: - channel = self.get_channel(int(channel_id)) - if not isinstance(channel, discord.TextChannel): - raise ValueError( - f"Channel {channel_id} is not a text channel. " "Only text channels are supported for now." - ) - threads = {} - - for thread in channel.threads: - threads[thread.id] = thread - - async for message in channel.history(limit=None): - messages.append(DiscordLoader._format_message(message)) - if message.id in threads: - async for thread_message in threads[message.id].history(limit=None): - messages.append(DiscordLoader._format_message(thread_message)) - - except Exception as e: - logger.error(e) - await self.close() - finally: - await self.close() - - intents = discord.Intents.default() - intents.message_content = True - client = DiscordClient(intents=intents) - client.run(self.token) - - metadata = { - "url": channel_id, - } - - messages = str(messages) - - doc_id = hashlib.sha256((messages + channel_id).encode()).hexdigest() - - return { - "doc_id": doc_id, - "data": [ - { - "content": messages, - "meta_data": metadata, - } - ], - } diff --git a/embedchain/embedchain/loaders/discourse.py b/embedchain/embedchain/loaders/discourse.py deleted file mode 100644 index 65c1dd756..000000000 --- a/embedchain/embedchain/loaders/discourse.py +++ /dev/null @@ -1,79 +0,0 @@ -import hashlib -import logging -import time -from typing import Any, Optional - -import requests - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -logger = logging.getLogger(__name__) - - -class DiscourseLoader(BaseLoader): - def __init__(self, config: Optional[dict[str, Any]] = None): - super().__init__() - if not config: - raise ValueError( - "DiscourseLoader requires a config. Check the documentation for the correct format - `https://docs.embedchain.ai/components/data-sources/discourse`" # noqa: E501 - ) - - self.domain = config.get("domain") - if not self.domain: - raise ValueError( - "DiscourseLoader requires a domain. Check the documentation for the correct format - `https://docs.embedchain.ai/components/data-sources/discourse`" # noqa: E501 - ) - - def _check_query(self, query): - if not query or not isinstance(query, str): - raise ValueError( - "DiscourseLoader requires a query. Check the documentation for the correct format - `https://docs.embedchain.ai/components/data-sources/discourse`" # noqa: E501 - ) - - def _load_post(self, post_id): - post_url = f"{self.domain}posts/{post_id}.json" - response = requests.get(post_url) - try: - response.raise_for_status() - except Exception as e: - logger.error(f"Failed to load post {post_id}: {e}") - return - response_data = response.json() - post_contents = clean_string(response_data.get("raw")) - metadata = { - "url": post_url, - "created_at": response_data.get("created_at", ""), - "username": response_data.get("username", ""), - "topic_slug": response_data.get("topic_slug", ""), - "score": response_data.get("score", ""), - } - data = { - "content": post_contents, - "meta_data": metadata, - } - return data - - def load_data(self, query): - self._check_query(query) - data = [] - data_contents = [] - logger.info(f"Searching data on discourse url: {self.domain}, for query: {query}") - search_url = f"{self.domain}search.json?q={query}" - response = requests.get(search_url) - try: - response.raise_for_status() - except Exception as e: - raise ValueError(f"Failed to search query {query}: {e}") - response_data = response.json() - post_ids = response_data.get("grouped_search_result").get("post_ids") - for id in post_ids: - post_data = self._load_post(id) - if post_data: - data.append(post_data) - data_contents.append(post_data.get("content")) - # Sleep for 0.4 sec, to avoid rate limiting. Check `https://meta.discourse.org/t/api-rate-limits/208405/6` - time.sleep(0.4) - doc_id = hashlib.sha256((query + ", ".join(data_contents)).encode()).hexdigest() - response_data = {"doc_id": doc_id, "data": data} - return response_data diff --git a/embedchain/embedchain/loaders/docs_site_loader.py b/embedchain/embedchain/loaders/docs_site_loader.py deleted file mode 100644 index b9831a9cd..000000000 --- a/embedchain/embedchain/loaders/docs_site_loader.py +++ /dev/null @@ -1,119 +0,0 @@ -import hashlib -import logging -from urllib.parse import urljoin, urlparse - -import requests - -try: - from bs4 import BeautifulSoup -except ImportError: - raise ImportError( - "DocsSite requires extra dependencies. Install with `pip install beautifulsoup4==4.12.3`" - ) from None - - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - -logger = logging.getLogger(__name__) - - -@register_deserializable -class DocsSiteLoader(BaseLoader): - def __init__(self): - self.visited_links = set() - - def _get_child_links_recursive(self, url): - if url in self.visited_links: - return - - parsed_url = urlparse(url) - base_url = f"{parsed_url.scheme}://{parsed_url.netloc}" - current_path = parsed_url.path - - response = requests.get(url) - if response.status_code != 200: - logger.info(f"Failed to fetch the website: {response.status_code}") - return - - soup = BeautifulSoup(response.text, "html.parser") - all_links = (link.get("href") for link in soup.find_all("a", href=True)) - - child_links = (link for link in all_links if link.startswith(current_path) and link != current_path) - - absolute_paths = set(urljoin(base_url, link) for link in child_links) - - self.visited_links.update(absolute_paths) - - [self._get_child_links_recursive(link) for link in absolute_paths if link not in self.visited_links] - - def _get_all_urls(self, url): - self.visited_links = set() - self._get_child_links_recursive(url) - urls = [link for link in self.visited_links if urlparse(link).netloc == urlparse(url).netloc] - return urls - - @staticmethod - def _load_data_from_url(url: str) -> list: - response = requests.get(url) - if response.status_code != 200: - logger.info(f"Failed to fetch the website: {response.status_code}") - return [] - - soup = BeautifulSoup(response.content, "html.parser") - selectors = [ - "article.bd-article", - 'article[role="main"]', - "div.md-content", - 'div[role="main"]', - "div.container", - "div.section", - "article", - "main", - ] - - output = [] - for selector in selectors: - element = soup.select_one(selector) - if element: - content = element.prettify() - break - else: - content = soup.get_text() - - soup = BeautifulSoup(content, "html.parser") - ignored_tags = [ - "nav", - "aside", - "form", - "header", - "noscript", - "svg", - "canvas", - "footer", - "script", - "style", - ] - for tag in soup(ignored_tags): - tag.decompose() - - content = " ".join(soup.stripped_strings) - output.append( - { - "content": content, - "meta_data": {"url": url}, - } - ) - - return output - - def load_data(self, url): - all_urls = self._get_all_urls(url) - output = [] - for u in all_urls: - output.extend(self._load_data_from_url(u)) - doc_id = hashlib.sha256((" ".join(all_urls) + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": output, - } diff --git a/embedchain/embedchain/loaders/docx_file.py b/embedchain/embedchain/loaders/docx_file.py deleted file mode 100644 index 219bb9914..000000000 --- a/embedchain/embedchain/loaders/docx_file.py +++ /dev/null @@ -1,26 +0,0 @@ -import hashlib - -try: - from langchain_community.document_loaders import Docx2txtLoader -except ImportError: - raise ImportError("Docx file requires extra dependencies. Install with `pip install docx2txt==0.8`") from None -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class DocxFileLoader(BaseLoader): - def load_data(self, url): - """Load data from a .docx file.""" - loader = Docx2txtLoader(url) - output = [] - data = loader.load() - content = data[0].page_content - metadata = data[0].metadata - metadata["url"] = "local" - output.append({"content": content, "meta_data": metadata}) - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": output, - } diff --git a/embedchain/embedchain/loaders/dropbox.py b/embedchain/embedchain/loaders/dropbox.py deleted file mode 100644 index 1fbaf2897..000000000 --- a/embedchain/embedchain/loaders/dropbox.py +++ /dev/null @@ -1,79 +0,0 @@ -import hashlib -import os - -from dropbox.files import FileMetadata - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.loaders.directory_loader import DirectoryLoader - - -@register_deserializable -class DropboxLoader(BaseLoader): - def __init__(self): - access_token = os.environ.get("DROPBOX_ACCESS_TOKEN") - if not access_token: - raise ValueError("Please set the `DROPBOX_ACCESS_TOKEN` environment variable.") - try: - from dropbox import Dropbox, exceptions - except ImportError: - raise ImportError("Dropbox requires extra dependencies. Install with `pip install dropbox==11.36.2`") - - try: - dbx = Dropbox(access_token) - dbx.users_get_current_account() - self.dbx = dbx - except exceptions.AuthError as ex: - raise ValueError("Invalid Dropbox access token. Please verify your token and try again.") from ex - - def _download_folder(self, path: str, local_root: str) -> list[FileMetadata]: - """Download a folder from Dropbox and save it preserving the directory structure.""" - entries = self.dbx.files_list_folder(path).entries - for entry in entries: - local_path = os.path.join(local_root, entry.name) - if isinstance(entry, FileMetadata): - self.dbx.files_download_to_file(local_path, f"{path}/{entry.name}") - else: - os.makedirs(local_path, exist_ok=True) - self._download_folder(f"{path}/{entry.name}", local_path) - return entries - - def _generate_dir_id_from_all_paths(self, path: str) -> str: - """Generate a unique ID for a directory based on all of its paths.""" - entries = self.dbx.files_list_folder(path).entries - paths = [f"{path}/{entry.name}" for entry in entries] - return hashlib.sha256("".join(paths).encode()).hexdigest() - - def load_data(self, path: str): - """Load data from a Dropbox URL, preserving the folder structure.""" - root_dir = f"dropbox_{self._generate_dir_id_from_all_paths(path)}" - os.makedirs(root_dir, exist_ok=True) - - for entry in self.dbx.files_list_folder(path).entries: - local_path = os.path.join(root_dir, entry.name) - if isinstance(entry, FileMetadata): - self.dbx.files_download_to_file(local_path, f"{path}/{entry.name}") - else: - os.makedirs(local_path, exist_ok=True) - self._download_folder(f"{path}/{entry.name}", local_path) - - dir_loader = DirectoryLoader() - data = dir_loader.load_data(root_dir)["data"] - - # Clean up - self._clean_directory(root_dir) - - return { - "doc_id": hashlib.sha256(path.encode()).hexdigest(), - "data": data, - } - - def _clean_directory(self, dir_path): - """Recursively delete a directory and its contents.""" - for item in os.listdir(dir_path): - item_path = os.path.join(dir_path, item) - if os.path.isdir(item_path): - self._clean_directory(item_path) - else: - os.remove(item_path) - os.rmdir(dir_path) diff --git a/embedchain/embedchain/loaders/excel_file.py b/embedchain/embedchain/loaders/excel_file.py deleted file mode 100644 index 585415770..000000000 --- a/embedchain/embedchain/loaders/excel_file.py +++ /dev/null @@ -1,41 +0,0 @@ -import hashlib -import importlib.util - -try: - import unstructured # noqa: F401 - from langchain_community.document_loaders import UnstructuredExcelLoader -except ImportError: - raise ImportError( - 'Excel file requires extra dependencies. Install with `pip install "unstructured[local-inference, all-docs]"`' - ) from None - -if importlib.util.find_spec("openpyxl") is None and importlib.util.find_spec("xlrd") is None: - raise ImportError("Excel file requires extra dependencies. Install with `pip install openpyxl xlrd`") from None - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - - -@register_deserializable -class ExcelFileLoader(BaseLoader): - def load_data(self, excel_url): - """Load data from a Excel file.""" - loader = UnstructuredExcelLoader(excel_url) - pages = loader.load_and_split() - - data = [] - for page in pages: - content = page.page_content - content = clean_string(content) - - metadata = page.metadata - metadata["url"] = excel_url - - data.append({"content": content, "meta_data": metadata}) - - doc_id = hashlib.sha256((content + excel_url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } diff --git a/embedchain/embedchain/loaders/github.py b/embedchain/embedchain/loaders/github.py deleted file mode 100644 index dac7241e0..000000000 --- a/embedchain/embedchain/loaders/github.py +++ /dev/null @@ -1,312 +0,0 @@ -import concurrent.futures -import hashlib -import logging -import re -import shlex -from typing import Any, Optional - -from tqdm import tqdm - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -GITHUB_URL = "https://github.com" -GITHUB_API_URL = "https://api.github.com" - -VALID_SEARCH_TYPES = set(["code", "repo", "pr", "issue", "discussion", "branch", "file"]) - - -class GithubLoader(BaseLoader): - """Load data from GitHub search query.""" - - def __init__(self, config: Optional[dict[str, Any]] = None): - super().__init__() - if not config: - raise ValueError( - "GithubLoader requires a personal access token to use github api. Check - `https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic`" # noqa: E501 - ) - - try: - from github import Github - except ImportError as e: - raise ValueError( - "GithubLoader requires extra dependencies. \ - Install with `pip install gitpython==3.1.38 PyGithub==1.59.1`" - ) from e - - self.config = config - token = config.get("token") - if not token: - raise ValueError( - "GithubLoader requires a personal access token to use github api. Check - `https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic`" # noqa: E501 - ) - - try: - self.client = Github(token) - except Exception as e: - logging.error(f"GithubLoader failed to initialize client: {e}") - self.client = None - - def _github_search_code(self, query: str): - """Search GitHub code.""" - data = [] - results = self.client.search_code(query) - for result in tqdm(results, total=results.totalCount, desc="Loading code files from github"): - url = result.html_url - logging.info(f"Added data from url: {url}") - content = result.decoded_content.decode("utf-8") - metadata = { - "url": url, - } - data.append( - { - "content": clean_string(content), - "meta_data": metadata, - } - ) - return data - - def _get_github_repo_data(self, repo_name: str, branch_name: str = None, file_path: str = None) -> list[dict]: - """Get file contents from Repo""" - data = [] - - repo = self.client.get_repo(repo_name) - repo_contents = repo.get_contents("") - - if branch_name: - repo_contents = repo.get_contents("", ref=branch_name) - if file_path: - repo_contents = [repo.get_contents(file_path)] - - with tqdm(desc="Loading files:", unit="item") as progress_bar: - while repo_contents: - file_content = repo_contents.pop(0) - if file_content.type == "dir": - try: - repo_contents.extend(repo.get_contents(file_content.path)) - except Exception: - logging.warning(f"Failed to read directory: {file_content.path}") - progress_bar.update(1) - continue - else: - try: - file_text = file_content.decoded_content.decode() - except Exception: - logging.warning(f"Failed to read file: {file_content.path}") - progress_bar.update(1) - continue - - file_path = file_content.path - data.append( - { - "content": clean_string(file_text), - "meta_data": { - "path": file_path, - }, - } - ) - - progress_bar.update(1) - - return data - - def _github_search_repo(self, query: str) -> list[dict]: - """Search GitHub repo.""" - - logging.info(f"Searching github repos with query: {query}") - updated_query = query.split(":")[-1] - data = self._get_github_repo_data(updated_query) - return data - - def _github_search_issues_and_pr(self, query: str, type: str) -> list[dict]: - """Search GitHub issues and PRs.""" - data = [] - - query = f"{query} is:{type}" - logging.info(f"Searching github for query: {query}") - - results = self.client.search_issues(query) - - logging.info(f"Total results: {results.totalCount}") - for result in tqdm(results, total=results.totalCount, desc=f"Loading {type} from github"): - url = result.html_url - title = result.title - body = result.body - if not body: - logging.warning(f"Skipping issue because empty content for: {url}") - continue - labels = " ".join([label.name for label in result.labels]) - issue_comments = result.get_comments() - comments = [] - comments_created_at = [] - for comment in issue_comments: - comments_created_at.append(str(comment.created_at)) - comments.append(f"{comment.user.name}:{comment.body}") - content = "\n".join([title, labels, body, *comments]) - metadata = { - "url": url, - "created_at": str(result.created_at), - "comments_created_at": " ".join(comments_created_at), - } - data.append( - { - "content": clean_string(content), - "meta_data": metadata, - } - ) - return data - - # need to test more for discussion - def _github_search_discussions(self, query: str): - """Search GitHub discussions.""" - data = [] - - query = f"{query} is:discussion" - logging.info(f"Searching github repo for query: {query}") - repos_results = self.client.search_repositories(query) - logging.info(f"Total repos found: {repos_results.totalCount}") - for repo_result in tqdm(repos_results, total=repos_results.totalCount, desc="Loading discussions from github"): - teams = repo_result.get_teams() - for team in teams: - team_discussions = team.get_discussions() - for discussion in team_discussions: - url = discussion.html_url - title = discussion.title - body = discussion.body - if not body: - logging.warning(f"Skipping discussion because empty content for: {url}") - continue - comments = [] - comments_created_at = [] - print("Discussion comments: ", discussion.comments_url) - content = "\n".join([title, body, *comments]) - metadata = { - "url": url, - "created_at": str(discussion.created_at), - "comments_created_at": " ".join(comments_created_at), - } - data.append( - { - "content": clean_string(content), - "meta_data": metadata, - } - ) - return data - - def _get_github_repo_branch(self, query: str, type: str) -> list[dict]: - """Get file contents for specific branch""" - - logging.info(f"Searching github repo for query: {query} is:{type}") - pattern = r"repo:(\S+) name:(\S+)" - match = re.search(pattern, query) - - if match: - repo_name = match.group(1) - branch_name = match.group(2) - else: - raise ValueError( - f"Repository name and Branch name not found, instead found this \ - Repo: {repo_name}, Branch: {branch_name}" - ) - - data = self._get_github_repo_data(repo_name=repo_name, branch_name=branch_name) - return data - - def _get_github_repo_file(self, query: str, type: str) -> list[dict]: - """Get specific file content""" - - logging.info(f"Searching github repo for query: {query} is:{type}") - pattern = r"repo:(\S+) path:(\S+)" - match = re.search(pattern, query) - - if match: - repo_name = match.group(1) - file_path = match.group(2) - else: - raise ValueError( - f"Repository name and File name not found, instead found this Repo: {repo_name}, File: {file_path}" - ) - - data = self._get_github_repo_data(repo_name=repo_name, file_path=file_path) - return data - - def _search_github_data(self, search_type: str, query: str): - """Search github data.""" - if search_type == "code": - data = self._github_search_code(query) - elif search_type == "repo": - data = self._github_search_repo(query) - elif search_type == "issue": - data = self._github_search_issues_and_pr(query, search_type) - elif search_type == "pr": - data = self._github_search_issues_and_pr(query, search_type) - elif search_type == "branch": - data = self._get_github_repo_branch(query, search_type) - elif search_type == "file": - data = self._get_github_repo_file(query, search_type) - elif search_type == "discussion": - raise ValueError("GithubLoader does not support searching discussions yet.") - else: - raise NotImplementedError(f"{search_type} not supported") - - return data - - @staticmethod - def _get_valid_github_query(query: str): - """Check if query is valid and return search types and valid GitHub query.""" - query_terms = shlex.split(query) - # query must provide repo to load data from - if len(query_terms) < 1 or "repo:" not in query: - raise ValueError( - "GithubLoader requires a search query with `repo:` term. Refer docs - `https://docs.embedchain.ai/data-sources/github`" # noqa: E501 - ) - - github_query = [] - types = set() - type_pattern = r"type:([a-zA-Z,]+)" - for term in query_terms: - term_match = re.search(type_pattern, term) - if term_match: - search_types = term_match.group(1).split(",") - types.update(search_types) - else: - github_query.append(term) - - # query must provide search type - if len(types) == 0: - raise ValueError( - "GithubLoader requires a search query with `type:` term. Refer docs - `https://docs.embedchain.ai/data-sources/github`" # noqa: E501 - ) - - for search_type in search_types: - if search_type not in VALID_SEARCH_TYPES: - raise ValueError( - f"Invalid search type: {search_type}. Valid types are: {', '.join(VALID_SEARCH_TYPES)}" - ) - - query = " ".join(github_query) - - return types, query - - def load_data(self, search_query: str, max_results: int = 1000): - """Load data from GitHub search query.""" - - if not self.client: - raise ValueError( - "GithubLoader client is not initialized, data will not be loaded. Refer docs - `https://docs.embedchain.ai/data-sources/github`" # noqa: E501 - ) - - search_types, query = self._get_valid_github_query(search_query) - logging.info(f"Searching github for query: {query}, with types: {', '.join(search_types)}") - - data = [] - - with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: - futures_map = executor.map(self._search_github_data, search_types, [query] * len(search_types)) - for search_data in tqdm(futures_map, total=len(search_types), desc="Searching data from github"): - data.extend(search_data) - - return { - "doc_id": hashlib.sha256(query.encode()).hexdigest(), - "data": data, - } diff --git a/embedchain/embedchain/loaders/gmail.py b/embedchain/embedchain/loaders/gmail.py deleted file mode 100644 index ec62a34b3..000000000 --- a/embedchain/embedchain/loaders/gmail.py +++ /dev/null @@ -1,144 +0,0 @@ -import base64 -import hashlib -import logging -import os -from email import message_from_bytes -from email.utils import parsedate_to_datetime -from textwrap import dedent -from typing import Optional - -from bs4 import BeautifulSoup - -try: - from google.auth.transport.requests import Request - from google.oauth2.credentials import Credentials - from google_auth_oauthlib.flow import InstalledAppFlow - from googleapiclient.discovery import build -except ImportError: - raise ImportError( - 'Gmail requires extra dependencies. Install with `pip install --upgrade "embedchain[gmail]"`' - ) from None - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -logger = logging.getLogger(__name__) - - -class GmailReader: - SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"] - - def __init__(self, query: str, service=None, results_per_page: int = 10): - self.query = query - self.service = service or self._initialize_service() - self.results_per_page = results_per_page - - @staticmethod - def _initialize_service(): - credentials = GmailReader._get_credentials() - return build("gmail", "v1", credentials=credentials) - - @staticmethod - def _get_credentials(): - if not os.path.exists("credentials.json"): - raise FileNotFoundError("Missing 'credentials.json'. Download it from your Google Developer account.") - - creds = ( - Credentials.from_authorized_user_file("token.json", GmailReader.SCOPES) - if os.path.exists("token.json") - else None - ) - - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - flow = InstalledAppFlow.from_client_secrets_file("credentials.json", GmailReader.SCOPES) - creds = flow.run_local_server(port=8080) - with open("token.json", "w") as token: - token.write(creds.to_json()) - return creds - - def load_emails(self) -> list[dict]: - response = self.service.users().messages().list(userId="me", q=self.query).execute() - messages = response.get("messages", []) - - return [self._parse_email(self._get_email(message["id"])) for message in messages] - - def _get_email(self, message_id: str): - raw_message = self.service.users().messages().get(userId="me", id=message_id, format="raw").execute() - return base64.urlsafe_b64decode(raw_message["raw"]) - - def _parse_email(self, raw_email) -> dict: - mime_msg = message_from_bytes(raw_email) - return { - "subject": self._get_header(mime_msg, "Subject"), - "from": self._get_header(mime_msg, "From"), - "to": self._get_header(mime_msg, "To"), - "date": self._format_date(mime_msg), - "body": self._get_body(mime_msg), - } - - @staticmethod - def _get_header(mime_msg, header_name: str) -> str: - return mime_msg.get(header_name, "") - - @staticmethod - def _format_date(mime_msg) -> Optional[str]: - date_header = GmailReader._get_header(mime_msg, "Date") - return parsedate_to_datetime(date_header).isoformat() if date_header else None - - @staticmethod - def _get_body(mime_msg) -> str: - def decode_payload(part): - charset = part.get_content_charset() or "utf-8" - try: - return part.get_payload(decode=True).decode(charset) - except UnicodeDecodeError: - return part.get_payload(decode=True).decode(charset, errors="replace") - - if mime_msg.is_multipart(): - for part in mime_msg.walk(): - ctype = part.get_content_type() - cdispo = str(part.get("Content-Disposition")) - - if ctype == "text/plain" and "attachment" not in cdispo: - return decode_payload(part) - elif ctype == "text/html": - return decode_payload(part) - else: - return decode_payload(mime_msg) - - return "" - - -class GmailLoader(BaseLoader): - def load_data(self, query: str): - reader = GmailReader(query=query) - emails = reader.load_emails() - logger.info(f"Gmail Loader: {len(emails)} emails found for query '{query}'") - - data = [] - for email in emails: - content = self._process_email(email) - data.append({"content": content, "meta_data": email}) - - return {"doc_id": self._generate_doc_id(query, data), "data": data} - - @staticmethod - def _process_email(email: dict) -> str: - content = BeautifulSoup(email["body"], "html.parser").get_text() - content = clean_string(content) - return dedent( - f""" - Email from '{email['from']}' to '{email['to']}' - Subject: {email['subject']} - Date: {email['date']} - Content: {content} - """ - ) - - @staticmethod - def _generate_doc_id(query: str, data: list[dict]) -> str: - content_strings = [email["content"] for email in data] - return hashlib.sha256((query + ", ".join(content_strings)).encode()).hexdigest() diff --git a/embedchain/embedchain/loaders/google_drive.py b/embedchain/embedchain/loaders/google_drive.py deleted file mode 100644 index d24046242..000000000 --- a/embedchain/embedchain/loaders/google_drive.py +++ /dev/null @@ -1,62 +0,0 @@ -import hashlib -import re - -try: - from googleapiclient.errors import HttpError -except ImportError: - raise ImportError( - "Google Drive requires extra dependencies. Install with `pip install embedchain[googledrive]`" - ) from None - -from langchain_community.document_loaders import GoogleDriveLoader as Loader - -try: - import unstructured # noqa: F401 - from langchain_community.document_loaders import UnstructuredFileIOLoader -except ImportError: - raise ImportError( - 'Unstructured file requires extra dependencies. Install with `pip install "unstructured[local-inference, all-docs]"`' # noqa: E501 - ) from None - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class GoogleDriveLoader(BaseLoader): - @staticmethod - def _get_drive_id_from_url(url: str): - regex = r"^https:\/\/drive\.google\.com\/drive\/(?:u\/\d+\/)folders\/([a-zA-Z0-9_-]+)$" - if re.match(regex, url): - return url.split("/")[-1] - raise ValueError( - f"The url provided {url} does not match a google drive folder url. Example drive url: " - f"https://drive.google.com/drive/u/0/folders/xxxx" - ) - - def load_data(self, url: str): - """Load data from a Google drive folder.""" - folder_id: str = self._get_drive_id_from_url(url) - - try: - loader = Loader( - folder_id=folder_id, - recursive=True, - file_loader_cls=UnstructuredFileIOLoader, - ) - - data = [] - all_content = [] - - docs = loader.load() - for doc in docs: - all_content.append(doc.page_content) - # renames source to url for later use. - doc.metadata["url"] = doc.metadata.pop("source") - data.append({"content": doc.page_content, "meta_data": doc.metadata}) - - doc_id = hashlib.sha256((" ".join(all_content) + url).encode()).hexdigest() - return {"doc_id": doc_id, "data": data} - - except HttpError: - raise FileNotFoundError("Unable to locate folder or files, check provided drive URL and try again") diff --git a/embedchain/embedchain/loaders/image.py b/embedchain/embedchain/loaders/image.py deleted file mode 100644 index 18b31873b..000000000 --- a/embedchain/embedchain/loaders/image.py +++ /dev/null @@ -1,50 +0,0 @@ -import base64 -import hashlib -import os -from pathlib import Path - -from openai import OpenAI - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - -DESCRIBE_IMAGE_PROMPT = "Describe the image:" - - -@register_deserializable -class ImageLoader(BaseLoader): - def __init__(self, max_tokens: int = 500, api_key: str = None, prompt: str = None): - super().__init__() - self.custom_prompt = prompt or DESCRIBE_IMAGE_PROMPT - self.max_tokens = max_tokens - self.api_key = api_key or os.environ["OPENAI_API_KEY"] - self.client = OpenAI(api_key=self.api_key) - - @staticmethod - def _encode_image(image_path: str): - with open(image_path, "rb") as image_file: - return base64.b64encode(image_file.read()).decode("utf-8") - - def _create_completion_request(self, content: str): - return self.client.chat.completions.create( - model="gpt-4o", messages=[{"role": "user", "content": content}], max_tokens=self.max_tokens - ) - - def _process_url(self, url: str): - if url.startswith("http"): - return [{"type": "text", "text": self.custom_prompt}, {"type": "image_url", "image_url": {"url": url}}] - elif Path(url).is_file(): - extension = Path(url).suffix.lstrip(".") - encoded_image = self._encode_image(url) - image_data = f"data:image/{extension};base64,{encoded_image}" - return [{"type": "text", "text": self.custom_prompt}, {"type": "image", "image_url": {"url": image_data}}] - else: - raise ValueError(f"Invalid URL or file path: {url}") - - def load_data(self, url: str): - content = self._process_url(url) - response = self._create_completion_request(content) - content = response.choices[0].message.content - - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return {"doc_id": doc_id, "data": [{"content": content, "meta_data": {"url": url, "type": "image"}}]} diff --git a/embedchain/embedchain/loaders/json.py b/embedchain/embedchain/loaders/json.py deleted file mode 100644 index 587aa1492..000000000 --- a/embedchain/embedchain/loaders/json.py +++ /dev/null @@ -1,93 +0,0 @@ -import hashlib -import json -import os -import re -from typing import Union - -import requests - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string, is_valid_json_string - - -class JSONReader: - def __init__(self) -> None: - """Initialize the JSONReader.""" - pass - - @staticmethod - def load_data(json_data: Union[dict, str]) -> list[str]: - """Load data from a JSON structure. - - Args: - json_data (Union[dict, str]): The JSON data to load. - - Returns: - list[str]: A list of strings representing the leaf nodes of the JSON. - """ - if isinstance(json_data, str): - json_data = json.loads(json_data) - else: - json_data = json_data - - json_output = json.dumps(json_data, indent=0) - lines = json_output.split("\n") - useful_lines = [line for line in lines if not re.match(r"^[{}\[\],]*$", line)] - return ["\n".join(useful_lines)] - - -VALID_URL_PATTERN = ( - "^https?://(?:www\.)?(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[a-zA-Z0-9.-]+)(?::\d+)?/(?:[^/\s]+/)*[^/\s]+\.json$" -) - - -class JSONLoader(BaseLoader): - @staticmethod - def _check_content(content): - if not isinstance(content, str): - raise ValueError( - "Invaid content input. \ - If you want to upload (list, dict, etc.), do \ - `json.dump(data, indent=0)` and add the stringified JSON. \ - Check - `https://docs.embedchain.ai/data-sources/json`" - ) - - @staticmethod - def load_data(content): - """Load a json file. Each data point is a key value pair.""" - - JSONLoader._check_content(content) - loader = JSONReader() - - data = [] - data_content = [] - - content_url_str = content - - if os.path.isfile(content): - with open(content, "r", encoding="utf-8") as json_file: - json_data = json.load(json_file) - elif re.match(VALID_URL_PATTERN, content): - response = requests.get(content) - if response.status_code == 200: - json_data = response.json() - else: - raise ValueError( - f"Loading data from the given url: {content} failed. \ - Make sure the url is working." - ) - elif is_valid_json_string(content): - json_data = content - content_url_str = hashlib.sha256((content).encode("utf-8")).hexdigest() - else: - raise ValueError(f"Invalid content to load json data from: {content}") - - docs = loader.load_data(json_data) - for doc in docs: - text = doc if isinstance(doc, str) else doc["text"] - doc_content = clean_string(text) - data.append({"content": doc_content, "meta_data": {"url": content_url_str}}) - data_content.append(doc_content) - - doc_id = hashlib.sha256((content_url_str + ", ".join(data_content)).encode()).hexdigest() - return {"doc_id": doc_id, "data": data} diff --git a/embedchain/embedchain/loaders/local_qna_pair.py b/embedchain/embedchain/loaders/local_qna_pair.py deleted file mode 100644 index c93adfdae..000000000 --- a/embedchain/embedchain/loaders/local_qna_pair.py +++ /dev/null @@ -1,24 +0,0 @@ -import hashlib - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class LocalQnaPairLoader(BaseLoader): - def load_data(self, content): - """Load data from a local QnA pair.""" - question, answer = content - content = f"Q: {question}\nA: {answer}" - url = "local" - metadata = {"url": url, "question": question} - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": [ - { - "content": content, - "meta_data": metadata, - } - ], - } diff --git a/embedchain/embedchain/loaders/local_text.py b/embedchain/embedchain/loaders/local_text.py deleted file mode 100644 index 98a98cd67..000000000 --- a/embedchain/embedchain/loaders/local_text.py +++ /dev/null @@ -1,24 +0,0 @@ -import hashlib - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class LocalTextLoader(BaseLoader): - def load_data(self, content): - """Load data from a local text file.""" - url = "local" - metadata = { - "url": url, - } - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": [ - { - "content": content, - "meta_data": metadata, - } - ], - } diff --git a/embedchain/embedchain/loaders/mdx.py b/embedchain/embedchain/loaders/mdx.py deleted file mode 100644 index 42b9b7fee..000000000 --- a/embedchain/embedchain/loaders/mdx.py +++ /dev/null @@ -1,25 +0,0 @@ -import hashlib - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class MdxLoader(BaseLoader): - def load_data(self, url): - """Load data from a mdx file.""" - with open(url, "r", encoding="utf-8") as infile: - content = infile.read() - metadata = { - "url": url, - } - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": [ - { - "content": content, - "meta_data": metadata, - } - ], - } diff --git a/embedchain/embedchain/loaders/mysql.py b/embedchain/embedchain/loaders/mysql.py deleted file mode 100644 index fd5b38ac2..000000000 --- a/embedchain/embedchain/loaders/mysql.py +++ /dev/null @@ -1,67 +0,0 @@ -import hashlib -import logging -from typing import Any, Optional - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -logger = logging.getLogger(__name__) - - -class MySQLLoader(BaseLoader): - def __init__(self, config: Optional[dict[str, Any]]): - super().__init__() - if not config: - raise ValueError( - f"Invalid sql config: {config}.", - "Provide the correct config, refer `https://docs.embedchain.ai/data-sources/mysql`.", - ) - - self.config = config - self.connection = None - self.cursor = None - self._setup_loader(config=config) - - def _setup_loader(self, config: dict[str, Any]): - try: - import mysql.connector as sqlconnector - except ImportError as e: - raise ImportError( - "Unable to import required packages for MySQL loader. Run `pip install --upgrade 'embedchain[mysql]'`." # noqa: E501 - ) from e - - try: - self.connection = sqlconnector.connection.MySQLConnection(**config) - self.cursor = self.connection.cursor() - except (sqlconnector.Error, IOError) as err: - logger.info(f"Connection failed: {err}") - raise ValueError( - f"Unable to connect with the given config: {config}.", - "Please provide the correct configuration to load data from you MySQL DB. \ - Refer `https://docs.embedchain.ai/data-sources/mysql`.", - ) - - @staticmethod - def _check_query(query): - if not isinstance(query, str): - raise ValueError( - f"Invalid mysql query: {query}", - "Provide the valid query to add from mysql, \ - make sure you are following `https://docs.embedchain.ai/data-sources/mysql`", - ) - - def load_data(self, query): - self._check_query(query=query) - data = [] - data_content = [] - self.cursor.execute(query) - rows = self.cursor.fetchall() - for row in rows: - doc_content = clean_string(str(row)) - data.append({"content": doc_content, "meta_data": {"url": query}}) - data_content.append(doc_content) - doc_id = hashlib.sha256((query + ", ".join(data_content)).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } diff --git a/embedchain/embedchain/loaders/notion.py b/embedchain/embedchain/loaders/notion.py deleted file mode 100644 index 2a3363818..000000000 --- a/embedchain/embedchain/loaders/notion.py +++ /dev/null @@ -1,121 +0,0 @@ -import hashlib -import logging -import os -from typing import Any, Optional - -import requests - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -logger = logging.getLogger(__name__) - - -class NotionDocument: - """ - A simple Document class to hold the text and additional information of a page. - """ - - def __init__(self, text: str, extra_info: dict[str, Any]): - self.text = text - self.extra_info = extra_info - - -class NotionPageLoader: - """ - Notion Page Loader. - Reads a set of Notion pages. - """ - - BLOCK_CHILD_URL_TMPL = "https://api.notion.com/v1/blocks/{block_id}/children" - - def __init__(self, integration_token: Optional[str] = None) -> None: - """Initialize with Notion integration token.""" - if integration_token is None: - integration_token = os.getenv("NOTION_INTEGRATION_TOKEN") - if integration_token is None: - raise ValueError( - "Must specify `integration_token` or set environment " "variable `NOTION_INTEGRATION_TOKEN`." - ) - self.token = integration_token - self.headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/json", - "Notion-Version": "2022-06-28", - } - - def _read_block(self, block_id: str, num_tabs: int = 0) -> str: - """Read a block from Notion.""" - done = False - result_lines_arr = [] - cur_block_id = block_id - while not done: - block_url = self.BLOCK_CHILD_URL_TMPL.format(block_id=cur_block_id) - res = requests.get(block_url, headers=self.headers) - data = res.json() - - for result in data["results"]: - result_type = result["type"] - result_obj = result[result_type] - - cur_result_text_arr = [] - if "rich_text" in result_obj: - for rich_text in result_obj["rich_text"]: - if "text" in rich_text: - text = rich_text["text"]["content"] - prefix = "\t" * num_tabs - cur_result_text_arr.append(prefix + text) - - result_block_id = result["id"] - has_children = result["has_children"] - if has_children: - children_text = self._read_block(result_block_id, num_tabs=num_tabs + 1) - cur_result_text_arr.append(children_text) - - cur_result_text = "\n".join(cur_result_text_arr) - result_lines_arr.append(cur_result_text) - - if data["next_cursor"] is None: - done = True - else: - cur_block_id = data["next_cursor"] - - result_lines = "\n".join(result_lines_arr) - return result_lines - - def load_data(self, page_ids: list[str]) -> list[NotionDocument]: - """Load data from the given list of page IDs.""" - docs = [] - for page_id in page_ids: - page_text = self._read_block(page_id) - docs.append(NotionDocument(text=page_text, extra_info={"page_id": page_id})) - return docs - - -@register_deserializable -class NotionLoader(BaseLoader): - def load_data(self, source): - """Load data from a Notion URL.""" - - id = source[-32:] - formatted_id = f"{id[:8]}-{id[8:12]}-{id[12:16]}-{id[16:20]}-{id[20:]}" - logger.debug(f"Extracted notion page id as: {formatted_id}") - - integration_token = os.getenv("NOTION_INTEGRATION_TOKEN") - reader = NotionPageLoader(integration_token=integration_token) - documents = reader.load_data(page_ids=[formatted_id]) - - raw_text = documents[0].text - - text = clean_string(raw_text) - doc_id = hashlib.sha256((text + source).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": [ - { - "content": text, - "meta_data": {"url": f"notion-{formatted_id}"}, - } - ], - } diff --git a/embedchain/embedchain/loaders/openapi.py b/embedchain/embedchain/loaders/openapi.py deleted file mode 100644 index 18983b9a3..000000000 --- a/embedchain/embedchain/loaders/openapi.py +++ /dev/null @@ -1,42 +0,0 @@ -import hashlib -from io import StringIO -from urllib.parse import urlparse - -import requests -import yaml - -from embedchain.loaders.base_loader import BaseLoader - - -class OpenAPILoader(BaseLoader): - @staticmethod - def _get_file_content(content): - url = urlparse(content) - if all([url.scheme, url.netloc]) and url.scheme not in ["file", "http", "https"]: - raise ValueError("Not a valid URL.") - - if url.scheme in ["http", "https"]: - response = requests.get(content) - response.raise_for_status() - return StringIO(response.text) - elif url.scheme == "file": - path = url.path - return open(path) - else: - return open(content) - - @staticmethod - def load_data(content): - """Load yaml file of openapi. Each pair is a document.""" - data = [] - file_path = content - data_content = [] - with OpenAPILoader._get_file_content(content=content) as file: - yaml_data = yaml.load(file, Loader=yaml.SafeLoader) - for i, (key, value) in enumerate(yaml_data.items()): - string_data = f"{key}: {value}" - metadata = {"url": file_path, "row": i + 1} - data.append({"content": string_data, "meta_data": metadata}) - data_content.append(string_data) - doc_id = hashlib.sha256((content + ", ".join(data_content)).encode()).hexdigest() - return {"doc_id": doc_id, "data": data} diff --git a/embedchain/embedchain/loaders/pdf_file.py b/embedchain/embedchain/loaders/pdf_file.py deleted file mode 100644 index a7f6d5540..000000000 --- a/embedchain/embedchain/loaders/pdf_file.py +++ /dev/null @@ -1,39 +0,0 @@ -import hashlib - -from langchain_community.document_loaders import PyPDFLoader - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - - -@register_deserializable -class PdfFileLoader(BaseLoader): - def load_data(self, url): - """Load data from a PDF file.""" - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36", # noqa:E501 - } - loader = PyPDFLoader(url, headers=headers) - data = [] - all_content = [] - pages = loader.load_and_split() - if not len(pages): - raise ValueError("No data found") - for page in pages: - content = page.page_content - content = clean_string(content) - metadata = page.metadata - metadata["url"] = url - data.append( - { - "content": content, - "meta_data": metadata, - } - ) - all_content.append(content) - doc_id = hashlib.sha256((" ".join(all_content) + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } diff --git a/embedchain/embedchain/loaders/postgres.py b/embedchain/embedchain/loaders/postgres.py deleted file mode 100644 index 2ef396f9d..000000000 --- a/embedchain/embedchain/loaders/postgres.py +++ /dev/null @@ -1,73 +0,0 @@ -import hashlib -import logging -from typing import Any, Optional - -from embedchain.loaders.base_loader import BaseLoader - -logger = logging.getLogger(__name__) - - -class PostgresLoader(BaseLoader): - def __init__(self, config: Optional[dict[str, Any]] = None): - super().__init__() - if not config: - raise ValueError(f"Must provide the valid config. Received: {config}") - - self.connection = None - self.cursor = None - self._setup_loader(config=config) - - def _setup_loader(self, config: dict[str, Any]): - try: - import psycopg - except ImportError as e: - raise ImportError( - "Unable to import required packages. \ - Run `pip install --upgrade 'embedchain[postgres]'`" - ) from e - - if "url" in config: - config_info = config.get("url") - else: - conn_params = [] - for key, value in config.items(): - conn_params.append(f"{key}={value}") - config_info = " ".join(conn_params) - - logger.info(f"Connecting to postrgres sql: {config_info}") - self.connection = psycopg.connect(conninfo=config_info) - self.cursor = self.connection.cursor() - - @staticmethod - def _check_query(query): - if not isinstance(query, str): - raise ValueError( - f"Invalid postgres query: {query}. Provide the valid source to add from postgres, make sure you are following `https://docs.embedchain.ai/data-sources/postgres`", # noqa:E501 - ) - - def load_data(self, query): - self._check_query(query) - try: - data = [] - data_content = [] - self.cursor.execute(query) - results = self.cursor.fetchall() - for result in results: - doc_content = str(result) - data.append({"content": doc_content, "meta_data": {"url": query}}) - data_content.append(doc_content) - doc_id = hashlib.sha256((query + ", ".join(data_content)).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } - except Exception as e: - raise ValueError(f"Failed to load data using query={query} with: {e}") - - def close_connection(self): - if self.cursor: - self.cursor.close() - self.cursor = None - if self.connection: - self.connection.close() - self.connection = None diff --git a/embedchain/embedchain/loaders/rss_feed.py b/embedchain/embedchain/loaders/rss_feed.py deleted file mode 100644 index bc17c68bc..000000000 --- a/embedchain/embedchain/loaders/rss_feed.py +++ /dev/null @@ -1,54 +0,0 @@ -import hashlib - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class RSSFeedLoader(BaseLoader): - """Loader for RSS Feed.""" - - def load_data(self, url): - """Load data from a rss feed.""" - output = self.get_rss_content(url) - doc_id = hashlib.sha256((str(output) + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": output, - } - - @staticmethod - def serialize_metadata(metadata): - for key, value in metadata.items(): - if not isinstance(value, (str, int, float, bool)): - metadata[key] = str(value) - - return metadata - - @staticmethod - def get_rss_content(url: str): - try: - from langchain_community.document_loaders import ( - RSSFeedLoader as LangchainRSSFeedLoader, - ) - except ImportError: - raise ImportError( - """RSSFeedLoader file requires extra dependencies. - Install with `pip install feedparser==6.0.10 newspaper3k==0.2.8 listparser==0.19`""" - ) from None - - output = [] - loader = LangchainRSSFeedLoader(urls=[url]) - data = loader.load() - - for entry in data: - metadata = RSSFeedLoader.serialize_metadata(entry.metadata) - metadata.update({"url": url}) - output.append( - { - "content": entry.page_content, - "meta_data": metadata, - } - ) - - return output diff --git a/embedchain/embedchain/loaders/sitemap.py b/embedchain/embedchain/loaders/sitemap.py deleted file mode 100644 index 098ca06df..000000000 --- a/embedchain/embedchain/loaders/sitemap.py +++ /dev/null @@ -1,79 +0,0 @@ -import concurrent.futures -import hashlib -import logging -import os -from urllib.parse import urlparse - -import requests -from tqdm import tqdm - -try: - from bs4 import BeautifulSoup - from bs4.builder import ParserRejectedMarkup -except ImportError: - raise ImportError( - "Sitemap requires extra dependencies. Install with `pip install beautifulsoup4==4.12.3`" - ) from None - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.loaders.web_page import WebPageLoader - -logger = logging.getLogger(__name__) - - -@register_deserializable -class SitemapLoader(BaseLoader): - """ - This method takes a sitemap URL or local file path as input and retrieves - all the URLs to use the WebPageLoader to load content - of each page. - """ - - def load_data(self, sitemap_source): - output = [] - web_page_loader = WebPageLoader() - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36", # noqa:E501 - } - - if urlparse(sitemap_source).scheme in ("http", "https"): - try: - response = requests.get(sitemap_source, headers=headers) - response.raise_for_status() - soup = BeautifulSoup(response.text, "xml") - except requests.RequestException as e: - logger.error(f"Error fetching sitemap from URL: {e}") - return - elif os.path.isfile(sitemap_source): - with open(sitemap_source, "r") as file: - soup = BeautifulSoup(file, "xml") - else: - raise ValueError("Invalid sitemap source. Please provide a valid URL or local file path.") - - links = [link.text for link in soup.find_all("loc") if link.parent.name == "url"] - if len(links) == 0: - links = [link.text for link in soup.find_all("loc")] - - doc_id = hashlib.sha256((" ".join(links) + sitemap_source).encode()).hexdigest() - - def load_web_page(link): - try: - loader_data = web_page_loader.load_data(link) - return loader_data.get("data") - except ParserRejectedMarkup as e: - logger.error(f"Failed to parse {link}: {e}") - return None - - with concurrent.futures.ThreadPoolExecutor() as executor: - future_to_link = {executor.submit(load_web_page, link): link for link in links} - for future in tqdm(concurrent.futures.as_completed(future_to_link), total=len(links), desc="Loading pages"): - link = future_to_link[future] - try: - data = future.result() - if data: - output.extend(data) - except Exception as e: - logger.error(f"Error loading page {link}: {e}") - - return {"doc_id": doc_id, "data": output} diff --git a/embedchain/embedchain/loaders/slack.py b/embedchain/embedchain/loaders/slack.py deleted file mode 100644 index 6fb6e9db8..000000000 --- a/embedchain/embedchain/loaders/slack.py +++ /dev/null @@ -1,115 +0,0 @@ -import hashlib -import logging -import os -import ssl -from typing import Any, Optional - -import certifi - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -SLACK_API_BASE_URL = "https://www.slack.com/api/" - -logger = logging.getLogger(__name__) - - -class SlackLoader(BaseLoader): - def __init__(self, config: Optional[dict[str, Any]] = None): - super().__init__() - - self.config = config if config else {} - - if "base_url" not in self.config: - self.config["base_url"] = SLACK_API_BASE_URL - - self.client = None - self._setup_loader(self.config) - - def _setup_loader(self, config: dict[str, Any]): - try: - from slack_sdk import WebClient - except ImportError as e: - raise ImportError( - "Slack loader requires extra dependencies. \ - Install with `pip install --upgrade embedchain[slack]`" - ) from e - - if os.getenv("SLACK_USER_TOKEN") is None: - raise ValueError( - "SLACK_USER_TOKEN environment variables not provided. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501 - ) - - logger.info(f"Creating Slack Loader with config: {config}") - # get slack client config params - slack_bot_token = os.getenv("SLACK_USER_TOKEN") - ssl_cert = ssl.create_default_context(cafile=certifi.where()) - base_url = config.get("base_url", SLACK_API_BASE_URL) - headers = config.get("headers") - # for Org-Wide App - team_id = config.get("team_id") - - self.client = WebClient( - token=slack_bot_token, - base_url=base_url, - ssl=ssl_cert, - headers=headers, - team_id=team_id, - ) - logger.info("Slack Loader setup successful!") - - @staticmethod - def _check_query(query): - if not isinstance(query, str): - raise ValueError( - f"Invalid query passed to Slack loader, found: {query}. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501 - ) - - def load_data(self, query): - self._check_query(query) - try: - data = [] - data_content = [] - - logger.info(f"Searching slack conversations for query: {query}") - results = self.client.search_messages( - query=query, - sort="timestamp", - sort_dir="desc", - count=self.config.get("count", 100), - ) - - messages = results.get("messages") - num_message = len(messages) - logger.info(f"Found {num_message} messages for query: {query}") - - matches = messages.get("matches", []) - for message in matches: - url = message.get("permalink") - text = message.get("text") - content = clean_string(text) - - message_meta_data_keys = ["iid", "team", "ts", "type", "user", "username"] - metadata = {} - for key in message.keys(): - if key in message_meta_data_keys: - metadata[key] = message.get(key) - metadata.update({"url": url}) - - data.append( - { - "content": content, - "meta_data": metadata, - } - ) - data_content.append(content) - doc_id = hashlib.md5((query + ", ".join(data_content)).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } - except Exception as e: - logger.warning(f"Error in loading slack data: {e}") - raise ValueError( - f"Error in loading slack data: {e}. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501 - ) from e diff --git a/embedchain/embedchain/loaders/substack.py b/embedchain/embedchain/loaders/substack.py deleted file mode 100644 index 15c08a5bb..000000000 --- a/embedchain/embedchain/loaders/substack.py +++ /dev/null @@ -1,107 +0,0 @@ -import hashlib -import logging -import time -from xml.etree import ElementTree - -import requests - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import is_readable - -logger = logging.getLogger(__name__) - - -@register_deserializable -class SubstackLoader(BaseLoader): - """ - This loader is used to load data from Substack URLs. - """ - - def load_data(self, url: str): - try: - from bs4 import BeautifulSoup - from bs4.builder import ParserRejectedMarkup - except ImportError: - raise ImportError( - "Substack requires extra dependencies. Install with `pip install beautifulsoup4==4.12.3`" - ) from None - - if not url.endswith("sitemap.xml"): - url = url + "/sitemap.xml" - - output = [] - response = requests.get(url) - - try: - response.raise_for_status() - except requests.exceptions.HTTPError as e: - raise ValueError( - f""" - Failed to load {url}: {e}. Please use the root substack URL. For example, https://example.substack.com - """ - ) - - try: - ElementTree.fromstring(response.content) - except ElementTree.ParseError: - raise ValueError( - f""" - Failed to parse {url}. Please use the root substack URL. For example, https://example.substack.com - """ - ) - - soup = BeautifulSoup(response.text, "xml") - links = [link.text for link in soup.find_all("loc") if link.parent.name == "url" and "/p/" in link.text] - if len(links) == 0: - links = [link.text for link in soup.find_all("loc") if "/p/" in link.text] - - doc_id = hashlib.sha256((" ".join(links) + url).encode()).hexdigest() - - def serialize_response(soup: BeautifulSoup): - data = {} - - h1_els = soup.find_all("h1") - if h1_els is not None and len(h1_els) > 0: - data["title"] = h1_els[1].text - - description_el = soup.find("meta", {"name": "description"}) - if description_el is not None: - data["description"] = description_el["content"] - - content_el = soup.find("div", {"class": "available-content"}) - if content_el is not None: - data["content"] = content_el.text - - like_btn = soup.find("div", {"class": "like-button-container"}) - if like_btn is not None: - no_of_likes_div = like_btn.find("div", {"class": "label"}) - if no_of_likes_div is not None: - data["no_of_likes"] = no_of_likes_div.text - - return data - - def load_link(link: str): - try: - substack_data = requests.get(link) - substack_data.raise_for_status() - - soup = BeautifulSoup(substack_data.text, "html.parser") - data = serialize_response(soup) - data = str(data) - if is_readable(data): - return data - else: - logger.warning(f"Page is not readable (too many invalid characters): {link}") - except ParserRejectedMarkup as e: - logger.error(f"Failed to parse {link}: {e}") - return None - - for link in links: - data = load_link(link) - if data: - output.append({"content": data, "meta_data": {"url": link}}) - # TODO: allow users to configure this - time.sleep(1.0) # added to avoid rate limiting - - return {"doc_id": doc_id, "data": output} diff --git a/embedchain/embedchain/loaders/text_file.py b/embedchain/embedchain/loaders/text_file.py deleted file mode 100644 index bc7fb4b09..000000000 --- a/embedchain/embedchain/loaders/text_file.py +++ /dev/null @@ -1,30 +0,0 @@ -import hashlib -import os - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader - - -@register_deserializable -class TextFileLoader(BaseLoader): - def load_data(self, url: str): - """Load data from a text file located at a local path.""" - if not os.path.exists(url): - raise FileNotFoundError(f"The file at {url} does not exist.") - - with open(url, "r", encoding="utf-8") as file: - content = file.read() - - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - - metadata = {"url": url, "file_size": os.path.getsize(url), "file_type": url.split(".")[-1]} - - return { - "doc_id": doc_id, - "data": [ - { - "content": content, - "meta_data": metadata, - } - ], - } diff --git a/embedchain/embedchain/loaders/unstructured_file.py b/embedchain/embedchain/loaders/unstructured_file.py deleted file mode 100644 index 856ac888b..000000000 --- a/embedchain/embedchain/loaders/unstructured_file.py +++ /dev/null @@ -1,42 +0,0 @@ -import hashlib - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - - -@register_deserializable -class UnstructuredLoader(BaseLoader): - def load_data(self, url): - """Load data from an Unstructured file.""" - try: - import unstructured # noqa: F401 - from langchain_community.document_loaders import UnstructuredFileLoader - except ImportError: - raise ImportError( - 'Unstructured file requires extra dependencies. Install with `pip install "unstructured[local-inference, all-docs]"`' # noqa: E501 - ) from None - - loader = UnstructuredFileLoader(url) - data = [] - all_content = [] - pages = loader.load_and_split() - if not len(pages): - raise ValueError("No data found") - for page in pages: - content = page.page_content - content = clean_string(content) - metadata = page.metadata - metadata["url"] = url - data.append( - { - "content": content, - "meta_data": metadata, - } - ) - all_content.append(content) - doc_id = hashlib.sha256((" ".join(all_content) + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } diff --git a/embedchain/embedchain/loaders/web_page.py b/embedchain/embedchain/loaders/web_page.py deleted file mode 100644 index 848bc2038..000000000 --- a/embedchain/embedchain/loaders/web_page.py +++ /dev/null @@ -1,126 +0,0 @@ -import hashlib -import logging -from typing import Any, Optional - -import requests - -try: - from bs4 import BeautifulSoup -except ImportError: - raise ImportError( - "Webpage requires extra dependencies. Install with `pip install beautifulsoup4==4.12.3`" - ) from None - -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - -logger = logging.getLogger(__name__) - - -@register_deserializable -class WebPageLoader(BaseLoader): - # Shared session for all instances - _session = requests.Session() - - def load_data(self, url, **kwargs: Optional[dict[str, Any]]): - """Load data from a web page using a shared requests' session.""" - all_references = False - for key, value in kwargs.items(): - if key == "all_references": - all_references = kwargs["all_references"] - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36", # noqa:E501 - } - response = self._session.get(url, headers=headers, timeout=30) - response.raise_for_status() - data = response.content - reference_links = self.fetch_reference_links(response) - if all_references: - for i in reference_links: - try: - response = self._session.get(i, headers=headers, timeout=30) - response.raise_for_status() - data += response.content - except Exception as e: - logging.error(f"Failed to add URL {url}: {e}") - continue - - content = self._get_clean_content(data, url) - - metadata = {"url": url} - - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": [ - { - "content": content, - "meta_data": metadata, - } - ], - } - - @staticmethod - def _get_clean_content(html, url) -> str: - soup = BeautifulSoup(html, "html.parser") - original_size = len(str(soup.get_text())) - - tags_to_exclude = [ - "nav", - "aside", - "form", - "header", - "noscript", - "svg", - "canvas", - "footer", - "script", - "style", - ] - for tag in soup(tags_to_exclude): - tag.decompose() - - ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"] - for id_ in ids_to_exclude: - tags = soup.find_all(id=id_) - for tag in tags: - tag.decompose() - - classes_to_exclude = [ - "elementor-location-header", - "navbar-header", - "nav", - "header-sidebar-wrapper", - "blog-sidebar-wrapper", - "related-posts", - ] - for class_name in classes_to_exclude: - tags = soup.find_all(class_=class_name) - for tag in tags: - tag.decompose() - - content = soup.get_text() - content = clean_string(content) - - cleaned_size = len(content) - if original_size != 0: - logger.info( - f"[{url}] Cleaned page size: {cleaned_size} characters, down from {original_size} (shrunk: {original_size-cleaned_size} chars, {round((1-(cleaned_size/original_size)) * 100, 2)}%)" # noqa:E501 - ) - - return content - - @classmethod - def close_session(cls): - cls._session.close() - - def fetch_reference_links(self, response): - if response.status_code == 200: - soup = BeautifulSoup(response.content, "html.parser") - a_tags = soup.find_all("a", href=True) - reference_links = [a["href"] for a in a_tags if a["href"].startswith("http")] - return reference_links - else: - print(f"Failed to retrieve the page. Status code: {response.status_code}") - return [] diff --git a/embedchain/embedchain/loaders/xml.py b/embedchain/embedchain/loaders/xml.py deleted file mode 100644 index 0c2c8c748..000000000 --- a/embedchain/embedchain/loaders/xml.py +++ /dev/null @@ -1,31 +0,0 @@ -import hashlib - -try: - import unstructured # noqa: F401 - from langchain_community.document_loaders import UnstructuredXMLLoader -except ImportError: - raise ImportError( - 'XML file requires extra dependencies. Install with `pip install "unstructured[local-inference, all-docs]"`' - ) from None -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - - -@register_deserializable -class XmlLoader(BaseLoader): - def load_data(self, xml_url): - """Load data from a XML file.""" - loader = UnstructuredXMLLoader(xml_url) - data = loader.load() - content = data[0].page_content - content = clean_string(content) - metadata = data[0].metadata - metadata["url"] = metadata["source"] - del metadata["source"] - output = [{"content": content, "meta_data": metadata}] - doc_id = hashlib.sha256((content + xml_url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": output, - } diff --git a/embedchain/embedchain/loaders/youtube_channel.py b/embedchain/embedchain/loaders/youtube_channel.py deleted file mode 100644 index ab235e19a..000000000 --- a/embedchain/embedchain/loaders/youtube_channel.py +++ /dev/null @@ -1,79 +0,0 @@ -import concurrent.futures -import hashlib -import logging - -from tqdm import tqdm - -from embedchain.loaders.base_loader import BaseLoader -from embedchain.loaders.youtube_video import YoutubeVideoLoader - -logger = logging.getLogger(__name__) - - -class YoutubeChannelLoader(BaseLoader): - """Loader for youtube channel.""" - - def load_data(self, channel_name): - try: - import yt_dlp - except ImportError as e: - raise ValueError( - "YoutubeChannelLoader requires extra dependencies. Install with `pip install yt_dlp==2023.11.14 youtube-transcript-api==0.6.1`" # noqa: E501 - ) from e - - data = [] - data_urls = [] - youtube_url = f"https://www.youtube.com/{channel_name}/videos" - youtube_video_loader = YoutubeVideoLoader() - - def _get_yt_video_links(): - try: - ydl_opts = { - "quiet": True, - "extract_flat": True, - } - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - info_dict = ydl.extract_info(youtube_url, download=False) - if "entries" in info_dict: - videos = [entry["url"] for entry in info_dict["entries"]] - return videos - except Exception: - logger.error(f"Failed to fetch youtube videos for channel: {channel_name}") - return [] - - def _load_yt_video(video_link): - try: - each_load_data = youtube_video_loader.load_data(video_link) - if each_load_data: - return each_load_data.get("data") - except Exception as e: - logger.error(f"Failed to load youtube video {video_link}: {e}") - return None - - def _add_youtube_channel(): - video_links = _get_yt_video_links() - logger.info("Loading videos from youtube channel...") - with concurrent.futures.ThreadPoolExecutor() as executor: - # Submitting all tasks and storing the future object with the video link - future_to_video = { - executor.submit(_load_yt_video, video_link): video_link for video_link in video_links - } - - for future in tqdm( - concurrent.futures.as_completed(future_to_video), total=len(video_links), desc="Processing videos" - ): - video = future_to_video[future] - try: - results = future.result() - if results: - data.extend(results) - data_urls.extend([result.get("meta_data").get("url") for result in results]) - except Exception as e: - logger.error(f"Failed to process youtube video {video}: {e}") - - _add_youtube_channel() - doc_id = hashlib.sha256((youtube_url + ", ".join(data_urls)).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": data, - } diff --git a/embedchain/embedchain/loaders/youtube_video.py b/embedchain/embedchain/loaders/youtube_video.py deleted file mode 100644 index 44acc0fcf..000000000 --- a/embedchain/embedchain/loaders/youtube_video.py +++ /dev/null @@ -1,57 +0,0 @@ -import hashlib -import json -import logging - -try: - from youtube_transcript_api import YouTubeTranscriptApi -except ImportError: - raise ImportError("YouTube video requires extra dependencies. Install with `pip install youtube-transcript-api`") -try: - from langchain_community.document_loaders import YoutubeLoader - from langchain_community.document_loaders.youtube import _parse_video_id -except ImportError: - raise ImportError("YouTube video requires extra dependencies. Install with `pip install pytube==15.0.0`") from None -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.loaders.base_loader import BaseLoader -from embedchain.utils.misc import clean_string - - -@register_deserializable -class YoutubeVideoLoader(BaseLoader): - def load_data(self, url): - """Load data from a Youtube video.""" - video_id = _parse_video_id(url) - - languages = ["en"] - try: - # Fetching transcript data - languages = [transcript.language_code for transcript in YouTubeTranscriptApi.list_transcripts(video_id)] - transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=languages) - # convert transcript to json to avoid unicode symboles - transcript = json.dumps(transcript, ensure_ascii=True) - except Exception: - logging.exception(f"Failed to fetch transcript for video {url}") - transcript = "Unavailable" - - loader = YoutubeLoader.from_youtube_url(url, add_video_info=True, language=languages) - doc = loader.load() - output = [] - if not len(doc): - raise ValueError(f"No data found for url: {url}") - content = doc[0].page_content - content = clean_string(content) - metadata = doc[0].metadata - metadata["url"] = url - metadata["transcript"] = transcript - - output.append( - { - "content": content, - "meta_data": metadata, - } - ) - doc_id = hashlib.sha256((content + url).encode()).hexdigest() - return { - "doc_id": doc_id, - "data": output, - } diff --git a/embedchain/embedchain/memory/__init__.py b/embedchain/embedchain/memory/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/memory/base.py b/embedchain/embedchain/memory/base.py deleted file mode 100644 index d6697625d..000000000 --- a/embedchain/embedchain/memory/base.py +++ /dev/null @@ -1,127 +0,0 @@ -import json -import logging -import uuid -from typing import Any, Optional - -from embedchain.core.db.database import get_session -from embedchain.core.db.models import ChatHistory as ChatHistoryModel -from embedchain.memory.message import ChatMessage -from embedchain.memory.utils import merge_metadata_dict - -logger = logging.getLogger(__name__) - - -class ChatHistory: - def __init__(self) -> None: - self.db_session = get_session() - - def add(self, app_id, session_id, chat_message: ChatMessage) -> Optional[str]: - memory_id = str(uuid.uuid4()) - metadata_dict = merge_metadata_dict(chat_message.human_message.metadata, chat_message.ai_message.metadata) - if metadata_dict: - metadata = self._serialize_json(metadata_dict) - self.db_session.add( - ChatHistoryModel( - app_id=app_id, - id=memory_id, - session_id=session_id, - question=chat_message.human_message.content, - answer=chat_message.ai_message.content, - metadata=metadata if metadata_dict else "{}", - ) - ) - try: - self.db_session.commit() - except Exception as e: - logger.error(f"Error adding chat memory to db: {e}") - self.db_session.rollback() - return None - - logger.info(f"Added chat memory to db with id: {memory_id}") - return memory_id - - def delete(self, app_id: str, session_id: Optional[str] = None): - """ - Delete all chat history for a given app_id and session_id. - This is useful for deleting chat history for a given user. - - :param app_id: The app_id to delete chat history for - :param session_id: The session_id to delete chat history for - - :return: None - """ - params = {"app_id": app_id} - if session_id: - params["session_id"] = session_id - self.db_session.query(ChatHistoryModel).filter_by(**params).delete() - try: - self.db_session.commit() - except Exception as e: - logger.error(f"Error deleting chat history: {e}") - self.db_session.rollback() - - def get( - self, app_id, session_id: str = "default", num_rounds=10, fetch_all: bool = False, display_format=False - ) -> list[ChatMessage]: - """ - Get the chat history for a given app_id. - - param: app_id - The app_id to get chat history - param: session_id (optional) - The session_id to get chat history. Defaults to "default" - param: num_rounds (optional) - The number of rounds to get chat history. Defaults to 10 - param: fetch_all (optional) - Whether to fetch all chat history or not. Defaults to False - param: display_format (optional) - Whether to return the chat history in display format. Defaults to False - """ - params = {"app_id": app_id} - if not fetch_all: - params["session_id"] = session_id - results = ( - self.db_session.query(ChatHistoryModel).filter_by(**params).order_by(ChatHistoryModel.created_at.asc()) - ) - results = results.limit(num_rounds) if not fetch_all else results - history = [] - for result in results: - metadata = self._deserialize_json(metadata=result.meta_data or "{}") - # Return list of dict if display_format is True - if display_format: - history.append( - { - "session_id": result.session_id, - "human": result.question, - "ai": result.answer, - "metadata": result.meta_data, - "timestamp": result.created_at, - } - ) - else: - memory = ChatMessage() - memory.add_user_message(result.question, metadata=metadata) - memory.add_ai_message(result.answer, metadata=metadata) - history.append(memory) - return history - - def count(self, app_id: str, session_id: Optional[str] = None): - """ - Count the number of chat messages for a given app_id and session_id. - - :param app_id: The app_id to count chat history for - :param session_id: The session_id to count chat history for - - :return: The number of chat messages for a given app_id and session_id - """ - # Rewrite the logic below with sqlalchemy - params = {"app_id": app_id} - if session_id: - params["session_id"] = session_id - return self.db_session.query(ChatHistoryModel).filter_by(**params).count() - - @staticmethod - def _serialize_json(metadata: dict[str, Any]): - return json.dumps(metadata) - - @staticmethod - def _deserialize_json(metadata: str): - return json.loads(metadata) - - def close_connection(self): - self.connection.close() diff --git a/embedchain/embedchain/memory/message.py b/embedchain/embedchain/memory/message.py deleted file mode 100644 index 5211b0f6a..000000000 --- a/embedchain/embedchain/memory/message.py +++ /dev/null @@ -1,74 +0,0 @@ -import logging -from typing import Any, Optional - -from embedchain.helpers.json_serializable import JSONSerializable - -logger = logging.getLogger(__name__) - - -class BaseMessage(JSONSerializable): - """ - The base abstract message class. - - Messages are the inputs and outputs of Models. - """ - - # The string content of the message. - content: str - - # The created_by of the message. AI, Human, Bot etc. - created_by: str - - # Any additional info. - metadata: dict[str, Any] - - def __init__(self, content: str, created_by: str, metadata: Optional[dict[str, Any]] = None) -> None: - super().__init__() - self.content = content - self.created_by = created_by - self.metadata = metadata - - @property - def type(self) -> str: - """Type of the Message, used for serialization.""" - - @classmethod - def is_lc_serializable(cls) -> bool: - """Return whether this class is serializable.""" - return True - - def __str__(self) -> str: - return f"{self.created_by}: {self.content}" - - -class ChatMessage(JSONSerializable): - """ - The base abstract chat message class. - - Chat messages are the pair of (question, answer) conversation - between human and model. - """ - - human_message: Optional[BaseMessage] = None - ai_message: Optional[BaseMessage] = None - - def add_user_message(self, message: str, metadata: Optional[dict] = None): - if self.human_message: - logger.info( - "Human message already exists in the chat message,\ - overwriting it with new message." - ) - - self.human_message = BaseMessage(content=message, created_by="human", metadata=metadata) - - def add_ai_message(self, message: str, metadata: Optional[dict] = None): - if self.ai_message: - logger.info( - "AI message already exists in the chat message,\ - overwriting it with new message." - ) - - self.ai_message = BaseMessage(content=message, created_by="ai", metadata=metadata) - - def __str__(self) -> str: - return f"{self.human_message}\n{self.ai_message}" diff --git a/embedchain/embedchain/memory/utils.py b/embedchain/embedchain/memory/utils.py deleted file mode 100644 index b849cffa6..000000000 --- a/embedchain/embedchain/memory/utils.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Any, Optional - - -def merge_metadata_dict(left: Optional[dict[str, Any]], right: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]: - """ - Merge the metadatas of two BaseMessage types. - - Args: - left (dict[str, Any]): metadata of human message - right (dict[str, Any]): metadata of AI message - - Returns: - dict[str, Any]: combined metadata dict with dedup - to be saved in db. - """ - if not left and not right: - return None - elif not left: - return right - elif not right: - return left - - merged = left.copy() - for k, v in right.items(): - if k not in merged: - merged[k] = v - elif type(merged[k]) is not type(v): - raise ValueError(f'additional_kwargs["{k}"] already exists in this message,' " but with a different type.") - elif isinstance(merged[k], str): - merged[k] += v - elif isinstance(merged[k], dict): - merged[k] = merge_metadata_dict(merged[k], v) - else: - raise ValueError(f"Additional kwargs key {k} already exists in this message.") - return merged diff --git a/embedchain/embedchain/migrations/env.py b/embedchain/embedchain/migrations/env.py deleted file mode 100644 index 8fb3cd805..000000000 --- a/embedchain/embedchain/migrations/env.py +++ /dev/null @@ -1,68 +0,0 @@ -import os - -from alembic import context -from sqlalchemy import engine_from_config, pool - -from embedchain.core.db.models import Base - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -target_metadata = Base.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. -config.set_main_option("sqlalchemy.url", os.environ.get("EMBEDCHAIN_DB_URI")) - - -def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - connectable = engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/embedchain/embedchain/migrations/script.py.mako b/embedchain/embedchain/migrations/script.py.mako deleted file mode 100644 index fbc4b07dc..000000000 --- a/embedchain/embedchain/migrations/script.py.mako +++ /dev/null @@ -1,26 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision: str = ${repr(up_revision)} -down_revision: Union[str, None] = ${repr(down_revision)} -branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} -depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} - - -def upgrade() -> None: - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - ${downgrades if downgrades else "pass"} diff --git a/embedchain/embedchain/migrations/versions/40a327b3debd_create_initial_migrations.py b/embedchain/embedchain/migrations/versions/40a327b3debd_create_initial_migrations.py deleted file mode 100644 index 1facc88e3..000000000 --- a/embedchain/embedchain/migrations/versions/40a327b3debd_create_initial_migrations.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Create initial migrations - -Revision ID: 40a327b3debd -Revises: -Create Date: 2024-02-18 15:29:19.409064 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "40a327b3debd" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "ec_chat_history", - sa.Column("app_id", sa.String(), nullable=False), - sa.Column("id", sa.String(), nullable=False), - sa.Column("session_id", sa.String(), nullable=False), - sa.Column("question", sa.Text(), nullable=True), - sa.Column("answer", sa.Text(), nullable=True), - sa.Column("metadata", sa.Text(), nullable=True), - sa.Column("created_at", sa.TIMESTAMP(), nullable=True), - sa.PrimaryKeyConstraint("app_id", "id", "session_id"), - ) - op.create_index(op.f("ix_ec_chat_history_created_at"), "ec_chat_history", ["created_at"], unique=False) - op.create_index(op.f("ix_ec_chat_history_session_id"), "ec_chat_history", ["session_id"], unique=False) - op.create_table( - "ec_data_sources", - sa.Column("id", sa.String(), nullable=False), - sa.Column("app_id", sa.Text(), nullable=True), - sa.Column("hash", sa.Text(), nullable=True), - sa.Column("type", sa.Text(), nullable=True), - sa.Column("value", sa.Text(), nullable=True), - sa.Column("metadata", sa.Text(), nullable=True), - sa.Column("is_uploaded", sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index(op.f("ix_ec_data_sources_hash"), "ec_data_sources", ["hash"], unique=False) - op.create_index(op.f("ix_ec_data_sources_app_id"), "ec_data_sources", ["app_id"], unique=False) - op.create_index(op.f("ix_ec_data_sources_type"), "ec_data_sources", ["type"], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f("ix_ec_data_sources_type"), table_name="ec_data_sources") - op.drop_index(op.f("ix_ec_data_sources_app_id"), table_name="ec_data_sources") - op.drop_index(op.f("ix_ec_data_sources_hash"), table_name="ec_data_sources") - op.drop_table("ec_data_sources") - op.drop_index(op.f("ix_ec_chat_history_session_id"), table_name="ec_chat_history") - op.drop_index(op.f("ix_ec_chat_history_created_at"), table_name="ec_chat_history") - op.drop_table("ec_chat_history") - # ### end Alembic commands ### diff --git a/embedchain/embedchain/models/__init__.py b/embedchain/embedchain/models/__init__.py deleted file mode 100644 index 48887545b..000000000 --- a/embedchain/embedchain/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .embedding_functions import EmbeddingFunctions # noqa: F401 -from .providers import Providers # noqa: F401 -from .vector_dimensions import VectorDimensions # noqa: F401 diff --git a/embedchain/embedchain/models/data_type.py b/embedchain/embedchain/models/data_type.py deleted file mode 100644 index 6370bf064..000000000 --- a/embedchain/embedchain/models/data_type.py +++ /dev/null @@ -1,85 +0,0 @@ -from enum import Enum - - -class DirectDataType(Enum): - """ - DirectDataType enum contains data types that contain raw data directly. - """ - - TEXT = "text" - - -class IndirectDataType(Enum): - """ - IndirectDataType enum contains data types that contain references to data stored elsewhere. - """ - - YOUTUBE_VIDEO = "youtube_video" - PDF_FILE = "pdf_file" - WEB_PAGE = "web_page" - SITEMAP = "sitemap" - XML = "xml" - DOCX = "docx" - DOCS_SITE = "docs_site" - NOTION = "notion" - CSV = "csv" - MDX = "mdx" - IMAGE = "image" - UNSTRUCTURED = "unstructured" - JSON = "json" - OPENAPI = "openapi" - GMAIL = "gmail" - SUBSTACK = "substack" - YOUTUBE_CHANNEL = "youtube_channel" - DISCORD = "discord" - CUSTOM = "custom" - RSSFEED = "rss_feed" - BEEHIIV = "beehiiv" - GOOGLE_DRIVE = "google_drive" - DIRECTORY = "directory" - SLACK = "slack" - DROPBOX = "dropbox" - TEXT_FILE = "text_file" - EXCEL_FILE = "excel_file" - AUDIO = "audio" - - -class SpecialDataType(Enum): - """ - SpecialDataType enum contains data types that are neither direct nor indirect, or simply require special attention. - """ - - QNA_PAIR = "qna_pair" - - -class DataType(Enum): - TEXT = DirectDataType.TEXT.value - YOUTUBE_VIDEO = IndirectDataType.YOUTUBE_VIDEO.value - PDF_FILE = IndirectDataType.PDF_FILE.value - WEB_PAGE = IndirectDataType.WEB_PAGE.value - SITEMAP = IndirectDataType.SITEMAP.value - XML = IndirectDataType.XML.value - DOCX = IndirectDataType.DOCX.value - DOCS_SITE = IndirectDataType.DOCS_SITE.value - NOTION = IndirectDataType.NOTION.value - CSV = IndirectDataType.CSV.value - MDX = IndirectDataType.MDX.value - QNA_PAIR = SpecialDataType.QNA_PAIR.value - IMAGE = IndirectDataType.IMAGE.value - UNSTRUCTURED = IndirectDataType.UNSTRUCTURED.value - JSON = IndirectDataType.JSON.value - OPENAPI = IndirectDataType.OPENAPI.value - GMAIL = IndirectDataType.GMAIL.value - SUBSTACK = IndirectDataType.SUBSTACK.value - YOUTUBE_CHANNEL = IndirectDataType.YOUTUBE_CHANNEL.value - DISCORD = IndirectDataType.DISCORD.value - CUSTOM = IndirectDataType.CUSTOM.value - RSSFEED = IndirectDataType.RSSFEED.value - BEEHIIV = IndirectDataType.BEEHIIV.value - GOOGLE_DRIVE = IndirectDataType.GOOGLE_DRIVE.value - DIRECTORY = IndirectDataType.DIRECTORY.value - SLACK = IndirectDataType.SLACK.value - DROPBOX = IndirectDataType.DROPBOX.value - TEXT_FILE = IndirectDataType.TEXT_FILE.value - EXCEL_FILE = IndirectDataType.EXCEL_FILE.value - AUDIO = IndirectDataType.AUDIO.value diff --git a/embedchain/embedchain/models/embedding_functions.py b/embedchain/embedchain/models/embedding_functions.py deleted file mode 100644 index 7171fadfa..000000000 --- a/embedchain/embedchain/models/embedding_functions.py +++ /dev/null @@ -1,10 +0,0 @@ -from enum import Enum - - -class EmbeddingFunctions(Enum): - OPENAI = "OPENAI" - HUGGING_FACE = "HUGGING_FACE" - VERTEX_AI = "VERTEX_AI" - AWS_BEDROCK = "AWS_BEDROCK" - GPT4ALL = "GPT4ALL" - OLLAMA = "OLLAMA" diff --git a/embedchain/embedchain/models/providers.py b/embedchain/embedchain/models/providers.py deleted file mode 100644 index 62c93675b..000000000 --- a/embedchain/embedchain/models/providers.py +++ /dev/null @@ -1,10 +0,0 @@ -from enum import Enum - - -class Providers(Enum): - OPENAI = "OPENAI" - ANTHROPHIC = "ANTHPROPIC" - VERTEX_AI = "VERTEX_AI" - GPT4ALL = "GPT4ALL" - OLLAMA = "OLLAMA" - AZURE_OPENAI = "AZURE_OPENAI" diff --git a/embedchain/embedchain/models/vector_dimensions.py b/embedchain/embedchain/models/vector_dimensions.py deleted file mode 100644 index bdea70756..000000000 --- a/embedchain/embedchain/models/vector_dimensions.py +++ /dev/null @@ -1,16 +0,0 @@ -from enum import Enum - - -# vector length created by embedding fn -class VectorDimensions(Enum): - GPT4ALL = 384 - OPENAI = 1536 - VERTEX_AI = 768 - HUGGING_FACE = 384 - GOOGLE_AI = 768 - MISTRAL_AI = 1024 - NVIDIA_AI = 1024 - COHERE = 384 - OLLAMA = 384 - AMAZON_TITAN_V1 = 1536 - AMAZON_TITAN_V2 = 1024 diff --git a/embedchain/embedchain/pipeline.py b/embedchain/embedchain/pipeline.py deleted file mode 100644 index 6f70bfb5d..000000000 --- a/embedchain/embedchain/pipeline.py +++ /dev/null @@ -1,9 +0,0 @@ -from embedchain.app import App - - -class Pipeline(App): - """ - This is deprecated. Use `App` instead. - """ - - pass diff --git a/embedchain/embedchain/store/__init__.py b/embedchain/embedchain/store/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/store/assistants.py b/embedchain/embedchain/store/assistants.py deleted file mode 100644 index b9ca151ab..000000000 --- a/embedchain/embedchain/store/assistants.py +++ /dev/null @@ -1,206 +0,0 @@ -import logging -import os -import re -import tempfile -import time -import uuid -from pathlib import Path -from typing import cast - -from openai import OpenAI -from openai.types.beta.threads import Message -from openai.types.beta.threads.text_content_block import TextContentBlock - -from embedchain import Client, Pipeline -from embedchain.config import AddConfig -from embedchain.data_formatter import DataFormatter -from embedchain.models.data_type import DataType -from embedchain.telemetry.posthog import AnonymousTelemetry -from embedchain.utils.misc import detect_datatype - -# Set up the user directory if it doesn't exist already -Client.setup() - - -class OpenAIAssistant: - def __init__( - self, - name=None, - instructions=None, - tools=None, - thread_id=None, - model="gpt-4-1106-preview", - data_sources=None, - assistant_id=None, - log_level=logging.INFO, - collect_metrics=True, - ): - self.name = name or "OpenAI Assistant" - self.instructions = instructions - self.tools = tools or [{"type": "retrieval"}] - self.model = model - self.data_sources = data_sources or [] - self.log_level = log_level - self._client = OpenAI() - self._initialize_assistant(assistant_id) - self.thread_id = thread_id or self._create_thread() - self._telemetry_props = {"class": self.__class__.__name__} - self.telemetry = AnonymousTelemetry(enabled=collect_metrics) - self.telemetry.capture(event_name="init", properties=self._telemetry_props) - - def add(self, source, data_type=None): - file_path = self._prepare_source_path(source, data_type) - self._add_file_to_assistant(file_path) - - event_props = { - **self._telemetry_props, - "data_type": data_type or detect_datatype(source), - } - self.telemetry.capture(event_name="add", properties=event_props) - logging.info("Data successfully added to the assistant.") - - def chat(self, message): - self._send_message(message) - self.telemetry.capture(event_name="chat", properties=self._telemetry_props) - return self._get_latest_response() - - def delete_thread(self): - self._client.beta.threads.delete(self.thread_id) - self.thread_id = self._create_thread() - - # Internal methods - def _initialize_assistant(self, assistant_id): - file_ids = self._generate_file_ids(self.data_sources) - self.assistant = ( - self._client.beta.assistants.retrieve(assistant_id) - if assistant_id - else self._client.beta.assistants.create( - name=self.name, model=self.model, file_ids=file_ids, instructions=self.instructions, tools=self.tools - ) - ) - - def _create_thread(self): - thread = self._client.beta.threads.create() - return thread.id - - def _prepare_source_path(self, source, data_type=None): - if Path(source).is_file(): - return source - data_type = data_type or detect_datatype(source) - formatter = DataFormatter(data_type=DataType(data_type), config=AddConfig()) - data = formatter.loader.load_data(source)["data"] - return self._save_temp_data(data=data[0]["content"].encode(), source=source) - - def _add_file_to_assistant(self, file_path): - file_obj = self._client.files.create(file=open(file_path, "rb"), purpose="assistants") - self._client.beta.assistants.files.create(assistant_id=self.assistant.id, file_id=file_obj.id) - - def _generate_file_ids(self, data_sources): - return [ - self._add_file_to_assistant(self._prepare_source_path(ds["source"], ds.get("data_type"))) - for ds in data_sources - ] - - def _send_message(self, message): - self._client.beta.threads.messages.create(thread_id=self.thread_id, role="user", content=message) - self._wait_for_completion() - - def _wait_for_completion(self): - run = self._client.beta.threads.runs.create( - thread_id=self.thread_id, - assistant_id=self.assistant.id, - instructions=self.instructions, - ) - run_id = run.id - run_status = run.status - - while run_status in ["queued", "in_progress", "requires_action"]: - time.sleep(0.1) # Sleep before making the next API call to avoid hitting rate limits - run = self._client.beta.threads.runs.retrieve(thread_id=self.thread_id, run_id=run_id) - run_status = run.status - if run_status == "failed": - raise ValueError(f"Thread run failed with the following error: {run.last_error}") - - def _get_latest_response(self): - history = self._get_history() - return self._format_message(history[0]) if history else None - - def _get_history(self): - messages = self._client.beta.threads.messages.list(thread_id=self.thread_id, order="desc") - return list(messages) - - @staticmethod - def _format_message(thread_message): - thread_message = cast(Message, thread_message) - content = [c.text.value for c in thread_message.content if isinstance(c, TextContentBlock)] - return " ".join(content) - - @staticmethod - def _save_temp_data(data, source): - special_chars_pattern = r'[\\/:*?"<>|&=% ]+' - sanitized_source = re.sub(special_chars_pattern, "_", source)[:256] - temp_dir = tempfile.mkdtemp() - file_path = os.path.join(temp_dir, sanitized_source) - with open(file_path, "wb") as file: - file.write(data) - return file_path - - -class AIAssistant: - def __init__( - self, - name=None, - instructions=None, - yaml_path=None, - assistant_id=None, - thread_id=None, - data_sources=None, - log_level=logging.INFO, - collect_metrics=True, - ): - self.name = name or "AI Assistant" - self.data_sources = data_sources or [] - self.log_level = log_level - self.instructions = instructions - self.assistant_id = assistant_id or str(uuid.uuid4()) - self.thread_id = thread_id or str(uuid.uuid4()) - self.pipeline = Pipeline.from_config(config_path=yaml_path) if yaml_path else Pipeline() - self.pipeline.local_id = self.pipeline.config.id = self.thread_id - - if self.instructions: - self.pipeline.system_prompt = self.instructions - - print( - f"🎉 Created AI Assistant with name: {self.name}, assistant_id: {self.assistant_id}, thread_id: {self.thread_id}" # noqa: E501 - ) - - # telemetry related properties - self._telemetry_props = {"class": self.__class__.__name__} - self.telemetry = AnonymousTelemetry(enabled=collect_metrics) - self.telemetry.capture(event_name="init", properties=self._telemetry_props) - - if self.data_sources: - for data_source in self.data_sources: - metadata = {"assistant_id": self.assistant_id, "thread_id": "global_knowledge"} - self.pipeline.add(data_source["source"], data_source.get("data_type"), metadata=metadata) - - def add(self, source, data_type=None): - metadata = {"assistant_id": self.assistant_id, "thread_id": self.thread_id} - self.pipeline.add(source, data_type=data_type, metadata=metadata) - event_props = { - **self._telemetry_props, - "data_type": data_type or detect_datatype(source), - } - self.telemetry.capture(event_name="add", properties=event_props) - - def chat(self, query): - where = { - "$and": [ - {"assistant_id": {"$eq": self.assistant_id}}, - {"thread_id": {"$in": [self.thread_id, "global_knowledge"]}}, - ] - } - return self.pipeline.chat(query, where=where) - - def delete(self): - self.pipeline.reset() diff --git a/embedchain/embedchain/telemetry/__init__.py b/embedchain/embedchain/telemetry/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/telemetry/posthog.py b/embedchain/embedchain/telemetry/posthog.py deleted file mode 100644 index 37c63ea61..000000000 --- a/embedchain/embedchain/telemetry/posthog.py +++ /dev/null @@ -1,60 +0,0 @@ -import json -import logging -import os -import uuid - -from posthog import Posthog - -import embedchain -from embedchain.constants import CONFIG_DIR, CONFIG_FILE - - -class AnonymousTelemetry: - def __init__(self, host="https://app.posthog.com", enabled=True): - self.project_api_key = "phc_PHQDA5KwztijnSojsxJ2c1DuJd52QCzJzT2xnSGvjN2" - self.host = host - self.posthog = Posthog(project_api_key=self.project_api_key, host=self.host) - self.user_id = self._get_user_id() - self.enabled = enabled - - # Check if telemetry tracking is disabled via environment variable - if "EC_TELEMETRY" in os.environ and os.environ["EC_TELEMETRY"].lower() not in [ - "1", - "true", - "yes", - ]: - self.enabled = False - - if not self.enabled: - self.posthog.disabled = True - - # Silence posthog logging - posthog_logger = logging.getLogger("posthog") - posthog_logger.disabled = True - - @staticmethod - def _get_user_id(): - os.makedirs(CONFIG_DIR, exist_ok=True) - if os.path.exists(CONFIG_FILE): - with open(CONFIG_FILE, "r") as f: - data = json.load(f) - if "user_id" in data: - return data["user_id"] - - user_id = str(uuid.uuid4()) - with open(CONFIG_FILE, "w") as f: - json.dump({"user_id": user_id}, f) - return user_id - - def capture(self, event_name, properties=None): - default_properties = { - "version": embedchain.__version__, - "language": "python", - "pid": os.getpid(), - } - properties.update(default_properties) - - try: - self.posthog.capture(self.user_id, event_name, properties) - except Exception: - logging.exception(f"Failed to send telemetry {event_name=}") diff --git a/embedchain/embedchain/utils/__init__.py b/embedchain/embedchain/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/utils/cli.py b/embedchain/embedchain/utils/cli.py deleted file mode 100644 index 13128df5a..000000000 --- a/embedchain/embedchain/utils/cli.py +++ /dev/null @@ -1,320 +0,0 @@ -import os -import re -import shutil -import subprocess - -import pkg_resources -from rich.console import Console - -console = Console() - - -def get_pkg_path_from_name(template: str): - try: - # Determine the installation location of the embedchain package - package_path = pkg_resources.resource_filename("embedchain", "") - except ImportError: - console.print("❌ [bold red]Failed to locate the 'embedchain' package. Is it installed?[/bold red]") - return - - # Construct the source path from the embedchain package - src_path = os.path.join(package_path, "deployment", template) - - if not os.path.exists(src_path): - console.print(f"❌ [bold red]Template '{template}' not found.[/bold red]") - return - - return src_path - - -def setup_fly_io_app(extra_args): - fly_launch_command = ["fly", "launch", "--region", "sjc", "--no-deploy"] + list(extra_args) - try: - console.print(f"🚀 [bold cyan]Running: {' '.join(fly_launch_command)}[/bold cyan]") - shutil.move(".env.example", ".env") - subprocess.run(fly_launch_command, check=True) - console.print("✅ [bold green]'fly launch' executed successfully.[/bold green]") - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except FileNotFoundError: - console.print( - "❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]" - ) - - -def setup_modal_com_app(extra_args): - modal_setup_file = os.path.join(os.path.expanduser("~"), ".modal.toml") - if os.path.exists(modal_setup_file): - console.print( - """✅ [bold green]Modal setup already done. You can now install the dependencies by doing \n - `pip install -r requirements.txt`[/bold green]""" - ) - else: - modal_setup_cmd = ["modal", "setup"] + list(extra_args) - console.print(f"🚀 [bold cyan]Running: {' '.join(modal_setup_cmd)}[/bold cyan]") - subprocess.run(modal_setup_cmd, check=True) - shutil.move(".env.example", ".env") - console.print( - """Great! Now you can install the dependencies by doing: \n - `pip install -r requirements.txt`\n - \n - To run your app locally:\n - `ec dev` - """ - ) - - -def setup_render_com_app(): - render_setup_file = os.path.join(os.path.expanduser("~"), ".render/config.yaml") - if os.path.exists(render_setup_file): - console.print( - """✅ [bold green]Render setup already done. You can now install the dependencies by doing \n - `pip install -r requirements.txt`[/bold green]""" - ) - else: - render_setup_cmd = ["render", "config", "init"] - console.print(f"🚀 [bold cyan]Running: {' '.join(render_setup_cmd)}[/bold cyan]") - subprocess.run(render_setup_cmd, check=True) - shutil.move(".env.example", ".env") - console.print( - """Great! Now you can install the dependencies by doing: \n - `pip install -r requirements.txt`\n - \n - To run your app locally:\n - `ec dev` - """ - ) - - -def setup_streamlit_io_app(): - # nothing needs to be done here - console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`") - - -def setup_gradio_app(): - # nothing needs to be done here - console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`") - - -def setup_hf_app(): - subprocess.run(["pip", "install", "huggingface_hub[cli]"], check=True) - hf_setup_file = os.path.join(os.path.expanduser("~"), ".cache/huggingface/token") - if os.path.exists(hf_setup_file): - console.print( - """✅ [bold green]HuggingFace setup already done. You can now install the dependencies by doing \n - `pip install -r requirements.txt`[/bold green]""" - ) - else: - console.print( - """🚀 [cyan]Running: huggingface-cli login \n - Please provide a [bold]WRITE[/bold] token so that we can directly deploy\n - your apps from the terminal.[/cyan] - """ - ) - subprocess.run(["huggingface-cli", "login"], check=True) - console.print("Great! Now you can install the dependencies by doing `pip install -r requirements.txt`") - - -def run_dev_fly_io(debug, host, port): - uvicorn_command = ["uvicorn", "app:app"] - - if debug: - uvicorn_command.append("--reload") - - uvicorn_command.extend(["--host", host, "--port", str(port)]) - - try: - console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]") - subprocess.run(uvicorn_command, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") - - -def run_dev_modal_com(): - modal_run_cmd = ["modal", "serve", "app"] - try: - console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(modal_run_cmd)}[/bold cyan]") - subprocess.run(modal_run_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") - - -def run_dev_streamlit_io(): - streamlit_run_cmd = ["streamlit", "run", "app.py"] - try: - console.print(f"🚀 [bold cyan]Running Streamlit app with command: {' '.join(streamlit_run_cmd)}[/bold cyan]") - subprocess.run(streamlit_run_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]Streamlit server stopped[/bold yellow]") - - -def run_dev_render_com(debug, host, port): - uvicorn_command = ["uvicorn", "app:app"] - - if debug: - uvicorn_command.append("--reload") - - uvicorn_command.extend(["--host", host, "--port", str(port)]) - - try: - console.print(f"🚀 [bold cyan]Running FastAPI app with command: {' '.join(uvicorn_command)}[/bold cyan]") - subprocess.run(uvicorn_command, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]FastAPI server stopped[/bold yellow]") - - -def run_dev_gradio(): - gradio_run_cmd = ["gradio", "app.py"] - try: - console.print(f"🚀 [bold cyan]Running Gradio app with command: {' '.join(gradio_run_cmd)}[/bold cyan]") - subprocess.run(gradio_run_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except KeyboardInterrupt: - console.print("\n🛑 [bold yellow]Gradio server stopped[/bold yellow]") - - -def read_env_file(env_file_path): - """ - Reads an environment file and returns a dictionary of key-value pairs. - - Args: - env_file_path (str): The path to the .env file. - - Returns: - dict: Dictionary of environment variables. - """ - env_vars = {} - pattern = re.compile(r"(\w+)=(.*)") # compile regular expression for better performance - with open(env_file_path, "r") as file: - lines = file.readlines() # readlines is faster as it reads all at once - for line in lines: - line = line.strip() - # Ignore comments and empty lines - if line and not line.startswith("#"): - # Assume each line is in the format KEY=VALUE - key_value_match = pattern.match(line) - if key_value_match: - key, value = key_value_match.groups() - env_vars[key] = value - return env_vars - - -def deploy_fly(): - app_name = "" - with open("fly.toml", "r") as file: - for line in file: - if line.strip().startswith("app ="): - app_name = line.split("=")[1].strip().strip('"') - - if not app_name: - console.print("❌ [bold red]App name not found in fly.toml[/bold red]") - return - - env_vars = read_env_file(".env") - secrets_command = ["flyctl", "secrets", "set", "-a", app_name] + [f"{k}={v}" for k, v in env_vars.items()] - - deploy_command = ["fly", "deploy"] - try: - # Set secrets - console.print(f"🔐 [bold cyan]Setting secrets for {app_name}[/bold cyan]") - subprocess.run(secrets_command, check=True) - - # Deploy application - console.print(f"🚀 [bold cyan]Running: {' '.join(deploy_command)}[/bold cyan]") - subprocess.run(deploy_command, check=True) - console.print("✅ [bold green]'fly deploy' executed successfully.[/bold green]") - - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except FileNotFoundError: - console.print( - "❌ [bold red]'fly' command not found. Please ensure Fly CLI is installed and in your PATH.[/bold red]" - ) - - -def deploy_modal(): - modal_deploy_cmd = ["modal", "deploy", "app"] - try: - console.print(f"🚀 [bold cyan]Running: {' '.join(modal_deploy_cmd)}[/bold cyan]") - subprocess.run(modal_deploy_cmd, check=True) - console.print("✅ [bold green]'modal deploy' executed successfully.[/bold green]") - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except FileNotFoundError: - console.print( - "❌ [bold red]'modal' command not found. Please ensure Modal CLI is installed and in your PATH.[/bold red]" - ) - - -def deploy_streamlit(): - streamlit_deploy_cmd = ["streamlit", "run", "app.py"] - try: - console.print(f"🚀 [bold cyan]Running: {' '.join(streamlit_deploy_cmd)}[/bold cyan]") - console.print( - """\n\n✅ [bold yellow]To deploy a streamlit app, you can directly it from the UI.\n - Click on the 'Deploy' button on the top right corner of the app.\n - For more information, please refer to https://docs.embedchain.ai/deployment/streamlit_io - [/bold yellow] - \n\n""" - ) - subprocess.run(streamlit_deploy_cmd, check=True) - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except FileNotFoundError: - console.print( - """❌ [bold red]'streamlit' command not found.\n - Please ensure Streamlit CLI is installed and in your PATH.[/bold red]""" - ) - - -def deploy_render(): - render_deploy_cmd = ["render", "blueprint", "launch"] - - try: - console.print(f"🚀 [bold cyan]Running: {' '.join(render_deploy_cmd)}[/bold cyan]") - subprocess.run(render_deploy_cmd, check=True) - console.print("✅ [bold green]'render blueprint launch' executed successfully.[/bold green]") - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except FileNotFoundError: - console.print( - "❌ [bold red]'render' command not found. Please ensure Render CLI is installed and in your PATH.[/bold red]" # noqa:E501 - ) - - -def deploy_gradio_app(): - gradio_deploy_cmd = ["gradio", "deploy"] - - try: - console.print(f"🚀 [bold cyan]Running: {' '.join(gradio_deploy_cmd)}[/bold cyan]") - subprocess.run(gradio_deploy_cmd, check=True) - console.print("✅ [bold green]'gradio deploy' executed successfully.[/bold green]") - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") - except FileNotFoundError: - console.print( - "❌ [bold red]'gradio' command not found. Please ensure Gradio CLI is installed and in your PATH.[/bold red]" # noqa:E501 - ) - - -def deploy_hf_spaces(ec_app_name): - if not ec_app_name: - console.print("❌ [bold red]'name' not found in embedchain.json[/bold red]") - return - hf_spaces_deploy_cmd = ["huggingface-cli", "upload", ec_app_name, ".", ".", "--repo-type=space"] - - try: - console.print(f"🚀 [bold cyan]Running: {' '.join(hf_spaces_deploy_cmd)}[/bold cyan]") - subprocess.run(hf_spaces_deploy_cmd, check=True) - console.print("✅ [bold green]'huggingface-cli upload' executed successfully.[/bold green]") - except subprocess.CalledProcessError as e: - console.print(f"❌ [bold red]An error occurred: {e}[/bold red]") diff --git a/embedchain/embedchain/utils/evaluation.py b/embedchain/embedchain/utils/evaluation.py deleted file mode 100644 index 62eaaeb70..000000000 --- a/embedchain/embedchain/utils/evaluation.py +++ /dev/null @@ -1,17 +0,0 @@ -from enum import Enum -from typing import Optional - -from pydantic import BaseModel - - -class EvalMetric(Enum): - CONTEXT_RELEVANCY = "context_relevancy" - ANSWER_RELEVANCY = "answer_relevancy" - GROUNDEDNESS = "groundedness" - - -class EvalData(BaseModel): - question: str - contexts: list[str] - answer: str - ground_truth: Optional[str] = None # Not used as of now diff --git a/embedchain/embedchain/utils/misc.py b/embedchain/embedchain/utils/misc.py deleted file mode 100644 index 7c5468ec9..000000000 --- a/embedchain/embedchain/utils/misc.py +++ /dev/null @@ -1,546 +0,0 @@ -import datetime -import itertools -import json -import logging -import os -import re -import string -from typing import Any - -from schema import Optional, Or, Schema -from tqdm import tqdm - -from embedchain.models.data_type import DataType - -logger = logging.getLogger(__name__) - - -def parse_content(content, type): - implemented = ["html.parser", "lxml", "lxml-xml", "xml", "html5lib"] - if type not in implemented: - raise ValueError(f"Parser type {type} not implemented. Please choose one of {implemented}") - - from bs4 import BeautifulSoup - - soup = BeautifulSoup(content, type) - original_size = len(str(soup.get_text())) - - tags_to_exclude = [ - "nav", - "aside", - "form", - "header", - "noscript", - "svg", - "canvas", - "footer", - "script", - "style", - ] - for tag in soup(tags_to_exclude): - tag.decompose() - - ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"] - for id in ids_to_exclude: - tags = soup.find_all(id=id) - for tag in tags: - tag.decompose() - - classes_to_exclude = [ - "elementor-location-header", - "navbar-header", - "nav", - "header-sidebar-wrapper", - "blog-sidebar-wrapper", - "related-posts", - ] - for class_name in classes_to_exclude: - tags = soup.find_all(class_=class_name) - for tag in tags: - tag.decompose() - - content = soup.get_text() - content = clean_string(content) - - cleaned_size = len(content) - if original_size != 0: - logger.info( - f"Cleaned page size: {cleaned_size} characters, down from {original_size} (shrunk: {original_size-cleaned_size} chars, {round((1-(cleaned_size/original_size)) * 100, 2)}%)" # noqa:E501 - ) - - return content - - -def clean_string(text): - """ - This function takes in a string and performs a series of text cleaning operations. - - Args: - text (str): The text to be cleaned. This is expected to be a string. - - Returns: - cleaned_text (str): The cleaned text after all the cleaning operations - have been performed. - """ - # Stripping and reducing multiple spaces to single: - cleaned_text = re.sub(r"\s+", " ", text.strip()) - - # Removing backslashes: - cleaned_text = cleaned_text.replace("\\", "") - - # Replacing hash characters: - cleaned_text = cleaned_text.replace("#", " ") - - # Eliminating consecutive non-alphanumeric characters: - # This regex identifies consecutive non-alphanumeric characters (i.e., not - # a word character [a-zA-Z0-9_] and not a whitespace) in the string - # and replaces each group of such characters with a single occurrence of - # that character. - # For example, "!!! hello !!!" would become "! hello !". - cleaned_text = re.sub(r"([^\w\s])\1*", r"\1", cleaned_text) - - return cleaned_text - - -def is_readable(s): - """ - Heuristic to determine if a string is "readable" (mostly contains printable characters and forms meaningful words) - - :param s: string - :return: True if the string is more than 95% printable. - """ - len_s = len(s) - if len_s == 0: - return False - printable_chars = set(string.printable) - printable_ratio = sum(c in printable_chars for c in s) / len_s - return printable_ratio > 0.95 # 95% of characters are printable - - -def use_pysqlite3(): - """ - Swap std-lib sqlite3 with pysqlite3. - """ - import platform - import sqlite3 - - if platform.system() == "Linux" and sqlite3.sqlite_version_info < (3, 35, 0): - try: - # According to the Chroma team, this patch only works on Linux - import datetime - import subprocess - import sys - - subprocess.check_call( - [sys.executable, "-m", "pip", "install", "pysqlite3-binary", "--quiet", "--disable-pip-version-check"] - ) - - __import__("pysqlite3") - sys.modules["sqlite3"] = sys.modules.pop("pysqlite3") - - # Let the user know what happened. - current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3] - print( - f"{current_time} [embedchain] [INFO]", - "Swapped std-lib sqlite3 with pysqlite3 for ChromaDb compatibility.", - f"Your original version was {sqlite3.sqlite_version}.", - ) - except Exception as e: - # Escape all exceptions - current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3] - print( - f"{current_time} [embedchain] [ERROR]", - "Failed to swap std-lib sqlite3 with pysqlite3 for ChromaDb compatibility.", - "Error:", - e, - ) - - -def format_source(source: str, limit: int = 20) -> str: - """ - Format a string to only take the first x and last x letters. - This makes it easier to display a URL, keeping familiarity while ensuring a consistent length. - If the string is too short, it is not sliced. - """ - if len(source) > 2 * limit: - return source[:limit] + "..." + source[-limit:] - return source - - -def detect_datatype(source: Any) -> DataType: - """ - Automatically detect the datatype of the given source. - - :param source: the source to base the detection on - :return: data_type string - """ - from urllib.parse import urlparse - - import requests - import yaml - - def is_openapi_yaml(yaml_content): - # currently the following two fields are required in openapi spec yaml config - return "openapi" in yaml_content and "info" in yaml_content - - def is_google_drive_folder(url): - # checks if url is a Google Drive folder url against a regex - regex = r"^drive\.google\.com\/drive\/(?:u\/\d+\/)folders\/([a-zA-Z0-9_-]+)$" - return re.match(regex, url) - - try: - if not isinstance(source, str): - raise ValueError("Source is not a string and thus cannot be a URL.") - url = urlparse(source) - # Check if both scheme and netloc are present. Local file system URIs are acceptable too. - if not all([url.scheme, url.netloc]) and url.scheme != "file": - raise ValueError("Not a valid URL.") - except ValueError: - url = False - - formatted_source = format_source(str(source), 30) - - if url: - YOUTUBE_ALLOWED_NETLOCKS = { - "www.youtube.com", - "m.youtube.com", - "youtu.be", - "youtube.com", - "vid.plus", - "www.youtube-nocookie.com", - } - - if url.netloc in YOUTUBE_ALLOWED_NETLOCKS: - logger.debug(f"Source of `{formatted_source}` detected as `youtube_video`.") - return DataType.YOUTUBE_VIDEO - - if url.netloc in {"notion.so", "notion.site"}: - logger.debug(f"Source of `{formatted_source}` detected as `notion`.") - return DataType.NOTION - - if url.path.endswith(".pdf"): - logger.debug(f"Source of `{formatted_source}` detected as `pdf_file`.") - return DataType.PDF_FILE - - if url.path.endswith(".xml"): - logger.debug(f"Source of `{formatted_source}` detected as `sitemap`.") - return DataType.SITEMAP - - if url.path.endswith(".csv"): - logger.debug(f"Source of `{formatted_source}` detected as `csv`.") - return DataType.CSV - - if url.path.endswith(".mdx") or url.path.endswith(".md"): - logger.debug(f"Source of `{formatted_source}` detected as `mdx`.") - return DataType.MDX - - if url.path.endswith(".docx"): - logger.debug(f"Source of `{formatted_source}` detected as `docx`.") - return DataType.DOCX - - if url.path.endswith( - (".mp3", ".mp4", ".mp2", ".aac", ".wav", ".flac", ".pcm", ".m4a", ".ogg", ".opus", ".webm") - ): - logger.debug(f"Source of `{formatted_source}` detected as `audio`.") - return DataType.AUDIO - - if url.path.endswith(".yaml"): - try: - response = requests.get(source) - response.raise_for_status() - try: - yaml_content = yaml.safe_load(response.text) - except yaml.YAMLError as exc: - logger.error(f"Error parsing YAML: {exc}") - raise TypeError(f"Not a valid data type. Error loading YAML: {exc}") - - if is_openapi_yaml(yaml_content): - logger.debug(f"Source of `{formatted_source}` detected as `openapi`.") - return DataType.OPENAPI - else: - logger.error( - f"Source of `{formatted_source}` does not contain all the required \ - fields of OpenAPI yaml. Check 'https://spec.openapis.org/oas/v3.1.0'" - ) - raise TypeError( - "Not a valid data type. Check 'https://spec.openapis.org/oas/v3.1.0', \ - make sure you have all the required fields in YAML config data" - ) - except requests.exceptions.RequestException as e: - logger.error(f"Error fetching URL {formatted_source}: {e}") - - if url.path.endswith(".json"): - logger.debug(f"Source of `{formatted_source}` detected as `json_file`.") - return DataType.JSON - - if "docs" in url.netloc or ("docs" in url.path and url.scheme != "file"): - # `docs_site` detection via path is not accepted for local filesystem URIs, - # because that would mean all paths that contain `docs` are now doc sites, which is too aggressive. - logger.debug(f"Source of `{formatted_source}` detected as `docs_site`.") - return DataType.DOCS_SITE - - if "github.com" in url.netloc: - logger.debug(f"Source of `{formatted_source}` detected as `github`.") - return DataType.GITHUB - - if is_google_drive_folder(url.netloc + url.path): - logger.debug(f"Source of `{formatted_source}` detected as `google drive folder`.") - return DataType.GOOGLE_DRIVE_FOLDER - - # If none of the above conditions are met, it's a general web page - logger.debug(f"Source of `{formatted_source}` detected as `web_page`.") - return DataType.WEB_PAGE - - elif not isinstance(source, str): - # For datatypes where source is not a string. - - if isinstance(source, tuple) and len(source) == 2 and isinstance(source[0], str) and isinstance(source[1], str): - logger.debug(f"Source of `{formatted_source}` detected as `qna_pair`.") - return DataType.QNA_PAIR - - # Raise an error if it isn't a string and also not a valid non-string type (one of the previous). - # We could stringify it, but it is better to raise an error and let the user decide how they want to do that. - raise TypeError( - "Source is not a string and a valid non-string type could not be detected. If you want to embed it, please stringify it, for instance by using `str(source)` or `(', ').join(source)`." # noqa: E501 - ) - - elif os.path.isfile(source): - # For datatypes that support conventional file references. - # Note: checking for string is not necessary anymore. - - if source.endswith(".docx"): - logger.debug(f"Source of `{formatted_source}` detected as `docx`.") - return DataType.DOCX - - if source.endswith(".csv"): - logger.debug(f"Source of `{formatted_source}` detected as `csv`.") - return DataType.CSV - - if source.endswith(".xml"): - logger.debug(f"Source of `{formatted_source}` detected as `xml`.") - return DataType.XML - - if source.endswith(".mdx") or source.endswith(".md"): - logger.debug(f"Source of `{formatted_source}` detected as `mdx`.") - return DataType.MDX - - if source.endswith(".txt"): - logger.debug(f"Source of `{formatted_source}` detected as `text`.") - return DataType.TEXT_FILE - - if source.endswith(".pdf"): - logger.debug(f"Source of `{formatted_source}` detected as `pdf_file`.") - return DataType.PDF_FILE - - if source.endswith(".yaml"): - with open(source, "r") as file: - yaml_content = yaml.safe_load(file) - if is_openapi_yaml(yaml_content): - logger.debug(f"Source of `{formatted_source}` detected as `openapi`.") - return DataType.OPENAPI - else: - logger.error( - f"Source of `{formatted_source}` does not contain all the required \ - fields of OpenAPI yaml. Check 'https://spec.openapis.org/oas/v3.1.0'" - ) - raise ValueError( - "Invalid YAML data. Check 'https://spec.openapis.org/oas/v3.1.0', \ - make sure to add all the required params" - ) - - if source.endswith(".json"): - logger.debug(f"Source of `{formatted_source}` detected as `json`.") - return DataType.JSON - - if os.path.exists(source) and is_readable(open(source).read()): - logger.debug(f"Source of `{formatted_source}` detected as `text_file`.") - return DataType.TEXT_FILE - - # If the source is a valid file, that's not detectable as a type, an error is raised. - # It does not fall back to text. - raise ValueError( - "Source points to a valid file, but based on the filename, no `data_type` can be detected. Please be aware, that not all data_types allow conventional file references, some require the use of the `file URI scheme`. Please refer to the embedchain documentation (https://docs.embedchain.ai/advanced/data_types#remote-data-types)." # noqa: E501 - ) - - else: - # Source is not a URL. - - # TODO: check if source is gmail query - - # check if the source is valid json string - if is_valid_json_string(source): - logger.debug(f"Source of `{formatted_source}` detected as `json`.") - return DataType.JSON - - # Use text as final fallback. - logger.debug(f"Source of `{formatted_source}` detected as `text`.") - return DataType.TEXT - - -# check if the source is valid json string -def is_valid_json_string(source: str): - try: - _ = json.loads(source) - return True - except json.JSONDecodeError: - return False - - -def validate_config(config_data): - schema = Schema( - { - Optional("app"): { - Optional("config"): { - Optional("id"): str, - Optional("name"): str, - Optional("log_level"): Or("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), - Optional("collect_metrics"): bool, - Optional("collection_name"): str, - } - }, - Optional("llm"): { - Optional("provider"): Or( - "openai", - "azure_openai", - "anthropic", - "huggingface", - "cohere", - "together", - "gpt4all", - "ollama", - "jina", - "llama2", - "vertexai", - "google", - "aws_bedrock", - "mistralai", - "clarifai", - "vllm", - "groq", - "nvidia", - ), - Optional("config"): { - Optional("model"): str, - Optional("model_name"): str, - Optional("number_documents"): int, - Optional("temperature"): float, - Optional("max_tokens"): int, - Optional("top_p"): Or(float, int), - Optional("stream"): bool, - Optional("online"): bool, - Optional("token_usage"): bool, - Optional("template"): str, - Optional("prompt"): str, - Optional("system_prompt"): str, - Optional("deployment_name"): str, - Optional("where"): dict, - Optional("query_type"): str, - Optional("api_key"): str, - Optional("base_url"): str, - Optional("endpoint"): str, - Optional("model_kwargs"): dict, - Optional("local"): bool, - Optional("base_url"): str, - Optional("default_headers"): dict, - Optional("api_version"): Or(str, datetime.date), - Optional("http_client_proxies"): Or(str, dict), - Optional("http_async_client_proxies"): Or(str, dict), - }, - }, - Optional("vectordb"): { - Optional("provider"): Or( - "chroma", "elasticsearch", "opensearch", "lancedb", "pinecone", "qdrant", "weaviate", "zilliz" - ), - Optional("config"): object, # TODO: add particular config schema for each provider - }, - Optional("embedder"): { - Optional("provider"): Or( - "openai", - "gpt4all", - "huggingface", - "vertexai", - "azure_openai", - "google", - "mistralai", - "clarifai", - "nvidia", - "ollama", - "cohere", - "aws_bedrock", - ), - Optional("config"): { - Optional("model"): Optional(str), - Optional("deployment_name"): Optional(str), - Optional("api_key"): str, - Optional("api_base"): str, - Optional("title"): str, - Optional("task_type"): str, - Optional("vector_dimension"): int, - Optional("base_url"): str, - Optional("endpoint"): str, - Optional("model_kwargs"): dict, - Optional("http_client_proxies"): Or(str, dict), - Optional("http_async_client_proxies"): Or(str, dict), - }, - }, - Optional("embedding_model"): { - Optional("provider"): Or( - "openai", - "gpt4all", - "huggingface", - "vertexai", - "azure_openai", - "google", - "mistralai", - "clarifai", - "nvidia", - "ollama", - "aws_bedrock", - ), - Optional("config"): { - Optional("model"): str, - Optional("deployment_name"): str, - Optional("api_key"): str, - Optional("title"): str, - Optional("task_type"): str, - Optional("vector_dimension"): int, - Optional("base_url"): str, - }, - }, - Optional("chunker"): { - Optional("chunk_size"): int, - Optional("chunk_overlap"): int, - Optional("length_function"): str, - Optional("min_chunk_size"): int, - }, - Optional("cache"): { - Optional("similarity_evaluation"): { - Optional("strategy"): Or("distance", "exact"), - Optional("max_distance"): float, - Optional("positive"): bool, - }, - Optional("config"): { - Optional("similarity_threshold"): float, - Optional("auto_flush"): int, - }, - }, - Optional("memory"): { - Optional("top_k"): int, - }, - } - ) - - return schema.validate(config_data) - - -def chunks(iterable, batch_size=100, desc="Processing chunks"): - """A helper function to break an iterable into chunks of size batch_size.""" - it = iter(iterable) - total_size = len(iterable) - - with tqdm(total=total_size, desc=desc, unit="batch") as pbar: - chunk = tuple(itertools.islice(it, batch_size)) - while chunk: - yield chunk - pbar.update(len(chunk)) - chunk = tuple(itertools.islice(it, batch_size)) diff --git a/embedchain/embedchain/vectordb/__init__.py b/embedchain/embedchain/vectordb/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/embedchain/vectordb/base.py b/embedchain/embedchain/vectordb/base.py deleted file mode 100644 index e65cde01a..000000000 --- a/embedchain/embedchain/vectordb/base.py +++ /dev/null @@ -1,82 +0,0 @@ -from embedchain.config.vector_db.base import BaseVectorDbConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.helpers.json_serializable import JSONSerializable - - -class BaseVectorDB(JSONSerializable): - """Base class for vector database.""" - - def __init__(self, config: BaseVectorDbConfig): - """Initialize the database. Save the config and client as an attribute. - - :param config: Database configuration class instance. - :type config: BaseVectorDbConfig - """ - self.client = self._get_or_create_db() - self.config: BaseVectorDbConfig = config - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - - So it's can't be done in __init__ in one step. - """ - raise NotImplementedError - - def _get_or_create_db(self): - """Get or create the database.""" - raise NotImplementedError - - def _get_or_create_collection(self): - """Get or create a named collection.""" - raise NotImplementedError - - def _set_embedder(self, embedder: BaseEmbedder): - """ - The database needs to access the embedder sometimes, with this method you can persistently set it. - - :param embedder: Embedder to be set as the embedder for this database. - :type embedder: BaseEmbedder - """ - self.embedder = embedder - - def get(self): - """Get database embeddings by id.""" - raise NotImplementedError - - def add(self): - """Add to database""" - raise NotImplementedError - - def query(self): - """Query contents from vector database based on vector similarity""" - raise NotImplementedError - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - raise NotImplementedError - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - raise NotImplementedError - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - raise NotImplementedError - - def delete(self): - """Delete from database.""" - - raise NotImplementedError diff --git a/embedchain/embedchain/vectordb/chroma.py b/embedchain/embedchain/vectordb/chroma.py deleted file mode 100644 index 746dc149b..000000000 --- a/embedchain/embedchain/vectordb/chroma.py +++ /dev/null @@ -1,290 +0,0 @@ -import logging -from typing import Any, Optional, Union - -from chromadb import Collection, QueryResult -from langchain.docstore.document import Document -from tqdm import tqdm - -from embedchain.config import ChromaDbConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.vectordb.base import BaseVectorDB - -try: - import chromadb - from chromadb.config import Settings - from chromadb.errors import InvalidDimensionException -except RuntimeError: - from embedchain.utils.misc import use_pysqlite3 - - use_pysqlite3() - import chromadb - from chromadb.config import Settings - from chromadb.errors import InvalidDimensionException - - -logger = logging.getLogger(__name__) - - -@register_deserializable -class ChromaDB(BaseVectorDB): - """Vector database using ChromaDB.""" - - def __init__(self, config: Optional[ChromaDbConfig] = None): - """Initialize a new ChromaDB instance - - :param config: Configuration options for Chroma, defaults to None - :type config: Optional[ChromaDbConfig], optional - """ - if config: - self.config = config - else: - self.config = ChromaDbConfig() - - self.settings = Settings(anonymized_telemetry=False) - self.settings.allow_reset = self.config.allow_reset if hasattr(self.config, "allow_reset") else False - self.batch_size = self.config.batch_size - if self.config.chroma_settings: - for key, value in self.config.chroma_settings.items(): - if hasattr(self.settings, key): - setattr(self.settings, key, value) - - if self.config.host and self.config.port: - logger.info(f"Connecting to ChromaDB server: {self.config.host}:{self.config.port}") - self.settings.chroma_server_host = self.config.host - self.settings.chroma_server_http_port = self.config.port - self.settings.chroma_api_impl = "chromadb.api.fastapi.FastAPI" - else: - if self.config.dir is None: - self.config.dir = "db" - - self.settings.persist_directory = self.config.dir - self.settings.is_persistent = True - - self.client = chromadb.Client(self.settings) - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - """ - if not self.embedder: - raise ValueError( - "Embedder not set. Please set an embedder with `_set_embedder()` function before initialization." - ) - self._get_or_create_collection(self.config.collection_name) - - def _get_or_create_db(self): - """Called during initialization""" - return self.client - - @staticmethod - def _generate_where_clause(where: dict[str, any]) -> dict[str, any]: - # If only one filter is supplied, return it as is - # (no need to wrap in $and based on chroma docs) - if where is None: - return {} - if len(where.keys()) <= 1: - return where - where_filters = [] - for k, v in where.items(): - if isinstance(v, str): - where_filters.append({k: v}) - return {"$and": where_filters} - - def _get_or_create_collection(self, name: str) -> Collection: - """ - Get or create a named collection. - - :param name: Name of the collection - :type name: str - :raises ValueError: No embedder configured. - :return: Created collection - :rtype: Collection - """ - if not hasattr(self, "embedder") or not self.embedder: - raise ValueError("Cannot create a Chroma database collection without an embedder.") - self.collection = self.client.get_or_create_collection( - name=name, - embedding_function=self.embedder.embedding_fn, - ) - return self.collection - - def get(self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - - :param ids: list of doc ids to check for existence - :type ids: list[str] - :param where: Optional. to filter data - :type where: dict[str, Any] - :param limit: Optional. maximum number of documents - :type limit: Optional[int] - :return: Existing documents. - :rtype: list[str] - """ - args = {} - if ids: - args["ids"] = ids - if where: - args["where"] = self._generate_where_clause(where) - if limit: - args["limit"] = limit - return self.collection.get(**args) - - def add( - self, - documents: list[str], - metadatas: list[object], - ids: list[str], - **kwargs: Optional[dict[str, Any]], - ) -> Any: - """ - Add vectors to chroma database - - :param documents: Documents - :type documents: list[str] - :param metadatas: Metadatas - :type metadatas: list[object] - :param ids: ids - :type ids: list[str] - """ - size = len(documents) - if len(documents) != size or len(metadatas) != size or len(ids) != size: - raise ValueError( - "Cannot add documents to chromadb with inconsistent sizes. Documents size: {}, Metadata size: {}," - " Ids size: {}".format(len(documents), len(metadatas), len(ids)) - ) - - for i in tqdm(range(0, len(documents), self.batch_size), desc="Inserting batches in chromadb"): - self.collection.add( - documents=documents[i : i + self.batch_size], - metadatas=metadatas[i : i + self.batch_size], - ids=ids[i : i + self.batch_size], - ) - self.config - - @staticmethod - def _format_result(results: QueryResult) -> list[tuple[Document, float]]: - """ - Format Chroma results - - :param results: ChromaDB query results to format. - :type results: QueryResult - :return: Formatted results - :rtype: list[tuple[Document, float]] - """ - return [ - (Document(page_content=result[0], metadata=result[1] or {}), result[2]) - for result in zip( - results["documents"][0], - results["metadatas"][0], - results["distances"][0], - ) - ] - - def query( - self, - input_query: str, - n_results: int, - where: Optional[dict[str, any]] = None, - raw_filter: Optional[dict[str, any]] = None, - citations: bool = False, - **kwargs: Optional[dict[str, any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - Query contents from vector database based on vector similarity - - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: to filter data - :type where: dict[str, Any] - :param raw_filter: Raw filter to apply - :type raw_filter: dict[str, Any] - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :raises InvalidDimensionException: Dimensions do not match. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - if where and raw_filter: - raise ValueError("Both `where` and `raw_filter` cannot be used together.") - - where_clause = None - if raw_filter: - where_clause = raw_filter - if where: - where_clause = self._generate_where_clause(where) - try: - result = self.collection.query( - query_texts=[ - input_query, - ], - n_results=n_results, - where=where_clause, - ) - except InvalidDimensionException as e: - raise InvalidDimensionException( - e.message() - + ". This is commonly a side-effect when an embedding function, different from the one used to add the" - " embeddings, is used to retrieve an embedding from the database." - ) from None - results_formatted = self._format_result(result) - contexts = [] - for result in results_formatted: - context = result[0].page_content - if citations: - metadata = result[0].metadata - metadata["score"] = result[1] - contexts.append((context, metadata)) - else: - contexts.append(context) - return contexts - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - self._get_or_create_collection(self.config.collection_name) - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - return self.collection.count() - - def delete(self, where): - return self.collection.delete(where=self._generate_where_clause(where)) - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - # Delete all data from the collection - try: - self.client.delete_collection(self.config.collection_name) - except ValueError: - raise ValueError( - "For safety reasons, resetting is disabled. " - "Please enable it by setting `allow_reset=True` in your ChromaDbConfig" - ) from None - # Recreate - self._get_or_create_collection(self.config.collection_name) - - # Todo: Automatically recreating a collection with the same name cannot be the best way to handle a reset. - # A downside of this implementation is, if you have two instances, - # the other instance will not get the updated `self.collection` attribute. - # A better way would be to create the collection if it is called again after being reset. - # That means, checking if collection exists in the db-consuming methods, and creating it if it doesn't. - # That's an extra steps for all uses, just to satisfy a niche use case in a niche method. For now, this will do. diff --git a/embedchain/embedchain/vectordb/elasticsearch.py b/embedchain/embedchain/vectordb/elasticsearch.py deleted file mode 100644 index 12b871762..000000000 --- a/embedchain/embedchain/vectordb/elasticsearch.py +++ /dev/null @@ -1,269 +0,0 @@ -import logging -from typing import Any, Optional, Union - -try: - from elasticsearch import Elasticsearch - from elasticsearch.helpers import bulk -except ImportError: - raise ImportError( - "Elasticsearch requires extra dependencies. Install with `pip install --upgrade embedchain[elasticsearch]`" - ) from None - -from embedchain.config import ElasticsearchDBConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.utils.misc import chunks -from embedchain.vectordb.base import BaseVectorDB - -logger = logging.getLogger(__name__) - - -@register_deserializable -class ElasticsearchDB(BaseVectorDB): - """ - Elasticsearch as vector database - """ - - def __init__( - self, - config: Optional[ElasticsearchDBConfig] = None, - es_config: Optional[ElasticsearchDBConfig] = None, # Backwards compatibility - ): - """Elasticsearch as vector database. - - :param config: Elasticsearch database config, defaults to None - :type config: ElasticsearchDBConfig, optional - :param es_config: `es_config` is supported as an alias for `config` (for backwards compatibility), - defaults to None - :type es_config: ElasticsearchDBConfig, optional - :raises ValueError: No config provided - """ - if config is None and es_config is None: - self.config = ElasticsearchDBConfig() - else: - if not isinstance(config, ElasticsearchDBConfig): - raise TypeError( - "config is not a `ElasticsearchDBConfig` instance. " - "Please make sure the type is right and that you are passing an instance." - ) - self.config = config or es_config - if self.config.ES_URL: - self.client = Elasticsearch(self.config.ES_URL, **self.config.ES_EXTRA_PARAMS) - elif self.config.CLOUD_ID: - self.client = Elasticsearch(cloud_id=self.config.CLOUD_ID, **self.config.ES_EXTRA_PARAMS) - else: - raise ValueError( - "Something is wrong with your config. Please check again - `https://docs.embedchain.ai/components/vector-databases#elasticsearch`" # noqa: E501 - ) - - self.batch_size = self.config.batch_size - # Call parent init here because embedder is needed - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - """ - logger.info(self.client.info()) - index_settings = { - "mappings": { - "properties": { - "text": {"type": "text"}, - "embeddings": {"type": "dense_vector", "index": False, "dims": self.embedder.vector_dimension}, - } - } - } - es_index = self._get_index() - if not self.client.indices.exists(index=es_index): - # create index if not exist - print("Creating index", es_index, index_settings) - self.client.indices.create(index=es_index, body=index_settings) - - def _get_or_create_db(self): - """Called during initialization""" - return self.client - - def _get_or_create_collection(self, name): - """Note: nothing to return here. Discuss later""" - - def get(self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - - :param ids: _list of doc ids to check for existence - :type ids: list[str] - :param where: to filter data - :type where: dict[str, any] - :return: ids - :rtype: Set[str] - """ - if ids: - query = {"bool": {"must": [{"ids": {"values": ids}}]}} - else: - query = {"bool": {"must": []}} - - if where: - for key, value in where.items(): - query["bool"]["must"].append({"term": {f"metadata.{key}.keyword": value}}) - - response = self.client.search(index=self._get_index(), query=query, _source=True, size=limit) - docs = response["hits"]["hits"] - ids = [doc["_id"] for doc in docs] - doc_ids = [doc["_source"]["metadata"]["doc_id"] for doc in docs] - - # Result is modified for compatibility with other vector databases - # TODO: Add method in vector database to return result in a standard format - result = {"ids": ids, "metadatas": []} - - for doc_id in doc_ids: - result["metadatas"].append({"doc_id": doc_id}) - - return result - - def add( - self, - documents: list[str], - metadatas: list[object], - ids: list[str], - **kwargs: Optional[dict[str, any]], - ) -> Any: - """ - add data in vector database - :param documents: list of texts to add - :type documents: list[str] - :param metadatas: list of metadata associated with docs - :type metadatas: list[object] - :param ids: ids of docs - :type ids: list[str] - """ - - embeddings = self.embedder.embedding_fn(documents) - - for chunk in chunks( - list(zip(ids, documents, metadatas, embeddings)), - self.batch_size, - desc="Inserting batches in elasticsearch", - ): # noqa: E501 - ids, docs, metadatas, embeddings = [], [], [], [] - for id, text, metadata, embedding in chunk: - ids.append(id) - docs.append(text) - metadatas.append(metadata) - embeddings.append(embedding) - - batch_docs = [] - for id, text, metadata, embedding in zip(ids, docs, metadatas, embeddings): - batch_docs.append( - { - "_index": self._get_index(), - "_id": id, - "_source": {"text": text, "metadata": metadata, "embeddings": embedding}, - } - ) - bulk(self.client, batch_docs, **kwargs) - self.client.indices.refresh(index=self._get_index()) - - def query( - self, - input_query: str, - n_results: int, - where: dict[str, any], - citations: bool = False, - **kwargs: Optional[dict[str, Any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - query contents from vector database based on vector similarity - - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: Optional. to filter data - :type where: dict[str, any] - :return: The context of the document that matched your query, url of the source, doc_id - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - input_query_vector = self.embedder.embedding_fn([input_query]) - query_vector = input_query_vector[0] - - # `https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-script-score-query.html` - query = { - "script_score": { - "query": {"bool": {"must": [{"exists": {"field": "text"}}]}}, - "script": { - "source": "cosineSimilarity(params.input_query_vector, 'embeddings') + 1.0", - "params": {"input_query_vector": query_vector}, - }, - } - } - - if where: - for key, value in where.items(): - query["script_score"]["query"]["bool"]["must"].append({"term": {f"metadata.{key}.keyword": value}}) - - _source = ["text", "metadata"] - response = self.client.search(index=self._get_index(), query=query, _source=_source, size=n_results) - docs = response["hits"]["hits"] - contexts = [] - for doc in docs: - context = doc["_source"]["text"] - if citations: - metadata = doc["_source"]["metadata"] - metadata["score"] = doc["_score"] - contexts.append(tuple((context, metadata))) - else: - contexts.append(context) - return contexts - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - query = {"match_all": {}} - response = self.client.count(index=self._get_index(), query=query) - doc_count = response["count"] - return doc_count - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - # Delete all data from the database - if self.client.indices.exists(index=self._get_index()): - # delete index in Es - self.client.indices.delete(index=self._get_index()) - - def _get_index(self) -> str: - """Get the Elasticsearch index for a collection - - :return: Elasticsearch index - :rtype: str - """ - # NOTE: The method is preferred to an attribute, because if collection name changes, - # it's always up-to-date. - return f"{self.config.collection_name}_{self.embedder.vector_dimension}".lower() - - def delete(self, where): - """Delete documents from the database.""" - query = {"query": {"bool": {"must": []}}} - for key, value in where.items(): - query["query"]["bool"]["must"].append({"term": {f"metadata.{key}.keyword": value}}) - self.client.delete_by_query(index=self._get_index(), body=query) - self.client.indices.refresh(index=self._get_index()) diff --git a/embedchain/embedchain/vectordb/lancedb.py b/embedchain/embedchain/vectordb/lancedb.py deleted file mode 100644 index d3d4b6898..000000000 --- a/embedchain/embedchain/vectordb/lancedb.py +++ /dev/null @@ -1,305 +0,0 @@ -from typing import Any, Dict, List, Optional, Union - -import pyarrow as pa - -try: - import lancedb -except ImportError: - raise ImportError('LanceDB is required. Install with pip install "embedchain[lancedb]"') from None - -from embedchain.config.vector_db.lancedb import LanceDBConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.vectordb.base import BaseVectorDB - - -@register_deserializable -class LanceDB(BaseVectorDB): - """ - LanceDB as vector database - """ - - def __init__( - self, - config: Optional[LanceDBConfig] = None, - ): - """LanceDB as vector database. - - :param config: LanceDB database config, defaults to None - :type config: LanceDBConfig, optional - """ - if config: - self.config = config - else: - self.config = LanceDBConfig() - - self.client = lancedb.connect(self.config.dir or "~/.lancedb") - self.embedder_check = True - - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - """ - if not self.embedder: - raise ValueError( - "Embedder not set. Please set an embedder with `_set_embedder()` function before initialization." - ) - else: - # check embedder function is working or not - try: - self.embedder.embedding_fn("Hello LanceDB") - except Exception: - self.embedder_check = False - - self._get_or_create_collection(self.config.collection_name) - - def _get_or_create_db(self): - """ - Called during initialization - """ - return self.client - - def _generate_where_clause(self, where: Dict[str, any]) -> str: - """ - This method generate where clause using dictionary containing attributes and their values - """ - - where_filters = "" - - if len(list(where.keys())) == 1: - where_filters = f"{list(where.keys())[0]} = {list(where.values())[0]}" - return where_filters - - where_items = list(where.items()) - where_count = len(where_items) - - for i, (key, value) in enumerate(where_items, start=1): - condition = f"{key} = {value} AND " - where_filters += condition - - if i == where_count: - condition = f"{key} = {value}" - where_filters += condition - - return where_filters - - def _get_or_create_collection(self, table_name: str, reset=False): - """ - Get or create a named collection. - - :param name: Name of the collection - :type name: str - :return: Created collection - :rtype: Collection - """ - if not self.embedder_check: - schema = pa.schema( - [ - pa.field("doc", pa.string()), - pa.field("metadata", pa.string()), - pa.field("id", pa.string()), - ] - ) - - else: - schema = pa.schema( - [ - pa.field("vector", pa.list_(pa.float32(), list_size=self.embedder.vector_dimension)), - pa.field("doc", pa.string()), - pa.field("metadata", pa.string()), - pa.field("id", pa.string()), - ] - ) - - if not reset: - if table_name not in self.client.table_names(): - self.collection = self.client.create_table(table_name, schema=schema) - - else: - self.client.drop_table(table_name) - self.collection = self.client.create_table(table_name, schema=schema) - - self.collection = self.client[table_name] - - return self.collection - - def get(self, ids: Optional[List[str]] = None, where: Optional[Dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - - :param ids: list of doc ids to check for existence - :type ids: List[str] - :param where: Optional. to filter data - :type where: Dict[str, Any] - :param limit: Optional. maximum number of documents - :type limit: Optional[int] - :return: Existing documents. - :rtype: List[str] - """ - if limit is not None: - max_limit = limit - else: - max_limit = 3 - results = {"ids": [], "metadatas": []} - - where_clause = {} - if where: - where_clause = self._generate_where_clause(where) - - if ids is not None: - records = ( - self.collection.to_lance().scanner(filter=f"id IN {tuple(ids)}", columns=["id"]).to_table().to_pydict() - ) - for id in records["id"]: - if where is not None: - result = ( - self.collection.search(query=id, vector_column_name="id") - .where(where_clause) - .limit(max_limit) - .to_list() - ) - else: - result = self.collection.search(query=id, vector_column_name="id").limit(max_limit).to_list() - results["ids"] = [r["id"] for r in result] - results["metadatas"] = [r["metadata"] for r in result] - - return results - - def add( - self, - documents: List[str], - metadatas: List[object], - ids: List[str], - ) -> Any: - """ - Add vectors to lancedb database - - :param documents: Documents - :type documents: List[str] - :param metadatas: Metadatas - :type metadatas: List[object] - :param ids: ids - :type ids: List[str] - """ - data = [] - to_ingest = list(zip(documents, metadatas, ids)) - - if not self.embedder_check: - for doc, meta, id in to_ingest: - temp = {} - temp["doc"] = doc - temp["metadata"] = str(meta) - temp["id"] = id - data.append(temp) - else: - for doc, meta, id in to_ingest: - temp = {} - temp["doc"] = doc - temp["vector"] = self.embedder.embedding_fn([doc])[0] - temp["metadata"] = str(meta) - temp["id"] = id - data.append(temp) - - self.collection.add(data=data) - - def _format_result(self, results) -> list: - """ - Format LanceDB results - - :param results: LanceDB query results to format. - :type results: QueryResult - :return: Formatted results - :rtype: list[tuple[Document, float]] - """ - return results.tolist() - - def query( - self, - input_query: str, - n_results: int = 3, - where: Optional[dict[str, any]] = None, - raw_filter: Optional[dict[str, any]] = None, - citations: bool = False, - **kwargs: Optional[dict[str, any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - Query contents from vector database based on vector similarity - - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: to filter data - :type where: dict[str, Any] - :param raw_filter: Raw filter to apply - :type raw_filter: dict[str, Any] - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :raises InvalidDimensionException: Dimensions do not match. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - if where and raw_filter: - raise ValueError("Both `where` and `raw_filter` cannot be used together.") - try: - query_embedding = self.embedder.embedding_fn(input_query)[0] - result = self.collection.search(query_embedding).limit(n_results).to_list() - except Exception as e: - e.message() - - results_formatted = result - - contexts = [] - for result in results_formatted: - if citations: - metadata = result["metadata"] - contexts.append((result["doc"], metadata)) - else: - contexts.append(result["doc"]) - return contexts - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - self._get_or_create_collection(self.config.collection_name) - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - return self.collection.count_rows() - - def delete(self, where): - return self.collection.delete(where=where) - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - # Delete all data from the collection and recreate collection - if self.config.allow_reset: - try: - self._get_or_create_collection(self.config.collection_name, reset=True) - except ValueError: - raise ValueError( - "For safety reasons, resetting is disabled. " - "Please enable it by setting `allow_reset=True` in your LanceDbConfig" - ) from None - # Recreate - else: - print( - "For safety reasons, resetting is disabled. " - "Please enable it by setting `allow_reset=True` in your LanceDbConfig" - ) diff --git a/embedchain/embedchain/vectordb/opensearch.py b/embedchain/embedchain/vectordb/opensearch.py deleted file mode 100644 index accec4324..000000000 --- a/embedchain/embedchain/vectordb/opensearch.py +++ /dev/null @@ -1,253 +0,0 @@ -import logging -import time -from typing import Any, Optional, Union - -from tqdm import tqdm - -try: - from opensearchpy import OpenSearch - from opensearchpy.helpers import bulk -except ImportError: - raise ImportError( - "OpenSearch requires extra dependencies. Install with `pip install --upgrade embedchain[opensearch]`" - ) from None - -from langchain_community.embeddings.openai import OpenAIEmbeddings -from langchain_community.vectorstores import OpenSearchVectorSearch - -from embedchain.config import OpenSearchDBConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.vectordb.base import BaseVectorDB - -logger = logging.getLogger(__name__) - - -@register_deserializable -class OpenSearchDB(BaseVectorDB): - """ - OpenSearch as vector database - """ - - def __init__(self, config: OpenSearchDBConfig): - """OpenSearch as vector database. - - :param config: OpenSearch domain config - :type config: OpenSearchDBConfig - """ - if config is None: - raise ValueError("OpenSearchDBConfig is required") - self.config = config - self.batch_size = self.config.batch_size - self.client = OpenSearch( - hosts=[self.config.opensearch_url], - http_auth=self.config.http_auth, - **self.config.extra_params, - ) - info = self.client.info() - logger.info(f"Connected to {info['version']['distribution']}. Version: {info['version']['number']}") - # Remove auth credentials from config after successful connection - super().__init__(config=self.config) - - def _initialize(self): - logger.info(self.client.info()) - index_name = self._get_index() - if self.client.indices.exists(index=index_name): - print(f"Index '{index_name}' already exists.") - return - - index_body = { - "settings": {"knn": True}, - "mappings": { - "properties": { - "text": {"type": "text"}, - "embeddings": { - "type": "knn_vector", - "index": False, - "dimension": self.config.vector_dimension, - }, - } - }, - } - self.client.indices.create(index_name, body=index_body) - print(self.client.indices.get(index_name)) - - def _get_or_create_db(self): - """Called during initialization""" - return self.client - - def _get_or_create_collection(self, name): - """Note: nothing to return here. Discuss later""" - - def get( - self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None - ) -> set[str]: - """ - Get existing doc ids present in vector database - - :param ids: _list of doc ids to check for existence - :type ids: list[str] - :param where: to filter data - :type where: dict[str, any] - :return: ids - :type: set[str] - """ - query = {} - if ids: - query["query"] = {"bool": {"must": [{"ids": {"values": ids}}]}} - else: - query["query"] = {"bool": {"must": []}} - - if where: - for key, value in where.items(): - query["query"]["bool"]["must"].append({"term": {f"metadata.{key}.keyword": value}}) - - # OpenSearch syntax is different from Elasticsearch - response = self.client.search(index=self._get_index(), body=query, _source=True, size=limit) - docs = response["hits"]["hits"] - ids = [doc["_id"] for doc in docs] - doc_ids = [doc["_source"]["metadata"]["doc_id"] for doc in docs] - - # Result is modified for compatibility with other vector databases - # TODO: Add method in vector database to return result in a standard format - result = {"ids": ids, "metadatas": []} - - for doc_id in doc_ids: - result["metadatas"].append({"doc_id": doc_id}) - return result - - def add(self, documents: list[str], metadatas: list[object], ids: list[str], **kwargs: Optional[dict[str, any]]): - """Adds documents to the opensearch index""" - - embeddings = self.embedder.embedding_fn(documents) - for batch_start in tqdm(range(0, len(documents), self.batch_size), desc="Inserting batches in opensearch"): - batch_end = batch_start + self.batch_size - batch_documents = documents[batch_start:batch_end] - batch_embeddings = embeddings[batch_start:batch_end] - - # Create document entries for bulk upload - batch_entries = [ - { - "_index": self._get_index(), - "_id": doc_id, - "_source": {"text": text, "metadata": metadata, "embeddings": embedding}, - } - for doc_id, text, metadata, embedding in zip( - ids[batch_start:batch_end], batch_documents, metadatas[batch_start:batch_end], batch_embeddings - ) - ] - - # Perform bulk operation - bulk(self.client, batch_entries, **kwargs) - self.client.indices.refresh(index=self._get_index()) - - # Sleep to avoid rate limiting - time.sleep(0.1) - - def query( - self, - input_query: str, - n_results: int, - where: dict[str, any], - citations: bool = False, - **kwargs: Optional[dict[str, Any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - query contents from vector database based on vector similarity - - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: Optional. to filter data - :type where: dict[str, any] - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - embeddings = OpenAIEmbeddings() - docsearch = OpenSearchVectorSearch( - index_name=self._get_index(), - embedding_function=embeddings, - opensearch_url=f"{self.config.opensearch_url}", - http_auth=self.config.http_auth, - use_ssl=hasattr(self.config, "use_ssl") and self.config.use_ssl, - verify_certs=hasattr(self.config, "verify_certs") and self.config.verify_certs, - ) - - pre_filter = {"match_all": {}} # default - if len(where) > 0: - pre_filter = {"bool": {"must": []}} - for key, value in where.items(): - pre_filter["bool"]["must"].append({"term": {f"metadata.{key}.keyword": value}}) - - docs = docsearch.similarity_search_with_score( - input_query, - search_type="script_scoring", - space_type="cosinesimil", - vector_field="embeddings", - text_field="text", - metadata_field="metadata", - pre_filter=pre_filter, - k=n_results, - **kwargs, - ) - - contexts = [] - for doc, score in docs: - context = doc.page_content - if citations: - metadata = doc.metadata - metadata["score"] = score - contexts.append(tuple((context, metadata))) - else: - contexts.append(context) - return contexts - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - query = {"query": {"match_all": {}}} - response = self.client.count(index=self._get_index(), body=query) - doc_count = response["count"] - return doc_count - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - # Delete all data from the database - if self.client.indices.exists(index=self._get_index()): - # delete index in ES - self.client.indices.delete(index=self._get_index()) - - def delete(self, where): - """Deletes a document from the OpenSearch index""" - query = {"query": {"bool": {"must": []}}} - for key, value in where.items(): - query["query"]["bool"]["must"].append({"term": {f"metadata.{key}.keyword": value}}) - self.client.delete_by_query(index=self._get_index(), body=query) - - def _get_index(self) -> str: - """Get the OpenSearch index for a collection - - :return: OpenSearch index - :rtype: str - """ - return self.config.collection_name diff --git a/embedchain/embedchain/vectordb/pinecone.py b/embedchain/embedchain/vectordb/pinecone.py deleted file mode 100644 index 3c0520ce3..000000000 --- a/embedchain/embedchain/vectordb/pinecone.py +++ /dev/null @@ -1,252 +0,0 @@ -import logging -import os -from typing import Optional, Union - -try: - import pinecone -except ImportError: - raise ImportError( - "Pinecone requires extra dependencies. Install with `pip install pinecone-text pinecone-client`" - ) from None - -from pinecone_text.sparse import BM25Encoder - -from embedchain.config.vector_db.pinecone import PineconeDBConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.utils.misc import chunks -from embedchain.vectordb.base import BaseVectorDB - -logger = logging.getLogger(__name__) - - -@register_deserializable -class PineconeDB(BaseVectorDB): - """ - Pinecone as vector database - """ - - def __init__( - self, - config: Optional[PineconeDBConfig] = None, - ): - """Pinecone as vector database. - - :param config: Pinecone database config, defaults to None - :type config: PineconeDBConfig, optional - :raises ValueError: No config provided - """ - if config is None: - self.config = PineconeDBConfig() - else: - if not isinstance(config, PineconeDBConfig): - raise TypeError( - "config is not a `PineconeDBConfig` instance. " - "Please make sure the type is right and that you are passing an instance." - ) - self.config = config - self._setup_pinecone_index() - - # Setup BM25Encoder if sparse vectors are to be used - self.bm25_encoder = None - self.batch_size = self.config.batch_size - if self.config.hybrid_search: - logger.info("Initializing BM25Encoder for sparse vectors..") - self.bm25_encoder = self.config.bm25_encoder if self.config.bm25_encoder else BM25Encoder.default() - - # Call parent init here because embedder is needed - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - """ - if not self.embedder: - raise ValueError("Embedder not set. Please set an embedder with `set_embedder` before initialization.") - - def _setup_pinecone_index(self): - """ - Loads the Pinecone index or creates it if not present. - """ - api_key = self.config.api_key or os.environ.get("PINECONE_API_KEY") - if not api_key: - raise ValueError("Please set the PINECONE_API_KEY environment variable or pass it in config.") - self.client = pinecone.Pinecone(api_key=api_key, **self.config.extra_params) - indexes = self.client.list_indexes().names() - if indexes is None or self.config.index_name not in indexes: - if self.config.pod_config: - spec = pinecone.PodSpec(**self.config.pod_config) - elif self.config.serverless_config: - spec = pinecone.ServerlessSpec(**self.config.serverless_config) - else: - raise ValueError("No pod_config or serverless_config found.") - - self.client.create_index( - name=self.config.index_name, - metric=self.config.metric, - dimension=self.config.vector_dimension, - spec=spec, - ) - self.pinecone_index = self.client.Index(self.config.index_name) - - def get(self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - - :param ids: _list of doc ids to check for existence - :type ids: list[str] - :param where: to filter data - :type where: dict[str, any] - :return: ids - :rtype: Set[str] - """ - existing_ids = list() - metadatas = [] - - if ids is not None: - for i in range(0, len(ids), self.batch_size): - result = self.pinecone_index.fetch(ids=ids[i : i + self.batch_size]) - vectors = result.get("vectors") - batch_existing_ids = list(vectors.keys()) - existing_ids.extend(batch_existing_ids) - metadatas.extend([vectors.get(ids).get("metadata") for ids in batch_existing_ids]) - return {"ids": existing_ids, "metadatas": metadatas} - - def add( - self, - documents: list[str], - metadatas: list[object], - ids: list[str], - **kwargs: Optional[dict[str, any]], - ): - """add data in vector database - - :param documents: list of texts to add - :type documents: list[str] - :param metadatas: list of metadata associated with docs - :type metadatas: list[object] - :param ids: ids of docs - :type ids: list[str] - """ - docs = [] - embeddings = self.embedder.embedding_fn(documents) - for id, text, metadata, embedding in zip(ids, documents, metadatas, embeddings): - # Insert sparse vectors as well if the user wants to do the hybrid search - sparse_vector_dict = ( - {"sparse_values": self.bm25_encoder.encode_documents(text)} if self.bm25_encoder else {} - ) - docs.append( - { - "id": id, - "values": embedding, - "metadata": {**metadata, "text": text}, - **sparse_vector_dict, - }, - ) - - for chunk in chunks(docs, self.batch_size, desc="Adding chunks in batches"): - self.pinecone_index.upsert(chunk, **kwargs) - - def query( - self, - input_query: str, - n_results: int, - where: Optional[dict[str, any]] = None, - raw_filter: Optional[dict[str, any]] = None, - citations: bool = False, - app_id: Optional[str] = None, - **kwargs: Optional[dict[str, any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - Query contents from vector database based on vector similarity. - - Args: - input_query (str): query string. - n_results (int): Number of similar documents to fetch from the database. - where (dict[str, any], optional): Filter criteria for the search. - raw_filter (dict[str, any], optional): Advanced raw filter criteria for the search. - citations (bool, optional): Flag to return context along with metadata. Defaults to False. - app_id (str, optional): Application ID to be passed to Pinecone. - - Returns: - Union[list[tuple[str, dict]], list[str]]: List of document contexts, optionally with metadata. - """ - query_filter = raw_filter if raw_filter is not None else self._generate_filter(where) - if app_id: - query_filter["app_id"] = {"$eq": app_id} - - query_vector = self.embedder.embedding_fn([input_query])[0] - params = { - "vector": query_vector, - "filter": query_filter, - "top_k": n_results, - "include_metadata": True, - **kwargs, - } - - if self.bm25_encoder: - sparse_query_vector = self.bm25_encoder.encode_queries(input_query) - params["sparse_vector"] = sparse_query_vector - - data = self.pinecone_index.query(**params) - return [ - (metadata.get("text"), {**metadata, "score": doc.get("score")}) if citations else metadata.get("text") - for doc in data.get("matches", []) - for metadata in [doc.get("metadata", {})] - ] - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - data = self.pinecone_index.describe_index_stats() - return data["total_vector_count"] - - def _get_or_create_db(self): - """Called during initialization""" - return self.client - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - # Delete all data from the database - self.client.delete_index(self.config.index_name) - self._setup_pinecone_index() - - @staticmethod - def _generate_filter(where: dict): - query = {} - if where is None: - return query - - for k, v in where.items(): - query[k] = {"$eq": v} - return query - - def delete(self, where: dict): - """Delete from database. - :param ids: list of ids to delete - :type ids: list[str] - """ - # Deleting with filters is not supported for `starter` index type. - # Follow `https://docs.pinecone.io/docs/metadata-filtering#deleting-vectors-by-metadata-filter` for more details - db_filter = self._generate_filter(where) - try: - self.pinecone_index.delete(filter=db_filter) - except Exception as e: - print(f"Failed to delete from Pinecone: {e}") - return diff --git a/embedchain/embedchain/vectordb/qdrant.py b/embedchain/embedchain/vectordb/qdrant.py deleted file mode 100644 index cdac19cfa..000000000 --- a/embedchain/embedchain/vectordb/qdrant.py +++ /dev/null @@ -1,253 +0,0 @@ -import copy -import os -from typing import Any, Optional, Union - -try: - from qdrant_client import QdrantClient - from qdrant_client.http import models - from qdrant_client.http.models import Batch - from qdrant_client.models import Distance, VectorParams -except ImportError: - raise ImportError("Qdrant requires extra dependencies. Install with `pip install embedchain[qdrant]`") from None - -from tqdm import tqdm - -from embedchain.config.vector_db.qdrant import QdrantDBConfig -from embedchain.vectordb.base import BaseVectorDB - - -class QdrantDB(BaseVectorDB): - """ - Qdrant as vector database - """ - - def __init__(self, config: QdrantDBConfig = None): - """ - Qdrant as vector database - :param config. Qdrant database config to be used for connection - """ - if config is None: - config = QdrantDBConfig() - else: - if not isinstance(config, QdrantDBConfig): - raise TypeError( - "config is not a `QdrantDBConfig` instance. " - "Please make sure the type is right and that you are passing an instance." - ) - self.config = config - self.batch_size = self.config.batch_size - self.client = QdrantClient(url=os.getenv("QDRANT_URL"), api_key=os.getenv("QDRANT_API_KEY")) - # Call parent init here because embedder is needed - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - """ - if not self.embedder: - raise ValueError("Embedder not set. Please set an embedder with `set_embedder` before initialization.") - - self.collection_name = self._get_or_create_collection() - all_collections = self.client.get_collections() - collection_names = [collection.name for collection in all_collections.collections] - if self.collection_name not in collection_names: - self.client.recreate_collection( - collection_name=self.collection_name, - vectors_config=VectorParams( - size=self.embedder.vector_dimension, - distance=Distance.COSINE, - hnsw_config=self.config.hnsw_config, - quantization_config=self.config.quantization_config, - on_disk=self.config.on_disk, - ), - ) - - def _get_or_create_db(self): - return self.client - - def _get_or_create_collection(self): - return f"{self.config.collection_name}-{self.embedder.vector_dimension}".lower().replace("_", "-") - - def get(self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - - :param ids: _list of doc ids to check for existence - :type ids: list[str] - :param where: to filter data - :type where: dict[str, any] - :param limit: The number of entries to be fetched - :type limit: Optional int, defaults to None - :return: All the existing IDs - :rtype: Set[str] - """ - - keys = set(where.keys() if where is not None else set()) - - qdrant_must_filters = [] - - if ids: - qdrant_must_filters.append( - models.FieldCondition( - key="identifier", - match=models.MatchAny( - any=ids, - ), - ) - ) - - if len(keys) > 0: - for key in keys: - qdrant_must_filters.append( - models.FieldCondition( - key="metadata.{}".format(key), - match=models.MatchValue( - value=where.get(key), - ), - ) - ) - - offset = 0 - existing_ids = [] - metadatas = [] - while offset is not None: - response = self.client.scroll( - collection_name=self.collection_name, - scroll_filter=models.Filter(must=qdrant_must_filters), - offset=offset, - limit=self.batch_size, - ) - offset = response[1] - for doc in response[0]: - existing_ids.append(doc.payload["identifier"]) - metadatas.append(doc.payload["metadata"]) - return {"ids": existing_ids, "metadatas": metadatas} - - def add( - self, - documents: list[str], - metadatas: list[object], - ids: list[str], - **kwargs: Optional[dict[str, any]], - ): - """add data in vector database - :param documents: list of texts to add - :type documents: list[str] - :param metadatas: list of metadata associated with docs - :type metadatas: list[object] - :param ids: ids of docs - :type ids: list[str] - """ - embeddings = self.embedder.embedding_fn(documents) - - payloads = [] - qdrant_ids = [] - for id, document, metadata in zip(ids, documents, metadatas): - metadata["text"] = document - qdrant_ids.append(id) - payloads.append({"identifier": id, "text": document, "metadata": copy.deepcopy(metadata)}) - - for i in tqdm(range(0, len(qdrant_ids), self.batch_size), desc="Adding data in batches"): - self.client.upsert( - collection_name=self.collection_name, - points=Batch( - ids=qdrant_ids[i : i + self.batch_size], - payloads=payloads[i : i + self.batch_size], - vectors=embeddings[i : i + self.batch_size], - ), - **kwargs, - ) - - def query( - self, - input_query: str, - n_results: int, - where: dict[str, any], - citations: bool = False, - **kwargs: Optional[dict[str, Any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - query contents from vector database based on vector similarity - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: Optional. to filter data - :type where: dict[str, any] - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - query_vector = self.embedder.embedding_fn([input_query])[0] - keys = set(where.keys() if where is not None else set()) - - qdrant_must_filters = [] - if len(keys) > 0: - for key in keys: - qdrant_must_filters.append( - models.FieldCondition( - key="metadata.{}".format(key), - match=models.MatchValue( - value=where.get(key), - ), - ) - ) - - results = self.client.search( - collection_name=self.collection_name, - query_filter=models.Filter(must=qdrant_must_filters), - query_vector=query_vector, - limit=n_results, - **kwargs, - ) - - contexts = [] - for result in results: - context = result.payload["text"] - if citations: - metadata = result.payload["metadata"] - metadata["score"] = result.score - contexts.append(tuple((context, metadata))) - else: - contexts.append(context) - return contexts - - def count(self) -> int: - response = self.client.get_collection(collection_name=self.collection_name) - return response.points_count - - def reset(self): - self.client.delete_collection(collection_name=self.collection_name) - self._initialize() - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - self.collection_name = self._get_or_create_collection() - - @staticmethod - def _generate_query(where: dict): - must_fields = [] - for key, value in where.items(): - must_fields.append( - models.FieldCondition( - key=f"metadata.{key}", - match=models.MatchValue( - value=value, - ), - ) - ) - return models.Filter(must=must_fields) - - def delete(self, where: dict): - db_filter = self._generate_query(where) - self.client.delete(collection_name=self.collection_name, points_selector=db_filter) diff --git a/embedchain/embedchain/vectordb/weaviate.py b/embedchain/embedchain/vectordb/weaviate.py deleted file mode 100644 index 897412a64..000000000 --- a/embedchain/embedchain/vectordb/weaviate.py +++ /dev/null @@ -1,363 +0,0 @@ -import copy -import os -from typing import Optional, Union - -try: - import weaviate -except ImportError: - raise ImportError( - "Weaviate requires extra dependencies. Install with `pip install --upgrade 'embedchain[weaviate]'`" - ) from None - -from embedchain.config.vector_db.weaviate import WeaviateDBConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.vectordb.base import BaseVectorDB - - -@register_deserializable -class WeaviateDB(BaseVectorDB): - """ - Weaviate as vector database - """ - - def __init__( - self, - config: Optional[WeaviateDBConfig] = None, - ): - """Weaviate as vector database. - :param config: Weaviate database config, defaults to None - :type config: WeaviateDBConfig, optional - :raises ValueError: No config provided - """ - if config is None: - self.config = WeaviateDBConfig() - else: - if not isinstance(config, WeaviateDBConfig): - raise TypeError( - "config is not a `WeaviateDBConfig` instance. " - "Please make sure the type is right and that you are passing an instance." - ) - self.config = config - self.batch_size = self.config.batch_size - self.client = weaviate.Client( - url=os.environ.get("WEAVIATE_ENDPOINT"), - auth_client_secret=weaviate.AuthApiKey(api_key=os.environ.get("WEAVIATE_API_KEY")), - **self.config.extra_params, - ) - # Since weaviate uses graphQL, we need to keep track of metadata keys added in the vectordb. - # This is needed to filter data while querying. - self.metadata_keys = {"data_type", "doc_id", "url", "hash", "app_id"} - - # Call parent init here because embedder is needed - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - """ - - if not self.embedder: - raise ValueError("Embedder not set. Please set an embedder with `set_embedder` before initialization.") - - self.index_name = self._get_index_name() - if not self.client.schema.exists(self.index_name): - # id is a reserved field in Weaviate, hence we had to change the name of the id field to identifier - # The none vectorizer is crucial as we have our own custom embedding function - """ - TODO: wait for weaviate to add indexing on `object[]` data-type so that we can add filter while querying. - Once that is done, change `dataType` of "metadata" field to `object[]` and update the query below. - """ - class_obj = { - "classes": [ - { - "class": self.index_name, - "vectorizer": "none", - "properties": [ - { - "name": "identifier", - "dataType": ["text"], - }, - { - "name": "text", - "dataType": ["text"], - }, - { - "name": "metadata", - "dataType": [self.index_name + "_metadata"], - }, - ], - }, - { - "class": self.index_name + "_metadata", - "vectorizer": "none", - "properties": [ - { - "name": "data_type", - "dataType": ["text"], - }, - { - "name": "doc_id", - "dataType": ["text"], - }, - { - "name": "url", - "dataType": ["text"], - }, - { - "name": "hash", - "dataType": ["text"], - }, - { - "name": "app_id", - "dataType": ["text"], - }, - ], - }, - ] - } - - self.client.schema.create(class_obj) - - def get(self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - :param ids: _list of doc ids to check for existance - :type ids: list[str] - :param where: to filter data - :type where: dict[str, any] - :return: ids - :rtype: Set[str] - """ - weaviate_where_operands = [] - - if ids: - for doc_id in ids: - weaviate_where_operands.append({"path": ["identifier"], "operator": "Equal", "valueText": doc_id}) - - keys = set(where.keys() if where is not None else set()) - if len(keys) > 0: - for key in keys: - weaviate_where_operands.append( - { - "path": ["metadata", self.index_name + "_metadata", key], - "operator": "Equal", - "valueText": where.get(key), - } - ) - - if len(weaviate_where_operands) == 1: - weaviate_where_clause = weaviate_where_operands[0] - else: - weaviate_where_clause = {"operator": "And", "operands": weaviate_where_operands} - - existing_ids = [] - metadatas = [] - cursor = None - offset = 0 - has_iterated_once = False - query_metadata_keys = self.metadata_keys.union(keys) - while cursor is not None or not has_iterated_once: - has_iterated_once = True - results = self._query_with_offset( - self.client.query.get( - self.index_name, - [ - "identifier", - weaviate.LinkTo("metadata", self.index_name + "_metadata", list(query_metadata_keys)), - ], - ) - .with_where(weaviate_where_clause) - .with_additional(["id"]) - .with_limit(limit or self.batch_size), - offset, - ) - - fetched_results = results["data"]["Get"].get(self.index_name, []) - if not fetched_results: - break - - for result in fetched_results: - existing_ids.append(result["identifier"]) - metadatas.append(result["metadata"][0]) - cursor = result["_additional"]["id"] - offset += 1 - - if limit is not None and len(existing_ids) >= limit: - break - - return {"ids": existing_ids, "metadatas": metadatas} - - def add(self, documents: list[str], metadatas: list[object], ids: list[str], **kwargs: Optional[dict[str, any]]): - """add data in vector database - :param documents: list of texts to add - :type documents: list[str] - :param metadatas: list of metadata associated with docs - :type metadatas: list[object] - :param ids: ids of docs - :type ids: list[str] - """ - embeddings = self.embedder.embedding_fn(documents) - self.client.batch.configure(batch_size=self.batch_size, timeout_retries=3) # Configure batch - with self.client.batch as batch: # Initialize a batch process - for id, text, metadata, embedding in zip(ids, documents, metadatas, embeddings): - doc = {"identifier": id, "text": text} - updated_metadata = {"text": text} - if metadata is not None: - updated_metadata.update(**metadata) - - obj_uuid = batch.add_data_object( - data_object=copy.deepcopy(doc), class_name=self.index_name, vector=embedding - ) - metadata_uuid = batch.add_data_object( - data_object=copy.deepcopy(updated_metadata), - class_name=self.index_name + "_metadata", - vector=embedding, - ) - batch.add_reference( - obj_uuid, self.index_name, "metadata", metadata_uuid, self.index_name + "_metadata", **kwargs - ) - - def query( - self, input_query: str, n_results: int, where: dict[str, any], citations: bool = False - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - query contents from vector database based on vector similarity - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: Optional. to filter data - :type where: dict[str, any] - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - query_vector = self.embedder.embedding_fn([input_query])[0] - keys = set(where.keys() if where is not None else set()) - data_fields = ["text"] - query_metadata_keys = self.metadata_keys.union(keys) - if citations: - data_fields.append(weaviate.LinkTo("metadata", self.index_name + "_metadata", list(query_metadata_keys))) - - if len(keys) > 0: - weaviate_where_operands = [] - for key in keys: - weaviate_where_operands.append( - { - "path": ["metadata", self.index_name + "_metadata", key], - "operator": "Equal", - "valueText": where.get(key), - } - ) - if len(weaviate_where_operands) == 1: - weaviate_where_clause = weaviate_where_operands[0] - else: - weaviate_where_clause = {"operator": "And", "operands": weaviate_where_operands} - - results = ( - self.client.query.get(self.index_name, data_fields) - .with_where(weaviate_where_clause) - .with_near_vector({"vector": query_vector}) - .with_limit(n_results) - .with_additional(["distance"]) - .do() - ) - else: - results = ( - self.client.query.get(self.index_name, data_fields) - .with_near_vector({"vector": query_vector}) - .with_limit(n_results) - .with_additional(["distance"]) - .do() - ) - - if results["data"]["Get"].get(self.index_name) is None: - return [] - - docs = results["data"]["Get"].get(self.index_name) - contexts = [] - for doc in docs: - context = doc["text"] - if citations: - metadata = doc["metadata"][0] - score = doc["_additional"]["distance"] - metadata["score"] = score - contexts.append((context, metadata)) - else: - contexts.append(context) - return contexts - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - :return: number of documents - :rtype: int - """ - data = self.client.query.aggregate(self.index_name).with_meta_count().do() - return data["data"]["Aggregate"].get(self.index_name)[0]["meta"]["count"] - - def _get_or_create_db(self): - """Called during initialization""" - return self.client - - def reset(self): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - # Delete all data from the database - self.client.batch.delete_objects( - self.index_name, where={"path": ["identifier"], "operator": "Like", "valueText": ".*"} - ) - - # Weaviate internally by default capitalizes the class name - def _get_index_name(self) -> str: - """Get the Weaviate index for a collection - :return: Weaviate index - :rtype: str - """ - return f"{self.config.collection_name}_{self.embedder.vector_dimension}".capitalize().replace("-", "_") - - @staticmethod - def _query_with_offset(query, offset): - if offset: - query.with_offset(offset) - results = query.do() - return results - - def _generate_query(self, where: dict): - weaviate_where_operands = [] - for key, value in where.items(): - weaviate_where_operands.append( - { - "path": ["metadata", self.index_name + "_metadata", key], - "operator": "Equal", - "valueText": value, - } - ) - - if len(weaviate_where_operands) == 1: - weaviate_where_clause = weaviate_where_operands[0] - else: - weaviate_where_clause = {"operator": "And", "operands": weaviate_where_operands} - - return weaviate_where_clause - - def delete(self, where: dict): - """Delete from database. - :param where: to filter data - :type where: dict[str, any] - """ - query = self._generate_query(where) - self.client.batch.delete_objects(self.index_name, where=query) diff --git a/embedchain/embedchain/vectordb/zilliz.py b/embedchain/embedchain/vectordb/zilliz.py deleted file mode 100644 index ca5544733..000000000 --- a/embedchain/embedchain/vectordb/zilliz.py +++ /dev/null @@ -1,252 +0,0 @@ -import logging -from typing import Any, Optional, Union - -from embedchain.config import ZillizDBConfig -from embedchain.helpers.json_serializable import register_deserializable -from embedchain.vectordb.base import BaseVectorDB - -try: - from pymilvus import ( - Collection, - CollectionSchema, - DataType, - FieldSchema, - MilvusClient, - connections, - utility, - ) -except ImportError: - raise ImportError( - "Zilliz requires extra dependencies. Install with `pip install --upgrade embedchain[milvus]`" - ) from None - -logger = logging.getLogger(__name__) - - -@register_deserializable -class ZillizVectorDB(BaseVectorDB): - """Base class for vector database.""" - - def __init__(self, config: ZillizDBConfig = None): - """Initialize the database. Save the config and client as an attribute. - - :param config: Database configuration class instance. - :type config: ZillizDBConfig - """ - - if config is None: - self.config = ZillizDBConfig() - else: - self.config = config - - self.client = MilvusClient( - uri=self.config.uri, - token=self.config.token, - ) - - self.connection = connections.connect( - uri=self.config.uri, - token=self.config.token, - ) - - super().__init__(config=self.config) - - def _initialize(self): - """ - This method is needed because `embedder` attribute needs to be set externally before it can be initialized. - - So it's can't be done in __init__ in one step. - """ - self._get_or_create_collection(self.config.collection_name) - - def _get_or_create_db(self): - """Get or create the database.""" - return self.client - - def _get_or_create_collection(self, name): - """ - Get or create a named collection. - - :param name: Name of the collection - :type name: str - """ - if utility.has_collection(name): - logger.info(f"[ZillizDB]: found an existing collection {name}, make sure the auto-id is disabled.") - self.collection = Collection(name) - else: - fields = [ - FieldSchema(name="id", dtype=DataType.VARCHAR, is_primary=True, max_length=512), - FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2048), - FieldSchema(name="embeddings", dtype=DataType.FLOAT_VECTOR, dim=self.embedder.vector_dimension), - FieldSchema(name="metadata", dtype=DataType.JSON), - ] - - schema = CollectionSchema(fields, enable_dynamic_field=True) - self.collection = Collection(name=name, schema=schema) - - index = { - "index_type": "AUTOINDEX", - "metric_type": self.config.metric_type, - } - self.collection.create_index("embeddings", index) - return self.collection - - def get(self, ids: Optional[list[str]] = None, where: Optional[dict[str, any]] = None, limit: Optional[int] = None): - """ - Get existing doc ids present in vector database - - :param ids: list of doc ids to check for existence - :type ids: list[str] - :param where: Optional. to filter data - :type where: dict[str, Any] - :param limit: Optional. maximum number of documents - :type limit: Optional[int] - :return: Existing documents. - :rtype: Set[str] - """ - data_ids = [] - metadatas = [] - if self.collection.num_entities == 0 or self.collection.is_empty: - return {"ids": data_ids, "metadatas": metadatas} - - filter_ = "" - if ids: - filter_ = f'id in "{ids}"' - - if where: - if filter_: - filter_ += " and " - filter_ = f"{self._generate_zilliz_filter(where)}" - - results = self.client.query(collection_name=self.config.collection_name, filter=filter_, output_fields=["*"]) - for res in results: - data_ids.append(res.get("id")) - metadatas.append(res.get("metadata", {})) - - return {"ids": data_ids, "metadatas": metadatas} - - def add( - self, - documents: list[str], - metadatas: list[object], - ids: list[str], - **kwargs: Optional[dict[str, any]], - ): - """Add to database""" - embeddings = self.embedder.embedding_fn(documents) - - for id, doc, metadata, embedding in zip(ids, documents, metadatas, embeddings): - data = {"id": id, "text": doc, "embeddings": embedding, "metadata": metadata} - self.client.insert(collection_name=self.config.collection_name, data=data, **kwargs) - - self.collection.load() - self.collection.flush() - self.client.flush(self.config.collection_name) - - def query( - self, - input_query: str, - n_results: int, - where: dict[str, Any], - citations: bool = False, - **kwargs: Optional[dict[str, Any]], - ) -> Union[list[tuple[str, dict]], list[str]]: - """ - Query contents from vector database based on vector similarity - - :param input_query: query string - :type input_query: str - :param n_results: no of similar documents to fetch from database - :type n_results: int - :param where: to filter data - :type where: dict[str, Any] - :raises InvalidDimensionException: Dimensions do not match. - :param citations: we use citations boolean param to return context along with the answer. - :type citations: bool, default is False. - :return: The content of the document that matched your query, - along with url of the source and doc_id (if citations flag is true) - :rtype: list[str], if citations=False, otherwise list[tuple[str, str, str]] - """ - - if self.collection.is_empty: - return [] - - output_fields = ["*"] - input_query_vector = self.embedder.embedding_fn([input_query]) - query_vector = input_query_vector[0] - - query_filter = self._generate_zilliz_filter(where) - query_result = self.client.search( - collection_name=self.config.collection_name, - data=[query_vector], - filter=query_filter, - limit=n_results, - output_fields=output_fields, - **kwargs, - ) - query_result = query_result[0] - contexts = [] - for query in query_result: - data = query["entity"] - score = query["distance"] - context = data["text"] - - if citations: - metadata = data.get("metadata", {}) - metadata["score"] = score - contexts.append(tuple((context, metadata))) - else: - contexts.append(context) - return contexts - - def count(self) -> int: - """ - Count number of documents/chunks embedded in the database. - - :return: number of documents - :rtype: int - """ - return self.collection.num_entities - - def reset(self, collection_names: list[str] = None): - """ - Resets the database. Deletes all embeddings irreversibly. - """ - if self.config.collection_name: - if collection_names: - for collection_name in collection_names: - if collection_name in self.client.list_collections(): - self.client.drop_collection(collection_name=collection_name) - else: - self.client.drop_collection(collection_name=self.config.collection_name) - self._get_or_create_collection(self.config.collection_name) - - def set_collection_name(self, name: str): - """ - Set the name of the collection. A collection is an isolated space for vectors. - - :param name: Name of the collection. - :type name: str - """ - if not isinstance(name, str): - raise TypeError("Collection name must be a string") - self.config.collection_name = name - - def _generate_zilliz_filter(self, where: dict[str, str]): - operands = [] - for key, value in where.items(): - operands.append(f'(metadata["{key}"] == "{value}")') - return " and ".join(operands) - - def delete(self, where: dict[str, Any]): - """ - Delete the embeddings from DB. Zilliz only support deleting with keys. - - - :param keys: Primary keys of the table entries to delete. - :type keys: Union[list, str, int] - """ - data = self.get(where=where) - keys = data.get("ids", []) - if keys: - self.client.delete(collection_name=self.config.collection_name, pks=keys) diff --git a/embedchain/examples/api_server/.dockerignore b/embedchain/examples/api_server/.dockerignore deleted file mode 100644 index 1dce42e87..000000000 --- a/embedchain/examples/api_server/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__/ -database -db -pyenv -venv -.env -.git -trash_files/ diff --git a/embedchain/examples/api_server/.gitignore b/embedchain/examples/api_server/.gitignore deleted file mode 100644 index 2227fe3e2..000000000 --- a/embedchain/examples/api_server/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__ -db -database -pyenv -venv -.env -trash_files/ -.ideas.md \ No newline at end of file diff --git a/embedchain/examples/api_server/Dockerfile b/embedchain/examples/api_server/Dockerfile deleted file mode 100644 index 6d5a7be87..000000000 --- a/embedchain/examples/api_server/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM python:3.11 AS backend - -WORKDIR /usr/src/api -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . . - -EXPOSE 5000 - -ENV FLASK_APP=api_server.py - -ENV FLASK_RUN_EXTRA_FILES=/usr/src/api/* -ENV FLASK_ENV=development - -CMD ["flask", "run", "--host=0.0.0.0", "--reload"] diff --git a/embedchain/examples/api_server/README.md b/embedchain/examples/api_server/README.md deleted file mode 100644 index 1d9fa612b..000000000 --- a/embedchain/examples/api_server/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# API Server - -This is a docker template to create your own API Server using the embedchain package. To know more about the API Server and how to use it, go [here](https://docs.embedchain.ai/examples/api_server). \ No newline at end of file diff --git a/embedchain/examples/api_server/api_server.py b/embedchain/examples/api_server/api_server.py deleted file mode 100644 index f8d4d4d1a..000000000 --- a/embedchain/examples/api_server/api_server.py +++ /dev/null @@ -1,57 +0,0 @@ -import logging - -from flask import Flask, jsonify, request - -from embedchain import App - -app = Flask(__name__) - - -logger = logging.getLogger(__name__) - - -@app.route("/add", methods=["POST"]) -def add(): - data = request.get_json() - data_type = data.get("data_type") - url_or_text = data.get("url_or_text") - if data_type and url_or_text: - try: - App().add(url_or_text, data_type=data_type) - return jsonify({"data": f"Added {data_type}: {url_or_text}"}), 200 - except Exception: - logger.exception(f"Failed to add {data_type=}: {url_or_text=}") - return jsonify({"error": f"Failed to add {data_type}: {url_or_text}"}), 500 - return jsonify({"error": "Invalid request. Please provide 'data_type' and 'url_or_text' in JSON format."}), 400 - - -@app.route("/query", methods=["POST"]) -def query(): - data = request.get_json() - question = data.get("question") - if question: - try: - response = App().query(question) - return jsonify({"data": response}), 200 - except Exception: - logger.exception(f"Failed to query {question=}") - return jsonify({"error": "An error occurred. Please try again!"}), 500 - return jsonify({"error": "Invalid request. Please provide 'question' in JSON format."}), 400 - - -@app.route("/chat", methods=["POST"]) -def chat(): - data = request.get_json() - question = data.get("question") - if question: - try: - response = App().chat(question) - return jsonify({"data": response}), 200 - except Exception: - logger.exception(f"Failed to chat {question=}") - return jsonify({"error": "An error occurred. Please try again!"}), 500 - return jsonify({"error": "Invalid request. Please provide 'question' in JSON format."}), 400 - - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000, debug=False) diff --git a/embedchain/examples/api_server/docker-compose.yml b/embedchain/examples/api_server/docker-compose.yml deleted file mode 100644 index 8fa3fc817..000000000 --- a/embedchain/examples/api_server/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: "3.9" - -services: - backend: - container_name: embedchain_api - restart: unless-stopped - build: - context: . - dockerfile: Dockerfile - env_file: - - variables.env - ports: - - "5000:5000" - volumes: - - .:/usr/src/api diff --git a/embedchain/examples/api_server/requirements.txt b/embedchain/examples/api_server/requirements.txt deleted file mode 100644 index 39e066ada..000000000 --- a/embedchain/examples/api_server/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -flask==2.3.2 -youtube-transcript-api==0.6.1 -pytube==15.0.0 -beautifulsoup4==4.12.3 -slack-sdk==3.21.3 -huggingface_hub==0.23.0 -gitpython==3.1.38 -yt_dlp==2023.11.14 -PyGithub==1.59.1 -feedparser==6.0.10 -newspaper3k==0.2.8 -listparser==0.19 \ No newline at end of file diff --git a/embedchain/examples/api_server/variables.env b/embedchain/examples/api_server/variables.env deleted file mode 100644 index da6725993..000000000 --- a/embedchain/examples/api_server/variables.env +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY="" \ No newline at end of file diff --git a/embedchain/examples/chainlit/.gitignore b/embedchain/examples/chainlit/.gitignore deleted file mode 100644 index 2121b2589..000000000 --- a/embedchain/examples/chainlit/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.chainlit diff --git a/embedchain/examples/chainlit/README.md b/embedchain/examples/chainlit/README.md deleted file mode 100644 index d54e69656..000000000 --- a/embedchain/examples/chainlit/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Chainlit + Embedchain Demo - -In this example, we will learn how to use Chainlit and Embedchain together - -## Setup - -First, install the required packages: - -```bash -pip install -r requirements.txt -``` - -## Run the app locally, - -``` -chainlit run app.py -``` diff --git a/embedchain/examples/chainlit/app.py b/embedchain/examples/chainlit/app.py deleted file mode 100644 index f2de4b0bd..000000000 --- a/embedchain/examples/chainlit/app.py +++ /dev/null @@ -1,35 +0,0 @@ -import os - -import chainlit as cl - -from embedchain import App - -os.environ["OPENAI_API_KEY"] = "sk-xxx" - - -@cl.on_chat_start -async def on_chat_start(): - app = App.from_config( - config={ - "app": {"config": {"name": "chainlit-app"}}, - "llm": { - "config": { - "stream": True, - } - }, - } - ) - # import your data here - app.add("https://www.forbes.com/profile/elon-musk/") - app.collect_metrics = False - cl.user_session.set("app", app) - - -@cl.on_message -async def on_message(message: cl.Message): - app = cl.user_session.get("app") - msg = cl.Message(content="") - for chunk in await cl.make_async(app.chat)(message.content): - await msg.stream_token(chunk) - - await msg.send() diff --git a/embedchain/examples/chainlit/chainlit.md b/embedchain/examples/chainlit/chainlit.md deleted file mode 100644 index d3de410e4..000000000 --- a/embedchain/examples/chainlit/chainlit.md +++ /dev/null @@ -1,15 +0,0 @@ -# Welcome to Embedchain! 🚀 - -Hello! 👋 Excited to see you join us. With Embedchain and Chainlit, create ChatGPT like apps effortlessly. - -## Quick Start 🌟 - -- **Embedchain Docs:** Get started with our comprehensive [Embedchain Documentation](https://docs.embedchain.ai/) 📚 -- **Discord Community:** Join our discord [Embedchain Discord](https://discord.gg/CUU9FPhRNt) to ask questions, share your projects, and connect with other developers! 💬 -- **UI Guide**: Master Chainlit with [Chainlit Documentation](https://docs.chainlit.io/) ⛓️ - -Happy building with Embedchain! 🎉 - -## Customize welcome screen - -Edit chainlit.md in your project root to change this welcome message. diff --git a/embedchain/examples/chainlit/requirements.txt b/embedchain/examples/chainlit/requirements.txt deleted file mode 100644 index 1604c5f9d..000000000 --- a/embedchain/examples/chainlit/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -chainlit==0.7.700 -embedchain==0.1.57 diff --git a/embedchain/examples/chat-pdf/README.md b/embedchain/examples/chat-pdf/README.md deleted file mode 100644 index 2a09c8bfc..000000000 --- a/embedchain/examples/chat-pdf/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Embedchain Chat with PDF App - -You can easily create and deploy your own `Chat-with-PDF` App using Embedchain. - -Checkout the live demo we created for [chat with PDF](https://embedchain.ai/demo/chat-pdf). - -Here are few simple steps for you to create and deploy your app: - -1. Fork the embedchain repo from [Github](https://github.com/embedchain/embedchain). - -If you run into problems with forking, please refer to [github docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) for forking a repo. - -2. Navigate to `chat-pdf` example app from your forked repo: - -```bash -cd /examples/chat-pdf -``` - -3. Run your app in development environment with simple commands - -```bash -pip install -r requirements.txt -ec dev -``` - -Feel free to improve our simple `chat-pdf` streamlit app and create pull request to showcase your app [here](https://docs.embedchain.ai/examples/showcase) - -4. You can easily deploy your app using Streamlit interface - -Connect your Github account with Streamlit and refer this [guide](https://docs.streamlit.io/streamlit-community-cloud/deploy-your-app) to deploy your app. - -You can also use the deploy button from your streamlit website you see when running `ec dev` command. diff --git a/embedchain/examples/chat-pdf/app.py b/embedchain/examples/chat-pdf/app.py deleted file mode 100644 index 73800605d..000000000 --- a/embedchain/examples/chat-pdf/app.py +++ /dev/null @@ -1,160 +0,0 @@ -import os -import queue -import re -import tempfile -import threading - -import streamlit as st - -from embedchain import App -from embedchain.config import BaseLlmConfig -from embedchain.helpers.callbacks import StreamingStdOutCallbackHandlerYield, generate - - -def embedchain_bot(db_path, api_key): - return App.from_config( - config={ - "llm": { - "provider": "openai", - "config": { - "model": "gpt-4o-mini", - "temperature": 0.5, - "max_tokens": 1000, - "top_p": 1, - "stream": True, - "api_key": api_key, - }, - }, - "vectordb": { - "provider": "chroma", - "config": {"collection_name": "chat-pdf", "dir": db_path, "allow_reset": True}, - }, - "embedder": {"provider": "openai", "config": {"api_key": api_key}}, - "chunker": {"chunk_size": 2000, "chunk_overlap": 0, "length_function": "len"}, - } - ) - - -def get_db_path(): - tmpdirname = tempfile.mkdtemp() - return tmpdirname - - -def get_ec_app(api_key): - if "app" in st.session_state: - print("Found app in session state") - app = st.session_state.app - else: - print("Creating app") - db_path = get_db_path() - app = embedchain_bot(db_path, api_key) - st.session_state.app = app - return app - - -with st.sidebar: - openai_access_token = st.text_input("OpenAI API Key", key="api_key", type="password") - "WE DO NOT STORE YOUR OPENAI KEY." - "Just paste your OpenAI API key here and we'll use it to power the chatbot. [Get your OpenAI API key](https://platform.openai.com/api-keys)" # noqa: E501 - - if st.session_state.api_key: - app = get_ec_app(st.session_state.api_key) - - pdf_files = st.file_uploader("Upload your PDF files", accept_multiple_files=True, type="pdf") - add_pdf_files = st.session_state.get("add_pdf_files", []) - for pdf_file in pdf_files: - file_name = pdf_file.name - if file_name in add_pdf_files: - continue - try: - if not st.session_state.api_key: - st.error("Please enter your OpenAI API Key") - st.stop() - temp_file_name = None - with tempfile.NamedTemporaryFile(mode="wb", delete=False, prefix=file_name, suffix=".pdf") as f: - f.write(pdf_file.getvalue()) - temp_file_name = f.name - if temp_file_name: - st.markdown(f"Adding {file_name} to knowledge base...") - app.add(temp_file_name, data_type="pdf_file") - st.markdown("") - add_pdf_files.append(file_name) - os.remove(temp_file_name) - st.session_state.messages.append({"role": "assistant", "content": f"Added {file_name} to knowledge base!"}) - except Exception as e: - st.error(f"Error adding {file_name} to knowledge base: {e}") - st.stop() - st.session_state["add_pdf_files"] = add_pdf_files - -st.title("📄 Embedchain - Chat with PDF") -styled_caption = '

🚀 An Embedchain app powered by OpenAI!

' # noqa: E501 -st.markdown(styled_caption, unsafe_allow_html=True) - -if "messages" not in st.session_state: - st.session_state.messages = [ - { - "role": "assistant", - "content": """ - Hi! I'm chatbot powered by Embedchain, which can answer questions about your pdf documents.\n - Upload your pdf documents here and I'll answer your questions about them! - """, - } - ] - -for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.markdown(message["content"]) - -if prompt := st.chat_input("Ask me anything!"): - if not st.session_state.api_key: - st.error("Please enter your OpenAI API Key", icon="🤖") - st.stop() - - app = get_ec_app(st.session_state.api_key) - - with st.chat_message("user"): - st.session_state.messages.append({"role": "user", "content": prompt}) - st.markdown(prompt) - - with st.chat_message("assistant"): - msg_placeholder = st.empty() - msg_placeholder.markdown("Thinking...") - full_response = "" - - q = queue.Queue() - - def app_response(result): - llm_config = app.llm.config.as_dict() - llm_config["callbacks"] = [StreamingStdOutCallbackHandlerYield(q=q)] - config = BaseLlmConfig(**llm_config) - answer, citations = app.chat(prompt, config=config, citations=True) - result["answer"] = answer - result["citations"] = citations - - results = {} - thread = threading.Thread(target=app_response, args=(results,)) - thread.start() - - for answer_chunk in generate(q): - full_response += answer_chunk - msg_placeholder.markdown(full_response) - - thread.join() - answer, citations = results["answer"], results["citations"] - if citations: - full_response += "\n\n**Sources**:\n" - sources = [] - for i, citation in enumerate(citations): - source = citation[1]["url"] - pattern = re.compile(r"([^/]+)\.[^\.]+\.pdf$") - match = pattern.search(source) - if match: - source = match.group(1) + ".pdf" - sources.append(source) - sources = list(set(sources)) - for source in sources: - full_response += f"- {source}\n" - - msg_placeholder.markdown(full_response) - print("Answer: ", full_response) - st.session_state.messages.append({"role": "assistant", "content": full_response}) diff --git a/embedchain/examples/chat-pdf/embedchain.json b/embedchain/examples/chat-pdf/embedchain.json deleted file mode 100644 index 32dec2933..000000000 --- a/embedchain/examples/chat-pdf/embedchain.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "provider": "streamlit.io" -} \ No newline at end of file diff --git a/embedchain/examples/chat-pdf/requirements.txt b/embedchain/examples/chat-pdf/requirements.txt deleted file mode 100644 index b9bbe5aad..000000000 --- a/embedchain/examples/chat-pdf/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -streamlit -embedchain -langchain-text-splitters -pysqlite3-binary diff --git a/embedchain/examples/discord_bot/.dockerignore b/embedchain/examples/discord_bot/.dockerignore deleted file mode 100644 index 1dce42e87..000000000 --- a/embedchain/examples/discord_bot/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__/ -database -db -pyenv -venv -.env -.git -trash_files/ diff --git a/embedchain/examples/discord_bot/.gitignore b/embedchain/examples/discord_bot/.gitignore deleted file mode 100644 index ba288ed39..000000000 --- a/embedchain/examples/discord_bot/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -__pycache__ -db -database -pyenv -venv -.env -trash_files/ diff --git a/embedchain/examples/discord_bot/Dockerfile b/embedchain/examples/discord_bot/Dockerfile deleted file mode 100644 index c4f45e58f..000000000 --- a/embedchain/examples/discord_bot/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /usr/src/discord_bot -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . . - -CMD ["python", "discord_bot.py"] diff --git a/embedchain/examples/discord_bot/README.md b/embedchain/examples/discord_bot/README.md deleted file mode 100644 index 2d581871c..000000000 --- a/embedchain/examples/discord_bot/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Discord Bot - -This is a docker template to create your own Discord bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/discord_bot). - -To run this use the following command, - -```bash -docker run --name discord-bot -e OPENAI_API_KEY=sk-xxx -e DISCORD_BOT_TOKEN=xxx -p 8080:8080 embedchain/discord-bot:latest -``` diff --git a/embedchain/examples/discord_bot/discord_bot.py b/embedchain/examples/discord_bot/discord_bot.py deleted file mode 100644 index c7bad2689..000000000 --- a/embedchain/examples/discord_bot/discord_bot.py +++ /dev/null @@ -1,76 +0,0 @@ -import os - -import discord -from discord.ext import commands -from dotenv import load_dotenv - -from embedchain import App - -load_dotenv() -intents = discord.Intents.default() -intents.message_content = True - -bot = commands.Bot(command_prefix="/ec ", intents=intents) -root_folder = os.getcwd() - - -def initialize_chat_bot(): - global chat_bot - chat_bot = App() - - -@bot.event -async def on_ready(): - print(f"Logged in as {bot.user.name}") - initialize_chat_bot() - - -@bot.event -async def on_command_error(ctx, error): - if isinstance(error, commands.CommandNotFound): - await send_response(ctx, "Invalid command. Please refer to the documentation for correct syntax.") - else: - print("Error occurred during command execution:", error) - - -@bot.command() -async def add(ctx, data_type: str, *, url_or_text: str): - print(f"User: {ctx.author.name}, Data Type: {data_type}, URL/Text: {url_or_text}") - try: - chat_bot.add(data_type, url_or_text) - await send_response(ctx, f"Added {data_type} : {url_or_text}") - except Exception as e: - await send_response(ctx, f"Failed to add {data_type} : {url_or_text}") - print("Error occurred during 'add' command:", e) - - -@bot.command() -async def query(ctx, *, question: str): - print(f"User: {ctx.author.name}, Query: {question}") - try: - response = chat_bot.query(question) - await send_response(ctx, response) - except Exception as e: - await send_response(ctx, "An error occurred. Please try again!") - print("Error occurred during 'query' command:", e) - - -@bot.command() -async def chat(ctx, *, question: str): - print(f"User: {ctx.author.name}, Query: {question}") - try: - response = chat_bot.chat(question) - await send_response(ctx, response) - except Exception as e: - await send_response(ctx, "An error occurred. Please try again!") - print("Error occurred during 'chat' command:", e) - - -async def send_response(ctx, message): - if ctx.guild is None: - await ctx.send(message) - else: - await ctx.reply(message) - - -bot.run(os.environ["DISCORD_BOT_TOKEN"]) diff --git a/embedchain/examples/discord_bot/docker-compose.yml b/embedchain/examples/discord_bot/docker-compose.yml deleted file mode 100644 index 69baff0d8..000000000 --- a/embedchain/examples/discord_bot/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: "3.9" - -services: - backend: - container_name: embedchain_discord_bot - restart: unless-stopped - build: - context: . - dockerfile: Dockerfile - env_file: - - variables.env \ No newline at end of file diff --git a/embedchain/examples/discord_bot/requirements.txt b/embedchain/examples/discord_bot/requirements.txt deleted file mode 100644 index 9cdaf53e3..000000000 --- a/embedchain/examples/discord_bot/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -discord==2.3.1 -embedchain==0.1.57 -python-dotenv==1.0.0 \ No newline at end of file diff --git a/embedchain/examples/discord_bot/variables.env b/embedchain/examples/discord_bot/variables.env deleted file mode 100644 index 7f3bd8975..000000000 --- a/embedchain/examples/discord_bot/variables.env +++ /dev/null @@ -1,2 +0,0 @@ -OPENAI_API_KEY="" -DISCORD_BOT_TOKEN="" \ No newline at end of file diff --git a/embedchain/examples/mistral-streamlit/README.md b/embedchain/examples/mistral-streamlit/README.md deleted file mode 100644 index 1bd80f83f..000000000 --- a/embedchain/examples/mistral-streamlit/README.md +++ /dev/null @@ -1,7 +0,0 @@ -### Streamlit Chat bot App (Embedchain + Mistral) - -To run it locally, - -```bash -streamlit run app.py -``` diff --git a/embedchain/examples/mistral-streamlit/app.py b/embedchain/examples/mistral-streamlit/app.py deleted file mode 100644 index 9df85fa32..000000000 --- a/embedchain/examples/mistral-streamlit/app.py +++ /dev/null @@ -1,72 +0,0 @@ -import os - -import streamlit as st - -from embedchain import App - - -@st.cache_resource -def ec_app(): - return App.from_config(config_path="config.yaml") - - -with st.sidebar: - huggingface_access_token = st.text_input("Hugging face Token", key="chatbot_api_key", type="password") - "[Get Hugging Face Access Token](https://huggingface.co/settings/tokens)" - "[View the source code](https://github.com/embedchain/examples/mistral-streamlit)" - - -st.title("💬 Chatbot") -st.caption("🚀 An Embedchain app powered by Mistral!") -if "messages" not in st.session_state: - st.session_state.messages = [ - { - "role": "assistant", - "content": """ - Hi! I'm a chatbot. I can answer questions and learn new things!\n - Ask me anything and if you want me to learn something do `/add `.\n - I can learn mostly everything. :) - """, - } - ] - -for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.markdown(message["content"]) - -if prompt := st.chat_input("Ask me anything!"): - if not st.session_state.chatbot_api_key: - st.error("Please enter your Hugging Face Access Token") - st.stop() - - os.environ["HUGGINGFACE_ACCESS_TOKEN"] = st.session_state.chatbot_api_key - app = ec_app() - - if prompt.startswith("/add"): - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - prompt = prompt.replace("/add", "").strip() - with st.chat_message("assistant"): - message_placeholder = st.empty() - message_placeholder.markdown("Adding to knowledge base...") - app.add(prompt) - message_placeholder.markdown(f"Added {prompt} to knowledge base!") - st.session_state.messages.append({"role": "assistant", "content": f"Added {prompt} to knowledge base!"}) - st.stop() - - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - - with st.chat_message("assistant"): - msg_placeholder = st.empty() - msg_placeholder.markdown("Thinking...") - full_response = "" - - for response in app.chat(prompt): - msg_placeholder.empty() - full_response += response - - msg_placeholder.markdown(full_response) - st.session_state.messages.append({"role": "assistant", "content": full_response}) diff --git a/embedchain/examples/mistral-streamlit/config.yaml b/embedchain/examples/mistral-streamlit/config.yaml deleted file mode 100644 index 6b5971348..000000000 --- a/embedchain/examples/mistral-streamlit/config.yaml +++ /dev/null @@ -1,17 +0,0 @@ -app: - config: - name: 'mistral-streamlit-app' - -llm: - provider: huggingface - config: - model: 'mistralai/Mixtral-8x7B-Instruct-v0.1' - temperature: 0.1 - max_tokens: 250 - top_p: 0.1 - stream: true - -embedder: - provider: huggingface - config: - model: 'sentence-transformers/all-mpnet-base-v2' diff --git a/embedchain/examples/mistral-streamlit/requirements.txt b/embedchain/examples/mistral-streamlit/requirements.txt deleted file mode 100644 index b864076ae..000000000 --- a/embedchain/examples/mistral-streamlit/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -streamlit==1.29.0 -embedchain diff --git a/embedchain/examples/nextjs/README.md b/embedchain/examples/nextjs/README.md deleted file mode 100644 index e2c87b3f6..000000000 --- a/embedchain/examples/nextjs/README.md +++ /dev/null @@ -1,129 +0,0 @@ -Fork this repo on [Github](https://github.com/embedchain/embedchain) to create your own NextJS discord and slack bot powered by Embedchain app. - -If you run into problems with forking, please refer to [github docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) for forking a repo. - -We will work from the examples/nextjs folder so change your current working directory by running the command - `cd /examples/nextjs` - -# Installation - -First, lets start by install all the required packages and dependencies. - -- Install all the required python packages by running `pip install -r requirements.txt`. - -- We will use [Fly.io](https://fly.io/) to deploy our embedchain app and discord/slack bot. Follow the step one to install [Fly.io CLI](https://docs.embedchain.ai/deployment/fly_io#step-1-install-flyctl-command-line) - -# Developement - -## Embedchain App - -First, lets get started by creating an Embedchain app powered with the knowledge of NextJS. We have already created an embedchain app using FastAPI in `ec_app` folder for you. Feel free to ingest data of your choice to power the App. - ---- -**NOTE** - -Create `.env` file in this folder and set your OpenAI API key as shown in `.env.example` file. If you want to use other open-source models, feel free to change the app config in `app.py`. More details for using custom configuration for Embedchain app is [available here](https://docs.embedchain.ai/api-reference/advanced/configuration). - ---- - -Before running the ec commands to develope/deploy the app, open `fly.toml` file and update the `name` variable to something unique. This is important as `fly.io` requires users to provide a globally unique deployment app names. - -Now, we need to launch this application with fly.io. You can see your app on [fly.io dashboard](https://fly.io/dashboard). Run the following command to launch your app on fly.io: -```bash -fly launch --no-deploy -``` - -To run the app in development: - -```bash -ec dev #To run the app in development environment -``` - -Run `ec deploy` to deploy your app on Fly.io. Once you deploy your app, save the endpoint on which our discord and slack bot will send requests. - - -## Discord bot - -For discord bot, you will need to create the bot on discord developer portal and get the discord bot token and your discord bot name. - -While keeping in mind the following note, create the discord bot by following the instructions from our [discord bot docs](https://docs.embedchain.ai/examples/discord_bot) and get discord bot token. - ---- -**NOTE** - -You do not need to set `OPENAI_API_KEY` to run this discord bot. Follow the remaining instructions to create a discord bot app. We recommend you to give the following sets of bot permissions to run the discord bot without errors: - -``` -(General Permissions) -Read Message/View Channels - -(Text Permissions) -Send Messages -Create Public Thread -Create Private Thread -Send Messages in Thread -Manage Threads -Embed Links -Read Message History -``` ---- - -Once you have your discord bot token and discord app name. Navigate to `nextjs_discord` folder and create `.env` file and define your discord bot token, discord bot name and endpoint of your embedchain app as shown in `.env.example` file. - -To run the app in development: - -```bash -python app.py #To run the app in development environment -``` - -Before deploying the app, open `fly.toml` file and update the `name` variable to something unique. This is important as `fly.io` requires users to provide a globally unique deployment app names. - -Now, we need to launch this application with fly.io. You can see your app on [fly.io dashboard](https://fly.io/dashboard). Run the following command to launch your app on fly.io: -```bash -fly launch --no-deploy -``` - -Run `ec deploy` to deploy your app on Fly.io. Once you deploy your app, your discord bot will be live! - - -## Slack bot - -For Slack bot, you will need to create the bot on slack developer portal and get the slack bot token and slack app token. - -### Setup - -- Create a workspace on Slack if you don't have one already by clicking [here](https://slack.com/intl/en-in/). -- Create a new App on your Slack account by going [here](https://api.slack.com/apps). -- Select `From Scratch`, then enter the Bot Name and select your workspace. -- Go to `App Credentials` section on the `Basic Information` tab from the left sidebar, create your app token and save it in your `.env` file as `SLACK_APP_TOKEN`. -- Go to `Socket Mode` tab from the left sidebar and enable the socket mode to listen to slack message from your workspace. -- (Optional) Under the `App Home` tab you can change your App display name and default name. -- Navigate to `Event Subscription` tab, and enable the event subscription so that we can listen to slack events. -- Once you enable the event subscription, you will need to subscribe to bot events to authorize the bot to listen to app mention events of the bot. Do that by tapping on `Add Bot User Event` button and select `app_mention`. -- On the left Sidebar, go to `OAuth and Permissions` and add the following scopes under `Bot Token Scopes`: -```text -app_mentions:read -channels:history -channels:read -chat:write -emoji:read -reactions:write -reactions:read -``` -- Now select the option `Install to Workspace` and after it's done, copy the `Bot User OAuth Token` and set it in your `.env` file as `SLACK_BOT_TOKEN`. - -Once you have your slack bot token and slack app token. Navigate to `nextjs_slack` folder and create `.env` file and define your slack bot token, slack app token and endpoint of your embedchain app as shown in `.env.example` file. - -To run the app in development: - -```bash -python app.py #To run the app in development environment -``` - -Before deploying the app, open `fly.toml` file and update the `name` variable to something unique. This is important as `fly.io` requires users to provide a globally unique deployment app names. - -Now, we need to launch this application with fly.io. You can see your app on [fly.io dashboard](https://fly.io/dashboard). Run the following command to launch your app on fly.io: -```bash -fly launch --no-deploy -``` - -Run `ec deploy` to deploy your app on Fly.io. Once you deploy your app, your slack bot will be live! diff --git a/embedchain/examples/nextjs/ec_app/.dockerignore b/embedchain/examples/nextjs/ec_app/.dockerignore deleted file mode 100644 index 9f4c740db..000000000 --- a/embedchain/examples/nextjs/ec_app/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -db/ \ No newline at end of file diff --git a/embedchain/examples/nextjs/ec_app/.env.example b/embedchain/examples/nextjs/ec_app/.env.example deleted file mode 100644 index b29363f94..000000000 --- a/embedchain/examples/nextjs/ec_app/.env.example +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY=sk-xxx \ No newline at end of file diff --git a/embedchain/examples/nextjs/ec_app/Dockerfile b/embedchain/examples/nextjs/ec_app/Dockerfile deleted file mode 100644 index 9eac80cee..000000000 --- a/embedchain/examples/nextjs/ec_app/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt /app/ - -RUN pip install -r requirements.txt - -COPY . /app - -EXPOSE 8080 - -CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/embedchain/examples/nextjs/ec_app/app.py b/embedchain/examples/nextjs/ec_app/app.py deleted file mode 100644 index 003543c46..000000000 --- a/embedchain/examples/nextjs/ec_app/app.py +++ /dev/null @@ -1,56 +0,0 @@ -from dotenv import load_dotenv -from fastapi import FastAPI, responses -from pydantic import BaseModel - -from embedchain import App - -load_dotenv(".env") - -app = FastAPI(title="Embedchain FastAPI App") -embedchain_app = App() - - -class SourceModel(BaseModel): - source: str - - -class QuestionModel(BaseModel): - question: str - - -@app.post("/add") -async def add_source(source_model: SourceModel): - """ - Adds a new source to the EmbedChain app. - Expects a JSON with a "source" key. - """ - source = source_model.source - embedchain_app.add(source) - return {"message": f"Source '{source}' added successfully."} - - -@app.post("/query") -async def handle_query(question_model: QuestionModel): - """ - Handles a query to the EmbedChain app. - Expects a JSON with a "question" key. - """ - question = question_model.question - answer = embedchain_app.query(question) - return {"answer": answer} - - -@app.post("/chat") -async def handle_chat(question_model: QuestionModel): - """ - Handles a chat request to the EmbedChain app. - Expects a JSON with a "question" key. - """ - question = question_model.question - response = embedchain_app.chat(question) - return {"response": response} - - -@app.get("/") -async def root(): - return responses.RedirectResponse(url="/docs") diff --git a/embedchain/examples/nextjs/ec_app/embedchain.json b/embedchain/examples/nextjs/ec_app/embedchain.json deleted file mode 100644 index 91074d5b4..000000000 --- a/embedchain/examples/nextjs/ec_app/embedchain.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "provider": "fly.io" -} \ No newline at end of file diff --git a/embedchain/examples/nextjs/ec_app/fly.toml b/embedchain/examples/nextjs/ec_app/fly.toml deleted file mode 100644 index a622c22d5..000000000 --- a/embedchain/examples/nextjs/ec_app/fly.toml +++ /dev/null @@ -1,22 +0,0 @@ -# fly.toml app configuration file generated for ec-app-crimson-dew-123 on 2024-01-04T06:48:40+05:30 -# -# See https://fly.io/docs/reference/configuration/ for information about how to use this file. -# - -app = "ec-app-crimson-dew-123" -primary_region = "sjc" - -[build] - -[http_service] - internal_port = 8080 - force_https = true - auto_stop_machines = false - auto_start_machines = true - min_machines_running = 0 - processes = ["app"] - -[[vm]] - cpu_kind = "shared" - cpus = 1 - memory_mb = 1024 diff --git a/embedchain/examples/nextjs/ec_app/requirements.txt b/embedchain/examples/nextjs/ec_app/requirements.txt deleted file mode 100644 index 3a7689298..000000000 --- a/embedchain/examples/nextjs/ec_app/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi==0.104.0 -uvicorn==0.23.2 -embedchain -beautifulsoup4 \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_discord/.dockerignore b/embedchain/examples/nextjs/nextjs_discord/.dockerignore deleted file mode 100644 index 9f4c740db..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -db/ \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_discord/.env.example b/embedchain/examples/nextjs/nextjs_discord/.env.example deleted file mode 100644 index 760a08d1b..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -DISCORD_BOT_TOKEN=xxxx -DISCORD_BOT_NAME=your_bot_name -EC_APP_URL=your_embedchain_app_url \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_discord/Dockerfile b/embedchain/examples/nextjs/nextjs_discord/Dockerfile deleted file mode 100644 index f151c915b..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt /app - -RUN pip install -r requirements.txt - -COPY . /app - -CMD ["python", "app.py"] diff --git a/embedchain/examples/nextjs/nextjs_discord/app.py b/embedchain/examples/nextjs/nextjs_discord/app.py deleted file mode 100644 index 74b245aba..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/app.py +++ /dev/null @@ -1,111 +0,0 @@ -import logging -import os - -import discord -import dotenv -import requests - -dotenv.load_dotenv(".env") - -intents = discord.Intents.default() -intents.message_content = True -client = discord.Client(intents=intents) -discord_bot_name = os.environ["DISCORD_BOT_NAME"] - -logger = logging.getLogger(__name__) - - -class NextJSBot: - def __init__(self) -> None: - logger.info("NextJS Bot powered with embedchain.") - - def add(self, _): - raise ValueError("Add is not implemented yet") - - def query(self, message, citations: bool = False): - url = os.environ["EC_APP_URL"] + "/query" - payload = { - "question": message, - "citations": citations, - } - try: - response = requests.request("POST", url, json=payload) - try: - response = response.json() - except Exception: - logger.error(f"Failed to parse response: {response}") - response = {} - return response - except Exception: - logger.exception(f"Failed to query {message}.") - response = "An error occurred. Please try again!" - return response - - def start(self): - discord_token = os.environ["DISCORD_BOT_TOKEN"] - client.run(discord_token) - - -NEXTJS_BOT = NextJSBot() - - -@client.event -async def on_ready(): - logger.info(f"User {client.user.name} logged in with id: {client.user.id}!") - - -def _get_question(message): - user_ids = message.raw_mentions - if len(user_ids) > 0: - for user_id in user_ids: - # remove mentions from message - question = message.content.replace(f"<@{user_id}>", "").strip() - return question - - -async def answer_query(message): - if ( - message.channel.type == discord.ChannelType.public_thread - or message.channel.type == discord.ChannelType.private_thread - ): - await message.channel.send( - "🧵 Currently, we don't support answering questions in threads. Could you please send your message in the channel for a swift response? Appreciate your understanding! 🚀" # noqa: E501 - ) - return - - question = _get_question(message) - print("Answering question: ", question) - thread = await message.create_thread(name=question) - await thread.send("🎭 Putting on my thinking cap, brb with an epic response!") - response = NEXTJS_BOT.query(question, citations=True) - - default_answer = "Sorry, I don't know the answer to that question. Please refer to the documentation.\nhttps://nextjs.org/docs" # noqa: E501 - answer = response.get("answer", default_answer) - - contexts = response.get("contexts", []) - if contexts: - sources = list(set(map(lambda x: x[1]["url"], contexts))) - answer += "\n\n**Sources**:\n" - for i, source in enumerate(sources): - answer += f"- {source}\n" - - sent_message = await thread.send(answer) - await sent_message.add_reaction("😮") - await sent_message.add_reaction("👍") - await sent_message.add_reaction("❤️") - await sent_message.add_reaction("👎") - - -@client.event -async def on_message(message): - mentions = message.mentions - if len(mentions) > 0 and any([user.bot and user.name == discord_bot_name for user in mentions]): - await answer_query(message) - - -def start_bot(): - NEXTJS_BOT.start() - - -if __name__ == "__main__": - start_bot() diff --git a/embedchain/examples/nextjs/nextjs_discord/embedchain.json b/embedchain/examples/nextjs/nextjs_discord/embedchain.json deleted file mode 100644 index 91074d5b4..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/embedchain.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "provider": "fly.io" -} \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_discord/fly.toml b/embedchain/examples/nextjs/nextjs_discord/fly.toml deleted file mode 100644 index 64a5c103f..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/fly.toml +++ /dev/null @@ -1,22 +0,0 @@ -# fly.toml app configuration file generated for nextjs-discord on 2024-01-04T06:56:01+05:30 -# -# See https://fly.io/docs/reference/configuration/ for information about how to use this file. -# - -app = "nextjs-discord" -primary_region = "sjc" - -[build] - -[http_service] - internal_port = 8080 - force_https = true - auto_stop_machines = true - auto_start_machines = true - min_machines_running = 0 - processes = ["app"] - -[[vm]] - cpu_kind = "shared" - cpus = 1 - memory_mb = 1024 diff --git a/embedchain/examples/nextjs/nextjs_discord/requirements.txt b/embedchain/examples/nextjs/nextjs_discord/requirements.txt deleted file mode 100644 index 3a7689298..000000000 --- a/embedchain/examples/nextjs/nextjs_discord/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi==0.104.0 -uvicorn==0.23.2 -embedchain -beautifulsoup4 \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_slack/.dockerignore b/embedchain/examples/nextjs/nextjs_slack/.dockerignore deleted file mode 100644 index 9f4c740db..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -db/ \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_slack/.env.example b/embedchain/examples/nextjs/nextjs_slack/.env.example deleted file mode 100644 index 8b23e52d2..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -SLACK_APP_TOKEN=xapp-xxxx -SLACK_BOT_TOKEN=xoxb-xxxx -EC_APP_URL=your_embedchain_app_url \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_slack/Dockerfile b/embedchain/examples/nextjs/nextjs_slack/Dockerfile deleted file mode 100644 index f151c915b..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt /app - -RUN pip install -r requirements.txt - -COPY . /app - -CMD ["python", "app.py"] diff --git a/embedchain/examples/nextjs/nextjs_slack/app.py b/embedchain/examples/nextjs/nextjs_slack/app.py deleted file mode 100644 index 005a4e29c..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/app.py +++ /dev/null @@ -1,124 +0,0 @@ -import logging -import os -import re - -import requests -from dotenv import load_dotenv -from slack_bolt import App as SlackApp -from slack_bolt.adapter.socket_mode import SocketModeHandler - -load_dotenv(".env") - -logger = logging.getLogger(__name__) - - -def remove_mentions(message): - mention_pattern = re.compile(r"<@[^>]+>") - cleaned_message = re.sub(mention_pattern, "", message) - cleaned_message.strip() - return cleaned_message - - -class SlackBotApp: - def __init__(self) -> None: - logger.info("Slack Bot using Embedchain!") - - def add(self, _): - raise ValueError("Add is not implemented yet") - - def query(self, query, citations: bool = False): - url = os.environ["EC_APP_URL"] + "/query" - payload = { - "question": query, - "citations": citations, - } - try: - response = requests.request("POST", url, json=payload) - try: - response = response.json() - except Exception: - logger.error(f"Failed to parse response: {response}") - response = {} - return response - except Exception: - logger.exception(f"Failed to query {query}.") - response = "An error occurred. Please try again!" - return response - - -SLACK_APP_TOKEN = os.environ["SLACK_APP_TOKEN"] -SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] - -slack_app = SlackApp(token=SLACK_BOT_TOKEN) -slack_bot = SlackBotApp() - - -@slack_app.event("message") -def app_message_handler(message, say): - pass - - -@slack_app.event("app_mention") -def app_mention_handler(body, say, client): - # Get the timestamp of the original message to reply in the thread - if "thread_ts" in body["event"]: - # thread is already created - thread_ts = body["event"]["thread_ts"] - say( - text="🧵 Currently, we don't support answering questions in threads. Could you please send your message in the channel for a swift response? Appreciate your understanding! 🚀", # noqa: E501 - thread_ts=thread_ts, - ) - return - - thread_ts = body["event"]["ts"] - say( - text="🎭 Putting on my thinking cap, brb with an epic response!", - thread_ts=thread_ts, - ) - query = body["event"]["text"] - question = remove_mentions(query) - print("Asking question: ", question) - response = slack_bot.query(question, citations=True) - default_answer = "Sorry, I don't know the answer to that question. Please refer to the documentation.\nhttps://nextjs.org/docs" # noqa: E501 - answer = response.get("answer", default_answer) - contexts = response.get("contexts", []) - if contexts: - sources = list(set(map(lambda x: x[1]["url"], contexts))) - answer += "\n\n*Sources*:\n" - for i, source in enumerate(sources): - answer += f"- {source}\n" - - print("Sending answer: ", answer) - result = say(text=answer, thread_ts=thread_ts) - if result["ok"]: - channel = result["channel"] - timestamp = result["ts"] - client.reactions_add( - channel=channel, - name="open_mouth", - timestamp=timestamp, - ) - client.reactions_add( - channel=channel, - name="thumbsup", - timestamp=timestamp, - ) - client.reactions_add( - channel=channel, - name="heart", - timestamp=timestamp, - ) - client.reactions_add( - channel=channel, - name="thumbsdown", - timestamp=timestamp, - ) - - -def start_bot(): - slack_socket_mode_handler = SocketModeHandler(slack_app, SLACK_APP_TOKEN) - slack_socket_mode_handler.start() - - -if __name__ == "__main__": - start_bot() diff --git a/embedchain/examples/nextjs/nextjs_slack/embedchain.json b/embedchain/examples/nextjs/nextjs_slack/embedchain.json deleted file mode 100644 index 91074d5b4..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/embedchain.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "provider": "fly.io" -} \ No newline at end of file diff --git a/embedchain/examples/nextjs/nextjs_slack/fly.toml b/embedchain/examples/nextjs/nextjs_slack/fly.toml deleted file mode 100644 index ca278bcd8..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/fly.toml +++ /dev/null @@ -1,22 +0,0 @@ -# fly.toml app configuration file generated for nextjs-slack on 2024-01-05T09:33:59+05:30 -# -# See https://fly.io/docs/reference/configuration/ for information about how to use this file. -# - -app = "nextjs-slack" -primary_region = "sjc" - -[build] - -[http_service] - internal_port = 8080 - force_https = true - auto_stop_machines = false - auto_start_machines = true - min_machines_running = 0 - processes = ["app"] - -[[vm]] - cpu_kind = "shared" - cpus = 1 - memory_mb = 1024 diff --git a/embedchain/examples/nextjs/nextjs_slack/requirements.txt b/embedchain/examples/nextjs/nextjs_slack/requirements.txt deleted file mode 100644 index da5e1e4d4..000000000 --- a/embedchain/examples/nextjs/nextjs_slack/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -python-dotenv -slack-sdk -slack_bolt -embedchain \ No newline at end of file diff --git a/embedchain/examples/nextjs/requirements.txt b/embedchain/examples/nextjs/requirements.txt deleted file mode 100644 index b245d1f0f..000000000 --- a/embedchain/examples/nextjs/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -fastapi==0.104.0 -uvicorn==0.23.2 -embedchain[opensource] -beautifulsoup4 -discord -python-dotenv -slack-sdk -slack_bolt diff --git a/embedchain/examples/private-ai/README.md b/embedchain/examples/private-ai/README.md deleted file mode 100644 index c0739ced8..000000000 --- a/embedchain/examples/private-ai/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Private AI - -In this example, we will create a private AI using embedchain. - -Private AI is useful when you want to chat with your data and you dont want to spend money and your data should stay on your machine. - -## How to install - -First create a virtual environment and install the requirements by running - -```bash -pip install -r requirements.txt -``` - -## How to use - -* Now open privateai.py file and change the line `app.add` to point to your directory or data source. -* If you want to add any other data type, you can browse the supported data types [here](https://docs.embedchain.ai/components/data-sources/overview) - -* Now simply run the file by - -```bash -python privateai.py -``` - -* Now you can enter and ask any questions from your data. \ No newline at end of file diff --git a/embedchain/examples/private-ai/config.yaml b/embedchain/examples/private-ai/config.yaml deleted file mode 100644 index bc243f340..000000000 --- a/embedchain/examples/private-ai/config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - max_tokens: 1000 - top_p: 1 -embedder: - provider: huggingface - config: - model: 'sentence-transformers/all-MiniLM-L6-v2' \ No newline at end of file diff --git a/embedchain/examples/private-ai/privateai.py b/embedchain/examples/private-ai/privateai.py deleted file mode 100644 index 613bc2a16..000000000 --- a/embedchain/examples/private-ai/privateai.py +++ /dev/null @@ -1,15 +0,0 @@ -from embedchain import App - -app = App.from_config("config.yaml") -app.add("/path/to/your/folder", data_type="directory") - -while True: - user_input = input("Enter your question (type 'exit' to quit): ") - - # Break the loop if the user types 'exit' - if user_input.lower() == "exit": - break - - # Process the input and provide a response - response = app.chat(user_input) - print(response) diff --git a/embedchain/examples/private-ai/requirements.txt b/embedchain/examples/private-ai/requirements.txt deleted file mode 100644 index 3ad57c3a3..000000000 --- a/embedchain/examples/private-ai/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -"embedchain[opensource]" \ No newline at end of file diff --git a/embedchain/examples/rest-api/.dockerignore b/embedchain/examples/rest-api/.dockerignore deleted file mode 100644 index 9d8eb1ab0..000000000 --- a/embedchain/examples/rest-api/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -.env -app.db -configs/**.yaml -db \ No newline at end of file diff --git a/embedchain/examples/rest-api/.gitignore b/embedchain/examples/rest-api/.gitignore deleted file mode 100644 index 60d5cd374..000000000 --- a/embedchain/examples/rest-api/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.env -app.db -configs/**.yaml -db diff --git a/embedchain/examples/rest-api/Dockerfile b/embedchain/examples/rest-api/Dockerfile deleted file mode 100644 index fe36d4adb..000000000 --- a/embedchain/examples/rest-api/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt /app/ - -RUN pip install --no-cache-dir -r requirements.txt - -COPY . /app - -EXPOSE 8080 - -ENV NAME embedchain - -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/embedchain/examples/rest-api/README.md b/embedchain/examples/rest-api/README.md deleted file mode 100644 index 4a20af78b..000000000 --- a/embedchain/examples/rest-api/README.md +++ /dev/null @@ -1,21 +0,0 @@ -## Single command to rule them all, - -```bash -docker run -d --name embedchain -p 8080:8080 embedchain/rest-api:latest -``` - -### To run the app locally, - -```bash -# will help reload on changes -DEVELOPMENT=True && python -m main -``` - -Using docker (locally), - -```bash -docker build -t embedchain/rest-api:latest . -docker run -d --name embedchain -p 8080:8080 embedchain/rest-api:latest -docker image push embedchain/rest-api:latest -``` - diff --git a/embedchain/examples/rest-api/__init__.py b/embedchain/examples/rest-api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/examples/rest-api/bruno/ec-rest-api/bruno.json b/embedchain/examples/rest-api/bruno/ec-rest-api/bruno.json deleted file mode 100644 index ed86683c3..000000000 --- a/embedchain/examples/rest-api/bruno/ec-rest-api/bruno.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "version": "1", - "name": "ec-rest-api", - "type": "collection" -} \ No newline at end of file diff --git a/embedchain/examples/rest-api/bruno/ec-rest-api/default_add.bru b/embedchain/examples/rest-api/bruno/ec-rest-api/default_add.bru deleted file mode 100644 index 1cd0144ba..000000000 --- a/embedchain/examples/rest-api/bruno/ec-rest-api/default_add.bru +++ /dev/null @@ -1,18 +0,0 @@ -meta { - name: default_add - type: http - seq: 3 -} - -post { - url: http://localhost:8080/add - body: json - auth: none -} - -body:json { - { - "source": "source_url", - "data_type": "data_type" - } -} diff --git a/embedchain/examples/rest-api/bruno/ec-rest-api/default_chat.bru b/embedchain/examples/rest-api/bruno/ec-rest-api/default_chat.bru deleted file mode 100644 index 4c4cbced4..000000000 --- a/embedchain/examples/rest-api/bruno/ec-rest-api/default_chat.bru +++ /dev/null @@ -1,17 +0,0 @@ -meta { - name: default_chat - type: http - seq: 4 -} - -post { - url: http://localhost:8080/chat - body: json - auth: none -} - -body:json { - { - "message": "message" - } -} diff --git a/embedchain/examples/rest-api/bruno/ec-rest-api/default_query.bru b/embedchain/examples/rest-api/bruno/ec-rest-api/default_query.bru deleted file mode 100644 index 61e55c6bd..000000000 --- a/embedchain/examples/rest-api/bruno/ec-rest-api/default_query.bru +++ /dev/null @@ -1,17 +0,0 @@ -meta { - name: default_query - type: http - seq: 2 -} - -post { - url: http://localhost:8080/query - body: json - auth: none -} - -body:json { - { - "query": "Who is Elon Musk?" - } -} diff --git a/embedchain/examples/rest-api/bruno/ec-rest-api/ping.bru b/embedchain/examples/rest-api/bruno/ec-rest-api/ping.bru deleted file mode 100644 index 22128827d..000000000 --- a/embedchain/examples/rest-api/bruno/ec-rest-api/ping.bru +++ /dev/null @@ -1,11 +0,0 @@ -meta { - name: ping - type: http - seq: 1 -} - -get { - url: http://localhost:8080/ping - body: json - auth: none -} diff --git a/embedchain/examples/rest-api/configs/README.md b/embedchain/examples/rest-api/configs/README.md deleted file mode 100644 index bf4bbc9ee..000000000 --- a/embedchain/examples/rest-api/configs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -### Config directory - -Here, all the YAML files will get stored. diff --git a/embedchain/examples/rest-api/database.py b/embedchain/examples/rest-api/database.py deleted file mode 100644 index 3eaafc95c..000000000 --- a/embedchain/examples/rest-api/database.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URI = "sqlite:///./app.db" - -engine = create_engine(SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False}) - -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/embedchain/examples/rest-api/default.yaml b/embedchain/examples/rest-api/default.yaml deleted file mode 100644 index bcdcf53f9..000000000 --- a/embedchain/examples/rest-api/default.yaml +++ /dev/null @@ -1,17 +0,0 @@ -app: - config: - id: 'default' - -llm: - provider: gpt4all - config: - model: 'orca-mini-3b-gguf2-q4_0.gguf' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: gpt4all - config: - model: 'all-MiniLM-L6-v2' diff --git a/embedchain/examples/rest-api/main.py b/embedchain/examples/rest-api/main.py deleted file mode 100644 index 66eef9276..000000000 --- a/embedchain/examples/rest-api/main.py +++ /dev/null @@ -1,326 +0,0 @@ -import logging -import os - -import aiofiles -import yaml -from database import Base, SessionLocal, engine -from fastapi import Depends, FastAPI, HTTPException, UploadFile -from models import DefaultResponse, DeployAppRequest, QueryApp, SourceApp -from services import get_app, get_apps, remove_app, save_app -from sqlalchemy.orm import Session -from utils import generate_error_message_for_api_keys - -from embedchain import App -from embedchain.client import Client - -logger = logging.getLogger(__name__) - -Base.metadata.create_all(bind=engine) - - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -app = FastAPI( - title="Embedchain REST API", - description="This is the REST API for Embedchain.", - version="0.0.1", - license_info={ - "name": "Apache 2.0", - "url": "https://github.com/embedchain/embedchain/blob/main/LICENSE", - }, -) - - -@app.get("/ping", tags=["Utility"]) -def check_status(): - """ - Endpoint to check the status of the API - """ - return {"ping": "pong"} - - -@app.get("/apps", tags=["Apps"]) -async def get_all_apps(db: Session = Depends(get_db)): - """ - Get all apps. - """ - apps = get_apps(db) - return {"results": apps} - - -@app.post("/create", tags=["Apps"], response_model=DefaultResponse) -async def create_app_using_default_config(app_id: str, config: UploadFile = None, db: Session = Depends(get_db)): - """ - Create a new app using App ID. - If you don't provide a config file, Embedchain will use the default config file\n - which uses opensource GPT4ALL model.\n - app_id: The ID of the app.\n - config: The YAML config file to create an App.\n - """ - try: - if app_id is None: - raise HTTPException(detail="App ID not provided.", status_code=400) - - if get_app(db, app_id) is not None: - raise HTTPException(detail=f"App with id '{app_id}' already exists.", status_code=400) - - yaml_path = "default.yaml" - if config is not None: - contents = await config.read() - try: - yaml.safe_load(contents) - # TODO: validate the config yaml file here - yaml_path = f"configs/{app_id}.yaml" - async with aiofiles.open(yaml_path, mode="w") as file_out: - await file_out.write(str(contents, "utf-8")) - except yaml.YAMLError as exc: - raise HTTPException(detail=f"Error parsing YAML: {exc}", status_code=400) - - save_app(db, app_id, yaml_path) - - return DefaultResponse(response=f"App created successfully. App ID: {app_id}") - except Exception as e: - logger.warning(str(e)) - raise HTTPException(detail=f"Error creating app: {str(e)}", status_code=400) - - -@app.get( - "/{app_id}/data", - tags=["Apps"], -) -async def get_datasources_associated_with_app_id(app_id: str, db: Session = Depends(get_db)): - """ - Get all data sources for an app.\n - app_id: The ID of the app. Use "default" for the default app.\n - """ - try: - if app_id is None: - raise HTTPException( - detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.", - status_code=400, - ) - - db_app = get_app(db, app_id) - - if db_app is None: - raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400) - - app = App.from_config(config_path=db_app.config) - - response = app.get_data_sources() - return {"results": response} - except ValueError as ve: - logger.warning(str(ve)) - raise HTTPException( - detail=generate_error_message_for_api_keys(ve), - status_code=400, - ) - except Exception as e: - logger.warning(str(e)) - raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400) - - -@app.post( - "/{app_id}/add", - tags=["Apps"], - response_model=DefaultResponse, -) -async def add_datasource_to_an_app(body: SourceApp, app_id: str, db: Session = Depends(get_db)): - """ - Add a source to an existing app.\n - app_id: The ID of the app. Use "default" for the default app.\n - source: The source to add.\n - data_type: The data type of the source. Remove it if you want Embedchain to detect it automatically.\n - """ - try: - if app_id is None: - raise HTTPException( - detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.", - status_code=400, - ) - - db_app = get_app(db, app_id) - - if db_app is None: - raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400) - - app = App.from_config(config_path=db_app.config) - - response = app.add(source=body.source, data_type=body.data_type) - return DefaultResponse(response=response) - except ValueError as ve: - logger.warning(str(ve)) - raise HTTPException( - detail=generate_error_message_for_api_keys(ve), - status_code=400, - ) - except Exception as e: - logger.warning(str(e)) - raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400) - - -@app.post( - "/{app_id}/query", - tags=["Apps"], - response_model=DefaultResponse, -) -async def query_an_app(body: QueryApp, app_id: str, db: Session = Depends(get_db)): - """ - Query an existing app.\n - app_id: The ID of the app. Use "default" for the default app.\n - query: The query that you want to ask the App.\n - """ - try: - if app_id is None: - raise HTTPException( - detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.", - status_code=400, - ) - - db_app = get_app(db, app_id) - - if db_app is None: - raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400) - - app = App.from_config(config_path=db_app.config) - - response = app.query(body.query) - return DefaultResponse(response=response) - except ValueError as ve: - logger.warning(str(ve)) - raise HTTPException( - detail=generate_error_message_for_api_keys(ve), - status_code=400, - ) - except Exception as e: - logger.warning(str(e)) - raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400) - - -# FIXME: The chat implementation of Embedchain needs to be modified to work with the REST API. -# @app.post( -# "/{app_id}/chat", -# tags=["Apps"], -# response_model=DefaultResponse, -# ) -# async def chat_with_an_app(body: MessageApp, app_id: str, db: Session = Depends(get_db)): -# """ -# Query an existing app.\n -# app_id: The ID of the app. Use "default" for the default app.\n -# message: The message that you want to send to the App.\n -# """ -# try: -# if app_id is None: -# raise HTTPException( -# detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.", -# status_code=400, -# ) - -# db_app = get_app(db, app_id) - -# if db_app is None: -# raise HTTPException( -# detail=f"App with id {app_id} does not exist, please create it first.", -# status_code=400 -# ) - -# app = App.from_config(config_path=db_app.config) - -# response = app.chat(body.message) -# return DefaultResponse(response=response) -# except ValueError as ve: -# raise HTTPException( -# detail=generate_error_message_for_api_keys(ve), -# status_code=400, -# ) -# except Exception as e: -# raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400) - - -@app.post( - "/{app_id}/deploy", - tags=["Apps"], - response_model=DefaultResponse, -) -async def deploy_app(body: DeployAppRequest, app_id: str, db: Session = Depends(get_db)): - """ - Query an existing app.\n - app_id: The ID of the app. Use "default" for the default app.\n - api_key: The API key to use for deployment. If not provided, - Embedchain will use the API key previously used (if any).\n - """ - try: - if app_id is None: - raise HTTPException( - detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.", - status_code=400, - ) - - db_app = get_app(db, app_id) - - if db_app is None: - raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400) - - app = App.from_config(config_path=db_app.config) - - api_key = body.api_key - # this will save the api key in the embedchain.db - Client(api_key=api_key) - - app.deploy() - return DefaultResponse(response="App deployed successfully.") - except ValueError as ve: - logger.warning(str(ve)) - raise HTTPException( - detail=generate_error_message_for_api_keys(ve), - status_code=400, - ) - except Exception as e: - logger.warning(str(e)) - raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400) - - -@app.delete( - "/{app_id}/delete", - tags=["Apps"], - response_model=DefaultResponse, -) -async def delete_app(app_id: str, db: Session = Depends(get_db)): - """ - Delete an existing app.\n - app_id: The ID of the app to be deleted. - """ - try: - if app_id is None: - raise HTTPException( - detail="App ID not provided. If you want to use the default app, use 'default' as the app_id.", - status_code=400, - ) - - db_app = get_app(db, app_id) - - if db_app is None: - raise HTTPException(detail=f"App with id {app_id} does not exist, please create it first.", status_code=400) - - app = App.from_config(config_path=db_app.config) - - # reset app.db - app.db.reset() - - remove_app(db, app_id) - return DefaultResponse(response=f"App with id {app_id} deleted successfully.") - except Exception as e: - raise HTTPException(detail=f"Error occurred: {str(e)}", status_code=400) - - -if __name__ == "__main__": - import uvicorn - - is_dev = os.getenv("DEVELOPMENT", "False") - uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=bool(is_dev)) diff --git a/embedchain/examples/rest-api/models.py b/embedchain/examples/rest-api/models.py deleted file mode 100644 index 1aecf00aa..000000000 --- a/embedchain/examples/rest-api/models.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Optional - -from database import Base -from pydantic import BaseModel, Field -from sqlalchemy import Column, Integer, String - - -class QueryApp(BaseModel): - query: str = Field("", description="The query that you want to ask the App.") - - model_config = { - "json_schema_extra": { - "example": { - "query": "Who is Elon Musk?", - } - } - } - - -class SourceApp(BaseModel): - source: str = Field("", description="The source that you want to add to the App.") - data_type: Optional[str] = Field("", description="The type of data to add, remove it for autosense.") - - model_config = {"json_schema_extra": {"example": {"source": "https://en.wikipedia.org/wiki/Elon_Musk"}}} - - -class DeployAppRequest(BaseModel): - api_key: str = Field("", description="The Embedchain API key for App deployments.") - - model_config = {"json_schema_extra": {"example": {"api_key": "ec-xxx"}}} - - -class MessageApp(BaseModel): - message: str = Field("", description="The message that you want to send to the App.") - - -class DefaultResponse(BaseModel): - response: str - - -class AppModel(Base): - __tablename__ = "apps" - - id = Column(Integer, primary_key=True, index=True) - app_id = Column(String, unique=True, index=True) - config = Column(String, unique=True, index=True) diff --git a/embedchain/examples/rest-api/requirements.txt b/embedchain/examples/rest-api/requirements.txt deleted file mode 100644 index 5c18f35bb..000000000 --- a/embedchain/examples/rest-api/requirements.txt +++ /dev/null @@ -1,24 +0,0 @@ -fastapi==0.104.0 -uvicorn==0.23.2 -streamlit==1.29.0 -embedchain==0.1.57 -slack-sdk==3.21.3 -flask==2.3.3 -fastapi-poe==0.0.16 -discord==2.3.2 -twilio==8.5.0 -huggingface-hub==0.17.3 -embedchain[community, opensource, elasticsearch, opensearch, weaviate, pinecone, qdrant, images, cohere, together, milvus, vertexai, llama2, gmail, json]==0.1.57 -sqlalchemy==2.0.22 -python-multipart==0.0.6 -youtube-transcript-api==0.6.1 -pytube==15.0.0 -beautifulsoup4==4.12.3 -slack-sdk==3.21.3 -huggingface_hub==0.23.0 -gitpython==3.1.38 -yt_dlp==2023.11.14 -PyGithub==1.59.1 -feedparser==6.0.10 -newspaper3k==0.2.8 -listparser==0.19 \ No newline at end of file diff --git a/embedchain/examples/rest-api/sample-config.yaml b/embedchain/examples/rest-api/sample-config.yaml deleted file mode 100644 index c7b867e41..000000000 --- a/embedchain/examples/rest-api/sample-config.yaml +++ /dev/null @@ -1,33 +0,0 @@ -app: - config: - id: 'default-app' - -llm: - provider: openai - config: - model: 'gpt-4o-mini' - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - template: | - Use the following pieces of context to answer the query at the end. - If you don't know the answer, just say that you don't know, don't try to make up an answer. - - $context - - Query: $query - - Helpful Answer: - -vectordb: - provider: chroma - config: - collection_name: 'rest-api-app' - dir: db - allow_reset: true - -embedder: - provider: openai - config: - model: 'text-embedding-ada-002' diff --git a/embedchain/examples/rest-api/services.py b/embedchain/examples/rest-api/services.py deleted file mode 100644 index c200454b8..000000000 --- a/embedchain/examples/rest-api/services.py +++ /dev/null @@ -1,25 +0,0 @@ -from models import AppModel -from sqlalchemy.orm import Session - - -def get_app(db: Session, app_id: str): - return db.query(AppModel).filter(AppModel.app_id == app_id).first() - - -def get_apps(db: Session, skip: int = 0, limit: int = 100): - return db.query(AppModel).offset(skip).limit(limit).all() - - -def save_app(db: Session, app_id: str, config: str): - db_app = AppModel(app_id=app_id, config=config) - db.add(db_app) - db.commit() - db.refresh(db_app) - return db_app - - -def remove_app(db: Session, app_id: str): - db_app = db.query(AppModel).filter(AppModel.app_id == app_id).first() - db.delete(db_app) - db.commit() - return db_app diff --git a/embedchain/examples/rest-api/utils.py b/embedchain/examples/rest-api/utils.py deleted file mode 100644 index ca41bed45..000000000 --- a/embedchain/examples/rest-api/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -def generate_error_message_for_api_keys(error: ValueError) -> str: - env_mapping = { - "OPENAI_API_KEY": "OPENAI_API_KEY", - "OPENAI_API_TYPE": "OPENAI_API_TYPE", - "OPENAI_API_BASE": "OPENAI_API_BASE", - "OPENAI_API_VERSION": "OPENAI_API_VERSION", - "COHERE_API_KEY": "COHERE_API_KEY", - "TOGETHER_API_KEY": "TOGETHER_API_KEY", - "ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY", - "JINACHAT_API_KEY": "JINACHAT_API_KEY", - "HUGGINGFACE_ACCESS_TOKEN": "HUGGINGFACE_ACCESS_TOKEN", - "REPLICATE_API_TOKEN": "REPLICATE_API_TOKEN", - } - - missing_keys = [env_mapping[key] for key in env_mapping if key in str(error)] - if missing_keys: - missing_keys_str = ", ".join(missing_keys) - return f"""Please set the {missing_keys_str} environment variable(s) when running the Docker container. -Example: `docker run -e {missing_keys[0]}=xxx embedchain/rest-api:latest` -""" - else: - return "Error: " + str(error) diff --git a/embedchain/examples/sadhguru-ai/README.md b/embedchain/examples/sadhguru-ai/README.md deleted file mode 100644 index e6ba224b2..000000000 --- a/embedchain/examples/sadhguru-ai/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Sadhguru AI - -This directory contains the code used to implement [Sadhguru AI](https://sadhguru-ai.streamlit.app/) using Embedchain. It is built on 3K+ videos and 1K+ articles of Sadhguru. You can find the full list of data sources [here](https://gist.github.com/deshraj/50b0597157e04829bbbb7bc418be6ccb). - -## Run locally - -You can run Sadhguru AI locally as a streamlit app using the following command: - -```bash -export OPENAI_API_KEY=sk-xxx -pip install -r requirements.txt -streamlit run app.py -``` - -Note: Remember to set your `OPENAI_API_KEY`. - -## Deploy to production - -You can create your own Sadhguru AI or similar RAG applications in production using one of the several deployment methods provided in [our docs](https://docs.embedchain.ai/get-started/deployment). diff --git a/embedchain/examples/sadhguru-ai/app.py b/embedchain/examples/sadhguru-ai/app.py deleted file mode 100644 index 5c123d600..000000000 --- a/embedchain/examples/sadhguru-ai/app.py +++ /dev/null @@ -1,100 +0,0 @@ -import csv -import queue -import threading -from io import StringIO - -import requests -import streamlit as st - -from embedchain import App -from embedchain.config import BaseLlmConfig -from embedchain.helpers.callbacks import StreamingStdOutCallbackHandlerYield, generate - - -@st.cache_resource -def sadhguru_ai(): - app = App() - return app - - -# Function to read the CSV file row by row -def read_csv_row_by_row(file_path): - with open(file_path, mode="r", newline="", encoding="utf-8") as file: - csv_reader = csv.DictReader(file) - for row in csv_reader: - yield row - - -@st.cache_resource -def add_data_to_app(): - app = sadhguru_ai() - url = "https://gist.githubusercontent.com/deshraj/50b0597157e04829bbbb7bc418be6ccb/raw/95b0f1547028c39691f5c7db04d362baa597f3f4/data.csv" # noqa:E501 - response = requests.get(url) - csv_file = StringIO(response.text) - for row in csv.reader(csv_file): - if row and row[0] != "url": - app.add(row[0], data_type="web_page") - - -app = sadhguru_ai() -add_data_to_app() - -assistant_avatar_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Sadhguru-Jaggi-Vasudev.jpg/640px-Sadhguru-Jaggi-Vasudev.jpg" # noqa: E501 - - -st.title("🙏 Sadhguru AI") - -styled_caption = '

🚀 An Embedchain app powered with Sadhguru\'s wisdom!

' # noqa: E501 -st.markdown(styled_caption, unsafe_allow_html=True) # noqa: E501 - -if "messages" not in st.session_state: - st.session_state.messages = [ - { - "role": "assistant", - "content": """ - Hi, I'm Sadhguru AI! I'm a mystic, yogi, visionary, and spiritual master. I'm here to answer your questions about life, the universe, and everything. - """, # noqa: E501 - } - ] - -for message in st.session_state.messages: - role = message["role"] - with st.chat_message(role, avatar=assistant_avatar_url if role == "assistant" else None): - st.markdown(message["content"]) - -if prompt := st.chat_input("Ask me anything!"): - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - - with st.chat_message("assistant", avatar=assistant_avatar_url): - msg_placeholder = st.empty() - msg_placeholder.markdown("Thinking...") - full_response = "" - - q = queue.Queue() - - def app_response(result): - config = BaseLlmConfig(stream=True, callbacks=[StreamingStdOutCallbackHandlerYield(q)]) - answer, citations = app.chat(prompt, config=config, citations=True) - result["answer"] = answer - result["citations"] = citations - - results = {} - thread = threading.Thread(target=app_response, args=(results,)) - thread.start() - - for answer_chunk in generate(q): - full_response += answer_chunk - msg_placeholder.markdown(full_response) - - thread.join() - answer, citations = results["answer"], results["citations"] - if citations: - full_response += "\n\n**Sources**:\n" - sources = list(set(map(lambda x: x[1]["url"], citations))) - for i, source in enumerate(sources): - full_response += f"{i+1}. {source}\n" - - msg_placeholder.markdown(full_response) - st.session_state.messages.append({"role": "assistant", "content": full_response}) diff --git a/embedchain/examples/sadhguru-ai/requirements.txt b/embedchain/examples/sadhguru-ai/requirements.txt deleted file mode 100644 index bba32905a..000000000 --- a/embedchain/examples/sadhguru-ai/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -embedchain -streamlit -pysqlite3-binary \ No newline at end of file diff --git a/embedchain/examples/slack_bot/Dockerfile b/embedchain/examples/slack_bot/Dockerfile deleted file mode 100644 index 1b07f204b..000000000 --- a/embedchain/examples/slack_bot/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /usr/src/ -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . . - -EXPOSE 8000 - -CMD ["python", "-m", "embedchain.bots.slack", "--port", "8000"] diff --git a/embedchain/examples/slack_bot/requirements.txt b/embedchain/examples/slack_bot/requirements.txt deleted file mode 100644 index af7258c94..000000000 --- a/embedchain/examples/slack_bot/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -slack-sdk==3.21.3 -flask==2.3.3 -fastapi-poe==0.0.16 \ No newline at end of file diff --git a/embedchain/examples/telegram_bot/.env.example b/embedchain/examples/telegram_bot/.env.example deleted file mode 100644 index cd80d5eff..000000000 --- a/embedchain/examples/telegram_bot/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -TELEGRAM_BOT_TOKEN= -OPENAI_API_KEY= diff --git a/embedchain/examples/telegram_bot/.gitignore b/embedchain/examples/telegram_bot/.gitignore deleted file mode 100644 index ba288ed39..000000000 --- a/embedchain/examples/telegram_bot/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -__pycache__ -db -database -pyenv -venv -.env -trash_files/ diff --git a/embedchain/examples/telegram_bot/Dockerfile b/embedchain/examples/telegram_bot/Dockerfile deleted file mode 100644 index aed7b62eb..000000000 --- a/embedchain/examples/telegram_bot/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /usr/src/ -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . . - -EXPOSE 8000 - -CMD ["python", "telegram_bot.py"] diff --git a/embedchain/examples/telegram_bot/README.md b/embedchain/examples/telegram_bot/README.md deleted file mode 100644 index 21fc2df50..000000000 --- a/embedchain/examples/telegram_bot/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Telegram Bot - -This is a replit template to create your own Telegram bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/telegram_bot). \ No newline at end of file diff --git a/embedchain/examples/telegram_bot/requirements.txt b/embedchain/examples/telegram_bot/requirements.txt deleted file mode 100644 index 3f6614632..000000000 --- a/embedchain/examples/telegram_bot/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -flask==2.3.2 -requests==2.31.0 -python-dotenv==1.0.0 -embedchain \ No newline at end of file diff --git a/embedchain/examples/telegram_bot/telegram_bot.py b/embedchain/examples/telegram_bot/telegram_bot.py deleted file mode 100644 index 8ff8892b3..000000000 --- a/embedchain/examples/telegram_bot/telegram_bot.py +++ /dev/null @@ -1,66 +0,0 @@ -import os - -import requests -from dotenv import load_dotenv -from flask import Flask, request - -from embedchain import App - -app = Flask(__name__) -load_dotenv() -bot_token = os.environ["TELEGRAM_BOT_TOKEN"] -chat_bot = App() - - -@app.route("/", methods=["POST"]) -def telegram_webhook(): - data = request.json - message = data["message"] - chat_id = message["chat"]["id"] - text = message["text"] - if text.startswith("/start"): - response_text = ( - "Welcome to Embedchain Bot! Try the following commands to use the bot:\n" - "For adding data sources:\n /add \n" - "For asking queries:\n /query " - ) - elif text.startswith("/add"): - _, data_type, url_or_text = text.split(maxsplit=2) - response_text = add_to_chat_bot(data_type, url_or_text) - elif text.startswith("/query"): - _, question = text.split(maxsplit=1) - response_text = query_chat_bot(question) - else: - response_text = "Invalid command. Please refer to the documentation for correct syntax." - send_message(chat_id, response_text) - return "OK" - - -def add_to_chat_bot(data_type, url_or_text): - try: - chat_bot.add(data_type, url_or_text) - response_text = f"Added {data_type} : {url_or_text}" - except Exception as e: - response_text = f"Failed to add {data_type} : {url_or_text}" - print("Error occurred during 'add' command:", e) - return response_text - - -def query_chat_bot(question): - try: - response = chat_bot.chat(question) - response_text = response - except Exception as e: - response_text = "An error occurred. Please try again!" - print("Error occurred during 'query' command:", e) - return response_text - - -def send_message(chat_id, text): - url = f"https://api.telegram.org/bot{bot_token}/sendMessage" - data = {"chat_id": chat_id, "text": text} - requests.post(url, json=data) - - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=8000, debug=False) diff --git a/embedchain/examples/unacademy-ai/README.md b/embedchain/examples/unacademy-ai/README.md deleted file mode 100644 index 013f0322f..000000000 --- a/embedchain/examples/unacademy-ai/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Unacademy UPSC AI - -This directory contains the code used to implement [Unacademy UPSC AI](https://unacademy-ai.streamlit.app/) using Embedchain. It is built on 16K+ youtube videos and 800+ course pages from Unacademy website. You can find the full list of data sources [here](https://gist.github.com/deshraj/7714feadccca13cefe574951652fa9b2). - -## Run locally - -You can run Unacademy AI locally as a streamlit app using the following command: - -```bash -export OPENAI_API_KEY=sk-xxx -pip install -r requirements.txt -streamlit run app.py -``` - -Note: Remember to set your `OPENAI_API_KEY`. - -## Deploy to production - -You can create your own Unacademy AI or similar RAG applications in production using one of the several deployment methods provided in [our docs](https://docs.embedchain.ai/get-started/deployment). diff --git a/embedchain/examples/unacademy-ai/app.py b/embedchain/examples/unacademy-ai/app.py deleted file mode 100644 index e31dba918..000000000 --- a/embedchain/examples/unacademy-ai/app.py +++ /dev/null @@ -1,104 +0,0 @@ -import queue - -import streamlit as st - -from embedchain import App -from embedchain.config import BaseLlmConfig -from embedchain.helpers.callbacks import StreamingStdOutCallbackHandlerYield, generate - - -@st.cache_resource -def unacademy_ai(): - app = App() - return app - - -app = unacademy_ai() - -assistant_avatar_url = "https://cdn-images-1.medium.com/v2/resize:fit:1200/1*LdFNhpOe7uIn-bHK9VUinA.jpeg" - -st.markdown(f"# Unacademy UPSC AI", unsafe_allow_html=True) - -styled_caption = """ -

-🚀 An Embedchain app powered with Unacademy\'s UPSC data! -

-""" -st.markdown(styled_caption, unsafe_allow_html=True) - -with st.expander(":grey[Want to create your own Unacademy UPSC AI?]"): - st.write( - """ - ```bash - pip install embedchain - ``` - - ```python - from embedchain import App - unacademy_ai_app = App() - unacademy_ai_app.add( - "https://unacademy.com/content/upsc/study-material/plan-policy/atma-nirbhar-bharat-3-0/", - data_type="web_page" - ) - unacademy_ai_app.chat("What is Atma Nirbhar 3.0?") - ``` - - For more information, checkout the [Embedchain docs](https://docs.embedchain.ai/get-started/quickstart). - """ - ) - -if "messages" not in st.session_state: - st.session_state.messages = [ - { - "role": "assistant", - "content": """Hi, I'm Unacademy UPSC AI bot, who can answer any questions related to UPSC preparation. - Let me help you prepare better for UPSC.\n -Sample questions: -- What are the subjects in UPSC CSE? -- What is the CSE scholarship price amount? -- What are different indian calendar forms? - """, - } - ] - -for message in st.session_state.messages: - role = message["role"] - with st.chat_message(role, avatar=assistant_avatar_url if role == "assistant" else None): - st.markdown(message["content"]) - -if prompt := st.chat_input("Ask me anything!"): - with st.chat_message("user"): - st.markdown(prompt) - st.session_state.messages.append({"role": "user", "content": prompt}) - - with st.chat_message("assistant", avatar=assistant_avatar_url): - msg_placeholder = st.empty() - msg_placeholder.markdown("Thinking...") - full_response = "" - - q = queue.Queue() - - def app_response(result): - llm_config = app.llm.config.as_dict() - llm_config["callbacks"] = [StreamingStdOutCallbackHandlerYield(q=q)] - config = BaseLlmConfig(**llm_config) - answer, citations = app.chat(prompt, config=config, citations=True) - result["answer"] = answer - result["citations"] = citations - - results = {} - - for answer_chunk in generate(q): - full_response += answer_chunk - msg_placeholder.markdown(full_response) - - answer, citations = results["answer"], results["citations"] - - if citations: - full_response += "\n\n**Sources**:\n" - sources = list(set(map(lambda x: x[1], citations))) - for i, source in enumerate(sources): - full_response += f"{i+1}. {source}\n" - - msg_placeholder.markdown(full_response) - st.session_state.messages.append({"role": "assistant", "content": full_response}) diff --git a/embedchain/examples/unacademy-ai/requirements.txt b/embedchain/examples/unacademy-ai/requirements.txt deleted file mode 100644 index bba32905a..000000000 --- a/embedchain/examples/unacademy-ai/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -embedchain -streamlit -pysqlite3-binary \ No newline at end of file diff --git a/embedchain/examples/whatsapp_bot/.env.example b/embedchain/examples/whatsapp_bot/.env.example deleted file mode 100644 index e570b8b55..000000000 --- a/embedchain/examples/whatsapp_bot/.env.example +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY= diff --git a/embedchain/examples/whatsapp_bot/.gitignore b/embedchain/examples/whatsapp_bot/.gitignore deleted file mode 100644 index 2227fe3e2..000000000 --- a/embedchain/examples/whatsapp_bot/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__ -db -database -pyenv -venv -.env -trash_files/ -.ideas.md \ No newline at end of file diff --git a/embedchain/examples/whatsapp_bot/Dockerfile b/embedchain/examples/whatsapp_bot/Dockerfile deleted file mode 100644 index 528f2eea6..000000000 --- a/embedchain/examples/whatsapp_bot/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /usr/src/ -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . . - -EXPOSE 8000 - -CMD ["python", "whatsapp_bot.py"] diff --git a/embedchain/examples/whatsapp_bot/README.md b/embedchain/examples/whatsapp_bot/README.md deleted file mode 100644 index 54cbf5c25..000000000 --- a/embedchain/examples/whatsapp_bot/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WhatsApp Bot - -This is a replit template to create your own WhatsApp bot using the embedchain package. To know more about the bot and how to use it, go [here](https://docs.embedchain.ai/examples/whatsapp_bot). \ No newline at end of file diff --git a/embedchain/examples/whatsapp_bot/requirements.txt b/embedchain/examples/whatsapp_bot/requirements.txt deleted file mode 100644 index ea2517403..000000000 --- a/embedchain/examples/whatsapp_bot/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -Flask==2.3.2 -twilio==8.5.0 -embedchain \ No newline at end of file diff --git a/embedchain/examples/whatsapp_bot/run.py b/embedchain/examples/whatsapp_bot/run.py deleted file mode 100644 index 92e26be15..000000000 --- a/embedchain/examples/whatsapp_bot/run.py +++ /dev/null @@ -1,10 +0,0 @@ -from embedchain.bots.whatsapp import WhatsAppBot - - -def main(): - whatsapp_bot = WhatsAppBot() - whatsapp_bot.start() - - -if __name__ == "__main__": - main() diff --git a/embedchain/examples/whatsapp_bot/whatsapp_bot.py b/embedchain/examples/whatsapp_bot/whatsapp_bot.py deleted file mode 100644 index 50f9c16dd..000000000 --- a/embedchain/examples/whatsapp_bot/whatsapp_bot.py +++ /dev/null @@ -1,51 +0,0 @@ -from flask import Flask, request -from twilio.twiml.messaging_response import MessagingResponse - -from embedchain import App - -app = Flask(__name__) -chat_bot = App() - - -@app.route("/chat", methods=["POST"]) -def chat(): - incoming_message = request.values.get("Body", "").lower() - response = handle_message(incoming_message) - twilio_response = MessagingResponse() - twilio_response.message(response) - return str(twilio_response) - - -def handle_message(message): - if message.startswith("add "): - response = add_sources(message) - else: - response = query(message) - return response - - -def add_sources(message): - message_parts = message.split(" ", 2) - if len(message_parts) == 3: - data_type = message_parts[1] - url_or_text = message_parts[2] - try: - chat_bot.add(data_type, url_or_text) - response = f"Added {data_type}: {url_or_text}" - except Exception as e: - response = f"Failed to add {data_type}: {url_or_text}.\nError: {str(e)}" - else: - response = "Invalid 'add' command format.\nUse: add " - return response - - -def query(message): - try: - response = chat_bot.chat(message) - except Exception: - response = "An error occurred. Please try again!" - return response - - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=8000, debug=False) diff --git a/embedchain/notebooks/anthropic.ipynb b/embedchain/notebooks/anthropic.ipynb deleted file mode 100644 index 2264d50fc..000000000 --- a/embedchain/notebooks/anthropic.ipynb +++ /dev/null @@ -1,161 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using Anthropic with Embedchain\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-NbXjAdlh0vJ", - "outputId": "efdce0dc-fb30-4e01-f5a8-ef1a7f4e8c09" - }, - "outputs": [], - "source": [ - "!pip install embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set Anthropic related environment variables\n", - "\n", - "You can find `OPENAI_API_KEY` on your [OpenAI dashboard](https://platform.openai.com/account/api-keys) and `ANTHROPIC_API_KEY` on your [Anthropic dashboard](https://console.anthropic.com/account/keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"\n", - "os.environ[\"ANTHROPIC_API_KEY\"] = \"xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3: Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"anthropic\",\n", - " \"config\": {\n", - " \"model\": \"claude-instant-1\",\n", - " \"temperature\": 0.5,\n", - " \"top_p\": 1,\n", - " \"stream\": False\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "dc17baec-39b5-4dc8-bd42-f2aad92697eb" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 391 - }, - "id": "cvIK7dWRjN_f", - "outputId": "3d1cb7ce-969e-4dad-d48c-b818b7447cc0" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/aws-bedrock.ipynb b/embedchain/notebooks/aws-bedrock.ipynb deleted file mode 100644 index 15995f706..000000000 --- a/embedchain/notebooks/aws-bedrock.ipynb +++ /dev/null @@ -1,226 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "63ab5e89", - "metadata": {}, - "source": [ - "## Cookbook for using Azure OpenAI with Embedchain" - ] - }, - { - "cell_type": "markdown", - "id": "e32a0265", - "metadata": {}, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b80ff15a", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install embedchain" - ] - }, - { - "cell_type": "markdown", - "id": "ac982a56", - "metadata": {}, - "source": [ - "### Step-2: Set AWS related environment variables\n", - "\n", - "You can find these env variables on your AWS Management Console." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "e0a36133", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "os.environ[\"AWS_ACCESS_KEY_ID\"] = \"AKIAIOSFODNN7EXAMPLE\" # replace with your AWS_ACCESS_KEY_ID\n", - "os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\" # replace with your AWS_SECRET_ACCESS_KEY\n", - "os.environ[\"AWS_SESSION_TOKEN\"] = \"IQoJb3JpZ2luX2VjEJr...==\" # replace with your AWS_SESSION_TOKEN\n", - "os.environ[\"AWS_DEFAULT_REGION\"] = \"us-east-1\" # replace with your AWS_DEFAULT_REGION\n", - "\n", - "from embedchain import App\n" - ] - }, - { - "cell_type": "markdown", - "id": "7d7b554e", - "metadata": {}, - "source": [ - "### Step-3: Define your llm and embedding model config\n", - "\n", - "May need to install langchain-anthropic to try with claude models" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "b9f52fc5", - "metadata": {}, - "outputs": [], - "source": [ - "config = \"\"\"\n", - "llm:\n", - " provider: aws_bedrock\n", - " config:\n", - " model: 'amazon.titan-text-express-v1'\n", - " deployment_name: ec_titan_express_v1\n", - " temperature: 0.5\n", - " max_tokens: 1000\n", - " top_p: 1\n", - " stream: false\n", - "\n", - "embedder:\n", - " provider: aws_bedrock\n", - " config:\n", - " model: amazon.titan-embed-text-v2:0\n", - " deployment_name: ec_embeddings_titan_v2\n", - "\"\"\"\n", - "\n", - "# Write the multi-line string to a YAML file\n", - "with open('aws_bedrock.yaml', 'w') as file:\n", - " file.write(config)" - ] - }, - { - "cell_type": "markdown", - "id": "98a11130", - "metadata": {}, - "source": [ - "### Step-4 Create two embedchain apps based on the config" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "1ee9bdd9", - "metadata": {}, - "outputs": [], - "source": [ - "app = App.from_config(config_path=\"aws_bedrock.yaml\")\n", - "app.reset() # Reset the app to clear the cache and start fresh" - ] - }, - { - "cell_type": "markdown", - "id": "554dc97b", - "metadata": {}, - "source": [ - "### Step-5: Add a data source to unrelated to the question you are asking" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "686ae765", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Inserting batches in chromadb: 100%|██████████| 1/1 [00:01<00:00, 1.62s/it]\n" - ] - }, - { - "data": { - "text/plain": [ - "'81b4936ef6f24974235a56acc1913c46'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "app.add(\"https://www.lipsum.com/\")" - ] - }, - { - "cell_type": "markdown", - "id": "ccc7d421", - "metadata": {}, - "source": [ - "### Step-6: Notice the underlying context changing with the updated data source" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "27868a7d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Context: 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of \"de Finibus Bonorum et Malorum\" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, \"Lorem ipsum dolor sit amet.\", comes from a line in section 1.10.32.The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from \"de Finibus Bonorum et Malorum\" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. Where can I get some? There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc. Donate: If you use this site regularly and would like to help keep the site on the Internet, please consider donating a small sum to help pay for the hosting and bandwidth bill. There is no minimum donation, any sum is appreciated - click here to donate using PayPal. Thank you for your support. Donate bitcoin: Lorem Ipsum - All the facts - Lipsum generator Հայերեն Shqip ‫العربية Български Català 中文简体 Hrvatski Česky Dansk Nederlands English Eesti Filipino Suomi Français ქართული Deutsch Ελληνικά ‫עברית हिन्दी Magyar Indonesia Italiano Latviski Lietuviškai македонски Melayu Norsk Polski Português Româna Pyccкий Српски Slovenčina Slovenščina Español Svenska ไทย Türkçe Українська Tiếng Việt Lorem Ipsum \"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.\" \"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain.\" What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). Where does it come from? Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 16UQLq1HZ3CNwhvgrarV6pMoA2CDjb4tyF Translations: Can you help translate this site into a foreign language ? Please email us with details if you can help. There is a set of mock banners available here in three colours and in a range of standard banner sizes: NodeJS Python Interface GTK Lipsum Rails .NET The standard Lorem Ipsum passage, used since the 1500s\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"Section 1.10.32 of \"de Finibus Bonorum et Malorum\", written by Cicero in 45 BC\"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?\" 1914 translation by H. Rackham \"But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Inserting batches in chromadb: 100%|██████████| 1/1 [00:01<00:00, 1.26s/it]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Context with updated memory: Elon Musk PROFILEElon MuskCEO, Tesla$234.1B$6.6B (2.73%)Real Time Net Worthas of 8/1/24Reflects change since 5 pm ET of prior trading day. 1 in the world todayPhoto by Martin Schoeller for ForbesAbout Elon MuskElon Musk cofounded six companies, including electric car maker Tesla, rocket producer SpaceX and tunneling startup Boring Company.He owns about 12% of Tesla excluding options, but has pledged more than half his shares as collateral for personal loans of up to $3.5 billion.In early 2024, a Delaware judge voided Musk's 2018 deal to receive options equaling an additional 9% of Tesla. Forbes has discounted the options by 50% pending Musk's appeal.SpaceX, founded in 2002, is worth nearly $180 billion after a December 2023 tender offer of up to $750 million; SpaceX stock has quintupled its value in four years.Musk bought Twitter in 2022 for $44 billion, after later trying to back out of the deal. He owns an estimated 74% of the company, now called X.Forbes estimates that Musk's stake in X is now worth nearly 70% less than he paid for it based on investor Fidelity's valuation of the company as of December 2023.Wealth HistoryHOVER TO REVEAL NET WORTH BY YEARForbes ListsThe Richest Person In Every State (2024) 2Billionaires (2024) 1Forbes 400 (2023) 1Innovative Leaders (2019) 25Powerful People (2018) 12Richest In Tech (2017)Global Game Changers (2016)More ListsPersonal StatsAge53Source of WealthTesla, SpaceX, Self MadeSelf-Made Score8Philanthropy Score1ResidenceAustin, TexasCitizenshipUnited StatesMarital StatusSingleChildren11EducationBachelor of Arts/Science, University of PennsylvaniaDid you knowMusk, who says he's worried about population collapse, has ten children with three women, including triplets and two sets of twins.As a kid in South Africa, Musk taught himself to code; he sold his first game, Blastar, for about $500.In Their Own WordsI operate on the physics approach to analysis. You boil things down to the first principles or fundamental truths in a particular area and then you reason up from there.Elon MuskRelated People & CompaniesReid HoffmanView ProfileTeslaHolds stake in TeslaView ProfileUniversity of PennsylvaniaAttended the schoolView ProfilePeter ThielCofounderView ProfileRobyn DenholmRelated by employment: TeslaView ProfileLarry EllisonRelated by financial asset: TeslaView ProfileSee MoreSee LessMore on Forbes2 hours agoDon Lemon Sues Elon Musk After $1.5 Million-Per-Year X Deal Fell ApartDon Lemon sues Elon Musk for refusing to pay him after an exclusive deal with the reporter on X fell apart.ByKirk OgunrindeContributor17 hours agoElon Musk’s Experimental School In Texas Is Now Looking For StudentsCalled Ad Astra, Musk has said the school will focus on “making all the children go through the same grade at the same time, like an assembly line.”BySarah EmersonForbes StaffJul 31, 2024Elon Musk Isn't Stopping Misinformation, He's Helped Spread ItThough hardly the most egregious example of a manipulated video, it is the fact that X failed to flag it that has raised concerns.ByPeter SuciuContributorJul 30, 2024Elon Musk Suddenly Breaks His Silence On Bitcoin After Issuing A Shock U.S. Dollar ‘Destruction’ Warning That Could Trigger A Crypto Price BoomElon Musk, the billionaire chief executive of Tesla, has mostly steered clear of bitcoin and crypto comments following the bitcoin price crash in 2022.ByBilly BambroughSenior ContributorJul 30, 20245 Reasons Deep Fakes (And Elon Musk) Won’t Destroy DemocracyWe've been dealing with things like deep fakes and people like Musk since the dawn of time. Five basic 'shadow skills' are why democracy is not in danger.ByPia LauritzenContributorJul 27, 2024Grimes’ Mother Blasts Musk—Accuses Him Of Keeping Children From Their MotherThe mother of billionaire Elon Musk’s former partner, musician Grimes, claimed Musk is withholding his children from their mother.ByBrian BushardForbes StaffJul 24, 2024Elon Musk Attends Netanyahu’s Speech To Congress As His GuestNetanyahu is speaking to Congress about Israel’s war with Hamas.ByAntonio Pequeño IVForbes StaffJul 24, 2024Elon Musk’s Net Worth Falls $16 Billion As Tesla Stock TanksMusk remains the richest person on Earth even after losing the equivalent of the 113th-wealthiest person’s entire fortune in one morning. ByDerek SaulForbes StaffJul 24, 2024Elon Musk’s Endorsement Of Trump Could Be A Grave Mistake For TeslaThe billionaire's embrace of the anti-EV presidential candidate risks politicizing a brand that sells best in California and, based on market studies, with Democrats.ByAlan OhnsmanForbes StaffJul 23, 2024The Prompt: Elon Musk’s ‘Gigafactory Of Compute’ Is Running In MemphisPlus: Target’s AI chatbot for employees misses the mark. ByRashi ShrivastavaForbes StaffJul 22, 2024‘Fortnite’ Is Getting Elon Musk’s Tesla Cybertruck As A New Combat VehicleAccording to a new trailer just released today, Elon Musk’s beloved Tesla Cybertruck is being released in Fortnite ByPaul TassiSenior ContributorJul 22, 2024Elon Musk’s Mad Dash To Build A Power-Hungry AI SupercomputerIn this week's Current Climate newsletter, Elon Musk's mad dash to build a water- and power-hungry AI supercomputer, Vietnamese billionaire's VinFast delays U.S. factory, and biomass-based carbon removalByAmy FeldmanForbes StaffJul 19, 2024There Are 10,000 Active Satellites In Orbit. Most Belong To Elon MuskIt’s a milestone that showcases decades of technical achievement, but might also make it harder to sleep at night if you think about it for too long. ByEric MackSenior ContributorJul 17, 2024Inside Elon Musk’s Mad Dash To Build A Giant xAI Supercomputer In MemphisElon Musk is “hauling ass” on his supercomputer project in Memphis. But a whiplash deal, NDAs and backroom promises made to the city have lawmakers demanding answers.BySarah EmersonForbes StaffJul 16, 2024Elon Musk To Move X And SpaceX Headquarters To TexasUpset with a new California law protecting the rights of transgender children, Elon Musk is moving his two\n" - ] - } - ], - "source": [ - "question = \"Who is Elon Musk?\"\n", - "context = \" \".join([a['context'] for a in app.search(question)])\n", - "print(\"Context:\", context)\n", - "app.add(\"https://www.forbes.com/profile/elon-musk\")\n", - "context = \" \".join([a['context'] for a in app.search(question)])\n", - "print(\"Context with updated memory:\", context)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2c607570", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/embedchain/notebooks/azure-openai.ipynb b/embedchain/notebooks/azure-openai.ipynb deleted file mode 100644 index 6d9d1a936..000000000 --- a/embedchain/notebooks/azure-openai.ipynb +++ /dev/null @@ -1,174 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "63ab5e89", - "metadata": {}, - "source": [ - "## Cookbook for using Azure OpenAI with Embedchain" - ] - }, - { - "cell_type": "markdown", - "id": "e32a0265", - "metadata": {}, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b80ff15a", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install embedchain" - ] - }, - { - "cell_type": "markdown", - "id": "ac982a56", - "metadata": {}, - "source": [ - "### Step-2: Set Azure OpenAI related environment variables\n", - "\n", - "You can find these env variables on your Azure OpenAI dashboard." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0a36133", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_TYPE\"] = \"azure\"\n", - "os.environ[\"OPENAI_API_BASE\"] = \"https://xxx.openai.azure.com/\"\n", - "os.environ[\"OPENAI_API_KEY\"] = \"xxx\"\n", - "os.environ[\"OPENAI_API_VERSION\"] = \"xxx\"" - ] - }, - { - "cell_type": "markdown", - "id": "7d7b554e", - "metadata": {}, - "source": [ - "### Step-3: Define your llm and embedding model config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b9f52fc5", - "metadata": {}, - "outputs": [], - "source": [ - "config = \"\"\"\n", - "llm:\n", - " provider: azure_openai\n", - " model: gpt-35-turbo\n", - " config:\n", - " deployment_name: ec_openai_azure\n", - " temperature: 0.5\n", - " max_tokens: 1000\n", - " top_p: 1\n", - " stream: false\n", - "\n", - "embedder:\n", - " provider: azure_openai\n", - " config:\n", - " model: text-embedding-ada-002\n", - " deployment_name: ec_embeddings_ada_002\n", - "\"\"\"\n", - "\n", - "# Write the multi-line string to a YAML file\n", - "with open('azure_openai.yaml', 'w') as file:\n", - " file.write(config)" - ] - }, - { - "cell_type": "markdown", - "id": "98a11130", - "metadata": {}, - "source": [ - "### Step-4 Create embedchain app based on the config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ee9bdd9", - "metadata": {}, - "outputs": [], - "source": [ - "app = App.from_config(config_path=\"azure_openai.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "554dc97b", - "metadata": {}, - "source": [ - "### Step-5: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "686ae765", - "metadata": {}, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "id": "ccc7d421", - "metadata": {}, - "source": [ - "### Step-6: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "27868a7d", - "metadata": {}, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/embedchain/notebooks/azure_openai.yaml b/embedchain/notebooks/azure_openai.yaml deleted file mode 100644 index 15a069032..000000000 --- a/embedchain/notebooks/azure_openai.yaml +++ /dev/null @@ -1,16 +0,0 @@ - -llm: - provider: azure_openai - model: gpt-35-turbo - config: - deployment_name: ec_openai_azure - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: azure_openai - config: - model: text-embedding-ada-002 - deployment_name: ec_embeddings_ada_002 diff --git a/embedchain/notebooks/chromadb.ipynb b/embedchain/notebooks/chromadb.ipynb deleted file mode 100644 index d74851efd..000000000 --- a/embedchain/notebooks/chromadb.ipynb +++ /dev/null @@ -1,147 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using ChromaDB with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "-NbXjAdlh0vJ" - }, - "outputs": [], - "source": [ - "!pip install embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set OpenAI environment variables\n", - "\n", - "You can find this env variable on your [OpenAI dashboard](https://platform.openai.com/account/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"vectordb\": {\n", - " \"provider\": \"chroma\",\n", - " \"config\": {\n", - " \"collection_name\": \"my-collection\",\n", - " \"host\": \"your-chromadb-url.com\",\n", - " \"port\": 5200,\n", - " \"allow_reset\": True\n", - " }\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/clarifai.ipynb b/embedchain/notebooks/clarifai.ipynb deleted file mode 100644 index e01400603..000000000 --- a/embedchain/notebooks/clarifai.ipynb +++ /dev/null @@ -1,135 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Cookbook for using Clarifai LLM and Embedders with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step-1: Install embedchain-clarifai package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install embedchain[clarifai]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step-2: Set Clarifai PAT as env variable.\n", - "Sign-up to [Clarifai](https://clarifai.com/signup?utm_source=clarifai_home&utm_medium=direct&) platform and you can obtain `CLARIFAI_PAT` by following this [link](https://docs.clarifai.com/clarifai-basics/authentication/personal-access-tokens/).\n", - "\n", - "optionally you can also pass `api_key` in config of llm/embedder class." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"CLARIFAI_PAT\"]=\"xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step-3 Create embedchain app using clarifai LLM and embedder and define your config.\n", - "\n", - "Browse through Clarifai community page to get the URL of different [LLM](https://clarifai.com/explore/models?page=1&perPage=24&filterData=%5B%7B%22field%22%3A%22use_cases%22%2C%22value%22%3A%5B%22llm%22%5D%7D%5D) and [embedding](https://clarifai.com/explore/models?page=1&perPage=24&filterData=%5B%7B%22field%22%3A%22input_fields%22%2C%22value%22%3A%5B%22text%22%5D%7D%2C%7B%22field%22%3A%22output_fields%22%2C%22value%22%3A%5B%22embeddings%22%5D%7D%5D) models available." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Use model_kwargs to pass all model specific parameters for inference.\n", - "app = App.from_config(config={\n", - " \"llm\": {\n", - " \"provider\": \"clarifai\",\n", - " \"config\": {\n", - " \"model\": \"https://clarifai.com/mistralai/completion/models/mistral-7B-Instruct\",\n", - " \"model_kwargs\": {\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000\n", - " }\n", - " }\n", - " },\n", - " \"embedder\": {\n", - " \"provider\": \"clarifai\",\n", - " \"config\": {\n", - " \"model\": \"https://clarifai.com/openai/embed/models/text-embedding-ada\",\n", - " }\n", - "}\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "v1", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.9.10" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/embedchain/notebooks/cohere.ipynb b/embedchain/notebooks/cohere.ipynb deleted file mode 100644 index 26df9c83f..000000000 --- a/embedchain/notebooks/cohere.ipynb +++ /dev/null @@ -1,165 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using Cohere with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-NbXjAdlh0vJ", - "outputId": "fae77912-4e6a-4c78-fcb7-fbbe46f7a9c7" - }, - "outputs": [], - "source": [ - "!pip install embedchain[cohere]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set Cohere related environment variables\n", - "\n", - "You can find `OPENAI_API_KEY` on your [OpenAI dashboard](https://platform.openai.com/account/api-keys) and `COHERE_API_KEY` key on your [Cohere dashboard](https://dashboard.cohere.com/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"\n", - "os.environ[\"COHERE_API_KEY\"] = \"xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 - }, - "id": "Amzxk3m-i3tD", - "outputId": "afe8afde-5cb8-46bc-c541-3ad26cc3fa6e" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"cohere\",\n", - " \"config\": {\n", - " \"model\": \"gptd-instruct-tft\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"top_p\": 1,\n", - " \"stream\": False\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 176 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "2f2718a4-3b7e-4844-fd46-3e0857653ca0" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "cvIK7dWRjN_f", - "outputId": "79e873c8-9594-45da-f5a3-0a893511267f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/elasticsearch.ipynb b/embedchain/notebooks/elasticsearch.ipynb deleted file mode 100644 index c507efd2b..000000000 --- a/embedchain/notebooks/elasticsearch.ipynb +++ /dev/null @@ -1,145 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using ElasticSearchDB with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "-NbXjAdlh0vJ" - }, - "outputs": [], - "source": [ - "!pip install embedchain[elasticsearch]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set OpenAI environment variables.\n", - "\n", - "You can find this env variable on your [OpenAI dashboard](https://platform.openai.com/account/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"elasticsearch\",\n", - " \"config\": {\n", - " \"collection_name\": \"es-index\",\n", - " \"es_url\": \"your-elasticsearch-url.com\",\n", - " \"allow_reset\": True,\n", - " \"api_key\": \"xxx\"\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/embedchain-chromadb-server.ipynb b/embedchain/notebooks/embedchain-chromadb-server.ipynb deleted file mode 100644 index 254c0bbce..000000000 --- a/embedchain/notebooks/embedchain-chromadb-server.ipynb +++ /dev/null @@ -1,111 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "553f2e71", - "metadata": {}, - "source": [ - "## Embedchain chromadb server example" - ] - }, - { - "cell_type": "markdown", - "id": "513e12e6", - "metadata": {}, - "source": [ - "This notebook shows an example of how you can use embedchain with chromdb (server). \n", - "\n", - "\n", - "First, run chroma inside docker using the following command:\n", - "\n", - "\n", - "```bash\n", - "git clone https://github.com/chroma-core/chroma\n", - "cd chroma && docker-compose up -d --build\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "92e7ad71", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "from embedchain.config import AppConfig\n", - "\n", - "\n", - "chromadb_host = \"localhost\"\n", - "chromadb_port = 8000\n", - "\n", - "config = AppConfig(host=chromadb_host, port=chromadb_port)\n", - "elon_bot = App(config)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "1a6d6841", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "All data from https://en.wikipedia.org/wiki/Elon_Musk already exists in the database.\n", - "All data from https://www.tesla.com/elon-musk already exists in the database.\n" - ] - } - ], - "source": [ - "# Embed Online Resources\n", - "elon_bot.add(\"web_page\", \"https://en.wikipedia.org/wiki/Elon_Musk\")\n", - "elon_bot.add(\"web_page\", \"https://www.tesla.com/elon-musk\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "34cda99c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Elon Musk runs four companies: Tesla, SpaceX, Neuralink, and The Boring Company.'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "elon_bot.query(\"How many companies does Elon Musk run?\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/embedchain/notebooks/embedchain-docs-site-example.ipynb b/embedchain/notebooks/embedchain-docs-site-example.ipynb deleted file mode 100644 index 27f28f322..000000000 --- a/embedchain/notebooks/embedchain-docs-site-example.ipynb +++ /dev/null @@ -1,121 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "e9a9dc6a", - "metadata": {}, - "outputs": [], - "source": [ - "from embedchain import App\n", - "\n", - "embedchain_docs_bot = App()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c1c24d68", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "All data from https://docs.embedchain.ai/ already exists in the database.\n" - ] - } - ], - "source": [ - "embedchain_docs_bot.add(\"docs_site\", \"https://docs.embedchain.ai/\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "48cdaecf", - "metadata": {}, - "outputs": [], - "source": [ - "answer = embedchain_docs_bot.query(\"Write a flask API for embedchain bot\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0fe18085", - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "To write a Flask API for the embedchain bot, you can use the following code snippet:\n", - "\n", - "```python\n", - "from flask import Flask, request, jsonify\n", - "from embedchain import App\n", - "\n", - "app = Flask(__name__)\n", - "bot = App()\n", - "\n", - "# Add datasets to the bot\n", - "bot.add(\"youtube_video\", \"https://www.youtube.com/watch?v=3qHkcs3kG44\")\n", - "bot.add(\"pdf_file\", \"https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf\")\n", - "\n", - "@app.route('/query', methods=['POST'])\n", - "def query():\n", - " data = request.get_json()\n", - " question = data['question']\n", - " response = bot.query(question)\n", - " return jsonify({'response': response})\n", - "\n", - "if __name__ == '__main__':\n", - " app.run()\n", - "```\n", - "\n", - "In this code, we create a Flask app and initialize an instance of the embedchain bot. We then add the desired datasets to the bot using the `add()` function.\n", - "\n", - "Next, we define a route `/query` that accepts POST requests. The request body should contain a JSON object with a `question` field. The bot's `query()` function is called with the provided question, and the response is returned as a JSON object.\n", - "\n", - "Finally, we run the Flask app using `app.run()`.\n", - "\n", - "Note: Make sure to install Flask and embedchain packages before running this code." - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Markdown\n", - "# Create a Markdown object and display it\n", - "markdown_answer = Markdown(answer)\n", - "display(markdown_answer)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/embedchain/notebooks/gpt4all.ipynb b/embedchain/notebooks/gpt4all.ipynb deleted file mode 100644 index 1bad7ebd3..000000000 --- a/embedchain/notebooks/gpt4all.ipynb +++ /dev/null @@ -1,169 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using GPT4All with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-NbXjAdlh0vJ", - "outputId": "077fa470-b51f-4c29-8c22-9c5f0a9cef47" - }, - "outputs": [], - "source": [ - "!pip install embedchain[opensource]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set GPT4ALL related environment variables\n", - "\n", - "GPT4All is free for all and doesn't require any API Key to use it. So you can use it for free!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "from embedchain import App" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "Amzxk3m-i3tD", - "outputId": "775db99b-e217-47db-f87f-788495d86f26" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"llm\": {\n", - " \"provider\": \"gpt4all\",\n", - " \"config\": {\n", - " \"model\": \"orca-mini-3b-gguf2-q4_0.gguf\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"top_p\": 1,\n", - " \"stream\": False\n", - " }\n", - " },\n", - " \"embedder\": {\n", - " \"provider\": \"gpt4all\",\n", - " \"config\": {\n", - " \"model\": \"all-MiniLM-L6-v2\"\n", - " }\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "c6514f17-3cb2-4fbc-c80d-79b3a311ff30" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 480 - }, - "id": "cvIK7dWRjN_f", - "outputId": "c74f356a-d2fb-426d-b36c-d84911397338" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/hugging_face_hub.ipynb b/embedchain/notebooks/hugging_face_hub.ipynb deleted file mode 100644 index eff2dc932..000000000 --- a/embedchain/notebooks/hugging_face_hub.ipynb +++ /dev/null @@ -1,168 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using Hugging Face Hub with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "-NbXjAdlh0vJ", - "outputId": "35ddc904-8067-44cf-dcc9-3c8b4cd29989" - }, - "outputs": [], - "source": [ - "!pip install embedchain[huggingface_hub,opensource]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set Hugging Face Hub related environment variables\n", - "\n", - "You can find your `HUGGINGFACE_ACCESS_TOKEN` key on your [Hugging Face Hub dashboard](https://huggingface.co/settings/tokens)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"HUGGINGFACE_ACCESS_TOKEN\"] = \"hf_xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"llm\": {\n", - " \"provider\": \"huggingface\",\n", - " \"config\": {\n", - " \"model\": \"google/flan-t5-xxl\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"top_p\": 0.8,\n", - " \"stream\": False\n", - " }\n", - " },\n", - " \"embedder\": {\n", - " \"provider\": \"huggingface\",\n", - " \"config\": {\n", - " \"model\": \"sentence-transformers/all-mpnet-base-v2\"\n", - " }\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 70 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "3c2a803a-3a93-4b0d-a6ae-17ae3c96c3c2" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "cvIK7dWRjN_f", - "outputId": "47a89d1c-b322-495c-822a-6c2ecef894d2" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/jina.ipynb b/embedchain/notebooks/jina.ipynb deleted file mode 100644 index b89a2aa22..000000000 --- a/embedchain/notebooks/jina.ipynb +++ /dev/null @@ -1,165 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using JinaChat with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "-NbXjAdlh0vJ", - "outputId": "69cb79a6-c758-4656-ccf7-9f3105c81d16" - }, - "outputs": [], - "source": [ - "!pip install embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set JinaChat related environment variables\n", - "\n", - "You can find `OPENAI_API_KEY` on your [OpenAI dashboard](https://platform.openai.com/account/api-keys) and `JINACHAT_API_KEY` key on your [Chat Jina dashboard](https://chat.jina.ai/api)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"\n", - "os.environ[\"JINACHAT_API_KEY\"] = \"xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 - }, - "id": "Amzxk3m-i3tD", - "outputId": "8d00da74-5f73-49bb-b868-dcf1c375ac85" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"jina\",\n", - " \"config\": {\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"top_p\": 1,\n", - " \"stream\": False\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "10eeacc7-9263-448e-876d-002af897ebe5" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "cvIK7dWRjN_f", - "outputId": "7dc7212f-a0e9-43c8-f119-f595ba79b4b7" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/lancedb.ipynb b/embedchain/notebooks/lancedb.ipynb deleted file mode 100644 index 08d99621a..000000000 --- a/embedchain/notebooks/lancedb.ipynb +++ /dev/null @@ -1,146 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using LanceDB with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "-NbXjAdlh0vJ" - }, - "outputs": [], - "source": [ - "! pip install embedchain lancedb" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set environment variables needed for LanceDB\n", - "\n", - "You can find this env variable on your [OpenAI](https://platform.openai.com/account/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"vectordb\": {\n", - " \"provider\": \"lancedb\",\n", - " \"config\": {\n", - " \"collection_name\": \"lancedb-index\"\n", - " }\n", - " }\n", - " }\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/llama2.ipynb b/embedchain/notebooks/llama2.ipynb deleted file mode 100644 index eabf7490b..000000000 --- a/embedchain/notebooks/llama2.ipynb +++ /dev/null @@ -1,161 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using LLAMA2 with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-NbXjAdlh0vJ", - "outputId": "86a4a9b2-4ed6-431c-da6f-c3eacb390f42" - }, - "outputs": [], - "source": [ - "!pip install embedchain[llama2]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set LLAMA2 related environment variables\n", - "\n", - "You can find `OPENAI_API_KEY` on your [OpenAI dashboard](https://platform.openai.com/account/api-keys) and `REPLICATE_API_TOKEN` key on your [Replicate dashboard](https://replicate.com/account/api-tokens)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"\n", - "os.environ[\"REPLICATE_API_TOKEN\"] = \"xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"llama2\",\n", - " \"config\": {\n", - " \"model\": \"a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"top_p\": 0.5,\n", - " \"stream\": False\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "ba158e9c-0f16-4c6b-a876-7543120985a2" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 599 - }, - "id": "cvIK7dWRjN_f", - "outputId": "e2d11a25-a2ed-4034-ec6a-e8a5986c89ae" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/ollama.ipynb b/embedchain/notebooks/ollama.ipynb deleted file mode 100644 index 550c23628..000000000 --- a/embedchain/notebooks/ollama.ipynb +++ /dev/null @@ -1,207 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using Ollama with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Setup Ollama, follow these instructions https://github.com/jmorganca/ollama\n", - "\n", - "Once Setup is done:\n", - "\n", - "- ollama pull llama2 (All supported models can be found here: https://ollama.ai/library)\n", - "- ollama run llama2 (Test out the model once)\n", - "- ollama serve" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-2 Create embedchain app and define your config (all local inference)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 - }, - "id": "Amzxk3m-i3tD", - "outputId": "afe8afde-5cb8-46bc-c541-3ad26cc3fa6e" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/sukkritsharma/workspace/embedchain/.venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], - "source": [ - "from embedchain import App\n", - "app = App.from_config(config={\n", - " \"llm\": {\n", - " \"provider\": \"ollama\",\n", - " \"config\": {\n", - " \"model\": \"llama2\",\n", - " \"temperature\": 0.5,\n", - " \"top_p\": 1,\n", - " \"stream\": True\n", - " }\n", - " },\n", - " \"embedder\": {\n", - " \"provider\": \"huggingface\",\n", - " \"config\": {\n", - " \"model\": \"BAAI/bge-small-en-v1.5\"\n", - " }\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-3: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 176 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "2f2718a4-3b7e-4844-fd46-3e0857653ca0" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Inserting batches in chromadb: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1.57it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully saved https://www.forbes.com/profile/elon-musk (DataType.WEB_PAGE). New chunks count: 4\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - }, - { - "data": { - "text/plain": [ - "'8cf46026cabf9b05394a2658bd1fe890'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-4: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "cvIK7dWRjN_f", - "outputId": "79e873c8-9594-45da-f5a3-0a893511267f" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elon Musk is a business magnate, investor, and engineer. He is the CEO of SpaceX and Tesla, Inc., and has been involved in other successful ventures such as Neuralink and The Boring Company. Musk is known for his innovative ideas, entrepreneurial spirit, and vision for the future of humanity.\n", - "\n", - "As the CEO of Tesla, Musk has played a significant role in popularizing electric vehicles and making them more accessible to the masses. Under his leadership, Tesla has grown into one of the most valuable companies in the world.\n", - "\n", - "SpaceX, another company founded by Musk, is a leading player in the commercial space industry. SpaceX has developed advanced rockets and spacecraft, including the Falcon 9 and Dragon, which have successfully launched numerous satellites and other payloads into orbit.\n", - "\n", - "Musk is also known for his ambitious goals, such as establishing a human settlement on Mars and developing sustainable energy solutions to address climate change. He has been recognized for his philanthropic efforts, particularly in the area of education, and has been awarded numerous honors and awards for his contributions to society.\n", - "\n", - "Overall, Elon Musk is a highly influential and innovative entrepreneur who has made significant impacts in various industries and has inspired many people around the world with his vision and leadership." - ] - } - ], - "source": [ - "answer = app.query(\"who is elon musk?\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/embedchain/notebooks/openai.ipynb b/embedchain/notebooks/openai.ipynb deleted file mode 100644 index 39da4bb37..000000000 --- a/embedchain/notebooks/openai.ipynb +++ /dev/null @@ -1,160 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using OpenAI with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "-NbXjAdlh0vJ", - "outputId": "6c630676-c7fc-4054-dc94-c613de58a037" - }, - "outputs": [], - "source": [ - "!pip install embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set OpenAI environment variables\n", - "\n", - "You can find this env variable on your [OpenAI dashboard](https://platform.openai.com/account/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"llm\": {\n", - " \"provider\": \"openai\",\n", - " \"config\": {\n", - " \"model\": \"gpt-4o-mini\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"top_p\": 1,\n", - " \"stream\": False\n", - " }\n", - " },\n", - " \"embedder\": {\n", - " \"provider\": \"openai\",\n", - " \"config\": {\n", - " \"model\": \"text-embedding-ada-002\"\n", - " }\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/openai_azure.yaml b/embedchain/notebooks/openai_azure.yaml deleted file mode 100644 index 15a069032..000000000 --- a/embedchain/notebooks/openai_azure.yaml +++ /dev/null @@ -1,16 +0,0 @@ - -llm: - provider: azure_openai - model: gpt-35-turbo - config: - deployment_name: ec_openai_azure - temperature: 0.5 - max_tokens: 1000 - top_p: 1 - stream: false - -embedder: - provider: azure_openai - config: - model: text-embedding-ada-002 - deployment_name: ec_embeddings_ada_002 diff --git a/embedchain/notebooks/opensearch.ipynb b/embedchain/notebooks/opensearch.ipynb deleted file mode 100644 index f9e678db5..000000000 --- a/embedchain/notebooks/opensearch.ipynb +++ /dev/null @@ -1,147 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using OpenSearchDB with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "-NbXjAdlh0vJ" - }, - "outputs": [], - "source": [ - "!pip install embedchain[opensearch]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set OpenAI environment variables and install the dependencies.\n", - "\n", - "You can find this env variable on your [OpenAI dashboard](https://platform.openai.com/account/api-keys). Now lets install the dependencies needed for Opensearch." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"opensearch\",\n", - " \"config\": {\n", - " \"opensearch_url\": \"your-opensearch-url.com\",\n", - " \"http_auth\": [\"admin\", \"admin\"],\n", - " \"vector_dimension\": 1536,\n", - " \"collection_name\": \"my-app\",\n", - " \"use_ssl\": False,\n", - " \"verify_certs\": False\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/pinecone.ipynb b/embedchain/notebooks/pinecone.ipynb deleted file mode 100644 index 9dee9b209..000000000 --- a/embedchain/notebooks/pinecone.ipynb +++ /dev/null @@ -1,146 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using PineconeDB with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "-NbXjAdlh0vJ" - }, - "outputs": [], - "source": [ - "!pip install embedchain pinecone-client pinecone-text" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set environment variables needed for Pinecone\n", - "\n", - "You can find this env variable on your [OpenAI dashboard](https://platform.openai.com/account/api-keys) and [Pinecone dashboard](https://app.pinecone.io/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"\n", - "os.environ[\"PINECONE_API_KEY\"] = \"xxx\"\n", - "os.environ[\"PINECONE_ENV\"] = \"xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Amzxk3m-i3tD" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"pinecone\",\n", - " \"config\": {\n", - " \"metric\": \"cosine\",\n", - " \"vector_dimension\": 768,\n", - " \"collection_name\": \"pc-index\"\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/together.ipynb b/embedchain/notebooks/together.ipynb deleted file mode 100644 index c2645cde8..000000000 --- a/embedchain/notebooks/together.ipynb +++ /dev/null @@ -1,211 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using Cohere with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-NbXjAdlh0vJ", - "outputId": "fae77912-4e6a-4c78-fcb7-fbbe46f7a9c7" - }, - "outputs": [], - "source": [ - "!pip install embedchain[together]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set Cohere related environment variables\n", - "\n", - "You can find `OPENAI_API_KEY` on your [OpenAI dashboard](https://platform.openai.com/account/api-keys) and `TOGETHER_API_KEY` key on your [Together dashboard](https://api.together.xyz/settings/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"\"\n", - "os.environ[\"TOGETHER_API_KEY\"] = \"\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 - }, - "id": "Amzxk3m-i3tD", - "outputId": "afe8afde-5cb8-46bc-c541-3ad26cc3fa6e" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"provider\": \"together\",\n", - " \"config\": {\n", - " \"model\": \"mistralai/Mixtral-8x7B-Instruct-v0.1\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 176 - }, - "id": "Sn_0rx9QjIY9", - "outputId": "2f2718a4-3b7e-4844-fd46-3e0857653ca0" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Inserting batches in chromadb: 100%|██████████| 1/1 [00:01<00:00, 1.16s/it]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully saved https://www.forbes.com/profile/elon-musk (DataType.WEB_PAGE). New chunks count: 4\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - }, - { - "data": { - "text/plain": [ - "'8cf46026cabf9b05394a2658bd1fe890'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "cvIK7dWRjN_f", - "outputId": "79e873c8-9594-45da-f5a3-0a893511267f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/notebooks/vertex_ai.ipynb b/embedchain/notebooks/vertex_ai.ipynb deleted file mode 100644 index 1cb41a77a..000000000 --- a/embedchain/notebooks/vertex_ai.ipynb +++ /dev/null @@ -1,162 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "b02n_zJ_hl3d" - }, - "source": [ - "## Cookbook for using VertexAI with Embedchain" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gyJ6ui2vhtMY" - }, - "source": [ - "### Step-1: Install embedchain package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-NbXjAdlh0vJ", - "outputId": "eb9be5b6-dc81-43d2-d515-df8f0116be11" - }, - "outputs": [], - "source": [ - "!pip install embedchain[vertexai]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nGnpSYAAh2bQ" - }, - "source": [ - "### Step-2: Set VertexAI related environment variables\n", - "\n", - "You can find `OPENAI_API_KEY` on your [OpenAI dashboard](https://platform.openai.com/account/api-keys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0fBdQ9GAiRvK" - }, - "outputs": [], - "source": [ - "import os\n", - "from embedchain import App\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PGt6uPLIi1CS" - }, - "source": [ - "### Step-3 Create embedchain app and define your config" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 582 - }, - "id": "Amzxk3m-i3tD", - "outputId": "5084b6ea-ec20-4281-9f36-e21e93c17475" - }, - "outputs": [], - "source": [ - "app = App.from_config(config={\n", - " \"llm\": {\n", - " \"provider\": \"vertexai\",\n", - " \"config\": {\n", - " \"model\": \"chat-bison\",\n", - " \"temperature\": 0.5,\n", - " \"max_tokens\": 1000,\n", - " \"stream\": False\n", - " }\n", - " },\n", - " \"embedder\": {\n", - " \"provider\": \"vertexai\",\n", - " \"config\": {\n", - " \"model\": \"textembedding-gecko\"\n", - " }\n", - " }\n", - "})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNXv4yZwi7ef" - }, - "source": [ - "### Step-4: Add data sources to your app" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Sn_0rx9QjIY9" - }, - "outputs": [], - "source": [ - "app.add(\"https://www.forbes.com/profile/elon-musk\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_7W6fDeAjMAP" - }, - "source": [ - "### Step-5: All set. Now start asking questions related to your data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cvIK7dWRjN_f" - }, - "outputs": [], - "source": [ - "while(True):\n", - " question = input(\"Enter question: \")\n", - " if question in ['q', 'exit', 'quit']:\n", - " break\n", - " answer = app.query(question)\n", - " print(answer)" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/embedchain/poetry.lock b/embedchain/poetry.lock deleted file mode 100644 index 6c027b87b..000000000 --- a/embedchain/poetry.lock +++ /dev/null @@ -1,7589 +0,0 @@ -# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand. - -[[package]] -name = "aiohttp" -version = "3.9.5" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, -] - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "alembic" -version = "1.13.2" -description = "A database migration tool for SQLAlchemy." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, - {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.3.0" -typing-extensions = ">=4" - -[package.extras] -tz = ["backports.zoneinfo ; python_version < \"3.9\""] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] -trio = ["trio (>=0.23)"] - -[[package]] -name = "asgiref" -version = "3.8.1" -description = "ASGI specs, helper code, and adapters" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, - {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} - -[package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version < \"3.11\"" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "authlib" -version = "1.6.10" -description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"weaviate\"" -files = [ - {file = "authlib-1.6.10-py2.py3-none-any.whl", hash = "sha256:aa639b43292554539924a3b4aaa9e81cd67ab64d3e28b22428c61f1200240287"}, - {file = "authlib-1.6.10.tar.gz", hash = "sha256:856a4f54d6ef3361ca6bb6d14a27e8b88f8097cca795fb428ffe13720e2ecde6"}, -] - -[package.dependencies] -cryptography = "*" - -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "bcrypt" -version = "4.1.3" -description = "Modern password hashing for your software and your servers" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74"}, - {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455"}, - {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a"}, - {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05"}, - {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3"}, - {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15"}, - {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d"}, - {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286"}, - {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64"}, - {file = "bcrypt-4.1.3-cp37-abi3-win32.whl", hash = "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf"}, - {file = "bcrypt-4.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978"}, - {file = "bcrypt-4.1.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c"}, - {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a"}, - {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84"}, - {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08"}, - {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611"}, - {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6"}, - {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834"}, - {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73"}, - {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d"}, - {file = "bcrypt-4.1.3-cp39-abi3-win32.whl", hash = "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2"}, - {file = "bcrypt-4.1.3-cp39-abi3-win_amd64.whl", hash = "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991"}, - {file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed"}, - {file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc"}, - {file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1"}, - {file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650"}, - {file = "bcrypt-4.1.3.tar.gz", hash = "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -groups = ["main"] -files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "black" -version = "23.12.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, - {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, - {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, - {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, - {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, - {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, - {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, - {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, - {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, - {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, - {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, - {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, - {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, - {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, - {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, - {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, - {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, - {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, - {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, - {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, - {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, - {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "boto3" -version = "1.34.144" -description = "The AWS SDK for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "boto3-1.34.144-py3-none-any.whl", hash = "sha256:b8433d481d50b68a0162c0379c0dd4aabfc3d1ad901800beb5b87815997511c1"}, - {file = "boto3-1.34.144.tar.gz", hash = "sha256:2f3e88b10b8fcc5f6100a9d74cd28230edc9d4fa226d99dd40a3ab38ac213673"}, -] - -[package.dependencies] -botocore = ">=1.34.144,<1.35.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.10.0,<0.11.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.34.144" -description = "Low-level, data-driven core of boto 3." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "botocore-1.34.144-py3-none-any.whl", hash = "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b"}, - {file = "botocore-1.34.144.tar.gz", hash = "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8"}, -] - -[package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = [ - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, -] - -[package.extras] -crt = ["awscrt (==0.20.11)"] - -[[package]] -name = "build" -version = "1.2.1" -description = "A simple, correct Python build frontend" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"}, - {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "os_name == \"nt\""} -importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} -packaging = ">=19.1" -pyproject_hooks = "*" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo (>=2023.8.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"] -typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] -uv = ["uv (>=0.1.18)"] -virtualenv = ["virtualenv (>=20.0.35)"] - -[[package]] -name = "cachetools" -version = "5.3.3" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, -] - -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, -] - -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"weaviate\" or platform_python_implementation == \"PyPy\"" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["main", "dev"] -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "chroma-hnswlib" -version = "0.7.6" -description = "Chromas fork of hnswlib" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f35192fbbeadc8c0633f0a69c3d3e9f1a4eab3a46b65458bbcbcabdd9e895c36"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f007b608c96362b8f0c8b6b2ac94f67f83fcbabd857c378ae82007ec92f4d82"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:456fd88fa0d14e6b385358515aef69fc89b3c2191706fd9aee62087b62aad09c"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dfaae825499c2beaa3b75a12d7ec713b64226df72a5c4097203e3ed532680da"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:2487201982241fb1581be26524145092c95902cb09fc2646ccfbc407de3328ec"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2fe6ea949047beed19a94b33f41fe882a691e58b70c55fdaa90274ae78be046f"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feceff971e2a2728c9ddd862a9dd6eb9f638377ad98438876c9aeac96c9482f5"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0633b60e00a2b92314d0bf5bbc0da3d3320be72c7e3f4a9b19f4609dc2b2ab"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a566abe32fab42291f766d667bdbfa234a7f457dcbd2ba19948b7a978c8ca624"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6be47853d9a58dedcfa90fc846af202b071f028bbafe1d8711bf64fe5a7f6111"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a7af35bdd39a88bffa49f9bb4bf4f9040b684514a024435a1ef5cdff980579d"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a53b1f1551f2b5ad94eb610207bde1bb476245fc5097a2bec2b476c653c58bde"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3085402958dbdc9ff5626ae58d696948e715aef88c86d1e3f9285a88f1afd3bc"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:77326f658a15adfb806a16543f7db7c45f06fd787d699e643642d6bde8ed49c4"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93b056ab4e25adab861dfef21e1d2a2756b18be5bc9c292aa252fa12bb44e6ae"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fe91f018b30452c16c811fd6c8ede01f84e5a9f3c23e0758775e57f1c3778871"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c0e627476f0f4d9e153420d36042dd9c6c3671cfd1fe511c0253e38c2a1039"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e9796a4536b7de6c6d76a792ba03e08f5aaa53e97e052709568e50b4d20c04f"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:d30e2db08e7ffdcc415bd072883a322de5995eb6ec28a8f8c054103bbd3ec1e0"}, - {file = "chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7"}, -] - -[package.dependencies] -numpy = "*" - -[[package]] -name = "chromadb" -version = "0.5.18" -description = "Chroma." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "chromadb-0.5.18-py3-none-any.whl", hash = "sha256:9dd3827b5e04b4ff0a5ea0df28a78bac88a09f45be37fcd7fe20f879b57c43cf"}, - {file = "chromadb-0.5.18.tar.gz", hash = "sha256:cfbb3e5aeeb1dd532b47d80ed9185e8a9886c09af41c8e6123edf94395d76aec"}, -] - -[package.dependencies] -bcrypt = ">=4.0.1" -build = ">=1.0.3" -chroma-hnswlib = "0.7.6" -fastapi = ">=0.95.2" -grpcio = ">=1.58.0" -httpx = ">=0.27.0" -importlib-resources = "*" -kubernetes = ">=28.1.0" -mmh3 = ">=4.0.1" -numpy = ">=1.22.5" -onnxruntime = ">=1.14.1" -opentelemetry-api = ">=1.2.0" -opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0" -opentelemetry-instrumentation-fastapi = ">=0.41b0" -opentelemetry-sdk = ">=1.2.0" -orjson = ">=3.9.12" -overrides = ">=7.3.1" -posthog = ">=2.4.0" -pydantic = ">=1.9" -pypika = ">=0.48.9" -PyYAML = ">=6.0.0" -rich = ">=10.11.0" -tenacity = ">=8.2.3" -tokenizers = ">=0.13.2" -tqdm = ">=4.65.0" -typer = ">=0.9.0" -typing-extensions = ">=4.5.0" -uvicorn = {version = ">=0.18.3", extras = ["standard"]} - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "cohere" -version = "5.5.8" -description = "" -optional = false -python-versions = "<4.0,>=3.8" -groups = ["main"] -files = [ - {file = "cohere-5.5.8-py3-none-any.whl", hash = "sha256:e1ed84b90eadd13c6a68ee28e378a0bb955f8945eadc6eb7ee126b3399cafd54"}, - {file = "cohere-5.5.8.tar.gz", hash = "sha256:84ce7666ff8fbdf4f41fb5f6ca452ab2639a514bc88967a2854a9b1b820d6ea0"}, -] - -[package.dependencies] -boto3 = ">=1.34.0,<2.0.0" -fastavro = ">=1.9.4,<2.0.0" -httpx = ">=0.21.2" -httpx-sse = ">=0.4.0,<0.5.0" -parameterized = ">=0.9.0,<0.10.0" -pydantic = ">=1.9.2" -requests = ">=2.0.0,<3.0.0" -tokenizers = ">=0.15,<1" -types-requests = ">=2.0.0,<3.0.0" -typing_extensions = ">=4.0.0" - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} - -[[package]] -name = "coloredlogs" -version = "15.0.1" -description = "Colored terminal output for Python's logging module" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -files = [ - {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, - {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, -] - -[package.dependencies] -humanfriendly = ">=9.1" - -[package.extras] -cron = ["capturer (>=2.4)"] - -[[package]] -name = "coverage" -version = "7.6.0" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, - {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, - {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, - {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, - {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, - {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, - {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, - {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, - {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, - {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, - {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, - {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, - {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, - {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] - -[[package]] -name = "cryptography" -version = "42.0.8" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"weaviate\"" -files = [ - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, - {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, - {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, - {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, - {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, - {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, - {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "cuda-bindings" -version = "13.2.0" -description = "Python bindings for CUDA" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b395f79cb89ce0cd8effff07c4a1e20101b873c256a1aeb286e8fd7bd0f556"}, - {file = "cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6f3682ec3c4769326aafc67c2ba669d97d688d0b7e63e659d36d2f8b72f32d6"}, - {file = "cuda_bindings-13.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:845025438a1b9e20718b9fb42add3e0eb72e85458bcab3eeb80bfd8f0a9dab33"}, - {file = "cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d"}, - {file = "cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1"}, - {file = "cuda_bindings-13.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:debb51b211d246f8326f6b6e982506a5d0d9906672c91bc478b66addc7ecc60a"}, - {file = "cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788"}, - {file = "cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955"}, - {file = "cuda_bindings-13.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:45815daeb595bf3b405c52671a2542b1f8e9329f3b029494acbfcc74aeaa1f2d"}, - {file = "cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0"}, - {file = "cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d"}, - {file = "cuda_bindings-13.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8cebe3ce4aeeca5af9c490e175f76c4b569bbf4a35a62294b777bc77bf7ac4d8"}, - {file = "cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e"}, - {file = "cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626"}, - {file = "cuda_bindings-13.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd658bb5c0e55b7b3e5dd0ed509c6addb298c665db26a9bfba35e1e626000ba2"}, - {file = "cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771"}, - {file = "cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b"}, - {file = "cuda_bindings-13.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6ccf14e0c1def3b7200100aafff3a9f7e210ecb6e409329e92dcf6cd2c00d5c7"}, -] - -[package.dependencies] -cuda-pathfinder = ">=1.1,<2.0" - -[package.extras] -all = ["cuda-toolkit[cufile] (==13.*) ; sys_platform == \"linux\"", "cuda-toolkit[nvfatbin,nvjitlink,nvrtc,nvvm] (==13.*)"] - -[[package]] -name = "cuda-pathfinder" -version = "1.5.3" -description = "Pathfinder for CUDA components" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "cuda_pathfinder-1.5.3-py3-none-any.whl", hash = "sha256:dff021123aedbb4117cc7ec81717bbfe198fb4e8b5f1ee57e0e084fec5c8577d"}, -] - -[[package]] -name = "cuda-toolkit" -version = "13.0.2" -description = "CUDA Toolkit meta-package" -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb"}, -] - -[package.dependencies] -nvidia-cublas = {version = "==13.1.0.3.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"cublas\""} -nvidia-cuda-cupti = {version = "==13.0.85.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"cupti\""} -nvidia-cuda-nvrtc = {version = "==13.0.88.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"nvrtc\""} -nvidia-cuda-runtime = {version = "==13.0.96.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"cudart\""} -nvidia-cufft = {version = "==12.0.0.61.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"cufft\""} -nvidia-cufile = {version = "==1.15.1.6.*", optional = true, markers = "sys_platform == \"linux\" and extra == \"cufile\""} -nvidia-curand = {version = "==10.4.0.35.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"curand\""} -nvidia-cusolver = {version = "==12.0.4.66.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"cusolver\""} -nvidia-cusparse = {version = "==12.6.3.3.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"cusparse\""} -nvidia-nvjitlink = {version = "==13.0.88.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"nvjitlink\""} -nvidia-nvtx = {version = "==13.0.85.*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"win32\") and extra == \"nvtx\""} - -[package.extras] -all = ["nvidia-cublas (==13.1.0.3.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-cccl (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-crt (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-culibos (==13.0.85.*) ; sys_platform == \"linux\"", "nvidia-cuda-cupti (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-cuxxfilt (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-nvcc (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-nvrtc (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-opencl (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-profiler-api (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-runtime (==13.0.96.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cuda-sanitizer-api (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cufft (==12.0.0.61.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cufile (==1.15.1.6.*) ; sys_platform == \"linux\"", "nvidia-curand (==10.4.0.35.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cusolver (==12.0.4.66.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-cusparse (==12.6.3.3.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-npp (==13.0.1.2.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvfatbin (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvjitlink (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvjpeg (==13.0.1.86.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvml-dev (==13.0.87.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvptxcompiler (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvtx (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\"", "nvidia-nvvm (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cccl = ["nvidia-cuda-cccl (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -crt = ["nvidia-cuda-crt (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cublas = ["nvidia-cublas (==13.1.0.3.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cudart = ["nvidia-cuda-runtime (==13.0.96.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cufft = ["nvidia-cufft (==12.0.0.61.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cufile = ["nvidia-cufile (==1.15.1.6.*) ; sys_platform == \"linux\""] -culibos = ["nvidia-cuda-culibos (==13.0.85.*) ; sys_platform == \"linux\""] -cupti = ["nvidia-cuda-cupti (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -curand = ["nvidia-curand (==10.4.0.35.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cusolver = ["nvidia-cusolver (==12.0.4.66.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cusparse = ["nvidia-cusparse (==12.6.3.3.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -cuxxfilt = ["nvidia-cuda-cuxxfilt (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -npp = ["nvidia-npp (==13.0.1.2.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvcc = ["nvidia-cuda-nvcc (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvfatbin = ["nvidia-nvfatbin (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvjitlink = ["nvidia-nvjitlink (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvjpeg = ["nvidia-nvjpeg (==13.0.1.86.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvml = ["nvidia-nvml-dev (==13.0.87.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvptxcompiler = ["nvidia-nvptxcompiler (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvrtc = ["nvidia-cuda-nvrtc (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvtx = ["nvidia-nvtx (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -nvvm = ["nvidia-nvvm (==13.0.88.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -opencl = ["nvidia-cuda-opencl (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -profiler = ["nvidia-cuda-profiler-api (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] -sanitizer = ["nvidia-cuda-sanitizer-api (==13.0.85.*) ; sys_platform == \"linux\" or sys_platform == \"win32\""] - -[[package]] -name = "dataclasses-json" -version = "0.6.7" -description = "Easily serialize dataclasses to and from JSON." -optional = false -python-versions = "<4.0,>=3.7" -groups = ["main"] -files = [ - {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, - {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, -] - -[package.dependencies] -marshmallow = ">=3.18.0,<4.0.0" -typing-inspect = ">=0.4.0,<1" - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -optional = true -python-versions = ">=3.5" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - -[[package]] -name = "deprecation" -version = "2.1.0" -description = "A library to handle automated deprecations" -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, - {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, -] - -[package.dependencies] -packaging = "*" - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - -[[package]] -name = "docstring-parser" -version = "0.16" -description = "Parse Python docstrings in reST, Google and Numpydoc format" -optional = true -python-versions = ">=3.6,<4.0" -groups = ["main"] -files = [ - {file = "docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637"}, - {file = "docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e"}, -] - -[[package]] -name = "elastic-transport" -version = "8.13.1" -description = "Transport classes and utilities shared among Python Elastic client libraries" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"elasticsearch\"" -files = [ - {file = "elastic_transport-8.13.1-py3-none-any.whl", hash = "sha256:5d4bb6b8e9d74a9c16de274e91a5caf65a3a8d12876f1e99152975e15b2746fe"}, - {file = "elastic_transport-8.13.1.tar.gz", hash = "sha256:16339d392b4bbe86ad00b4bdeecff10edf516d32bc6c16053846625f2c6ea250"}, -] - -[package.dependencies] -certifi = "*" -urllib3 = ">=1.26.2,<3" - -[package.extras] -develop = ["aiohttp", "furo", "httpx", "mock", "opentelemetry-api", "opentelemetry-sdk", "orjson", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "respx", "sphinx (>2)", "sphinx-autodoc-typehints", "trustme"] - -[[package]] -name = "elasticsearch" -version = "8.14.0" -description = "Python client for Elasticsearch" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"elasticsearch\"" -files = [ - {file = "elasticsearch-8.14.0-py3-none-any.whl", hash = "sha256:cef8ef70a81af027f3da74a4f7d9296b390c636903088439087b8262a468c130"}, - {file = "elasticsearch-8.14.0.tar.gz", hash = "sha256:aa2490029dd96f4015b333c1827aa21fd6c0a4d223b00dfb0fe933b8d09a511b"}, -] - -[package.dependencies] -elastic-transport = ">=8.13,<9" - -[package.extras] -async = ["aiohttp (>=3,<4)"] -orjson = ["orjson (>=3)"] -requests = ["requests (>=2.4.0,!=2.32.2,<3.0.0)"] -vectorstore-mmr = ["numpy (>=1)", "simsimd (>=3)"] - -[[package]] -name = "environs" -version = "9.5.0" -description = "simplified environment variable parsing" -optional = true -python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"milvus\"" -files = [ - {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, - {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, -] - -[package.dependencies] -marshmallow = ">=3.0.0" -python-dotenv = "*" - -[package.extras] -dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] -django = ["dj-database-url", "dj-email-url", "django-cache-url"] -lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] - -[[package]] -name = "eval-type-backport" -version = "0.2.0" -description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"together\"" -files = [ - {file = "eval_type_backport-0.2.0-py3-none-any.whl", hash = "sha256:ac2f73d30d40c5a30a80b8739a789d6bb5e49fdffa66d7912667e2015d9c9933"}, - {file = "eval_type_backport-0.2.0.tar.gz", hash = "sha256:68796cfbc7371ebf923f03bdf7bef415f3ec098aeced24e054b253a0e78f7b37"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "fastapi" -version = "0.110.3" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"}, - {file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.38.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "fastavro" -version = "1.9.5" -description = "Fast read/write of AVRO files" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "fastavro-1.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61253148e95dd2b6457247b441b7555074a55de17aef85f5165bfd5facf600fc"}, - {file = "fastavro-1.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b604935d671ad47d888efc92a106f98e9440874108b444ac10e28d643109c937"}, - {file = "fastavro-1.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0adbf4956fd53bd74c41e7855bb45ccce953e0eb0e44f5836d8d54ad843f9944"}, - {file = "fastavro-1.9.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:53d838e31457db8bf44460c244543f75ed307935d5fc1d93bc631cc7caef2082"}, - {file = "fastavro-1.9.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:07b6288e8681eede16ff077632c47395d4925c2f51545cd7a60f194454db2211"}, - {file = "fastavro-1.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:ef08cf247fdfd61286ac0c41854f7194f2ad05088066a756423d7299b688d975"}, - {file = "fastavro-1.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c52d7bb69f617c90935a3e56feb2c34d4276819a5c477c466c6c08c224a10409"}, - {file = "fastavro-1.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85e05969956003df8fa4491614bc62fe40cec59e94d06e8aaa8d8256ee3aab82"}, - {file = "fastavro-1.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e6df8527493a9f0d9a8778df82bab8b1aa6d80d1b004e5aec0a31dc4dc501c"}, - {file = "fastavro-1.9.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27820da3b17bc01cebb6d1687c9d7254b16d149ef458871aaa207ed8950f3ae6"}, - {file = "fastavro-1.9.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:195a5b8e33eb89a1a9b63fa9dce7a77d41b3b0cd785bac6044df619f120361a2"}, - {file = "fastavro-1.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:be612c109efb727bfd36d4d7ed28eb8e0506617b7dbe746463ebbf81e85eaa6b"}, - {file = "fastavro-1.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b133456c8975ec7d2a99e16a7e68e896e45c821b852675eac4ee25364b999c14"}, - {file = "fastavro-1.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf586373c3d1748cac849395aad70c198ee39295f92e7c22c75757b5c0300fbe"}, - {file = "fastavro-1.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:724ef192bc9c55d5b4c7df007f56a46a21809463499856349d4580a55e2b914c"}, - {file = "fastavro-1.9.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bfd11fe355a8f9c0416803afac298960eb4c603a23b1c74ff9c1d3e673ea7185"}, - {file = "fastavro-1.9.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9827d1654d7bcb118ef5efd3e5b2c9ab2a48d44dac5e8c6a2327bc3ac3caa828"}, - {file = "fastavro-1.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:d84b69dca296667e6137ae7c9a96d060123adbc0c00532cc47012b64d38b47e9"}, - {file = "fastavro-1.9.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:fb744e9de40fb1dc75354098c8db7da7636cba50a40f7bef3b3fb20f8d189d88"}, - {file = "fastavro-1.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:240df8bacd13ff5487f2465604c007d686a566df5cbc01d0550684eaf8ff014a"}, - {file = "fastavro-1.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3bb35c25bbc3904e1c02333bc1ae0173e0a44aa37a8e95d07e681601246e1f1"}, - {file = "fastavro-1.9.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b47a54a9700de3eabefd36dabfb237808acae47bc873cada6be6990ef6b165aa"}, - {file = "fastavro-1.9.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:48c7b5e6d2f3bf7917af301c275b05c5be3dd40bb04e80979c9e7a2ab31a00d1"}, - {file = "fastavro-1.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:05d13f98d4e325be40387e27da9bd60239968862fe12769258225c62ec906f04"}, - {file = "fastavro-1.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5b47948eb196263f6111bf34e1cd08d55529d4ed46eb50c1bc8c7c30a8d18868"}, - {file = "fastavro-1.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85b7a66ad521298ad9373dfe1897a6ccfc38feab54a47b97922e213ae5ad8870"}, - {file = "fastavro-1.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44cb154f863ad80e41aea72a709b12e1533b8728c89b9b1348af91a6154ab2f5"}, - {file = "fastavro-1.9.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b5f7f2b1fe21231fd01f1a2a90e714ae267fe633cd7ce930c0aea33d1c9f4901"}, - {file = "fastavro-1.9.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88fbbe16c61d90a89d78baeb5a34dc1c63a27b115adccdbd6b1fb6f787deacf2"}, - {file = "fastavro-1.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:753f5eedeb5ca86004e23a9ce9b41c5f25eb64a876f95edcc33558090a7f3e4b"}, - {file = "fastavro-1.9.5.tar.gz", hash = "sha256:6419ebf45f88132a9945c51fe555d4f10bb97c236288ed01894f957c6f914553"}, -] - -[package.extras] -codecs = ["cramjam", "lz4", "zstandard"] -lz4 = ["lz4"] -snappy = ["cramjam"] -zstandard = ["zstandard"] - -[[package]] -name = "filelock" -version = "3.15.4" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] - -[[package]] -name = "flatbuffers" -version = "24.3.25" -description = "The FlatBuffers serialization format for Python" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, - {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, -] - -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - -[[package]] -name = "fsspec" -version = "2024.6.1" -description = "File-system specification" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "fsspec-2024.6.1-py3-none-any.whl", hash = "sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e"}, - {file = "fsspec-2024.6.1.tar.gz", hash = "sha256:fad7d7e209dd4c1208e3bbfda706620e0da5142bebbd9c384afb95b07e798e49"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -dev = ["pre-commit", "ruff"] -doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] -test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] -test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] -tqdm = ["tqdm"] - -[[package]] -name = "google-ai-generativelanguage" -version = "0.4.0" -description = "Google Ai Generativelanguage API client library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"google\"" -files = [ - {file = "google-ai-generativelanguage-0.4.0.tar.gz", hash = "sha256:c8199066c08f74c4e91290778329bb9f357ba1ea5d6f82de2bc0d10552bf4f8c"}, - {file = "google_ai_generativelanguage-0.4.0-py3-none-any.whl", hash = "sha256:e4c425376c1ee26c78acbc49a24f735f90ebfa81bf1a06495fae509a2433232c"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0.dev0", extras = ["grpc"]} -proto-plus = ">=1.22.3,<2.0.0.dev0" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" - -[[package]] -name = "google-api-core" -version = "2.19.1" -description = "Google API client core library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, - {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -grpcio = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, -] -grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, -] -proto-plus = ">=1.22.3,<2.0.0.dev0" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.33.2,<2.0.dev0)", "grpcio (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-api-python-client" -version = "2.137.0" -description = "Google API Client Library for Python" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"gmail\" or extra == \"googledrive\"" -files = [ - {file = "google_api_python_client-2.137.0-py2.py3-none-any.whl", hash = "sha256:a8b5c5724885e5be9f5368739aa0ccf416627da4ebd914b410a090c18f84d692"}, - {file = "google_api_python_client-2.137.0.tar.gz", hash = "sha256:e739cb74aac8258b1886cb853b0722d47c81fe07ad649d7f2206f06530513c04"}, -] - -[package.dependencies] -google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" -google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" -google-auth-httplib2 = ">=0.2.0,<1.0.0" -httplib2 = ">=0.19.0,<1.dev0" -uritemplate = ">=3.0.1,<5" - -[[package]] -name = "google-auth" -version = "2.32.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, - {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-auth-httplib2" -version = "0.2.0" -description = "Google Authentication Library: httplib2 transport" -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"gmail\" or extra == \"googledrive\"" -files = [ - {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, - {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, -] - -[package.dependencies] -google-auth = "*" -httplib2 = ">=0.19.0" - -[[package]] -name = "google-auth-oauthlib" -version = "1.2.1" -description = "Google Authentication Library" -optional = true -python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"gmail\" or extra == \"googledrive\"" -files = [ - {file = "google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f"}, - {file = "google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263"}, -] - -[package.dependencies] -google-auth = ">=2.15.0" -requests-oauthlib = ">=0.7.0" - -[package.extras] -tool = ["click (>=6.0.0)"] - -[[package]] -name = "google-cloud-aiplatform" -version = "1.59.0" -description = "Vertex AI API client library" -optional = true -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "google-cloud-aiplatform-1.59.0.tar.gz", hash = "sha256:2bebb59c0ba3e3b4b568305418ca1b021977988adbee8691a5bed09b037e7e63"}, - {file = "google_cloud_aiplatform-1.59.0-py2.py3-none-any.whl", hash = "sha256:549e6eb1844b0f853043309138ebe2db00de4bbd8197b3bde26804ac163ef52a"}, -] - -[package.dependencies] -docstring-parser = "<1" -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.8.dev0,<3.0.0.dev0", extras = ["grpc"]} -google-auth = ">=2.14.1,<3.0.0.dev0" -google-cloud-bigquery = ">=1.15.0,<3.20.0 || >3.20.0,<4.0.0.dev0" -google-cloud-resource-manager = ">=1.3.3,<3.0.0.dev0" -google-cloud-storage = ">=1.32.0,<3.0.0.dev0" -packaging = ">=14.3" -proto-plus = ">=1.22.3,<2.0.0.dev0" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" -pydantic = "<3" -shapely = "<3.0.0.dev0" - -[package.extras] -autologging = ["mlflow (>=1.27.0,<=2.1.1)"] -cloud-profiler = ["tensorboard-plugin-profile (>=2.4.0,<3.0.0.dev0)", "tensorflow (>=2.4.0,<3.0.0.dev0)", "werkzeug (>=2.0.0,<2.1.0.dev0)"] -datasets = ["pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0.dev0) ; python_version < \"3.11\""] -endpoint = ["requests (>=2.28.1)"] -full = ["cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0.dev0) ; python_version < \"3.11\"", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.9.3) ; python_version == \"3.11\"", "requests (>=2.28.1)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0.dev0)", "tensorflow (>=2.3.0,<3.0.0.dev0)", "tensorflow (>=2.3.0,<3.0.0.dev0) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0.dev0)", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0.dev0)"] -langchain = ["langchain (>=0.1.16,<0.3)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "tenacity (<=8.3)"] -langchain-testing = ["absl-py", "cloudpickle (>=3.0,<4.0)", "langchain (>=0.1.16,<0.3)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist", "tenacity (<=8.3)"] -lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0.dev0)"] -metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"] -pipelines = ["pyyaml (>=5.3.1,<7)"] -prediction = ["docker (>=5.0.3)", "fastapi (>=0.71.0,<=0.109.1)", "httpx (>=0.23.0,<0.25.0)", "starlette (>=0.17.1)", "uvicorn[standard] (>=0.16.0)"] -preview = ["cloudpickle (<3.0)", "google-cloud-logging (<4.0)"] -private-endpoints = ["requests (>=2.28.1)", "urllib3 (>=1.21.1,<1.27)"] -rapid-evaluation = ["pandas (>=1.0.0,<2.2.0)", "tqdm (>=4.23.0)"] -ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.9.3) ; python_version == \"3.11\"", "setuptools (<70.0.0)"] -ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.9.3) ; python_version == \"3.11\"", "ray[train] (==2.9.3)", "scikit-learn", "setuptools (<70.0.0)", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"] -reasoningengine = ["cloudpickle (>=3.0,<4.0)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)"] -tensorboard = ["tensorboard-plugin-profile (>=2.4.0,<3.0.0.dev0)", "tensorflow (>=2.3.0,<3.0.0.dev0) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0.dev0)", "werkzeug (>=2.0.0,<2.1.0.dev0)"] -testing = ["bigframes ; python_version >= \"3.10\"", "cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nltk", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0.dev0) ; python_version < \"3.11\"", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyfakefs", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.9.3) ; python_version == \"3.11\"", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "sentencepiece (>=0.2.0)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0.dev0)", "tensorflow (==2.13.0) ; python_version <= \"3.11\"", "tensorflow (==2.16.1) ; python_version > \"3.11\"", "tensorflow (>=2.3.0,<3.0.0.dev0)", "tensorflow (>=2.3.0,<3.0.0.dev0) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0.dev0)", "torch (>=2.0.0,<2.1.0) ; python_version <= \"3.11\"", "torch (>=2.2.0) ; python_version > \"3.11\"", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0.dev0)", "xgboost"] -tokenization = ["sentencepiece (>=0.2.0)"] -vizier = ["google-vizier (>=0.1.6)"] -xai = ["tensorflow (>=2.3.0,<3.0.0.dev0)"] - -[[package]] -name = "google-cloud-bigquery" -version = "3.25.0" -description = "Google BigQuery API client library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-cloud-bigquery-3.25.0.tar.gz", hash = "sha256:5b2aff3205a854481117436836ae1403f11f2594e6810a98886afd57eda28509"}, - {file = "google_cloud_bigquery-3.25.0-py2.py3-none-any.whl", hash = "sha256:7f0c371bc74d2a7fb74dacbc00ac0f90c8c2bec2289b51dd6685a275873b1ce9"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0.dev0", extras = ["grpc"]} -google-auth = ">=2.14.1,<3.0.0.dev0" -google-cloud-core = ">=1.6.0,<3.0.0.dev0" -google-resumable-media = ">=0.6.0,<3.0.dev0" -packaging = ">=20.0.0" -python-dateutil = ">=2.7.2,<3.0.dev0" -requests = ">=2.21.0,<3.0.0.dev0" - -[package.extras] -all = ["Shapely (>=1.8.4,<3.0.0.dev0)", "db-dtypes (>=0.3.0,<2.0.0.dev0)", "geopandas (>=0.9.0,<1.0.dev0)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0.dev0)", "grpcio (>=1.47.0,<2.0.dev0)", "grpcio (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\"", "importlib-metadata (>=1.0.0) ; python_version < \"3.8\"", "ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)", "ipywidgets (>=7.7.0)", "opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)", "pandas (>=1.1.0)", "proto-plus (>=1.15.0,<2.0.0.dev0)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0.dev0)", "pyarrow (>=3.0.0)", "tqdm (>=4.7.4,<5.0.0.dev0)"] -bigquery-v2 = ["proto-plus (>=1.15.0,<2.0.0.dev0)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0.dev0)"] -bqstorage = ["google-cloud-bigquery-storage (>=2.6.0,<3.0.0.dev0)", "grpcio (>=1.47.0,<2.0.dev0)", "grpcio (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\"", "pyarrow (>=3.0.0)"] -geopandas = ["Shapely (>=1.8.4,<3.0.0.dev0)", "geopandas (>=0.9.0,<1.0.dev0)"] -ipython = ["ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)"] -ipywidgets = ["ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)"] -opentelemetry = ["opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)"] -pandas = ["db-dtypes (>=0.3.0,<2.0.0.dev0)", "importlib-metadata (>=1.0.0) ; python_version < \"3.8\"", "pandas (>=1.1.0)", "pyarrow (>=3.0.0)"] -tqdm = ["tqdm (>=4.7.4,<5.0.0.dev0)"] - -[[package]] -name = "google-cloud-core" -version = "2.4.1" -description = "Google Cloud API client core library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, - {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0.dev0" -google-auth = ">=1.25.0,<3.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0.dev0)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-resource-manager" -version = "1.12.4" -description = "Google Cloud Resource Manager API client library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-cloud-resource-manager-1.12.4.tar.gz", hash = "sha256:3eda914a925e92465ef80faaab7e0f7a9312d486dd4e123d2c76e04bac688ff0"}, - {file = "google_cloud_resource_manager-1.12.4-py2.py3-none-any.whl", hash = "sha256:0b6663585f7f862166c0fb4c55fdda721fce4dc2dc1d5b52d03ee4bf2653a85f"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0.dev0", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0.dev0" -proto-plus = ">=1.22.3,<2.0.0.dev0" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[[package]] -name = "google-cloud-storage" -version = "2.17.0" -description = "Google Cloud Storage API client library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-cloud-storage-2.17.0.tar.gz", hash = "sha256:49378abff54ef656b52dca5ef0f2eba9aa83dc2b2c72c78714b03a1a95fe9388"}, - {file = "google_cloud_storage-2.17.0-py2.py3-none-any.whl", hash = "sha256:5b393bc766b7a3bc6f5407b9e665b2450d36282614b7945e570b3480a456d1e1"}, -] - -[package.dependencies] -google-api-core = ">=2.15.0,<3.0.0.dev0" -google-auth = ">=2.26.1,<3.0.dev0" -google-cloud-core = ">=2.3.0,<3.0.dev0" -google-crc32c = ">=1.0,<2.0.dev0" -google-resumable-media = ">=2.6.0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -protobuf = ["protobuf (<5.0.0.dev0)"] - -[[package]] -name = "google-crc32c" -version = "1.5.0" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, - {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, - {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, - {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, - {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, - {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, - {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, - {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, - {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, - {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, - {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, - {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, - {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, - {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, - {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, - {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-generativeai" -version = "0.3.2" -description = "Google Generative AI High level API client library and tools." -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"google\"" -files = [ - {file = "google_generativeai-0.3.2-py3-none-any.whl", hash = "sha256:8761147e6e167141932dc14a7b7af08f2310dd56668a78d206c19bb8bd85bcd7"}, -] - -[package.dependencies] -google-ai-generativelanguage = "0.4.0" -google-api-core = "*" -google-auth = "*" -protobuf = "*" -tqdm = "*" -typing-extensions = "*" - -[package.extras] -dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"] - -[[package]] -name = "google-resumable-media" -version = "2.7.1" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-resumable-media-2.7.1.tar.gz", hash = "sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33"}, - {file = "google_resumable_media-2.7.1-py2.py3-none-any.whl", hash = "sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0.dev0" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "google-auth (>=1.22.0,<2.0.dev0)"] -requests = ["requests (>=2.18.0,<3.0.0.dev0)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.63.2" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, - {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, -] - -[package.dependencies] -grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "gpt4all" -version = "2.0.2" -description = "Python bindings for GPT4All" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "gpt4all-2.0.2-py3-none-macosx_10_15_universal2.whl", hash = "sha256:f18f348d21e2ce8e45dbf8334960670660b53f69a8e47a26bb7e64924e6ed130"}, - {file = "gpt4all-2.0.2-py3-none-manylinux1_x86_64.whl", hash = "sha256:e4c19df94f45829565563017577b299c012ebed18ebea1d6df0273ef89c92a01"}, - {file = "gpt4all-2.0.2-py3-none-win_amd64.whl", hash = "sha256:c09440bfb3463b9e278875fc726cf1f75d2a2b19bb73d97dde5e57b0b1f6e059"}, -] - -[package.dependencies] -requests = "*" -tqdm = "*" - -[package.extras] -dev = ["black", "isort", "mkautodoc", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]", "pytest", "setuptools", "twine", "wheel"] - -[[package]] -name = "gptcache" -version = "0.1.43" -description = "GPTCache, a powerful caching library that can be used to speed up and lower the cost of chat applications that rely on the LLM service. GPTCache works as a memcache for AIGC applications, similar to how Redis works for traditional applications." -optional = false -python-versions = ">=3.8.1" -groups = ["main"] -files = [ - {file = "gptcache-0.1.43-py3-none-any.whl", hash = "sha256:9c557ec9cc14428942a0ebf1c838520dc6d2be801d67bb6964807043fc2feaf5"}, - {file = "gptcache-0.1.43.tar.gz", hash = "sha256:cebe7ec5e32a3347bf839e933a34e67c7fcae620deaa7cb8c6d7d276c8686f1a"}, -] - -[package.dependencies] -cachetools = "*" -numpy = "*" -requests = "*" - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "(platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and python_version < \"3.13\"" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "grpc-google-iam-v1" -version = "0.13.1" -description = "IAM API client library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"}, - {file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"}, -] - -[package.dependencies] -googleapis-common-protos = {version = ">=1.56.0,<2.0.0.dev0", extras = ["grpc"]} -grpcio = ">=1.44.0,<2.0.0.dev0" -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[[package]] -name = "grpcio" -version = "1.63.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "grpcio-1.63.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2e93aca840c29d4ab5db93f94ed0a0ca899e241f2e8aec6334ab3575dc46125c"}, - {file = "grpcio-1.63.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:91b73d3f1340fefa1e1716c8c1ec9930c676d6b10a3513ab6c26004cb02d8b3f"}, - {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b3afbd9d6827fa6f475a4f91db55e441113f6d3eb9b7ebb8fb806e5bb6d6bd0d"}, - {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f3f6883ce54a7a5f47db43289a0a4c776487912de1a0e2cc83fdaec9685cc9f"}, - {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf8dae9cc0412cb86c8de5a8f3be395c5119a370f3ce2e69c8b7d46bb9872c8d"}, - {file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08e1559fd3b3b4468486b26b0af64a3904a8dbc78d8d936af9c1cf9636eb3e8b"}, - {file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c039ef01516039fa39da8a8a43a95b64e288f79f42a17e6c2904a02a319b357"}, - {file = "grpcio-1.63.0-cp310-cp310-win32.whl", hash = "sha256:ad2ac8903b2eae071055a927ef74121ed52d69468e91d9bcbd028bd0e554be6d"}, - {file = "grpcio-1.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2e44f59316716532a993ca2966636df6fbe7be4ab6f099de6815570ebe4383a"}, - {file = "grpcio-1.63.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f28f8b2db7b86c77916829d64ab21ff49a9d8289ea1564a2b2a3a8ed9ffcccd3"}, - {file = "grpcio-1.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:65bf975639a1f93bee63ca60d2e4951f1b543f498d581869922910a476ead2f5"}, - {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5194775fec7dc3dbd6a935102bb156cd2c35efe1685b0a46c67b927c74f0cfb"}, - {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4cbb2100ee46d024c45920d16e888ee5d3cf47c66e316210bc236d5bebc42b3"}, - {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff737cf29b5b801619f10e59b581869e32f400159e8b12d7a97e7e3bdeee6a2"}, - {file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd1e68776262dd44dedd7381b1a0ad09d9930ffb405f737d64f505eb7f77d6c7"}, - {file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f45f27f516548e23e4ec3fbab21b060416007dbe768a111fc4611464cc773f"}, - {file = "grpcio-1.63.0-cp311-cp311-win32.whl", hash = "sha256:878b1d88d0137df60e6b09b74cdb73db123f9579232c8456f53e9abc4f62eb3c"}, - {file = "grpcio-1.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:756fed02dacd24e8f488f295a913f250b56b98fb793f41d5b2de6c44fb762434"}, - {file = "grpcio-1.63.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:93a46794cc96c3a674cdfb59ef9ce84d46185fe9421baf2268ccb556f8f81f57"}, - {file = "grpcio-1.63.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a7b19dfc74d0be7032ca1eda0ed545e582ee46cd65c162f9e9fc6b26ef827dc6"}, - {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:8064d986d3a64ba21e498b9a376cbc5d6ab2e8ab0e288d39f266f0fca169b90d"}, - {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:219bb1848cd2c90348c79ed0a6b0ea51866bc7e72fa6e205e459fedab5770172"}, - {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d60cd1d58817bc5985fae6168d8b5655c4981d448d0f5b6194bbcc038090d2"}, - {file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e350cb096e5c67832e9b6e018cf8a0d2a53b2a958f6251615173165269a91b0"}, - {file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:56cdf96ff82e3cc90dbe8bac260352993f23e8e256e063c327b6cf9c88daf7a9"}, - {file = "grpcio-1.63.0-cp312-cp312-win32.whl", hash = "sha256:3a6d1f9ea965e750db7b4ee6f9fdef5fdf135abe8a249e75d84b0a3e0c668a1b"}, - {file = "grpcio-1.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:d2497769895bb03efe3187fb1888fc20e98a5f18b3d14b606167dacda5789434"}, - {file = "grpcio-1.63.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fdf348ae69c6ff484402cfdb14e18c1b0054ac2420079d575c53a60b9b2853ae"}, - {file = "grpcio-1.63.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a3abfe0b0f6798dedd2e9e92e881d9acd0fdb62ae27dcbbfa7654a57e24060c0"}, - {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:6ef0ad92873672a2a3767cb827b64741c363ebaa27e7f21659e4e31f4d750280"}, - {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b416252ac5588d9dfb8a30a191451adbf534e9ce5f56bb02cd193f12d8845b7f"}, - {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b77eaefc74d7eb861d3ffbdf91b50a1bb1639514ebe764c47773b833fa2d91"}, - {file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b005292369d9c1f80bf70c1db1c17c6c342da7576f1c689e8eee4fb0c256af85"}, - {file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdcda1156dcc41e042d1e899ba1f5c2e9f3cd7625b3d6ebfa619806a4c1aadda"}, - {file = "grpcio-1.63.0-cp38-cp38-win32.whl", hash = "sha256:01799e8649f9e94ba7db1aeb3452188048b0019dc37696b0f5ce212c87c560c3"}, - {file = "grpcio-1.63.0-cp38-cp38-win_amd64.whl", hash = "sha256:6a1a3642d76f887aa4009d92f71eb37809abceb3b7b5a1eec9c554a246f20e3a"}, - {file = "grpcio-1.63.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:75f701ff645858a2b16bc8c9fc68af215a8bb2d5a9b647448129de6e85d52bce"}, - {file = "grpcio-1.63.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cacdef0348a08e475a721967f48206a2254a1b26ee7637638d9e081761a5ba86"}, - {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:0697563d1d84d6985e40ec5ec596ff41b52abb3fd91ec240e8cb44a63b895094"}, - {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6426e1fb92d006e47476d42b8f240c1d916a6d4423c5258ccc5b105e43438f61"}, - {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48cee31bc5f5a31fb2f3b573764bd563aaa5472342860edcc7039525b53e46a"}, - {file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:50344663068041b34a992c19c600236e7abb42d6ec32567916b87b4c8b8833b3"}, - {file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:259e11932230d70ef24a21b9fb5bb947eb4703f57865a404054400ee92f42f5d"}, - {file = "grpcio-1.63.0-cp39-cp39-win32.whl", hash = "sha256:a44624aad77bf8ca198c55af811fd28f2b3eaf0a50ec5b57b06c034416ef2d0a"}, - {file = "grpcio-1.63.0-cp39-cp39-win_amd64.whl", hash = "sha256:166e5c460e5d7d4656ff9e63b13e1f6029b122104c1633d5f37eaea348d7356d"}, - {file = "grpcio-1.63.0.tar.gz", hash = "sha256:f3023e14805c61bc439fb40ca545ac3d5740ce66120a678a3c6c2c55b70343d1"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.63.0)"] - -[[package]] -name = "grpcio-status" -version = "1.62.2" -description = "Status proto mapping for gRPC" -optional = true -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "grpcio-status-1.62.2.tar.gz", hash = "sha256:62e1bfcb02025a1cd73732a2d33672d3e9d0df4d21c12c51e0bbcaf09bab742a"}, - {file = "grpcio_status-1.62.2-py3-none-any.whl", hash = "sha256:206ddf0eb36bc99b033f03b2c8e95d319f0044defae9b41ae21408e7e0cda48f"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.2" -protobuf = ">=4.21.6" - -[[package]] -name = "grpcio-tools" -version = "1.62.2" -description = "Protobuf code generator for gRPC" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "grpcio-tools-1.62.2.tar.gz", hash = "sha256:5fd5e1582b678e6b941ee5f5809340be5e0724691df5299aae8226640f94e18f"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:1679b4903aed2dc5bd8cb22a452225b05dc8470a076f14fd703581efc0740cdb"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:9d41e0e47dd075c075bb8f103422968a65dd0d8dc8613288f573ae91eb1053ba"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:987e774f74296842bbffd55ea8826370f70c499e5b5f71a8cf3103838b6ee9c3"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd4eeea4b25bcb6903b82930d579027d034ba944393c4751cdefd9c49e6989"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6746bc823958499a3cf8963cc1de00072962fb5e629f26d658882d3f4c35095"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2ed775e844566ce9ce089be9a81a8b928623b8ee5820f5e4d58c1a9d33dfc5ae"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bdc5dd3f57b5368d5d661d5d3703bcaa38bceca59d25955dff66244dbc987271"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-win32.whl", hash = "sha256:3a8d6f07e64c0c7756f4e0c4781d9d5a2b9cc9cbd28f7032a6fb8d4f847d0445"}, - {file = "grpcio_tools-1.62.2-cp310-cp310-win_amd64.whl", hash = "sha256:e33b59fb3efdddeb97ded988a871710033e8638534c826567738d3edce528752"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:472505d030135d73afe4143b0873efe0dcb385bd6d847553b4f3afe07679af00"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:ec674b4440ef4311ac1245a709e87b36aca493ddc6850eebe0b278d1f2b6e7d1"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:184b4174d4bd82089d706e8223e46c42390a6ebac191073b9772abc77308f9fa"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c195d74fe98541178ece7a50dad2197d43991e0f77372b9a88da438be2486f12"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34d97c62e61bfe9e6cff0410fe144ac8cca2fc979ad0be46b7edf026339d161"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb8453ae83a1db2452b7fe0f4b78e4a8dd32be0f2b2b73591ae620d4d784d3d"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f989e5cebead3ae92c6abf6bf7b19949e1563a776aea896ac5933f143f0c45d"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-win32.whl", hash = "sha256:c48fabe40b9170f4e3d7dd2c252e4f1ff395dc24e49ac15fc724b1b6f11724da"}, - {file = "grpcio_tools-1.62.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c616d0ad872e3780693fce6a3ac8ef00fc0963e6d7815ce9dcfae68ba0fc287"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:10cc3321704ecd17c93cf68c99c35467a8a97ffaaed53207e9b2da6ae0308ee1"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:9be84ff6d47fd61462be7523b49d7ba01adf67ce4e1447eae37721ab32464dd8"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:d82f681c9a9d933a9d8068e8e382977768e7779ddb8870fa0cf918d8250d1532"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04c607029ae3660fb1624ed273811ffe09d57d84287d37e63b5b802a35897329"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72b61332f1b439c14cbd3815174a8f1d35067a02047c32decd406b3a09bb9890"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8214820990d01b52845f9fbcb92d2b7384a0c321b303e3ac614c219dc7d1d3af"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:462e0ab8dd7c7b70bfd6e3195eebc177549ede5cf3189814850c76f9a340d7ce"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-win32.whl", hash = "sha256:fa107460c842e4c1a6266150881694fefd4f33baa544ea9489601810c2210ef8"}, - {file = "grpcio_tools-1.62.2-cp312-cp312-win_amd64.whl", hash = "sha256:759c60f24c33a181bbbc1232a6752f9b49fbb1583312a4917e2b389fea0fb0f2"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:45db5da2bcfa88f2b86b57ef35daaae85c60bd6754a051d35d9449c959925b57"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:ab84bae88597133f6ea7a2bdc57b2fda98a266fe8d8d4763652cbefd20e73ad7"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:7a49bccae1c7d154b78e991885c3111c9ad8c8fa98e91233de425718f47c6139"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7e439476b29d6dac363b321781a113794397afceeb97dad85349db5f1cb5e9a"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ea369c4d1567d1acdf69c8ea74144f4ccad9e545df7f9a4fc64c94fa7684ba3"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f955702dc4b530696375251319d05223b729ed24e8673c2129f7a75d2caefbb"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3708a747aa4b6b505727282ca887041174e146ae030ebcadaf4c1d346858df62"}, - {file = "grpcio_tools-1.62.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce149ea55eadb486a7fb75a20f63ef3ac065ee6a0240ed25f3549ce7954c653"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:58cbb24b3fa6ae35aa9c210fcea3a51aa5fef0cd25618eb4fd94f746d5a9b703"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:6413581e14a80e0b4532577766cf0586de4dd33766a31b3eb5374a746771c07d"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:47117c8a7e861382470d0e22d336e5a91fdc5f851d1db44fa784b9acea190d87"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f1ba79a253df9e553d20319c615fa2b429684580fa042dba618d7f6649ac7e4"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04a394cf5e51ba9be412eb9f6c482b6270bd81016e033e8eb7d21b8cc28fe8b5"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3c53b221378b035ae2f1881cbc3aca42a6075a8e90e1a342c2f205eb1d1aa6a1"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c384c838b34d1b67068e51b5bbe49caa6aa3633acd158f1ab16b5da8d226bc53"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-win32.whl", hash = "sha256:19ea69e41c3565932aa28a202d1875ec56786aea46a2eab54a3b28e8a27f9517"}, - {file = "grpcio_tools-1.62.2-cp38-cp38-win_amd64.whl", hash = "sha256:1d768a5c07279a4c461ebf52d0cec1c6ca85c6291c71ec2703fe3c3e7e28e8c4"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:5b07b5874187e170edfbd7aa2ca3a54ebf3b2952487653e8c0b0d83601c33035"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:d58389fe8be206ddfb4fa703db1e24c956856fcb9a81da62b13577b3a8f7fda7"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:7d8b4e00c3d7237b92260fc18a561cd81f1da82e8be100db1b7d816250defc66"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe08d2038f2b7c53259b5c49e0ad08c8e0ce2b548d8185993e7ef67e8592cca"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19216e1fb26dbe23d12a810517e1b3fbb8d4f98b1a3fbebeec9d93a79f092de4"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b8574469ecc4ff41d6bb95f44e0297cdb0d95bade388552a9a444db9cd7485cd"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4f6f32d39283ea834a493fccf0ebe9cfddee7577bdcc27736ad4be1732a36399"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-win32.whl", hash = "sha256:76eb459bdf3fb666e01883270beee18f3f11ed44488486b61cd210b4e0e17cc1"}, - {file = "grpcio_tools-1.62.2-cp39-cp39-win_amd64.whl", hash = "sha256:217c2ee6a7ce519a55958b8622e21804f6fdb774db08c322f4c9536c35fdce7c"}, -] - -[package.dependencies] -grpcio = ">=1.62.2" -protobuf = ">=4.21.6,<5.0.dev0" -setuptools = "*" - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "h2" -version = "4.1.0" -description = "HTTP/2 State-Machine based protocol implementation" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, - {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, -] - -[package.dependencies] -hpack = ">=4.0,<5" -hyperframe = ">=6.0,<7" - -[[package]] -name = "hpack" -version = "4.0.0" -description = "Pure-Python HPACK header compression" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, - {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.16" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httplib2" -version = "0.22.0" -description = "A comprehensive HTTP client library." -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -markers = "extra == \"gmail\" or extra == \"googledrive\"" -files = [ - {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, - {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, -] - -[package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} - -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "httpx" -version = "0.27.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "httpx-sse" -version = "0.4.0" -description = "Consume Server-Sent Event (SSE) messages with HTTPX." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, - {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, -] - -[[package]] -name = "huggingface-hub" -version = "0.23.4" -description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "huggingface_hub-0.23.4-py3-none-any.whl", hash = "sha256:3a0b957aa87150addf0cc7bd71b4d954b78e749850e1e7fb29ebbd2db64ca037"}, - {file = "huggingface_hub-0.23.4.tar.gz", hash = "sha256:35d99016433900e44ae7efe1c209164a5a81dbbcd53a52f99c281dcd7ce22431"}, -] - -[package.dependencies] -filelock = "*" -fsspec = ">=2023.5.0" -packaging = ">=20.9" -pyyaml = ">=5.1" -requests = "*" -tqdm = ">=4.42.1" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.3.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.3.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -hf-transfer = ["hf-transfer (>=0.1.4)"] -inference = ["aiohttp", "minijinja (>=1.0)"] -quality = ["mypy (==1.5.1)", "ruff (>=0.3.0)"] -tensorflow = ["graphviz", "pydot", "tensorflow"] -tensorflow-testing = ["keras (<3.0)", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "numpy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] -torch = ["safetensors", "torch"] -typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] - -[[package]] -name = "humanfriendly" -version = "10.0" -description = "Human friendly output for text interfaces using Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -files = [ - {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, - {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, -] - -[package.dependencies] -pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} - -[[package]] -name = "hyperframe" -version = "6.0.1" -description = "HTTP/2 framing layer for Python" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, - {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, -] - -[[package]] -name = "identify" -version = "2.6.0" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -groups = ["main", "dev"] -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "importlib-metadata" -version = "7.1.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "importlib-resources" -version = "6.4.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, - {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jiter" -version = "0.5.0" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "jiter-0.5.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b599f4e89b3def9a94091e6ee52e1d7ad7bc33e238ebb9c4c63f211d74822c3f"}, - {file = "jiter-0.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a063f71c4b06225543dddadbe09d203dc0c95ba352d8b85f1221173480a71d5"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc0d5b8b3dd12e91dd184b87273f864b363dfabc90ef29a1092d269f18c7e28"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c22541f0b672f4d741382a97c65609332a783501551445ab2df137ada01e019e"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63314832e302cc10d8dfbda0333a384bf4bcfce80d65fe99b0f3c0da8945a91a"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a25fbd8a5a58061e433d6fae6d5298777c0814a8bcefa1e5ecfff20c594bd749"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:503b2c27d87dfff5ab717a8200fbbcf4714516c9d85558048b1fc14d2de7d8dc"}, - {file = "jiter-0.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d1f3d27cce923713933a844872d213d244e09b53ec99b7a7fdf73d543529d6d"}, - {file = "jiter-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c95980207b3998f2c3b3098f357994d3fd7661121f30669ca7cb945f09510a87"}, - {file = "jiter-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afa66939d834b0ce063f57d9895e8036ffc41c4bd90e4a99631e5f261d9b518e"}, - {file = "jiter-0.5.0-cp310-none-win32.whl", hash = "sha256:f16ca8f10e62f25fd81d5310e852df6649af17824146ca74647a018424ddeccf"}, - {file = "jiter-0.5.0-cp310-none-win_amd64.whl", hash = "sha256:b2950e4798e82dd9176935ef6a55cf6a448b5c71515a556da3f6b811a7844f1e"}, - {file = "jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553"}, - {file = "jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e"}, - {file = "jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06"}, - {file = "jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403"}, - {file = "jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646"}, - {file = "jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb"}, - {file = "jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae"}, - {file = "jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a"}, - {file = "jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e"}, - {file = "jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a"}, - {file = "jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e"}, - {file = "jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338"}, - {file = "jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4"}, - {file = "jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5"}, - {file = "jiter-0.5.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f04bc2fc50dc77be9d10f73fcc4e39346402ffe21726ff41028f36e179b587e6"}, - {file = "jiter-0.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f433a4169ad22fcb550b11179bb2b4fd405de9b982601914ef448390b2954f3"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad4a6398c85d3a20067e6c69890ca01f68659da94d74c800298581724e426c7e"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6baa88334e7af3f4d7a5c66c3a63808e5efbc3698a1c57626541ddd22f8e4fbf"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ece0a115c05efca597c6d938f88c9357c843f8c245dbbb53361a1c01afd7148"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:335942557162ad372cc367ffaf93217117401bf930483b4b3ebdb1223dbddfa7"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649b0ee97a6e6da174bffcb3c8c051a5935d7d4f2f52ea1583b5b3e7822fbf14"}, - {file = "jiter-0.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f4be354c5de82157886ca7f5925dbda369b77344b4b4adf2723079715f823989"}, - {file = "jiter-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5206144578831a6de278a38896864ded4ed96af66e1e63ec5dd7f4a1fce38a3a"}, - {file = "jiter-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8120c60f8121ac3d6f072b97ef0e71770cc72b3c23084c72c4189428b1b1d3b6"}, - {file = "jiter-0.5.0-cp38-none-win32.whl", hash = "sha256:6f1223f88b6d76b519cb033a4d3687ca157c272ec5d6015c322fc5b3074d8a5e"}, - {file = "jiter-0.5.0-cp38-none-win_amd64.whl", hash = "sha256:c59614b225d9f434ea8fc0d0bec51ef5fa8c83679afedc0433905994fb36d631"}, - {file = "jiter-0.5.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0af3838cfb7e6afee3f00dc66fa24695199e20ba87df26e942820345b0afc566"}, - {file = "jiter-0.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:550b11d669600dbc342364fd4adbe987f14d0bbedaf06feb1b983383dcc4b961"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:489875bf1a0ffb3cb38a727b01e6673f0f2e395b2aad3c9387f94187cb214bbf"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b250ca2594f5599ca82ba7e68785a669b352156260c5362ea1b4e04a0f3e2389"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ea18e01f785c6667ca15407cd6dabbe029d77474d53595a189bdc813347218e"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462a52be85b53cd9bffd94e2d788a09984274fe6cebb893d6287e1c296d50653"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92cc68b48d50fa472c79c93965e19bd48f40f207cb557a8346daa020d6ba973b"}, - {file = "jiter-0.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c834133e59a8521bc87ebcad773608c6fa6ab5c7a022df24a45030826cf10bc"}, - {file = "jiter-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab3a71ff31cf2d45cb216dc37af522d335211f3a972d2fe14ea99073de6cb104"}, - {file = "jiter-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cccd3af9c48ac500c95e1bcbc498020c87e1781ff0345dd371462d67b76643eb"}, - {file = "jiter-0.5.0-cp39-none-win32.whl", hash = "sha256:368084d8d5c4fc40ff7c3cc513c4f73e02c85f6009217922d0823a48ee7adf61"}, - {file = "jiter-0.5.0-cp39-none-win_amd64.whl", hash = "sha256:ce03f7b4129eb72f1687fa11300fbf677b02990618428934662406d2a76742a1"}, - {file = "jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a"}, -] - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - -[[package]] -name = "joblib" -version = "1.4.2" -description = "Lightweight pipelining with Python functions" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, - {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main"] -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "kubernetes" -version = "30.1.0" -description = "Kubernetes python client" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "kubernetes-30.1.0-py2.py3-none-any.whl", hash = "sha256:e212e8b7579031dd2e512168b617373bc1e03888d41ac4e04039240a292d478d"}, - {file = "kubernetes-30.1.0.tar.gz", hash = "sha256:41e4c77af9f28e7a6c314e3bd06a8c6229ddd787cad684e0ab9f69b498e98ebc"}, -] - -[package.dependencies] -certifi = ">=14.5.14" -google-auth = ">=1.0.1" -oauthlib = ">=3.2.2" -python-dateutil = ">=2.5.3" -pyyaml = ">=5.4.1" -requests = "*" -requests-oauthlib = "*" -six = ">=1.9.0" -urllib3 = ">=1.24.2" -websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" - -[package.extras] -adal = ["adal (>=1.0.2)"] - -[[package]] -name = "lancedb" -version = "0.6.13" -description = "lancedb" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "lancedb-0.6.13-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:4667353ca7fa187e94cb0ca4c5f9577d65eb5160f6f3fe9e57902d86312c3869"}, - {file = "lancedb-0.6.13-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:2e22533fe6f6b2d7037dcdbbb4019a62402bbad4ce18395be68f4aa007bf8bc0"}, - {file = "lancedb-0.6.13-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:837eaceafb87e3ae4c261eef45c4f73715f892a36165572c3da621dbdb45afcf"}, - {file = "lancedb-0.6.13-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:61af2d72b2a2f0ea419874c3f32760fe5e51530da3be2d65251a0e6ded74419b"}, - {file = "lancedb-0.6.13-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:31b24e57ee313f4ce6255e45d42e8bee19b90ddcd13a9e07030ac04f76e7dfde"}, - {file = "lancedb-0.6.13-cp38-abi3-win_amd64.whl", hash = "sha256:b851182d8492b1e5b57a441af64c95da65ca30b045d6618dc7d203c6d60d70fa"}, -] - -[package.dependencies] -attrs = ">=21.3.0" -cachetools = "*" -deprecation = "*" -overrides = ">=0.7" -pydantic = ">=1.10" -pylance = "0.10.12" -ratelimiter = ">=1.0,<2.0" -requests = ">=2.31.0" -retry = ">=0.9.2" -semver = "*" -tqdm = ">=4.27.0" - -[package.extras] -azure = ["adlfs (>=2024.2.0)"] -clip = ["open-clip", "pillow", "torch"] -dev = ["pre-commit", "ruff"] -docs = ["mkdocs", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]"] -embeddings = ["awscli (>=1.29.57)", "boto3 (>=1.28.57)", "botocore (>=1.31.57)", "cohere", "google-generativeai", "huggingface-hub", "instructorembedding", "open-clip-torch", "openai (>=1.6.1)", "pillow", "sentence-transformers", "torch"] -tests = ["aiohttp", "boto3", "duckdb", "pandas (>=1.4)", "polars (>=0.19)", "pytest", "pytest-asyncio", "pytest-mock", "pytz", "tantivy"] - -[[package]] -name = "langchain" -version = "0.3.21" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "langchain-0.3.21-py3-none-any.whl", hash = "sha256:c8bd2372440cc5d48cb50b2d532c2e24036124f1c467002ceb15bc7b86c92579"}, - {file = "langchain-0.3.21.tar.gz", hash = "sha256:a10c81f8c450158af90bf37190298d996208cfd15dd3accc1c585f068473d619"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.3.45,<1.0.0" -langchain-text-splitters = ">=0.3.7,<1.0.0" -langsmith = ">=0.1.17,<0.4" -pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" - -[package.extras] -anthropic = ["langchain-anthropic"] -aws = ["langchain-aws"] -azure-ai = ["langchain-azure-ai"] -cohere = ["langchain-cohere"] -community = ["langchain-community"] -deepseek = ["langchain-deepseek"] -fireworks = ["langchain-fireworks"] -google-genai = ["langchain-google-genai"] -google-vertexai = ["langchain-google-vertexai"] -groq = ["langchain-groq"] -huggingface = ["langchain-huggingface"] -mistralai = ["langchain-mistralai"] -ollama = ["langchain-ollama"] -openai = ["langchain-openai"] -together = ["langchain-together"] -xai = ["langchain-xai"] - -[[package]] -name = "langchain-aws" -version = "0.2.1" -description = "An integration package connecting AWS and LangChain" -optional = true -python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "extra == \"aws\"" -files = [ - {file = "langchain_aws-0.2.1-py3-none-any.whl", hash = "sha256:a866ca91d11798b06925cd39b7297db97e1ab438b91cbe2feeca443ed59e5b7a"}, - {file = "langchain_aws-0.2.1.tar.gz", hash = "sha256:e07ba5c16c7ef942072c3b3561cc517d34e01de8c05a9f9bc3d986e6b90f43b1"}, -] - -[package.dependencies] -boto3 = ">=1.34.131" -langchain-core = ">=0.3.2,<0.4" -numpy = [ - {version = ">=1,<2", markers = "python_version < \"3.12\""}, - {version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""}, -] -pydantic = ">=2,<3" - -[[package]] -name = "langchain-cohere" -version = "0.3.0" -description = "An integration package connecting Cohere and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "langchain_cohere-0.3.0-py3-none-any.whl", hash = "sha256:4c075fb227ed954e2be8b5448ee04e9850b88703799861d5bda8b014633ff069"}, - {file = "langchain_cohere-0.3.0.tar.gz", hash = "sha256:cf5b6d0f41df1294b76c7109adf371d9883ff50c4a07ed825221768a98d7bdd4"}, -] - -[package.dependencies] -cohere = ">=5.5.6,<6.0" -langchain-core = ">=0.3.0,<0.4" -langchain-experimental = ">=0.3.0" -pandas = ">=1.4.3" -pydantic = ">=2,<3" -tabulate = ">=0.9.0,<0.10.0" - -[package.extras] -langchain-community = ["langchain-community (>=0.3.0)"] - -[[package]] -name = "langchain-community" -version = "0.3.20" -description = "Community contributed LangChain integrations." -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "langchain_community-0.3.20-py3-none-any.whl", hash = "sha256:ea3dbf37fbc21020eca8850627546f3c95a8770afc06c4142b40b9ba86b970f7"}, - {file = "langchain_community-0.3.20.tar.gz", hash = "sha256:bd83b4f2f818338423439aff3b5be362e1d686342ffada0478cd34c6f5ef5969"}, -] - -[package.dependencies] -aiohttp = ">=3.8.3,<4.0.0" -dataclasses-json = ">=0.5.7,<0.7" -httpx-sse = ">=0.4.0,<1.0.0" -langchain = ">=0.3.21,<1.0.0" -langchain-core = ">=0.3.45,<1.0.0" -langsmith = ">=0.1.125,<0.4" -numpy = ">=1.26.2,<3" -pydantic-settings = ">=2.4.0,<3.0.0" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" - -[[package]] -name = "langchain-core" -version = "0.3.84" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0.0,>=3.9.0" -groups = ["main"] -files = [ - {file = "langchain_core-0.3.84-py3-none-any.whl", hash = "sha256:d0b3a7b6473e30a2b3d4588ee09dc6471b8d38c46cd48f3e7c3d1ab6547f63cb"}, - {file = "langchain_core-0.3.84.tar.gz", hash = "sha256:814b75bfe67a8460a53f5839bae9505bbfffc7af6f1aa0a5155715563f5cc490"}, -] - -[package.dependencies] -jsonpatch = ">=1.33.0,<2.0.0" -langsmith = ">=0.3.45,<1.0.0" -packaging = ">=23.2.0,<26.0.0" -pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3.0,<7.0.0" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" -typing-extensions = ">=4.7.0,<5.0.0" -uuid-utils = ">=0.12.0,<1.0" - -[[package]] -name = "langchain-experimental" -version = "0.3.2" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "langchain_experimental-0.3.2-py3-none-any.whl", hash = "sha256:b6a26f2a05e056a27ad30535ed306a6b9d8cc2e3c0326d15030d11b6e7505dbb"}, - {file = "langchain_experimental-0.3.2.tar.gz", hash = "sha256:d41cc28c46f58616d18a1230595929f80a58d1982c4053dc3afe7f1c03f22426"}, -] - -[package.dependencies] -langchain-community = ">=0.3.0,<0.4.0" -langchain-core = ">=0.3.6,<0.4.0" - -[[package]] -name = "langchain-google-vertexai" -version = "2.0.3" -description = "An integration package connecting Google VertexAI and LangChain" -optional = true -python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "extra == \"vertexai\"" -files = [ - {file = "langchain_google_vertexai-2.0.3-py3-none-any.whl", hash = "sha256:43835bed9f03f6969b3f8b73356c44d7898d209c69bd5124b0a80c35d8cebdd0"}, - {file = "langchain_google_vertexai-2.0.3.tar.gz", hash = "sha256:6f71061b578c0cd44fd5a147b61f66a1486bfc8b1dc69b4ac31e0f3c470d90d8"}, -] - -[package.dependencies] -google-cloud-aiplatform = ">=1.56.0,<2.0.0" -google-cloud-storage = ">=2.17.0,<3.0.0" -httpx = ">=0.27.0,<0.28.0" -httpx-sse = ">=0.4.0,<0.5.0" -langchain-core = ">=0.3.0,<0.4" -pydantic = ">=2,<3" - -[package.extras] -anthropic = ["anthropic[vertexai] (>=0.30.0,<1)"] -mistral = ["langchain-mistralai (>=0.2.0,<1)"] - -[[package]] -name = "langchain-mistralai" -version = "0.2.0" -description = "An integration package connecting Mistral and LangChain" -optional = true -python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "extra == \"mistralai\"" -files = [ - {file = "langchain_mistralai-0.2.0-py3-none-any.whl", hash = "sha256:1463093815f018d3a5860b4a41db25103235a12112e2c1e93c7576d09eee6382"}, - {file = "langchain_mistralai-0.2.0.tar.gz", hash = "sha256:f89ebec41daae18871c5820cf7105afb05333a23e9ee7b3199d9a8ecdbe30a97"}, -] - -[package.dependencies] -httpx = ">=0.25.2,<1" -httpx-sse = ">=0.3.1,<1" -langchain-core = ">=0.3.0,<0.4.0" -pydantic = ">=2,<3" -tokenizers = ">=0.15.1,<1" - -[[package]] -name = "langchain-openai" -version = "0.2.1" -description = "An integration package connecting OpenAI and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "langchain_openai-0.2.1-py3-none-any.whl", hash = "sha256:215efa4526c88f8105f002b43b7cbf98cebd9baeb4f62c3b58faebdb578715bc"}, - {file = "langchain_openai-0.2.1.tar.gz", hash = "sha256:a131ea18736f1a8792925391b91a8c8bd834431ffc2055c92ba49f59c3dcaaf0"}, -] - -[package.dependencies] -langchain-core = ">=0.3,<0.4" -openai = ">=1.40.0,<2.0.0" -tiktoken = ">=0.7,<1" - -[[package]] -name = "langchain-text-splitters" -version = "0.3.7" -description = "LangChain text splitting utilities" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "langchain_text_splitters-0.3.7-py3-none-any.whl", hash = "sha256:31ba826013e3f563359d7c7f1e99b1cdb94897f665675ee505718c116e7e20ad"}, - {file = "langchain_text_splitters-0.3.7.tar.gz", hash = "sha256:7dbf0fb98e10bb91792a1d33f540e2287f9cc1dc30ade45b7aedd2d5cd3dc70b"}, -] - -[package.dependencies] -langchain-core = ">=0.3.45,<1.0.0" - -[[package]] -name = "langsmith" -version = "0.3.45" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "langsmith-0.3.45-py3-none-any.whl", hash = "sha256:5b55f0518601fa65f3bb6b1a3100379a96aa7b3ed5e9380581615ba9c65ed8ed"}, - {file = "langsmith-0.3.45.tar.gz", hash = "sha256:1df3c6820c73ed210b2c7bc5cdb7bfa19ddc9126cd03fdf0da54e2e171e6094d"}, -] - -[package.dependencies] -httpx = ">=0.23.0,<1" -orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} -packaging = ">=23.2" -pydantic = [ - {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -requests = ">=2,<3" -requests-toolbelt = ">=1.0.0,<2.0.0" -zstandard = ">=0.23.0,<0.24.0" - -[package.extras] -langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] -openai-agents = ["openai-agents (>=0.0.3,<0.1)"] -otel = ["opentelemetry-api (>=1.30.0,<2.0.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0,<2.0.0)", "opentelemetry-sdk (>=1.30.0,<2.0.0)"] -pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"] - -[[package]] -name = "mako" -version = "1.3.5" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "marshmallow" -version = "3.21.3" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, - {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mem0ai" -version = "0.1.54" -description = "Long-term memory for AI Agents" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "mem0ai-0.1.54-py3-none-any.whl", hash = "sha256:026c3262d714ebe536fb796c53e553051dbe6da66a8a313587efebfd420a0f7a"}, - {file = "mem0ai-0.1.54.tar.gz", hash = "sha256:f7a0dd2303e59a0131c1dea72058ba165d91e2b3e36d159cc7bbbb062610cc87"}, -] - -[package.dependencies] -openai = ">=1.33.0,<2.0.0" -posthog = ">=3.5.0,<4.0.0" -pydantic = ">=2.7.3,<3.0.0" -pytz = ">=2024.1,<2025.0" -qdrant-client = ">=1.9.1,<2.0.0" -sqlalchemy = ">=2.0.31,<3.0.0" - -[package.extras] -graph = ["langchain-community (>=0.3.1,<0.4.0)", "neo4j (>=5.23.1,<6.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)"] - -[[package]] -name = "milvus-lite" -version = "2.4.8" -description = "A lightweight version of Milvus wrapped with Python." -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"milvus\"" -files = [ - {file = "milvus_lite-2.4.8-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:b7e90b34b214884cd44cdc112ab243d4cb197b775498355e2437b6cafea025fe"}, - {file = "milvus_lite-2.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:519dfc62709d8f642d98a1c5b1dcde7080d107e6e312d677fef5a3412a40ac08"}, - {file = "milvus_lite-2.4.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b21f36d24cbb0e920b4faad607019bb28c1b2c88b4d04680ac8c7697a4ae8a4d"}, - {file = "milvus_lite-2.4.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:08332a2b9abfe7c4e1d7926068937e46f8fb81f2707928b7bc02c9dc99cebe41"}, -] - -[package.dependencies] -tqdm = "*" - -[[package]] -name = "mmh3" -version = "4.1.0" -description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5ac76a8b0cd8095784e51e4c1c9c318c19edcd1709a06eb14979c8d850c31a"}, - {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98a49121afdfab67cd80e912b36404139d7deceb6773a83620137aaa0da5714c"}, - {file = "mmh3-4.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5259ac0535874366e7d1a5423ef746e0d36a9e3c14509ce6511614bdc5a7ef5b"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5950827ca0453a2be357696da509ab39646044e3fa15cad364eb65d78797437"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dd0f652ae99585b9dd26de458e5f08571522f0402155809fd1dc8852a613a39"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d25548070942fab1e4a6f04d1626d67e66d0b81ed6571ecfca511f3edf07e6"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53db8d9bad3cb66c8f35cbc894f336273f63489ce4ac416634932e3cbe79eb5b"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75da0f615eb55295a437264cc0b736753f830b09d102aa4c2a7d719bc445ec05"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b926b07fd678ea84b3a2afc1fa22ce50aeb627839c44382f3d0291e945621e1a"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c5b053334f9b0af8559d6da9dc72cef0a65b325ebb3e630c680012323c950bb6"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bf33dc43cd6de2cb86e0aa73a1cc6530f557854bbbe5d59f41ef6de2e353d7b"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fa7eacd2b830727ba3dd65a365bed8a5c992ecd0c8348cf39a05cc77d22f4970"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42dfd6742b9e3eec599f85270617debfa0bbb913c545bb980c8a4fa7b2d047da"}, - {file = "mmh3-4.1.0-cp310-cp310-win32.whl", hash = "sha256:2974ad343f0d39dcc88e93ee6afa96cedc35a9883bc067febd7ff736e207fa47"}, - {file = "mmh3-4.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:74699a8984ded645c1a24d6078351a056f5a5f1fe5838870412a68ac5e28d865"}, - {file = "mmh3-4.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f0dc874cedc23d46fc488a987faa6ad08ffa79e44fb08e3cd4d4cf2877c00a00"}, - {file = "mmh3-4.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3280a463855b0eae64b681cd5b9ddd9464b73f81151e87bb7c91a811d25619e6"}, - {file = "mmh3-4.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97ac57c6c3301769e757d444fa7c973ceb002cb66534b39cbab5e38de61cd896"}, - {file = "mmh3-4.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b6502cdb4dbd880244818ab363c8770a48cdccecf6d729ade0241b736b5ec0"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ba2da04671a9621580ddabf72f06f0e72c1c9c3b7b608849b58b11080d8f14"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a5fef4c4ecc782e6e43fbeab09cff1bac82c998a1773d3a5ee6a3605cde343e"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5135358a7e00991f73b88cdc8eda5203bf9de22120d10a834c5761dbeb07dd13"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cff9ae76a54f7c6fe0167c9c4028c12c1f6de52d68a31d11b6790bb2ae685560"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f02576a4d106d7830ca90278868bf0983554dd69183b7bbe09f2fcd51cf54f"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:073d57425a23721730d3ff5485e2da489dd3c90b04e86243dd7211f889898106"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:71e32ddec7f573a1a0feb8d2cf2af474c50ec21e7a8263026e8d3b4b629805db"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7cbb20b29d57e76a58b40fd8b13a9130db495a12d678d651b459bf61c0714cea"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a42ad267e131d7847076bb7e31050f6c4378cd38e8f1bf7a0edd32f30224d5c9"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a013979fc9390abadc445ea2527426a0e7a4495c19b74589204f9b71bcaafeb"}, - {file = "mmh3-4.1.0-cp311-cp311-win32.whl", hash = "sha256:1d3b1cdad7c71b7b88966301789a478af142bddcb3a2bee563f7a7d40519a00f"}, - {file = "mmh3-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dc6dc32eb03727467da8e17deffe004fbb65e8b5ee2b502d36250d7a3f4e2ec"}, - {file = "mmh3-4.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9ae3a5c1b32dda121c7dc26f9597ef7b01b4c56a98319a7fe86c35b8bc459ae6"}, - {file = "mmh3-4.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0033d60c7939168ef65ddc396611077a7268bde024f2c23bdc283a19123f9e9c"}, - {file = "mmh3-4.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6af3e2287644b2b08b5924ed3a88c97b87b44ad08e79ca9f93d3470a54a41c5"}, - {file = "mmh3-4.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d82eb4defa245e02bb0b0dc4f1e7ee284f8d212633389c91f7fba99ba993f0a2"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba245e94b8d54765e14c2d7b6214e832557e7856d5183bc522e17884cab2f45d"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb04e2feeabaad6231e89cd43b3d01a4403579aa792c9ab6fdeef45cc58d4ec0"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3b1a27def545ce11e36158ba5d5390cdbc300cfe456a942cc89d649cf7e3b2"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce0ab79ff736d7044e5e9b3bfe73958a55f79a4ae672e6213e92492ad5e734d5"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b02268be6e0a8eeb8a924d7db85f28e47344f35c438c1e149878bb1c47b1cd3"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:deb887f5fcdaf57cf646b1e062d56b06ef2f23421c80885fce18b37143cba828"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99dd564e9e2b512eb117bd0cbf0f79a50c45d961c2a02402787d581cec5448d5"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:08373082dfaa38fe97aa78753d1efd21a1969e51079056ff552e687764eafdfe"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:54b9c6a2ea571b714e4fe28d3e4e2db37abfd03c787a58074ea21ee9a8fd1740"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a7b1edf24c69e3513f879722b97ca85e52f9032f24a52284746877f6a7304086"}, - {file = "mmh3-4.1.0-cp312-cp312-win32.whl", hash = "sha256:411da64b951f635e1e2284b71d81a5a83580cea24994b328f8910d40bed67276"}, - {file = "mmh3-4.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bebc3ecb6ba18292e3d40c8712482b4477abd6981c2ebf0e60869bd90f8ac3a9"}, - {file = "mmh3-4.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:168473dd608ade6a8d2ba069600b35199a9af837d96177d3088ca91f2b3798e3"}, - {file = "mmh3-4.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:372f4b7e1dcde175507640679a2a8790185bb71f3640fc28a4690f73da986a3b"}, - {file = "mmh3-4.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:438584b97f6fe13e944faf590c90fc127682b57ae969f73334040d9fa1c7ffa5"}, - {file = "mmh3-4.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6e27931b232fc676675fac8641c6ec6b596daa64d82170e8597f5a5b8bdcd3b6"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:571a92bad859d7b0330e47cfd1850b76c39b615a8d8e7aa5853c1f971fd0c4b1"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a69d6afe3190fa08f9e3a58e5145549f71f1f3fff27bd0800313426929c7068"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afb127be0be946b7630220908dbea0cee0d9d3c583fa9114a07156f98566dc28"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:940d86522f36348ef1a494cbf7248ab3f4a1638b84b59e6c9e90408bd11ad729"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dcccc4935686619a8e3d1f7b6e97e3bd89a4a796247930ee97d35ea1a39341"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01bb9b90d61854dfc2407c5e5192bfb47222d74f29d140cb2dd2a69f2353f7cc"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bcb1b8b951a2c0b0fb8a5426c62a22557e2ffc52539e0a7cc46eb667b5d606a9"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6477a05d5e5ab3168e82e8b106e316210ac954134f46ec529356607900aea82a"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:da5892287e5bea6977364b15712a2573c16d134bc5fdcdd4cf460006cf849278"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:99180d7fd2327a6fffbaff270f760576839dc6ee66d045fa3a450f3490fda7f5"}, - {file = "mmh3-4.1.0-cp38-cp38-win32.whl", hash = "sha256:9b0d4f3949913a9f9a8fb1bb4cc6ecd52879730aab5ff8c5a3d8f5b593594b73"}, - {file = "mmh3-4.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:598c352da1d945108aee0c3c3cfdd0e9b3edef74108f53b49d481d3990402169"}, - {file = "mmh3-4.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:475d6d1445dd080f18f0f766277e1237fa2914e5fe3307a3b2a3044f30892103"}, - {file = "mmh3-4.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5ca07c41e6a2880991431ac717c2a049056fff497651a76e26fc22224e8b5732"}, - {file = "mmh3-4.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ebe052fef4bbe30c0548d12ee46d09f1b69035ca5208a7075e55adfe091be44"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaefd42e85afb70f2b855a011f7b4d8a3c7e19c3f2681fa13118e4d8627378c5"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0ae43caae5a47afe1b63a1ae3f0986dde54b5fb2d6c29786adbfb8edc9edfb"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6218666f74c8c013c221e7f5f8a693ac9cf68e5ac9a03f2373b32d77c48904de"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac59294a536ba447b5037f62d8367d7d93b696f80671c2c45645fa9f1109413c"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086844830fcd1e5c84fec7017ea1ee8491487cfc877847d96f86f68881569d2e"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e42b38fad664f56f77f6fbca22d08450f2464baa68acdbf24841bf900eb98e87"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d08b790a63a9a1cde3b5d7d733ed97d4eb884bfbc92f075a091652d6bfd7709a"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:73ea4cc55e8aea28c86799ecacebca09e5f86500414870a8abaedfcbaf74d288"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f90938ff137130e47bcec8dc1f4ceb02f10178c766e2ef58a9f657ff1f62d124"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa1f13e94b8631c8cd53259250556edcf1de71738936b60febba95750d9632bd"}, - {file = "mmh3-4.1.0-cp39-cp39-win32.whl", hash = "sha256:a3b680b471c181490cf82da2142029edb4298e1bdfcb67c76922dedef789868d"}, - {file = "mmh3-4.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:fefef92e9c544a8dbc08f77a8d1b6d48006a750c4375bbcd5ff8199d761e263b"}, - {file = "mmh3-4.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:8e2c1f6a2b41723a4f82bd5a762a777836d29d664fc0095f17910bea0adfd4a6"}, - {file = "mmh3-4.1.0.tar.gz", hash = "sha256:a1cf25348b9acd229dda464a094d6170f47d2850a1fcb762a3b6172d2ce6ca4a"}, -] - -[package.extras] -test = ["mypy (>=1.0)", "pytest (>=7.0.0)"] - -[[package]] -name = "mock" -version = "5.1.0" -description = "Rolling backport of unittest.mock for all Pythons" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, - {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, -] - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "monotonic" -version = "1.6" -description = "An implementation of time.monotonic() for Python 2 & < 3.3" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, - {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -description = "Python library for arbitrary-precision floating-point arithmetic" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, - {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, -] - -[package.extras] -develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] -docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] -tests = ["pytest (>=4.6)"] - -[[package]] -name = "multidict" -version = "6.0.5" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["main", "dev"] -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "mysql-connector-python" -version = "8.4.0" -description = "MySQL driver written in Python" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"mysql\"" -files = [ - {file = "mysql-connector-python-8.4.0.tar.gz", hash = "sha256:42542d131d63c78416d410fdc9e84b9acb960d715c2e7b28c57ac9577c6d8165"}, - {file = "mysql_connector_python-8.4.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:c0a2688d95d53cfbea9352ed61926b47bc9042570570fb8fe0a8d19b1e20f1c4"}, - {file = "mysql_connector_python-8.4.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:276bae0d5d44abb7ba1205003b55628e4e6f1d399f1825d518bc607320997b1f"}, - {file = "mysql_connector_python-8.4.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a6d24ea29b3c2bdbba6861590de557665420bfb938f74b5cecc630bac5457d35"}, - {file = "mysql_connector_python-8.4.0-cp310-cp310-manylinux_2_17_x86_64.whl", hash = "sha256:b7876358d9e51f25edc492088c4ce16cd14c2db87c279a965b0f9c327723359c"}, - {file = "mysql_connector_python-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:085024bf12d15f9b428938fdbeb50bd9b15dda9c4d3a474e6df061cb08713e6a"}, - {file = "mysql_connector_python-8.4.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4e83fc8ed95005b171ffa36a289dac48625048263b09b56718e8395539ea07d9"}, - {file = "mysql_connector_python-8.4.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:cd89d1c8c2d1e33e5ac2d4eac5813422c150a8427fb60a16c59be18c29dd9a94"}, - {file = "mysql_connector_python-8.4.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:76c13fde35a038afe50550a9af7b31b28ca3a04cce06f3030980afb20460d28c"}, - {file = "mysql_connector_python-8.4.0-cp311-cp311-manylinux_2_17_x86_64.whl", hash = "sha256:accf10425c6af39a9595a47e7119ebcbcd7351f7df28755dbee01bca5a605b7c"}, - {file = "mysql_connector_python-8.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cda868bb4e1641362d148f5b0d2a86188cffa2f7188831589781b13f2df6f51a"}, - {file = "mysql_connector_python-8.4.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:3ae951f2e16d089975cb9f05b3f3e58807dc33a2e5a627047bba1c8ad5439d82"}, - {file = "mysql_connector_python-8.4.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:af40b5bdd91547d3dbf5fa62bde37e9e840bd7cba3b9246b55c09e6a1cde536f"}, - {file = "mysql_connector_python-8.4.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:bb4f3edab78f3fd6f80c6c0a9e5a533704044fc01bfb9e8736e1a993f74aa42d"}, - {file = "mysql_connector_python-8.4.0-cp312-cp312-manylinux_2_17_x86_64.whl", hash = "sha256:e549674c72b596a7386f4a76bbac2ee9581f6632e6713618a70468713b162964"}, - {file = "mysql_connector_python-8.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:aed505adc76b58282c76e6cbf3da195be0f84029a41f05c470be977481896074"}, - {file = "mysql_connector_python-8.4.0-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:d343a4a8133ae9561bd537fc8cdbcab74a0607a5f40698569010fa3c7d4a048f"}, - {file = "mysql_connector_python-8.4.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:74b1759d8bd9ccd4296dc2e5abe22ec7efbd1ad12a9032c2cb4d17fa5d0ca6e0"}, - {file = "mysql_connector_python-8.4.0-cp38-cp38-manylinux_2_17_x86_64.whl", hash = "sha256:e6d5a418ef124dd1b18a73fd89431a1862ce7bf68f61275c7d006e8e2f8afcd2"}, - {file = "mysql_connector_python-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:427a84027b8314c73f5ff3eb1abdc709a8201b44a491d7b580bdf430b4820a16"}, - {file = "mysql_connector_python-8.4.0-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:44a99d44a925ea29c2e423e6d8b1d97ce740c3078d8b41923a81bbcd0a821972"}, - {file = "mysql_connector_python-8.4.0-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:ed276c4e7907da0ad95a9ad122004294d6fb425127064af2ae880033b8e72166"}, - {file = "mysql_connector_python-8.4.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:2b5c6fea6513cf208c7116a4a5e36b3ae54e0d37f324a7cfe43fb01cfdf03be6"}, - {file = "mysql_connector_python-8.4.0-cp39-cp39-manylinux_2_17_x86_64.whl", hash = "sha256:651c7824af57eb50f4a79ea04bf6f453b24381e1bb56eee45c0035b4c0c624c0"}, - {file = "mysql_connector_python-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:655dccbdc0e2943e62cf69e10a024329248b17b58bfac59c60fd2103db3ba0b0"}, - {file = "mysql_connector_python-8.4.0-py2.py3-none-any.whl", hash = "sha256:35939c4ff28f395a5550bae67bafa4d1658ea72ea3206f457fff64a0fbec17e4"}, -] - -[package.extras] -dns-srv = ["dnspython (>=1.16.0,<=2.3.0)"] -fido2 = ["fido2 (==1.1.2)"] -gssapi = ["gssapi (>=1.6.9,<=1.8.2)"] -opentelemetry = ["Deprecated (>=1.2.6)", "typing-extensions (>=3.7.4)", "zipp (>=0.5)"] - -[[package]] -name = "networkx" -version = "3.2.1" -description = "Python package for creating and manipulating graphs and networks" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, - {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, -] - -[package.extras] -default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "nvidia-cublas" -version = "13.1.0.3" -description = "CUBLAS native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2"}, - {file = "nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171"}, - {file = "nvidia_cublas-13.1.0.3-py3-none-win_amd64.whl", hash = "sha256:2a3b94a37def342471c59fad7856caee4926809a72dd5270155d6a31b5b277be"}, -] - -[[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" -description = "CUBLAS native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0"}, - {file = "nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142"}, - {file = "nvidia_cublas_cu12-12.8.4.1-py3-none-win_amd64.whl", hash = "sha256:47e9b82132fa8d2b4944e708049229601448aaad7e6f296f630f2d1a32de35af"}, -] - -[[package]] -name = "nvidia-cuda-cupti" -version = "13.0.85" -description = "CUDA profiling tools runtime libs." -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151"}, - {file = "nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8"}, - {file = "nvidia_cuda_cupti-13.0.85-py3-none-win_amd64.whl", hash = "sha256:683f58d301548deeefcb8f6fac1b8d907691b9d8b18eccab417f51e362102f00"}, -] - -[[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" -description = "CUDA profiling tools runtime libs." -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed"}, - {file = "nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182"}, - {file = "nvidia_cuda_cupti_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:bb479dcdf7e6d4f8b0b01b115260399bf34154a1a2e9fe11c85c517d87efd98e"}, -] - -[[package]] -name = "nvidia-cuda-nvrtc" -version = "13.0.88" -description = "NVRTC native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575"}, - {file = "nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b"}, - {file = "nvidia_cuda_nvrtc-13.0.88-py3-none-win_amd64.whl", hash = "sha256:6bcd4e7f8e205cbe644f5a98f2f799bef9556fefc89dd786e79a16312ce49872"}, -] - -[[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" -description = "NVRTC native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994"}, - {file = "nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8"}, - {file = "nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:7a4b6b2904850fe78e0bd179c4b655c404d4bb799ef03ddc60804247099ae909"}, -] - -[[package]] -name = "nvidia-cuda-runtime" -version = "13.0.96" -description = "CUDA Runtime native Libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55"}, - {file = "nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548"}, - {file = "nvidia_cuda_runtime-13.0.96-py3-none-win_amd64.whl", hash = "sha256:f79298c8a098cec150a597c8eba58ecdab96e3bdc4b9bc4f9983635031740492"}, -] - -[[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" -description = "CUDA Runtime native Libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d"}, - {file = "nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90"}, - {file = "nvidia_cuda_runtime_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:c0c6027f01505bfed6c3b21ec546f69c687689aad5f1a377554bc6ca4aa993a8"}, -] - -[[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" -description = "cuDNN runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c9132cc3f8958447b4910a1720036d9eff5928cc3179b0a51fb6d167c6cc87d8"}, - {file = "nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8"}, - {file = "nvidia_cudnn_cu12-9.10.2.21-py3-none-win_amd64.whl", hash = "sha256:c6288de7d63e6cf62988f0923f96dc339cea362decb1bf5b3141883392a7d65e"}, -] - -[package.dependencies] -nvidia-cublas-cu12 = "*" - -[[package]] -name = "nvidia-cudnn-cu13" -version = "9.19.0.56" -description = "cuDNN runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4"}, - {file = "nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf"}, - {file = "nvidia_cudnn_cu13-9.19.0.56-py3-none-win_amd64.whl", hash = "sha256:40d8c375005bcb01495f8edf375230b203a411a0c05fb6dc92a3781edcb23eac"}, -] - -[package.dependencies] -nvidia-cublas = "*" - -[[package]] -name = "nvidia-cufft" -version = "12.0.0.61" -description = "CUFFT native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5"}, - {file = "nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3"}, - {file = "nvidia_cufft-12.0.0.61-py3-none-win_amd64.whl", hash = "sha256:2abce5b39d2f5ae12730fb7e5db6696533e36c26e2d3e8fd1750bdd2853364eb"}, -] - -[package.dependencies] -nvidia-nvjitlink = "*" - -[[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" -description = "CUFFT native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a"}, - {file = "nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74"}, - {file = "nvidia_cufft_cu12-11.3.3.83-py3-none-win_amd64.whl", hash = "sha256:7a64a98ef2a7c47f905aaf8931b69a3a43f27c55530c698bb2ed7c75c0b42cb7"}, -] - -[package.dependencies] -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-cufile" -version = "1.15.1.6" -description = "cuFile GPUDirect libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and sys_platform == \"linux\" and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44"}, - {file = "nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1"}, -] - -[[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" -description = "cuFile GPUDirect libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc"}, - {file = "nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a"}, -] - -[[package]] -name = "nvidia-curand" -version = "10.4.0.35" -description = "CURAND native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a"}, - {file = "nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc"}, - {file = "nvidia_curand-10.4.0.35-py3-none-win_amd64.whl", hash = "sha256:65b1710aa6961d326b411e314b374290904c5ddf41dc3f766ebc3f1d7d4ca69f"}, -] - -[[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" -description = "CURAND native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd"}, - {file = "nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9"}, - {file = "nvidia_curand_cu12-10.3.9.90-py3-none-win_amd64.whl", hash = "sha256:f149a8ca457277da854f89cf282d6ef43176861926c7ac85b2a0fbd237c587ec"}, -] - -[[package]] -name = "nvidia-cusolver" -version = "12.0.4.66" -description = "CUDA solver native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2"}, - {file = "nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112"}, - {file = "nvidia_cusolver-12.0.4.66-py3-none-win_amd64.whl", hash = "sha256:16515bd33a8e76bb54d024cfa068fa68d30e80fc34b9e1090813ea9362e0cb65"}, -] - -[package.dependencies] -nvidia-cublas = "*" -nvidia-cusparse = "*" -nvidia-nvjitlink = "*" - -[[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" -description = "CUDA solver native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0"}, - {file = "nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450"}, - {file = "nvidia_cusolver_cu12-11.7.3.90-py3-none-win_amd64.whl", hash = "sha256:4a550db115fcabc4d495eb7d39ac8b58d4ab5d8e63274d3754df1c0ad6a22d34"}, -] - -[package.dependencies] -nvidia-cublas-cu12 = "*" -nvidia-cusparse-cu12 = "*" -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-cusparse" -version = "12.6.3.3" -description = "CUSPARSE native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c"}, - {file = "nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b"}, - {file = "nvidia_cusparse-12.6.3.3-py3-none-win_amd64.whl", hash = "sha256:cbcf42feb737bd7ec15b4c0a63e62351886bd3f975027b8815d7f720a2b5ea79"}, -] - -[package.dependencies] -nvidia-nvjitlink = "*" - -[[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" -description = "CUSPARSE native runtime libraries" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc"}, - {file = "nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b"}, - {file = "nvidia_cusparse_cu12-12.5.8.93-py3-none-win_amd64.whl", hash = "sha256:9a33604331cb2cac199f2e7f5104dfbb8a5a898c367a53dfda9ff2acb6b6b4dd"}, -] - -[package.dependencies] -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" -description = "NVIDIA cuSPARSELt" -optional = true -python-versions = "*" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5"}, - {file = "nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623"}, - {file = "nvidia_cusparselt_cu12-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f67fbb5831940ec829c9117b7f33807db9f9678dc2a617fbe781cac17b4e1075"}, -] - -[[package]] -name = "nvidia-cusparselt-cu13" -version = "0.8.0" -description = "NVIDIA cuSPARSELt" -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c"}, - {file = "nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd"}, - {file = "nvidia_cusparselt_cu13-0.8.0-py3-none-win_amd64.whl", hash = "sha256:e80212ed7b1afc97102fbb2b5c82487aa73f6a0edfa6d26c5a152593e520bb8f"}, -] - -[[package]] -name = "nvidia-nccl-cu12" -version = "2.27.3" -description = "NVIDIA Collective Communication Library (NCCL) Runtime" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9ddf1a245abc36c550870f26d537a9b6087fb2e2e3d6e0ef03374c6fd19d984f"}, - {file = "nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039"}, -] - -[[package]] -name = "nvidia-nccl-cu13" -version = "2.28.9" -description = "NVIDIA Collective Communication Library (NCCL) Runtime" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643"}, - {file = "nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42"}, -] - -[[package]] -name = "nvidia-nvjitlink" -version = "13.0.88" -description = "Nvidia JIT LTO Library" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b"}, - {file = "nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c"}, - {file = "nvidia_nvjitlink-13.0.88-py3-none-win_amd64.whl", hash = "sha256:634e96e3da9ef845ae744097a1f289238ecf946ce0b82e93cdce14b9782e682f"}, -] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" -description = "Nvidia JIT LTO Library" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88"}, - {file = "nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7"}, - {file = "nvidia_nvjitlink_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:bd93fbeeee850917903583587f4fc3a4eafa022e34572251368238ab5e6bd67f"}, -] - -[[package]] -name = "nvidia-nvshmem-cu13" -version = "3.4.5" -description = "NVSHMEM creates a global address space that provides efficient and scalable communication for NVIDIA GPU clusters." -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9"}, - {file = "nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80"}, -] - -[[package]] -name = "nvidia-nvtx" -version = "13.0.85" -description = "NVIDIA Tools Extension" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and (sys_platform == \"linux\" or sys_platform == \"win32\") and python_version >= \"3.10\"" -files = [ - {file = "nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4"}, - {file = "nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6"}, - {file = "nvidia_nvtx-13.0.85-py3-none-win_amd64.whl", hash = "sha256:d66ea44254dd3c6eacc300047af6e1288d2269dd072b417e0adffbf479e18519"}, -] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" -description = "NVIDIA Tools Extension" -optional = true -python-versions = ">=3" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615"}, - {file = "nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f"}, - {file = "nvidia_nvtx_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:619c8304aedc69f02ea82dd244541a83c3d9d40993381b3b590f1adaed3db41e"}, -] - -[[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, -] - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[package]] -name = "onnxruntime" -version = "1.18.1" -description = "ONNX Runtime is a runtime accelerator for Machine Learning models" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "onnxruntime-1.18.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ef7683312393d4ba04252f1b287d964bd67d5e6048b94d2da3643986c74d80"}, - {file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc706eb1df06ddf55776e15a30519fb15dda7697f987a2bbda4962845e3cec05"}, - {file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7de69f5ced2a263531923fa68bbec52a56e793b802fcd81a03487b5e292bc3a"}, - {file = "onnxruntime-1.18.1-cp310-cp310-win32.whl", hash = "sha256:221e5b16173926e6c7de2cd437764492aa12b6811f45abd37024e7cf2ae5d7e3"}, - {file = "onnxruntime-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:75211b619275199c861ee94d317243b8a0fcde6032e5a80e1aa9ded8ab4c6060"}, - {file = "onnxruntime-1.18.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:f26582882f2dc581b809cfa41a125ba71ad9e715738ec6402418df356969774a"}, - {file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef36f3a8b768506d02be349ac303fd95d92813ba3ba70304d40c3cd5c25d6a4c"}, - {file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:170e711393e0618efa8ed27b59b9de0ee2383bd2a1f93622a97006a5ad48e434"}, - {file = "onnxruntime-1.18.1-cp311-cp311-win32.whl", hash = "sha256:9b6a33419b6949ea34e0dc009bc4470e550155b6da644571ecace4b198b0d88f"}, - {file = "onnxruntime-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c1380a9f1b7788da742c759b6a02ba771fe1ce620519b2b07309decbd1a2fe1"}, - {file = "onnxruntime-1.18.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:31bd57a55e3f983b598675dfc7e5d6f0877b70ec9864b3cc3c3e1923d0a01919"}, - {file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9e03c4ba9f734500691a4d7d5b381cd71ee2f3ce80a1154ac8f7aed99d1ecaa"}, - {file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:781aa9873640f5df24524f96f6070b8c550c66cb6af35710fd9f92a20b4bfbf6"}, - {file = "onnxruntime-1.18.1-cp312-cp312-win32.whl", hash = "sha256:3a2d9ab6254ca62adbb448222e630dc6883210f718065063518c8f93a32432be"}, - {file = "onnxruntime-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:ad93c560b1c38c27c0275ffd15cd7f45b3ad3fc96653c09ce2931179982ff204"}, - {file = "onnxruntime-1.18.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:3b55dc9d3c67626388958a3eb7ad87eb7c70f75cb0f7ff4908d27b8b42f2475c"}, - {file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f80dbcfb6763cc0177a31168b29b4bd7662545b99a19e211de8c734b657e0669"}, - {file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1ff2c61a16d6c8631796c54139bafea41ee7736077a0fc64ee8ae59432f5c58"}, - {file = "onnxruntime-1.18.1-cp38-cp38-win32.whl", hash = "sha256:219855bd272fe0c667b850bf1a1a5a02499269a70d59c48e6f27f9c8bcb25d02"}, - {file = "onnxruntime-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:afdf16aa607eb9a2c60d5ca2d5abf9f448e90c345b6b94c3ed14f4fb7e6a2d07"}, - {file = "onnxruntime-1.18.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:128df253ade673e60cea0955ec9d0e89617443a6d9ce47c2d79eb3f72a3be3de"}, - {file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9839491e77e5c5a175cab3621e184d5a88925ee297ff4c311b68897197f4cde9"}, - {file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad3187c1faff3ac15f7f0e7373ef4788c582cafa655a80fdbb33eaec88976c66"}, - {file = "onnxruntime-1.18.1-cp39-cp39-win32.whl", hash = "sha256:34657c78aa4e0b5145f9188b550ded3af626651b15017bf43d280d7e23dbf195"}, - {file = "onnxruntime-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c14fd97c3ddfa97da5feef595e2c73f14c2d0ec1d4ecbea99c8d96603c89589"}, -] - -[package.dependencies] -coloredlogs = "*" -flatbuffers = "*" -numpy = ">=1.21.6,<2.0" -packaging = "*" -protobuf = "*" -sympy = "*" - -[[package]] -name = "openai" -version = "1.51.0" -description = "The official Python library for the openai API" -optional = false -python-versions = ">=3.7.1" -groups = ["main"] -files = [ - {file = "openai-1.51.0-py3-none-any.whl", hash = "sha256:d9affafb7e51e5a27dce78589d4964ce4d6f6d560307265933a94b2e3f3c5d2c"}, - {file = "openai-1.51.0.tar.gz", hash = "sha256:8dc4f9d75ccdd5466fc8c99a952186eddceb9fd6ba694044773f3736a847149d"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -tqdm = ">4" -typing-extensions = ">=4.11,<5" - -[package.extras] -datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] - -[[package]] -name = "opensearch-py" -version = "2.3.1" -description = "Python client for OpenSearch" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -groups = ["main"] -markers = "extra == \"opensearch\"" -files = [ - {file = "opensearch-py-2.3.1.tar.gz", hash = "sha256:f82a2e914835f7d645a632777de9a62d0c0de60ffd2f8cdae2ccfa4cfc40a185"}, - {file = "opensearch_py-2.3.1-py2.py3-none-any.whl", hash = "sha256:eafbc5d56a7ca696afba7d77bcda1bbb849050cbf9265d57d8476576cb576395"}, -] - -[package.dependencies] -certifi = ">=2022.12.7" -python-dateutil = "*" -requests = ">=2.4.0,<3.0.0" -six = "*" -urllib3 = ">=1.21.1,<2" - -[package.extras] -async = ["aiohttp (>=3,<4)"] -develop = ["black", "botocore ; python_version >= \"3.6\"", "coverage (<7.0.0)", "jinja2", "mock", "myst-parser", "pytest (>=3.0.0)", "pytest-cov", "pytest-mock (<4.0.0)", "pytz", "pyyaml", "requests (>=2.0.0,<3.0.0)", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -docs = ["myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -kerberos = ["requests-kerberos"] - -[[package]] -name = "opentelemetry-api" -version = "1.25.0" -description = "OpenTelemetry Python API" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"}, - {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=7.1" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-common" -version = "1.25.0" -description = "OpenTelemetry Protobuf encoding" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl", hash = "sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.25.0.tar.gz", hash = "sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3"}, -] - -[package.dependencies] -opentelemetry-proto = "1.25.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.25.0" -description = "OpenTelemetry Collector Protobuf over gRPC Exporter" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0-py3-none-any.whl", hash = "sha256:3131028f0c0a155a64c430ca600fd658e8e37043cb13209f0109db5c1a3e4eb4"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0.tar.gz", hash = "sha256:c0b1661415acec5af87625587efa1ccab68b873745ca0ee96b69bb1042087eac"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -googleapis-common-protos = ">=1.52,<2.0" -grpcio = ">=1.0.0,<2.0.0" -opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.25.0" -opentelemetry-proto = "1.25.0" -opentelemetry-sdk = ">=1.25.0,<1.26.0" - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.46b0" -description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_instrumentation-0.46b0-py3-none-any.whl", hash = "sha256:89cd721b9c18c014ca848ccd11181e6b3fd3f6c7669e35d59c48dc527408c18b"}, - {file = "opentelemetry_instrumentation-0.46b0.tar.gz", hash = "sha256:974e0888fb2a1e01c38fbacc9483d024bb1132aad92d6d24e2e5543887a7adda"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.4,<2.0" -setuptools = ">=16.0" -wrapt = ">=1.0.0,<2.0.0" - -[[package]] -name = "opentelemetry-instrumentation-asgi" -version = "0.46b0" -description = "ASGI instrumentation for OpenTelemetry" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_instrumentation_asgi-0.46b0-py3-none-any.whl", hash = "sha256:f13c55c852689573057837a9500aeeffc010c4ba59933c322e8f866573374759"}, - {file = "opentelemetry_instrumentation_asgi-0.46b0.tar.gz", hash = "sha256:02559f30cf4b7e2a737ab17eb52aa0779bcf4cc06573064f3e2cb4dcc7d3040a"}, -] - -[package.dependencies] -asgiref = ">=3.0,<4.0" -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.46b0" -opentelemetry-semantic-conventions = "0.46b0" -opentelemetry-util-http = "0.46b0" - -[package.extras] -instruments = ["asgiref (>=3.0,<4.0)"] - -[[package]] -name = "opentelemetry-instrumentation-fastapi" -version = "0.46b0" -description = "OpenTelemetry FastAPI Instrumentation" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_instrumentation_fastapi-0.46b0-py3-none-any.whl", hash = "sha256:e0f5d150c6c36833dd011f0e6ef5ede6d7406c1aed0c7c98b2d3b38a018d1b33"}, - {file = "opentelemetry_instrumentation_fastapi-0.46b0.tar.gz", hash = "sha256:928a883a36fc89f9702f15edce43d1a7104da93d740281e32d50ffd03dbb4365"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.46b0" -opentelemetry-instrumentation-asgi = "0.46b0" -opentelemetry-semantic-conventions = "0.46b0" -opentelemetry-util-http = "0.46b0" - -[package.extras] -instruments = ["fastapi (>=0.58,<1.0)"] - -[[package]] -name = "opentelemetry-proto" -version = "1.25.0" -description = "OpenTelemetry Python Proto" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_proto-1.25.0-py3-none-any.whl", hash = "sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f"}, - {file = "opentelemetry_proto-1.25.0.tar.gz", hash = "sha256:35b6ef9dc4a9f7853ecc5006738ad40443701e52c26099e197895cbda8b815a3"}, -] - -[package.dependencies] -protobuf = ">=3.19,<5.0" - -[[package]] -name = "opentelemetry-sdk" -version = "1.25.0" -description = "OpenTelemetry Python SDK" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"}, - {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"}, -] - -[package.dependencies] -opentelemetry-api = "1.25.0" -opentelemetry-semantic-conventions = "0.46b0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.46b0" -description = "OpenTelemetry Semantic Conventions" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"}, - {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"}, -] - -[package.dependencies] -opentelemetry-api = "1.25.0" - -[[package]] -name = "opentelemetry-util-http" -version = "0.46b0" -description = "Web util for OpenTelemetry" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_util_http-0.46b0-py3-none-any.whl", hash = "sha256:8dc1949ce63caef08db84ae977fdc1848fe6dc38e6bbaad0ae3e6ecd0d451629"}, - {file = "opentelemetry_util_http-0.46b0.tar.gz", hash = "sha256:03b6e222642f9c7eae58d9132343e045b50aca9761fcb53709bd2b663571fdf6"}, -] - -[[package]] -name = "orjson" -version = "3.10.6" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"}, - {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"}, - {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"}, - {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"}, - {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"}, - {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"}, - {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"}, - {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"}, - {file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"}, - {file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"}, - {file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"}, - {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"}, - {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"}, - {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"}, - {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"}, - {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"}, - {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"}, - {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"}, - {file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"}, - {file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"}, - {file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"}, - {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"}, - {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"}, - {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"}, - {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"}, - {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"}, - {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"}, - {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"}, - {file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"}, - {file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"}, - {file = "orjson-3.10.6-cp313-none-win32.whl", hash = "sha256:efdf2c5cde290ae6b83095f03119bdc00303d7a03b42b16c54517baa3c4ca3d0"}, - {file = "orjson-3.10.6-cp313-none-win_amd64.whl", hash = "sha256:8e190fe7888e2e4392f52cafb9626113ba135ef53aacc65cd13109eb9746c43e"}, - {file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"}, - {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"}, - {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"}, - {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"}, - {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"}, - {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"}, - {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"}, - {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"}, - {file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"}, - {file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"}, - {file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"}, - {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"}, - {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"}, - {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"}, - {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"}, - {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"}, - {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"}, - {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"}, - {file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"}, - {file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"}, - {file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"}, -] - -[[package]] -name = "overrides" -version = "7.7.0" -description = "A decorator to automatically detect mismatch when overriding a method." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, - {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pandas" -version = "2.2.2" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "parameterized" -version = "0.9.0" -description = "Parameterized testing with any Python test framework" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, - {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, -] - -[package.extras] -dev = ["jinja2"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"opensource\" or extra == \"together\"" -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions ; python_version < \"3.10\""] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "portalocker" -version = "2.10.0" -description = "Wraps the portalocker recipe for easy usage" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "portalocker-2.10.0-py3-none-any.whl", hash = "sha256:48944147b2cd42520549bc1bb8fe44e220296e56f7c3d551bc6ecce69d9b0de1"}, - {file = "portalocker-2.10.0.tar.gz", hash = "sha256:49de8bc0a2f68ca98bf9e219c81a3e6b27097c7bf505a87c5a112ce1aaeb9b81"}, -] - -[package.dependencies] -pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} - -[package.extras] -docs = ["sphinx (>=1.7.1)"] -redis = ["redis"] -tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] - -[[package]] -name = "posthog" -version = "3.5.0" -description = "Integrate PostHog into any python application." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "posthog-3.5.0-py2.py3-none-any.whl", hash = "sha256:3c672be7ba6f95d555ea207d4486c171d06657eb34b3ce25eb043bfe7b6b5b76"}, - {file = "posthog-3.5.0.tar.gz", hash = "sha256:8f7e3b2c6e8714d0c0c542a2109b83a7549f63b7113a133ab2763a89245ef2ef"}, -] - -[package.dependencies] -backoff = ">=1.10.0" -monotonic = ">=1.5" -python-dateutil = ">2.1" -requests = ">=2.7,<3.0" -six = ">=1.5" - -[package.extras] -dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"] -sentry = ["django", "sentry-sdk"] -test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest", "pytest-timeout"] - -[[package]] -name = "pre-commit" -version = "3.7.1" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, - {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "proto-plus" -version = "1.24.0" -description = "Beautiful, Pythonic protocol buffers." -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, - {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<6.0.0.dev0" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "4.25.3" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, - {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, - {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, - {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, - {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, - {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, - {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, - {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, - {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, -] - -[[package]] -name = "psycopg" -version = "3.2.1" -description = "PostgreSQL database adapter for Python" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"postgres\"" -files = [ - {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, - {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, -] - -[package.dependencies] -typing-extensions = ">=4.4" -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -binary = ["psycopg-binary (==3.2.1) ; implementation_name != \"pypy\""] -c = ["psycopg-c (==3.2.1) ; implementation_name != \"pypy\""] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] - -[[package]] -name = "psycopg-binary" -version = "3.2.1" -description = "PostgreSQL database adapter for Python -- C optimisation distribution" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"postgres\"" -files = [ - {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd"}, - {file = "psycopg_binary-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b"}, - {file = "psycopg_binary-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f"}, - {file = "psycopg_binary-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22"}, - {file = "psycopg_binary-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2"}, - {file = "psycopg_binary-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073"}, -] - -[[package]] -name = "psycopg-pool" -version = "3.2.2" -description = "Connection Pool for Psycopg" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"postgres\"" -files = [ - {file = "psycopg_pool-3.2.2-py3-none-any.whl", hash = "sha256:273081d0fbfaced4f35e69200c89cb8fbddfe277c38cc86c235b90a2ec2c8153"}, - {file = "psycopg_pool-3.2.2.tar.gz", hash = "sha256:9e22c370045f6d7f2666a5ad1b0caf345f9f1912195b0b25d0d3bcc4f3a7389c"}, -] - -[package.dependencies] -typing-extensions = ">=4.4" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - -[[package]] -name = "pyarrow" -version = "15.0.0" -description = "Python library for Apache Arrow" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"together\" or extra == \"lancedb\"" -files = [ - {file = "pyarrow-15.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0a524532fd6dd482edaa563b686d754c70417c2f72742a8c990b322d4c03a15d"}, - {file = "pyarrow-15.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60a6bdb314affa9c2e0d5dddf3d9cbb9ef4a8dddaa68669975287d47ece67642"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66958fd1771a4d4b754cd385835e66a3ef6b12611e001d4e5edfcef5f30391e2"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f500956a49aadd907eaa21d4fff75f73954605eaa41f61cb94fb008cf2e00c6"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6f87d9c4f09e049c2cade559643424da84c43a35068f2a1c4653dc5b1408a929"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85239b9f93278e130d86c0e6bb455dcb66fc3fd891398b9d45ace8799a871a1e"}, - {file = "pyarrow-15.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b8d43e31ca16aa6e12402fcb1e14352d0d809de70edd185c7650fe80e0769e3"}, - {file = "pyarrow-15.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:fa7cd198280dbd0c988df525e50e35b5d16873e2cdae2aaaa6363cdb64e3eec5"}, - {file = "pyarrow-15.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8780b1a29d3c8b21ba6b191305a2a607de2e30dab399776ff0aa09131e266340"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0ec198ccc680f6c92723fadcb97b74f07c45ff3fdec9dd765deb04955ccf19"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036a7209c235588c2f07477fe75c07e6caced9b7b61bb897c8d4e52c4b5f9555"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2bd8a0e5296797faf9a3294e9fa2dc67aa7f10ae2207920dbebb785c77e9dbe5"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e8ebed6053dbe76883a822d4e8da36860f479d55a762bd9e70d8494aed87113e"}, - {file = "pyarrow-15.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d53a9d1b2b5bd7d5e4cd84d018e2a45bc9baaa68f7e6e3ebed45649900ba99"}, - {file = "pyarrow-15.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9950a9c9df24090d3d558b43b97753b8f5867fb8e521f29876aa021c52fda351"}, - {file = "pyarrow-15.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:003d680b5e422d0204e7287bb3fa775b332b3fce2996aa69e9adea23f5c8f970"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f75fce89dad10c95f4bf590b765e3ae98bcc5ba9f6ce75adb828a334e26a3d40"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca9cb0039923bec49b4fe23803807e4ef39576a2bec59c32b11296464623dc2"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ed5a78ed29d171d0acc26a305a4b7f83c122d54ff5270810ac23c75813585e4"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6eda9e117f0402dfcd3cd6ec9bfee89ac5071c48fc83a84f3075b60efa96747f"}, - {file = "pyarrow-15.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a3a6180c0e8f2727e6f1b1c87c72d3254cac909e609f35f22532e4115461177"}, - {file = "pyarrow-15.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:19a8918045993349b207de72d4576af0191beef03ea655d8bdb13762f0cd6eac"}, - {file = "pyarrow-15.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0ec076b32bacb6666e8813a22e6e5a7ef1314c8069d4ff345efa6246bc38593"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db1769e5d0a77eb92344c7382d6543bea1164cca3704f84aa44e26c67e320fb"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2617e3bf9df2a00020dd1c1c6dce5cc343d979efe10bc401c0632b0eef6ef5b"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:d31c1d45060180131caf10f0f698e3a782db333a422038bf7fe01dace18b3a31"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:c8c287d1d479de8269398b34282e206844abb3208224dbdd7166d580804674b7"}, - {file = "pyarrow-15.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:07eb7f07dc9ecbb8dace0f58f009d3a29ee58682fcdc91337dfeb51ea618a75b"}, - {file = "pyarrow-15.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:47af7036f64fce990bb8a5948c04722e4e3ea3e13b1007ef52dfe0aa8f23cf7f"}, - {file = "pyarrow-15.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93768ccfff85cf044c418bfeeafce9a8bb0cee091bd8fd19011aff91e58de540"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6ee87fd6892700960d90abb7b17a72a5abb3b64ee0fe8db6c782bcc2d0dc0b4"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001fca027738c5f6be0b7a3159cc7ba16a5c52486db18160909a0831b063c4e4"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:d1c48648f64aec09accf44140dccb92f4f94394b8d79976c426a5b79b11d4fa7"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:972a0141be402bb18e3201448c8ae62958c9c7923dfaa3b3d4530c835ac81aed"}, - {file = "pyarrow-15.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:f01fc5cf49081426429127aa2d427d9d98e1cb94a32cb961d583a70b7c4504e6"}, - {file = "pyarrow-15.0.0.tar.gz", hash = "sha256:876858f549d540898f927eba4ef77cd549ad8d24baa3207cf1b72e5788b50e83"}, -] - -[package.dependencies] -numpy = ">=1.16.6,<2" - -[[package]] -name = "pyasn1" -version = "0.6.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.0" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, - {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"weaviate\" or platform_python_implementation == \"PyPy\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "2.8.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, -] - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.20.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydantic-settings" -version = "2.5.2" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, - {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" - -[package.extras] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pylance" -version = "0.10.12" -description = "python wrapper for Lance columnar format" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "pylance-0.10.12-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:30cbcca078edeb37e11ae86cf9287d81ce6c0c07ba77239284b369a4b361497b"}, - {file = "pylance-0.10.12-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:e558163ff6035d518706cc66848497219ccc755e2972b8f3b1706a3e1fd800fd"}, - {file = "pylance-0.10.12-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75afb39f71d7f12429f9b4d380eb6cf6aed179ae5a1c5d16cc768373a1521f87"}, - {file = "pylance-0.10.12-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:3de391dfc3a99bdb245fd1e27ef242be769a94853f802ef57f246e9a21358d32"}, - {file = "pylance-0.10.12-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:34a5278b90f4cbcf21261353976127aa2ffbbd7d068810f0a2b0c1aa0334022a"}, - {file = "pylance-0.10.12-cp38-abi3-win_amd64.whl", hash = "sha256:6cef5975d513097fd2c22692296c9a5a138928f38d02cd34ab63a7369abc1463"}, -] - -[package.dependencies] -numpy = ">=1.22" -pyarrow = ">=12,<15.0.1" - -[package.extras] -benchmarks = ["pytest-benchmark"] -dev = ["ruff (==0.2.2)"] -ray = ["ray[data] ; python_version < \"3.12\""] -tests = ["boto3", "datasets", "duckdb", "h5py (<3.11)", "ml-dtypes", "pandas", "pillow", "polars[pandas,pyarrow]", "pytest", "tensorflow", "tqdm"] -torch = ["torch"] - -[[package]] -name = "pymilvus" -version = "2.4.3" -description = "Python Sdk for Milvus" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"milvus\"" -files = [ - {file = "pymilvus-2.4.3-py3-none-any.whl", hash = "sha256:38239e89f8d739f665141d0b80908990b5f59681e889e135c234a4a45669a5c8"}, - {file = "pymilvus-2.4.3.tar.gz", hash = "sha256:703ac29296cdce03d6dc2aaebbe959e57745c141a94150e371dc36c61c226cc1"}, -] - -[package.dependencies] -environs = "<=9.5.0" -grpcio = ">=1.49.1,<=1.63.0" -milvus-lite = ">=2.4.0,<2.5.0" -pandas = ">=1.2.4" -protobuf = ">=3.20.0" -setuptools = ">=67" -ujson = ">=2.0.0" - -[package.extras] -bulk-writer = ["azure-storage-blob", "minio (>=7.0.0)", "pyarrow (>=12.0.0)", "requests"] -dev = ["black", "grpcio (==1.62.2)", "grpcio-testing (==1.62.2)", "grpcio-tools (==1.62.2)", "pytest (>=5.3.4)", "pytest-cov (>=2.8.1)", "pytest-timeout (>=1.3.4)", "ruff (>0.4.0)"] -model = ["milvus-model (>=0.1.0)"] - -[[package]] -name = "pyparsing" -version = "3.1.2" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = true -python-versions = ">=3.6.8" -groups = ["main"] -markers = "extra == \"gmail\" or extra == \"googledrive\"" -files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pypdf" -version = "5.0.0" -description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "pypdf-5.0.0-py3-none-any.whl", hash = "sha256:67603e2e96cdf70e676564520933c017d450f16075b9966be5fee128812ace8c"}, - {file = "pypdf-5.0.0.tar.gz", hash = "sha256:5c536ec0f7af8e2f80eb32806964652a562b9abc5477b3c195e2b842725ce55b"}, -] - -[package.dependencies] -typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} - -[package.extras] -crypto = ["PyCryptodome ; python_version == \"3.6\"", "cryptography ; python_version >= \"3.7\""] -dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"] -docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] -full = ["Pillow (>=8.0.0)", "PyCryptodome ; python_version == \"3.6\"", "cryptography ; python_version >= \"3.7\""] -image = ["Pillow (>=8.0.0)"] - -[[package]] -name = "pypika" -version = "0.48.9" -description = "A SQL query builder API for Python" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378"}, -] - -[[package]] -name = "pyproject-hooks" -version = "1.1.0" -description = "Wrappers to call pyproject.toml-based build backend hooks." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"}, - {file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"}, -] - -[[package]] -name = "pyreadline3" -version = "3.4.1" -description = "A python implementation of GNU readline." -optional = false -python-versions = "*" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, - {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, -] - -[[package]] -name = "pysbd" -version = "0.3.4" -description = "pysbd (Python Sentence Boundary Disambiguation) is a rule-based sentence boundary detection that works out-of-the-box across many languages." -optional = false -python-versions = ">=3" -groups = ["main"] -files = [ - {file = "pysbd-0.3.4-py3-none-any.whl", hash = "sha256:cd838939b7b0b185fcf86b0baf6636667dfb6e474743beeff878e9f42e022953"}, -] - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.21.2" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, - {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pytest-env" -version = "0.8.2" -description = "py.test plugin that allows you to add environment variables." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest_env-0.8.2-py3-none-any.whl", hash = "sha256:5e533273f4d9e6a41c3a3120e0c7944aae5674fa773b329f00a5eb1f23c53a38"}, - {file = "pytest_env-0.8.2.tar.gz", hash = "sha256:baed9b3b6bae77bd75b9238e0ed1ee6903a42806ae9d6aeffb8754cd5584d4ff"}, -] - -[package.dependencies] -pytest = ">=7.3.1" - -[package.extras] -test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, -] - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -groups = ["main"] -markers = "platform_system == \"Windows\"" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "qdrant-client" -version = "1.10.1" -description = "Client library for the Qdrant vector search engine" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "qdrant_client-1.10.1-py3-none-any.whl", hash = "sha256:b9fb8fe50dd168d92b2998be7c6135d5a229b3a3258ad158cc69c8adf9ff1810"}, - {file = "qdrant_client-1.10.1.tar.gz", hash = "sha256:2284c8c5bb1defb0d9dbacb07d16f344972f395f4f2ed062318476a7951fd84c"}, -] - -[package.dependencies] -grpcio = ">=1.41.0" -grpcio-tools = ">=1.41.0" -httpx = {version = ">=0.20.0", extras = ["http2"]} -numpy = [ - {version = ">=1.21", markers = "python_version >= \"3.8\" and python_version < \"3.12\""}, - {version = ">=1.26", markers = "python_version >= \"3.12\""}, -] -portalocker = ">=2.7.0,<3.0.0" -pydantic = ">=1.10.8" -urllib3 = ">=1.26.14,<3" - -[package.extras] -fastembed = ["fastembed (==0.2.7) ; python_version < \"3.13\""] -fastembed-gpu = ["fastembed-gpu (==0.2.7) ; python_version < \"3.13\""] - -[[package]] -name = "ratelimiter" -version = "1.2.0.post0" -description = "Simple python rate limiting object" -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "ratelimiter-1.2.0.post0-py3-none-any.whl", hash = "sha256:a52be07bc0bb0b3674b4b304550f10c769bbb00fead3072e035904474259809f"}, - {file = "ratelimiter-1.2.0.post0.tar.gz", hash = "sha256:5c395dcabdbbde2e5178ef3f89b568a3066454a6ddc223b76473dac22f89b4f7"}, -] - -[package.extras] -test = ["pytest (>=3.0)", "pytest-asyncio ; python_version >= \"3.5\""] - -[[package]] -name = "regex" -version = "2024.5.15" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, - {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, - {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, - {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, - {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, - {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, - {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, - {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, - {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, - {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, - {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, - {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, - {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, - {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, -] - -[[package]] -name = "replicate" -version = "0.15.8" -description = "Python client for Replicate" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"llama2\"" -files = [ - {file = "replicate-0.15.8-py3-none-any.whl", hash = "sha256:a5a42e1b2f8a091325ebb7fb14f2c000e79bf818ef1d085730a8247fe28a9067"}, - {file = "replicate-0.15.8.tar.gz", hash = "sha256:a6ca2be3ed5cfe6c3e24602caa61210279b821a5f91ccf818e4c5165ab8db51a"}, -] - -[package.dependencies] -httpx = ">=0.21.0,<1" -packaging = "*" -pydantic = ">1" - -[package.extras] -dev = ["mypy", "pylint", "pytest", "pytest-asyncio", "pytest-recording", "respx", "ruff (>=0.1.3)"] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -description = "OAuthlib authentication support for Requests." -optional = false -python-versions = ">=3.4" -groups = ["main"] -files = [ - {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, - {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, -] - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -description = "A utility belt for advanced users of python-requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, - {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, -] - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "responses" -version = "0.23.3" -description = "A utility library for mocking out the `requests` Python library." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "responses-0.23.3-py3-none-any.whl", hash = "sha256:e6fbcf5d82172fecc0aa1860fd91e58cbfd96cee5e96da5b63fa6eb3caa10dd3"}, - {file = "responses-0.23.3.tar.gz", hash = "sha256:205029e1cb334c21cb4ec64fc7599be48b859a0fd381a42443cdd600bfe8b16a"}, -] - -[package.dependencies] -pyyaml = "*" -requests = ">=2.30.0,<3.0" -types-PyYAML = "*" -urllib3 = ">=1.25.10,<3.0" - -[package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-requests"] - -[[package]] -name = "retry" -version = "0.9.2" -description = "Easy to use retry decorator." -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"}, - {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"}, -] - -[package.dependencies] -decorator = ">=3.4.2" -py = ">=1.4.26,<2.0.0" - -[[package]] -name = "rich" -version = "13.7.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -groups = ["main"] -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "ruff" -version = "0.1.15" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, - {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, - {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, - {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, - {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, - {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, -] - -[[package]] -name = "s3transfer" -version = "0.10.2" -description = "An Amazon S3 Transfer Manager" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69"}, - {file = "s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6"}, -] - -[package.dependencies] -botocore = ">=1.33.2,<2.0a0" - -[package.extras] -crt = ["botocore[crt] (>=1.33.2,<2.0a0)"] - -[[package]] -name = "safetensors" -version = "0.4.3" -description = "" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "safetensors-0.4.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:dcf5705cab159ce0130cd56057f5f3425023c407e170bca60b4868048bae64fd"}, - {file = "safetensors-0.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb4f8c5d0358a31e9a08daeebb68f5e161cdd4018855426d3f0c23bb51087055"}, - {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70a5319ef409e7f88686a46607cbc3c428271069d8b770076feaf913664a07ac"}, - {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb9c65bd82f9ef3ce4970dc19ee86be5f6f93d032159acf35e663c6bea02b237"}, - {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edb5698a7bc282089f64c96c477846950358a46ede85a1c040e0230344fdde10"}, - {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efcc860be094b8d19ac61b452ec635c7acb9afa77beb218b1d7784c6d41fe8ad"}, - {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d88b33980222085dd6001ae2cad87c6068e0991d4f5ccf44975d216db3b57376"}, - {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5fc6775529fb9f0ce2266edd3e5d3f10aab068e49f765e11f6f2a63b5367021d"}, - {file = "safetensors-0.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9c6ad011c1b4e3acff058d6b090f1da8e55a332fbf84695cf3100c649cc452d1"}, - {file = "safetensors-0.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c496c5401c1b9c46d41a7688e8ff5b0310a3b9bae31ce0f0ae870e1ea2b8caf"}, - {file = "safetensors-0.4.3-cp310-none-win32.whl", hash = "sha256:38e2a8666178224a51cca61d3cb4c88704f696eac8f72a49a598a93bbd8a4af9"}, - {file = "safetensors-0.4.3-cp310-none-win_amd64.whl", hash = "sha256:393e6e391467d1b2b829c77e47d726f3b9b93630e6a045b1d1fca67dc78bf632"}, - {file = "safetensors-0.4.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:22f3b5d65e440cec0de8edaa672efa888030802e11c09b3d6203bff60ebff05a"}, - {file = "safetensors-0.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c4fa560ebd4522adddb71dcd25d09bf211b5634003f015a4b815b7647d62ebe"}, - {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9afd5358719f1b2cf425fad638fc3c887997d6782da317096877e5b15b2ce93"}, - {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8c5093206ef4b198600ae484230402af6713dab1bd5b8e231905d754022bec7"}, - {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0b2104df1579d6ba9052c0ae0e3137c9698b2d85b0645507e6fd1813b70931a"}, - {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cf18888606dad030455d18f6c381720e57fc6a4170ee1966adb7ebc98d4d6a3"}, - {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bf4f9d6323d9f86eef5567eabd88f070691cf031d4c0df27a40d3b4aaee755b"}, - {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:585c9ae13a205807b63bef8a37994f30c917ff800ab8a1ca9c9b5d73024f97ee"}, - {file = "safetensors-0.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faefeb3b81bdfb4e5a55b9bbdf3d8d8753f65506e1d67d03f5c851a6c87150e9"}, - {file = "safetensors-0.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:befdf0167ad626f22f6aac6163477fcefa342224a22f11fdd05abb3995c1783c"}, - {file = "safetensors-0.4.3-cp311-none-win32.whl", hash = "sha256:a7cef55929dcbef24af3eb40bedec35d82c3c2fa46338bb13ecf3c5720af8a61"}, - {file = "safetensors-0.4.3-cp311-none-win_amd64.whl", hash = "sha256:840b7ac0eff5633e1d053cc9db12fdf56b566e9403b4950b2dc85393d9b88d67"}, - {file = "safetensors-0.4.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:22d21760dc6ebae42e9c058d75aa9907d9f35e38f896e3c69ba0e7b213033856"}, - {file = "safetensors-0.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d22c1a10dff3f64d0d68abb8298a3fd88ccff79f408a3e15b3e7f637ef5c980"}, - {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1648568667f820b8c48317c7006221dc40aced1869908c187f493838a1362bc"}, - {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:446e9fe52c051aeab12aac63d1017e0f68a02a92a027b901c4f8e931b24e5397"}, - {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fef5d70683643618244a4f5221053567ca3e77c2531e42ad48ae05fae909f542"}, - {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a1f4430cc0c9d6afa01214a4b3919d0a029637df8e09675ceef1ca3f0dfa0df"}, - {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d603846a8585b9432a0fd415db1d4c57c0f860eb4aea21f92559ff9902bae4d"}, - {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a844cdb5d7cbc22f5f16c7e2a0271170750763c4db08381b7f696dbd2c78a361"}, - {file = "safetensors-0.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:88887f69f7a00cf02b954cdc3034ffb383b2303bc0ab481d4716e2da51ddc10e"}, - {file = "safetensors-0.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ee463219d9ec6c2be1d331ab13a8e0cd50d2f32240a81d498266d77d07b7e71e"}, - {file = "safetensors-0.4.3-cp312-none-win32.whl", hash = "sha256:d0dd4a1db09db2dba0f94d15addc7e7cd3a7b0d393aa4c7518c39ae7374623c3"}, - {file = "safetensors-0.4.3-cp312-none-win_amd64.whl", hash = "sha256:d14d30c25897b2bf19b6fb5ff7e26cc40006ad53fd4a88244fdf26517d852dd7"}, - {file = "safetensors-0.4.3-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d1456f814655b224d4bf6e7915c51ce74e389b413be791203092b7ff78c936dd"}, - {file = "safetensors-0.4.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:455d538aa1aae4a8b279344a08136d3f16334247907b18a5c3c7fa88ef0d3c46"}, - {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf476bca34e1340ee3294ef13e2c625833f83d096cfdf69a5342475602004f95"}, - {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02ef3a24face643456020536591fbd3c717c5abaa2737ec428ccbbc86dffa7a4"}, - {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7de32d0d34b6623bb56ca278f90db081f85fb9c5d327e3c18fd23ac64f465768"}, - {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a0deb16a1d3ea90c244ceb42d2c6c276059616be21a19ac7101aa97da448faf"}, - {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59d51f182c729f47e841510b70b967b0752039f79f1de23bcdd86462a9b09ee"}, - {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f598b713cc1a4eb31d3b3203557ac308acf21c8f41104cdd74bf640c6e538e3"}, - {file = "safetensors-0.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5757e4688f20df083e233b47de43845d1adb7e17b6cf7da5f8444416fc53828d"}, - {file = "safetensors-0.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fe746d03ed8d193674a26105e4f0fe6c726f5bb602ffc695b409eaf02f04763d"}, - {file = "safetensors-0.4.3-cp37-none-win32.whl", hash = "sha256:0d5ffc6a80f715c30af253e0e288ad1cd97a3d0086c9c87995e5093ebc075e50"}, - {file = "safetensors-0.4.3-cp37-none-win_amd64.whl", hash = "sha256:a11c374eb63a9c16c5ed146457241182f310902bd2a9c18255781bb832b6748b"}, - {file = "safetensors-0.4.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1e31be7945f66be23f4ec1682bb47faa3df34cb89fc68527de6554d3c4258a4"}, - {file = "safetensors-0.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:03a4447c784917c9bf01d8f2ac5080bc15c41692202cd5f406afba16629e84d6"}, - {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d244bcafeb1bc06d47cfee71727e775bca88a8efda77a13e7306aae3813fa7e4"}, - {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53c4879b9c6bd7cd25d114ee0ef95420e2812e676314300624594940a8d6a91f"}, - {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74707624b81f1b7f2b93f5619d4a9f00934d5948005a03f2c1845ffbfff42212"}, - {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d52c958dc210265157573f81d34adf54e255bc2b59ded6218500c9b15a750eb"}, - {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f9568f380f513a60139971169c4a358b8731509cc19112369902eddb33faa4d"}, - {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0d9cd8e1560dfc514b6d7859247dc6a86ad2f83151a62c577428d5102d872721"}, - {file = "safetensors-0.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:89f9f17b0dacb913ed87d57afbc8aad85ea42c1085bd5de2f20d83d13e9fc4b2"}, - {file = "safetensors-0.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1139eb436fd201c133d03c81209d39ac57e129f5e74e34bb9ab60f8d9b726270"}, - {file = "safetensors-0.4.3-cp38-none-win32.whl", hash = "sha256:d9c289f140a9ae4853fc2236a2ffc9a9f2d5eae0cb673167e0f1b8c18c0961ac"}, - {file = "safetensors-0.4.3-cp38-none-win_amd64.whl", hash = "sha256:622afd28968ef3e9786562d352659a37de4481a4070f4ebac883f98c5836563e"}, - {file = "safetensors-0.4.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8651c7299cbd8b4161a36cd6a322fa07d39cd23535b144d02f1c1972d0c62f3c"}, - {file = "safetensors-0.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e375d975159ac534c7161269de24ddcd490df2157b55c1a6eeace6cbb56903f0"}, - {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084fc436e317f83f7071fc6a62ca1c513b2103db325cd09952914b50f51cf78f"}, - {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41a727a7f5e6ad9f1db6951adee21bbdadc632363d79dc434876369a17de6ad6"}, - {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7dbbde64b6c534548696808a0e01276d28ea5773bc9a2dfb97a88cd3dffe3df"}, - {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbae3b4b9d997971431c346edbfe6e41e98424a097860ee872721e176040a893"}, - {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e4b22e3284cd866edeabe4f4d896229495da457229408d2e1e4810c5187121"}, - {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dd37306546b58d3043eb044c8103a02792cc024b51d1dd16bd3dd1f334cb3ed"}, - {file = "safetensors-0.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8815b5e1dac85fc534a97fd339e12404db557878c090f90442247e87c8aeaea"}, - {file = "safetensors-0.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e011cc162503c19f4b1fd63dfcddf73739c7a243a17dac09b78e57a00983ab35"}, - {file = "safetensors-0.4.3-cp39-none-win32.whl", hash = "sha256:01feb3089e5932d7e662eda77c3ecc389f97c0883c4a12b5cfdc32b589a811c3"}, - {file = "safetensors-0.4.3-cp39-none-win_amd64.whl", hash = "sha256:3f9cdca09052f585e62328c1c2923c70f46814715c795be65f0b93f57ec98a02"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1b89381517891a7bb7d1405d828b2bf5d75528299f8231e9346b8eba092227f9"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cd6fff9e56df398abc5866b19a32124815b656613c1c5ec0f9350906fd798aac"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:840caf38d86aa7014fe37ade5d0d84e23dcfbc798b8078015831996ecbc206a3"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9650713b2cfa9537a2baf7dd9fee458b24a0aaaa6cafcea8bdd5fb2b8efdc34"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4119532cd10dba04b423e0f86aecb96cfa5a602238c0aa012f70c3a40c44b50"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e066e8861eef6387b7c772344d1fe1f9a72800e04ee9a54239d460c400c72aab"}, - {file = "safetensors-0.4.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:90964917f5b0fa0fa07e9a051fbef100250c04d150b7026ccbf87a34a54012e0"}, - {file = "safetensors-0.4.3-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c41e1893d1206aa7054029681778d9a58b3529d4c807002c156d58426c225173"}, - {file = "safetensors-0.4.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae7613a119a71a497d012ccc83775c308b9c1dab454806291427f84397d852fd"}, - {file = "safetensors-0.4.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9bac020faba7f5dc481e881b14b6425265feabb5bfc552551d21189c0eddc3"}, - {file = "safetensors-0.4.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:420a98f593ff9930f5822560d14c395ccbc57342ddff3b463bc0b3d6b1951550"}, - {file = "safetensors-0.4.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f5e6883af9a68c0028f70a4c19d5a6ab6238a379be36ad300a22318316c00cb0"}, - {file = "safetensors-0.4.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:cdd0a3b5da66e7f377474599814dbf5cbf135ff059cc73694de129b58a5e8a2c"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9bfb92f82574d9e58401d79c70c716985dc049b635fef6eecbb024c79b2c46ad"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3615a96dd2dcc30eb66d82bc76cda2565f4f7bfa89fcb0e31ba3cea8a1a9ecbb"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:868ad1b6fc41209ab6bd12f63923e8baeb1a086814cb2e81a65ed3d497e0cf8f"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffba80aa49bd09195145a7fd233a7781173b422eeb995096f2b30591639517"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0acbe31340ab150423347e5b9cc595867d814244ac14218932a5cf1dd38eb39"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19bbdf95de2cf64f25cd614c5236c8b06eb2cfa47cbf64311f4b5d80224623a3"}, - {file = "safetensors-0.4.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b852e47eb08475c2c1bd8131207b405793bfc20d6f45aff893d3baaad449ed14"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d07cbca5b99babb692d76d8151bec46f461f8ad8daafbfd96b2fca40cadae65"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ab6527a20586d94291c96e00a668fa03f86189b8a9defa2cdd34a1a01acc7d5"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02318f01e332cc23ffb4f6716e05a492c5f18b1d13e343c49265149396284a44"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec4b52ce9a396260eb9731eb6aea41a7320de22ed73a1042c2230af0212758ce"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:018b691383026a2436a22b648873ed11444a364324e7088b99cd2503dd828400"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:309b10dbcab63269ecbf0e2ca10ce59223bb756ca5d431ce9c9eeabd446569da"}, - {file = "safetensors-0.4.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b277482120df46e27a58082df06a15aebda4481e30a1c21eefd0921ae7e03f65"}, - {file = "safetensors-0.4.3.tar.gz", hash = "sha256:2f85fc50c4e07a21e95c24e07460fe6f7e2859d0ce88092838352b798ce711c2"}, -] - -[package.extras] -all = ["safetensors[jax]", "safetensors[numpy]", "safetensors[paddlepaddle]", "safetensors[pinned-tf]", "safetensors[quality]", "safetensors[testing]", "safetensors[torch]"] -dev = ["safetensors[all]"] -jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[numpy]"] -mlx = ["mlx (>=0.0.9)"] -numpy = ["numpy (>=1.21.6)"] -paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] -pinned-tf = ["safetensors[numpy]", "tensorflow (==2.11.0)"] -quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] -tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] -testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] -torch = ["safetensors[numpy]", "torch (>=1.10)"] - -[[package]] -name = "schema" -version = "0.7.7" -description = "Simple data validation library" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "schema-0.7.7-py2.py3-none-any.whl", hash = "sha256:5d976a5b50f36e74e2157b47097b60002bd4d42e65425fcc9c9befadb4255dde"}, - {file = "schema-0.7.7.tar.gz", hash = "sha256:7da553abd2958a19dc2547c388cde53398b39196175a9be59ea1caf5ab0a1807"}, -] - -[[package]] -name = "scikit-learn" -version = "1.5.1" -description = "A set of python modules for machine learning and data mining" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"}, - {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"}, - {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"}, - {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"}, - {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"}, - {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"}, - {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"}, - {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"}, - {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"}, - {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"}, - {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"}, - {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"}, - {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"}, - {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"}, - {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"}, - {file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"}, - {file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"}, - {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"}, - {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"}, - {file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"}, - {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"}, -] - -[package.dependencies] -joblib = ">=1.2.0" -numpy = ">=1.19.5" -scipy = ">=1.6.0" -threadpoolctl = ">=3.1.0" - -[package.extras] -benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] -examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] -install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] -maintenance = ["conda-lock (==2.5.6)"] -tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] - -[[package]] -name = "scipy" -version = "1.13.1" -description = "Fundamental algorithms for scientific computing in Python" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, - {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, - {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, - {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, - {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, - {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, - {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, - {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, - {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, - {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, - {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, -] - -[package.dependencies] -numpy = ">=1.22.4,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "semver" -version = "3.0.2" -description = "Python helper for Semantic Versioning (https://semver.org)" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"lancedb\"" -files = [ - {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, - {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, -] - -[[package]] -name = "sentence-transformers" -version = "2.7.0" -description = "Multilingual text embeddings" -optional = true -python-versions = ">=3.8.0" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "sentence_transformers-2.7.0-py3-none-any.whl", hash = "sha256:6a7276b05a95931581bbfa4ba49d780b2cf6904fa4a171ec7fd66c343f761c98"}, - {file = "sentence_transformers-2.7.0.tar.gz", hash = "sha256:2f7df99d1c021dded471ed2d079e9d1e4fc8e30ecb06f957be060511b36f24ea"}, -] - -[package.dependencies] -huggingface-hub = ">=0.15.1" -numpy = "*" -Pillow = "*" -scikit-learn = "*" -scipy = "*" -torch = ">=1.11.0" -tqdm = "*" -transformers = ">=4.34.0,<5.0.0" - -[package.extras] -dev = ["pre-commit", "pytest", "ruff (>=0.3.0)"] - -[[package]] -name = "setuptools" -version = "70.3.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"}, - {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-ruff (>=0.3.2) ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "shapely" -version = "2.0.4" -description = "Manipulation and analysis of geometric objects" -optional = true -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f"}, - {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17"}, - {file = "shapely-2.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd"}, - {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6"}, - {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58"}, - {file = "shapely-2.0.4-cp310-cp310-win32.whl", hash = "sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7"}, - {file = "shapely-2.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07"}, - {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47"}, - {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d"}, - {file = "shapely-2.0.4-cp311-cp311-win32.whl", hash = "sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879"}, - {file = "shapely-2.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6"}, - {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac"}, - {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30"}, - {file = "shapely-2.0.4-cp312-cp312-win32.whl", hash = "sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f"}, - {file = "shapely-2.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0"}, - {file = "shapely-2.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b"}, - {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf"}, - {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113"}, - {file = "shapely-2.0.4-cp37-cp37m-win32.whl", hash = "sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53"}, - {file = "shapely-2.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e"}, - {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679"}, - {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"}, - {file = "shapely-2.0.4-cp38-cp38-win32.whl", hash = "sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7"}, - {file = "shapely-2.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc"}, - {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242"}, - {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc"}, - {file = "shapely-2.0.4-cp39-cp39-win32.whl", hash = "sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c"}, - {file = "shapely-2.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398"}, - {file = "shapely-2.0.4.tar.gz", hash = "sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8"}, -] - -[package.dependencies] -numpy = ">=1.14,<3" - -[package.extras] -docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "soupsieve" -version = "2.5" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.31" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, - {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, - {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "starlette" -version = "0.37.2" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "sympy" -version = "1.14.0" -description = "Computer algebra system (CAS) in Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, - {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, -] - -[package.dependencies] -mpmath = ">=1.1.0,<1.4" - -[package.extras] -dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "tenacity" -version = "8.5.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, - {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "threadpoolctl" -version = "3.5.0" -description = "threadpoolctl" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, - {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, -] - -[[package]] -name = "tiktoken" -version = "0.7.0" -description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, - {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, - {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79383a6e2c654c6040e5f8506f3750db9ddd71b550c724e673203b4f6b4b4590"}, - {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4511c52caacf3c4981d1ae2df85908bd31853f33d30b345c8b6830763f769c"}, - {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13c94efacdd3de9aff824a788353aa5749c0faee1fbe3816df365ea450b82311"}, - {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e58c7eb29d2ab35a7a8929cbeea60216a4ccdf42efa8974d8e176d50c9a3df5"}, - {file = "tiktoken-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:21a20c3bd1dd3e55b91c1331bf25f4af522c525e771691adbc9a69336fa7f702"}, - {file = "tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f"}, - {file = "tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f"}, - {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b"}, - {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992"}, - {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1"}, - {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89"}, - {file = "tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb"}, - {file = "tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908"}, - {file = "tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410"}, - {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704"}, - {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350"}, - {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4"}, - {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97"}, - {file = "tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f"}, - {file = "tiktoken-0.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2398fecd38c921bcd68418675a6d155fad5f5e14c2e92fcf5fe566fa5485a858"}, - {file = "tiktoken-0.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f5f6afb52fb8a7ea1c811e435e4188f2bef81b5e0f7a8635cc79b0eef0193d6"}, - {file = "tiktoken-0.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:861f9ee616766d736be4147abac500732b505bf7013cfaf019b85892637f235e"}, - {file = "tiktoken-0.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54031f95c6939f6b78122c0aa03a93273a96365103793a22e1793ee86da31685"}, - {file = "tiktoken-0.7.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fffdcb319b614cf14f04d02a52e26b1d1ae14a570f90e9b55461a72672f7b13d"}, - {file = "tiktoken-0.7.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c72baaeaefa03ff9ba9688624143c858d1f6b755bb85d456d59e529e17234769"}, - {file = "tiktoken-0.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:131b8aeb043a8f112aad9f46011dced25d62629091e51d9dc1adbf4a1cc6aa98"}, - {file = "tiktoken-0.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cabc6dc77460df44ec5b879e68692c63551ae4fae7460dd4ff17181df75f1db7"}, - {file = "tiktoken-0.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8d57f29171255f74c0aeacd0651e29aa47dff6f070cb9f35ebc14c82278f3b25"}, - {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ee92776fdbb3efa02a83f968c19d4997a55c8e9ce7be821ceee04a1d1ee149c"}, - {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e215292e99cb41fbc96988ef62ea63bb0ce1e15f2c147a61acc319f8b4cbe5bf"}, - {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a81bac94769cab437dd3ab0b8a4bc4e0f9cf6835bcaa88de71f39af1791727a"}, - {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d6d73ea93e91d5ca771256dfc9d1d29f5a554b83821a1dc0891987636e0ae226"}, - {file = "tiktoken-0.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:2bcb28ddf79ffa424f171dfeef9a4daff61a94c631ca6813f43967cb263b83b9"}, - {file = "tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6"}, -] - -[package.dependencies] -regex = ">=2022.1.18" -requests = ">=2.26.0" - -[package.extras] -blobfile = ["blobfile (>=2)"] - -[[package]] -name = "together" -version = "1.2.1" -description = "Python client for Together's Cloud Platform!" -optional = true -python-versions = "<4.0,>=3.8" -groups = ["main"] -markers = "extra == \"together\"" -files = [ - {file = "together-1.2.1-py3-none-any.whl", hash = "sha256:a94408074e0e50b3dab1d4001cb36a3fdbd0e4d6a0e659ecaae6b7b6355f5369"}, - {file = "together-1.2.1.tar.gz", hash = "sha256:c67f724f4612fc76283c92beaf0cc0cc076543021d19dae04fb4e950d9bf0e68"}, -] - -[package.dependencies] -aiohttp = ">=3.9.3,<4.0.0" -click = ">=8.1.7,<9.0.0" -eval-type-backport = ">=0.1.3,<0.3.0" -filelock = ">=3.13.1,<4.0.0" -numpy = [ - {version = ">=1.23.5", markers = "python_version < \"3.12\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -pillow = ">=10.3.0,<11.0.0" -pyarrow = ">=10.0.1" -pydantic = ">=2.6.3,<3.0.0" -requests = ">=2.31.0,<3.0.0" -tabulate = ">=0.9.0,<0.10.0" -tqdm = ">=4.66.2,<5.0.0" -typer = ">=0.9,<0.13" - -[[package]] -name = "tokenizers" -version = "0.19.1" -description = "" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tokenizers-0.19.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:952078130b3d101e05ecfc7fc3640282d74ed26bcf691400f872563fca15ac97"}, - {file = "tokenizers-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82c8b8063de6c0468f08e82c4e198763e7b97aabfe573fd4cf7b33930ca4df77"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f03727225feaf340ceeb7e00604825addef622d551cbd46b7b775ac834c1e1c4"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:453e4422efdfc9c6b6bf2eae00d5e323f263fff62b29a8c9cd526c5003f3f642"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02e81bf089ebf0e7f4df34fa0207519f07e66d8491d963618252f2e0729e0b46"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b07c538ba956843833fee1190cf769c60dc62e1cf934ed50d77d5502194d63b1"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28cab1582e0eec38b1f38c1c1fb2e56bce5dc180acb1724574fc5f47da2a4fe"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b01afb7193d47439f091cd8f070a1ced347ad0f9144952a30a41836902fe09e"}, - {file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7fb297edec6c6841ab2e4e8f357209519188e4a59b557ea4fafcf4691d1b4c98"}, - {file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e8a3dd055e515df7054378dc9d6fa8c8c34e1f32777fb9a01fea81496b3f9d3"}, - {file = "tokenizers-0.19.1-cp310-none-win32.whl", hash = "sha256:7ff898780a155ea053f5d934925f3902be2ed1f4d916461e1a93019cc7250837"}, - {file = "tokenizers-0.19.1-cp310-none-win_amd64.whl", hash = "sha256:bea6f9947e9419c2fda21ae6c32871e3d398cba549b93f4a65a2d369662d9403"}, - {file = "tokenizers-0.19.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5c88d1481f1882c2e53e6bb06491e474e420d9ac7bdff172610c4f9ad3898059"}, - {file = "tokenizers-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddf672ed719b4ed82b51499100f5417d7d9f6fb05a65e232249268f35de5ed14"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dadc509cc8a9fe460bd274c0e16ac4184d0958117cf026e0ea8b32b438171594"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfedf31824ca4915b511b03441784ff640378191918264268e6923da48104acc"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac11016d0a04aa6487b1513a3a36e7bee7eec0e5d30057c9c0408067345c48d2"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76951121890fea8330d3a0df9a954b3f2a37e3ec20e5b0530e9a0044ca2e11fe"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b342d2ce8fc8d00f376af068e3274e2e8649562e3bc6ae4a67784ded6b99428d"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d16ff18907f4909dca9b076b9c2d899114dd6abceeb074eca0c93e2353f943aa"}, - {file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:706a37cc5332f85f26efbe2bdc9ef8a9b372b77e4645331a405073e4b3a8c1c6"}, - {file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16baac68651701364b0289979ecec728546133e8e8fe38f66fe48ad07996b88b"}, - {file = "tokenizers-0.19.1-cp311-none-win32.whl", hash = "sha256:9ed240c56b4403e22b9584ee37d87b8bfa14865134e3e1c3fb4b2c42fafd3256"}, - {file = "tokenizers-0.19.1-cp311-none-win_amd64.whl", hash = "sha256:ad57d59341710b94a7d9dbea13f5c1e7d76fd8d9bcd944a7a6ab0b0da6e0cc66"}, - {file = "tokenizers-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:621d670e1b1c281a1c9698ed89451395d318802ff88d1fc1accff0867a06f153"}, - {file = "tokenizers-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d924204a3dbe50b75630bd16f821ebda6a5f729928df30f582fb5aade90c818a"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f3fefdc0446b1a1e6d81cd4c07088ac015665d2e812f6dbba4a06267d1a2c95"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9620b78e0b2d52ef07b0d428323fb34e8ea1219c5eac98c2596311f20f1f9266"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04ce49e82d100594715ac1b2ce87d1a36e61891a91de774755f743babcd0dd52"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5c2ff13d157afe413bf7e25789879dd463e5a4abfb529a2d8f8473d8042e28f"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3174c76efd9d08f836bfccaca7cfec3f4d1c0a4cf3acbc7236ad577cc423c840"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9d5b6c0e7a1e979bec10ff960fae925e947aab95619a6fdb4c1d8ff3708ce3"}, - {file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a179856d1caee06577220ebcfa332af046d576fb73454b8f4d4b0ba8324423ea"}, - {file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:952b80dac1a6492170f8c2429bd11fcaa14377e097d12a1dbe0ef2fb2241e16c"}, - {file = "tokenizers-0.19.1-cp312-none-win32.whl", hash = "sha256:01d62812454c188306755c94755465505836fd616f75067abcae529c35edeb57"}, - {file = "tokenizers-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:b70bfbe3a82d3e3fb2a5e9b22a39f8d1740c96c68b6ace0086b39074f08ab89a"}, - {file = "tokenizers-0.19.1-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:bb9dfe7dae85bc6119d705a76dc068c062b8b575abe3595e3c6276480e67e3f1"}, - {file = "tokenizers-0.19.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:1f0360cbea28ea99944ac089c00de7b2e3e1c58f479fb8613b6d8d511ce98267"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:71e3ec71f0e78780851fef28c2a9babe20270404c921b756d7c532d280349214"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b82931fa619dbad979c0ee8e54dd5278acc418209cc897e42fac041f5366d626"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8ff5b90eabdcdaa19af697885f70fe0b714ce16709cf43d4952f1f85299e73a"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e742d76ad84acbdb1a8e4694f915fe59ff6edc381c97d6dfdd054954e3478ad4"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8c5d59d7b59885eab559d5bc082b2985555a54cda04dda4c65528d90ad252ad"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b2da5c32ed869bebd990c9420df49813709e953674c0722ff471a116d97b22d"}, - {file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:638e43936cc8b2cbb9f9d8dde0fe5e7e30766a3318d2342999ae27f68fdc9bd6"}, - {file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:78e769eb3b2c79687d9cb0f89ef77223e8e279b75c0a968e637ca7043a84463f"}, - {file = "tokenizers-0.19.1-cp37-none-win32.whl", hash = "sha256:72791f9bb1ca78e3ae525d4782e85272c63faaef9940d92142aa3eb79f3407a3"}, - {file = "tokenizers-0.19.1-cp37-none-win_amd64.whl", hash = "sha256:f3bbb7a0c5fcb692950b041ae11067ac54826204318922da754f908d95619fbc"}, - {file = "tokenizers-0.19.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:07f9295349bbbcedae8cefdbcfa7f686aa420be8aca5d4f7d1ae6016c128c0c5"}, - {file = "tokenizers-0.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10a707cc6c4b6b183ec5dbfc5c34f3064e18cf62b4a938cb41699e33a99e03c1"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6309271f57b397aa0aff0cbbe632ca9d70430839ca3178bf0f06f825924eca22"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ad23d37d68cf00d54af184586d79b84075ada495e7c5c0f601f051b162112dc"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:427c4f0f3df9109314d4f75b8d1f65d9477033e67ffaec4bca53293d3aca286d"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e83a31c9cf181a0a3ef0abad2b5f6b43399faf5da7e696196ddd110d332519ee"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c27b99889bd58b7e301468c0838c5ed75e60c66df0d4db80c08f43462f82e0d3"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bac0b0eb952412b0b196ca7a40e7dce4ed6f6926489313414010f2e6b9ec2adf"}, - {file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8a6298bde623725ca31c9035a04bf2ef63208d266acd2bed8c2cb7d2b7d53ce6"}, - {file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:08a44864e42fa6d7d76d7be4bec62c9982f6f6248b4aa42f7302aa01e0abfd26"}, - {file = "tokenizers-0.19.1-cp38-none-win32.whl", hash = "sha256:1de5bc8652252d9357a666e609cb1453d4f8e160eb1fb2830ee369dd658e8975"}, - {file = "tokenizers-0.19.1-cp38-none-win_amd64.whl", hash = "sha256:0bcce02bf1ad9882345b34d5bd25ed4949a480cf0e656bbd468f4d8986f7a3f1"}, - {file = "tokenizers-0.19.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0b9394bd204842a2a1fd37fe29935353742be4a3460b6ccbaefa93f58a8df43d"}, - {file = "tokenizers-0.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4692ab92f91b87769d950ca14dbb61f8a9ef36a62f94bad6c82cc84a51f76f6a"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6258c2ef6f06259f70a682491c78561d492e885adeaf9f64f5389f78aa49a051"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85cf76561fbd01e0d9ea2d1cbe711a65400092bc52b5242b16cfd22e51f0c58"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:670b802d4d82bbbb832ddb0d41df7015b3e549714c0e77f9bed3e74d42400fbe"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85aa3ab4b03d5e99fdd31660872249df5e855334b6c333e0bc13032ff4469c4a"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbf001afbbed111a79ca47d75941e9e5361297a87d186cbfc11ed45e30b5daba"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c89aa46c269e4e70c4d4f9d6bc644fcc39bb409cb2a81227923404dd6f5227"}, - {file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:39c1ec76ea1027438fafe16ecb0fb84795e62e9d643444c1090179e63808c69d"}, - {file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c2a0d47a89b48d7daa241e004e71fb5a50533718897a4cd6235cb846d511a478"}, - {file = "tokenizers-0.19.1-cp39-none-win32.whl", hash = "sha256:61b7fe8886f2e104d4caf9218b157b106207e0f2a4905c9c7ac98890688aabeb"}, - {file = "tokenizers-0.19.1-cp39-none-win_amd64.whl", hash = "sha256:f97660f6c43efd3e0bfd3f2e3e5615bf215680bad6ee3d469df6454b8c6e8256"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b11853f17b54c2fe47742c56d8a33bf49ce31caf531e87ac0d7d13d327c9334"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d26194ef6c13302f446d39972aaa36a1dda6450bc8949f5eb4c27f51191375bd"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e8d1ed93beda54bbd6131a2cb363a576eac746d5c26ba5b7556bc6f964425594"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca407133536f19bdec44b3da117ef0d12e43f6d4b56ac4c765f37eca501c7bda"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce05fde79d2bc2e46ac08aacbc142bead21614d937aac950be88dc79f9db9022"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:35583cd46d16f07c054efd18b5d46af4a2f070a2dd0a47914e66f3ff5efb2b1e"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:43350270bfc16b06ad3f6f07eab21f089adb835544417afda0f83256a8bf8b75"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b4399b59d1af5645bcee2072a463318114c39b8547437a7c2d6a186a1b5a0e2d"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6852c5b2a853b8b0ddc5993cd4f33bfffdca4fcc5d52f89dd4b8eada99379285"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd266ae85c3d39df2f7e7d0e07f6c41a55e9a3123bb11f854412952deacd828"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecb2651956eea2aa0a2d099434134b1b68f1c31f9a5084d6d53f08ed43d45ff2"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b279ab506ec4445166ac476fb4d3cc383accde1ea152998509a94d82547c8e2a"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:89183e55fb86e61d848ff83753f64cded119f5d6e1f553d14ffee3700d0a4a49"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2edbc75744235eea94d595a8b70fe279dd42f3296f76d5a86dde1d46e35f574"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0e64bfde9a723274e9a71630c3e9494ed7b4c0f76a1faacf7fe294cd26f7ae7c"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0b5ca92bfa717759c052e345770792d02d1f43b06f9e790ca0a1db62838816f3"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f8a20266e695ec9d7a946a019c1d5ca4eddb6613d4f466888eee04f16eedb85"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63c38f45d8f2a2ec0f3a20073cccb335b9f99f73b3c69483cd52ebc75369d8a1"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dd26e3afe8a7b61422df3176e06664503d3f5973b94f45d5c45987e1cb711876"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:eddd5783a4a6309ce23432353cdb36220e25cbb779bfa9122320666508b44b88"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:56ae39d4036b753994476a1b935584071093b55c7a72e3b8288e68c313ca26e7"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f9939ca7e58c2758c01b40324a59c034ce0cebad18e0d4563a9b1beab3018243"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c330c0eb815d212893c67a032e9dc1b38a803eccb32f3e8172c19cc69fbb439"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec11802450a2487cdf0e634b750a04cbdc1c4d066b97d94ce7dd2cb51ebb325b"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b718f316b596f36e1dae097a7d5b91fc5b85e90bf08b01ff139bd8953b25af"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ed69af290c2b65169f0ba9034d1dc39a5db9459b32f1dd8b5f3f32a3fcf06eab"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f8a9c828277133af13f3859d1b6bf1c3cb6e9e1637df0e45312e6b7c2e622b1f"}, - {file = "tokenizers-0.19.1.tar.gz", hash = "sha256:ee59e6680ed0fdbe6b724cf38bd70400a0c1dd623b07ac729087270caeac88e3"}, -] - -[package.dependencies] -huggingface-hub = ">=0.16.4,<1.0" - -[package.extras] -dev = ["tokenizers[testing]"] -docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] -testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "torch" -version = "2.8.0" -description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" -optional = true -python-versions = ">=3.9.0" -groups = ["main"] -markers = "python_version == \"3.9\" and extra == \"opensource\"" -files = [ - {file = "torch-2.8.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0be92c08b44009d4131d1ff7a8060d10bafdb7ddcb7359ef8d8c5169007ea905"}, - {file = "torch-2.8.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:89aa9ee820bb39d4d72b794345cccef106b574508dd17dbec457949678c76011"}, - {file = "torch-2.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8e5bf982e87e2b59d932769938b698858c64cc53753894be25629bdf5cf2f46"}, - {file = "torch-2.8.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:a3f16a58a9a800f589b26d47ee15aca3acf065546137fc2af039876135f4c760"}, - {file = "torch-2.8.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:220a06fd7af8b653c35d359dfe1aaf32f65aa85befa342629f716acb134b9710"}, - {file = "torch-2.8.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c12fa219f51a933d5f80eeb3a7a5d0cbe9168c0a14bbb4055f1979431660879b"}, - {file = "torch-2.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c7ef765e27551b2fbfc0f41bcf270e1292d9bf79f8e0724848b1682be6e80aa"}, - {file = "torch-2.8.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:5ae0524688fb6707c57a530c2325e13bb0090b745ba7b4a2cd6a3ce262572916"}, - {file = "torch-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2fab4153768d433f8ed9279c8133a114a034a61e77a3a104dcdf54388838705"}, - {file = "torch-2.8.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2aca0939fb7e4d842561febbd4ffda67a8e958ff725c1c27e244e85e982173c"}, - {file = "torch-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f4ac52f0130275d7517b03a33d2493bab3693c83dcfadf4f81688ea82147d2e"}, - {file = "torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0"}, - {file = "torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f96814e0345f5a5aed9bf9734efa913678ed19caf6dc2cddb7930672d6128"}, - {file = "torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:65616ca8ec6f43245e1f5f296603e33923f4c30f93d65e103d9e50c25b35150b"}, - {file = "torch-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:659df54119ae03e83a800addc125856effda88b016dfc54d9f65215c3975be16"}, - {file = "torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767"}, - {file = "torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:83c13411a26fac3d101fe8035a6b0476ae606deb8688e904e796a3534c197def"}, - {file = "torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8f0a9d617a66509ded240add3754e462430a6c1fc5589f86c17b433dd808f97a"}, - {file = "torch-2.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a7242b86f42be98ac674b88a4988643b9bc6145437ec8f048fea23f72feb5eca"}, - {file = "torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211"}, - {file = "torch-2.8.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:da6afa31c13b669d4ba49d8a2169f0db2c3ec6bec4af898aa714f401d4c38904"}, - {file = "torch-2.8.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:06fcee8000e5c62a9f3e52a688b9c5abb7c6228d0e56e3452983416025c41381"}, - {file = "torch-2.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:5128fe752a355d9308e56af1ad28b15266fe2da5948660fad44de9e3a9e36e8c"}, - {file = "torch-2.8.0-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:e9f071f5b52a9f6970dc8a919694b27a91ae9dc08898b2b988abbef5eddfd1ae"}, -] - -[package.dependencies] -filelock = "*" -fsspec = "*" -jinja2 = "*" -networkx = "*" -nvidia-cublas-cu12 = {version = "12.8.4.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-cupti-cu12 = {version = "12.8.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-nvrtc-cu12 = {version = "12.8.93", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-runtime-cu12 = {version = "12.8.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cudnn-cu12 = {version = "9.10.2.21", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cufft-cu12 = {version = "11.3.3.83", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cufile-cu12 = {version = "1.13.1.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-curand-cu12 = {version = "10.3.9.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusolver-cu12 = {version = "11.7.3.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusparse-cu12 = {version = "12.5.8.93", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusparselt-cu12 = {version = "0.7.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nccl-cu12 = {version = "2.27.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nvjitlink-cu12 = {version = "12.8.93", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nvtx-cu12 = {version = "12.8.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -sympy = ">=1.13.3" -triton = {version = "3.4.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -typing-extensions = ">=4.10.0" - -[package.extras] -opt-einsum = ["opt-einsum (>=3.3)"] -optree = ["optree (>=0.13.0)"] -pyyaml = ["pyyaml"] - -[[package]] -name = "torch" -version = "2.11.0" -description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"opensource\"" -files = [ - {file = "torch-2.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c0d7fcfbc0c4e8bb5ebc3907cbc0c6a0da1b8f82b1fc6e14e914fa0b9baf74e"}, - {file = "torch-2.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4cf8687f4aec3900f748d553483ef40e0ac38411c3c48d0a86a438f6d7a99b18"}, - {file = "torch-2.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1b32ceda909818a03b112006709b02be1877240c31750a8d9c6b7bf5f2d8a6e5"}, - {file = "torch-2.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3c712ae6fb8e7a949051a953fc412fe0a6940337336c3b6f905e905dac5157f"}, - {file = "torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4"}, - {file = "torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6"}, - {file = "torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a"}, - {file = "torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708"}, - {file = "torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34"}, - {file = "torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f"}, - {file = "torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756"}, - {file = "torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10"}, - {file = "torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18"}, - {file = "torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd"}, - {file = "torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db"}, - {file = "torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd"}, - {file = "torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4"}, - {file = "torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea"}, - {file = "torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778"}, - {file = "torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db"}, - {file = "torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7"}, - {file = "torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7"}, - {file = "torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60"}, - {file = "torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718"}, - {file = "torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd"}, - {file = "torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6"}, - {file = "torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2"}, - {file = "torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0"}, -] - -[package.dependencies] -cuda-bindings = {version = ">=13.0.3,<14", markers = "platform_system == \"Linux\""} -cuda-toolkit = {version = "13.0.2", extras = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], markers = "platform_system == \"Linux\""} -filelock = "*" -fsspec = ">=0.8.5" -jinja2 = "*" -networkx = ">=2.5.1" -nvidia-cudnn-cu13 = {version = "9.19.0.56", markers = "platform_system == \"Linux\""} -nvidia-cusparselt-cu13 = {version = "0.8.0", markers = "platform_system == \"Linux\""} -nvidia-nccl-cu13 = {version = "2.28.9", markers = "platform_system == \"Linux\""} -nvidia-nvshmem-cu13 = {version = "3.4.5", markers = "platform_system == \"Linux\""} -setuptools = "<82" -sympy = ">=1.13.3" -triton = {version = "3.6.0", markers = "platform_system == \"Linux\""} -typing-extensions = ">=4.10.0" - -[package.extras] -opt-einsum = ["opt-einsum (>=3.3)"] -optree = ["optree (>=0.13.0)"] -pyyaml = ["pyyaml"] - -[[package]] -name = "tqdm" -version = "4.66.4" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, - {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "transformers" -version = "4.42.4" -description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" -optional = true -python-versions = ">=3.8.0" -groups = ["main"] -markers = "extra == \"opensource\"" -files = [ - {file = "transformers-4.42.4-py3-none-any.whl", hash = "sha256:6d59061392d0f1da312af29c962df9017ff3c0108c681a56d1bc981004d16d24"}, - {file = "transformers-4.42.4.tar.gz", hash = "sha256:f956e25e24df851f650cb2c158b6f4352dfae9d702f04c113ed24fc36ce7ae2d"}, -] - -[package.dependencies] -filelock = "*" -huggingface-hub = ">=0.23.2,<1.0" -numpy = ">=1.17,<2.0" -packaging = ">=20.0" -pyyaml = ">=5.1" -regex = "!=2019.12.17" -requests = "*" -safetensors = ">=0.4.1" -tokenizers = ">=0.19,<0.20" -tqdm = ">=4.27" - -[package.extras] -accelerate = ["accelerate (>=0.21.0)"] -agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch"] -all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision"] -audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -benchmark = ["optimum-benchmark (>=0.2.0)"] -codecarbon = ["codecarbon (==1.2.0)"] -deepspeed = ["accelerate (>=0.21.0)", "deepspeed (>=0.9.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.21.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.19,<0.20)", "urllib3 (<2.0.0)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] -flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -ftfy = ["ftfy"] -integrations = ["optuna", "ray[tune] (>=2.7.0)", "sigopt"] -ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] -modelcreation = ["cookiecutter (==1.7.3)"] -natten = ["natten (>=0.14.6,<0.15.0)"] -onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] -onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] -optuna = ["optuna"] -quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "isort (>=5.5.4)", "ruff (==0.4.4)", "urllib3 (<2.0.0)"] -ray = ["ray[tune] (>=2.7.0)"] -retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] -ruff = ["ruff (==0.4.4)"] -sagemaker = ["sagemaker (>=2.31.0)"] -sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] -serving = ["fastapi", "pydantic", "starlette", "uvicorn"] -sigopt = ["sigopt"] -sklearn = ["scikit-learn"] -speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -timm = ["timm (<=0.9.16)"] -tokenizers = ["tokenizers (>=0.19,<0.20)"] -torch = ["accelerate (>=0.21.0)", "torch"] -torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.23.2,<1.0)", "importlib-metadata", "numpy (>=1.17,<2.0)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.19,<0.20)", "torch", "tqdm (>=4.27)"] -video = ["av (==9.2.0)", "decord (==0.6.0)"] -vision = ["Pillow (>=10.0.1,<=15.0)"] - -[[package]] -name = "triton" -version = "3.4.0" -description = "A language and compiler for custom Deep Learning operations" -optional = true -python-versions = "<3.14,>=3.9" -groups = ["main"] -markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and extra == \"opensource\" and python_version == \"3.9\"" -files = [ - {file = "triton-3.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ff2785de9bc02f500e085420273bb5cc9c9bb767584a4aa28d6e360cec70128"}, - {file = "triton-3.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b70f5e6a41e52e48cfc087436c8a28c17ff98db369447bcaff3b887a3ab4467"}, - {file = "triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c1d84a5c0ec2c0f8e8a072d7fd150cab84a9c239eaddc6706c081bfae4eb04"}, - {file = "triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00be2964616f4c619193cb0d1b29a99bd4b001d7dc333816073f92cf2a8ccdeb"}, - {file = "triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7936b18a3499ed62059414d7df563e6c163c5e16c3773678a3ee3d417865035d"}, - {file = "triton-3.4.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e5c1442eaeabae2e2452ae765801bd53cd4ce873cab0d1bdd59a32ab2d9397"}, -] - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.10\""} -setuptools = ">=40.8.0" - -[package.extras] -build = ["cmake (>=3.20,<4.0)", "lit"] -tests = ["autopep8", "isort", "llnl-hatchet", "numpy", "pytest", "pytest-forked", "pytest-xdist", "scipy (>=1.7.1)"] -tutorials = ["matplotlib", "pandas", "tabulate"] - -[[package]] -name = "triton" -version = "3.6.0" -description = "A language and compiler for custom Deep Learning operations" -optional = true -python-versions = "<3.15,>=3.10" -groups = ["main"] -markers = "extra == \"opensource\" and platform_system == \"Linux\" and python_version >= \"3.10\"" -files = [ - {file = "triton-3.6.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781"}, - {file = "triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea"}, - {file = "triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651"}, - {file = "triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3"}, - {file = "triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4"}, - {file = "triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca"}, - {file = "triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd"}, - {file = "triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9"}, - {file = "triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6"}, - {file = "triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f"}, - {file = "triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43"}, - {file = "triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803"}, - {file = "triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d"}, - {file = "triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7"}, -] - -[package.extras] -build = ["cmake (>=3.20,<4.0)", "lit"] -tests = ["autopep8", "isort", "llnl-hatchet", "numpy", "pytest", "pytest-forked", "pytest-xdist", "scipy (>=1.7.1)"] -tutorials = ["matplotlib", "pandas", "tabulate"] - -[[package]] -name = "typer" -version = "0.9.4" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "typer-0.9.4-py3-none-any.whl", hash = "sha256:aa6c4a4e2329d868b80ecbaf16f807f2b54e192209d7ac9dd42691d63f7a54eb"}, - {file = "typer-0.9.4.tar.gz", hash = "sha256:f714c2d90afae3a7929fcd72a3abb08df305e1ff61719381384211c4070af57f"}, -] - -[package.dependencies] -click = ">=7.1.1,<9.0.0" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] -dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] -test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20240311" -description = "Typing stubs for PyYAML" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-PyYAML-6.0.12.20240311.tar.gz", hash = "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342"}, - {file = "types_PyYAML-6.0.12.20240311-py3-none-any.whl", hash = "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6"}, -] - -[[package]] -name = "types-requests" -version = "2.31.0.6" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, - {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, -] - -[package.dependencies] -types-urllib3 = "*" - -[[package]] -name = "types-urllib3" -version = "1.26.25.14" -description = "Typing stubs for urllib3" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, - {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] -markers = {dev = "python_version < \"3.11\""} - -[[package]] -name = "typing-inspect" -version = "0.9.0" -description = "Runtime inspection utilities for typing module." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, - {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, -] - -[package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["main"] -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "ujson" -version = "5.10.0" -description = "Ultra fast JSON encoder and decoder for Python" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"milvus\"" -files = [ - {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, - {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"}, - {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"}, - {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"}, - {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"}, - {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"}, - {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"}, - {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"}, - {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"}, - {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"}, - {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"}, - {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"}, - {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"}, - {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"}, - {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"}, - {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"}, - {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"}, - {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"}, - {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"}, - {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"}, - {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"}, - {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"}, - {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"}, - {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"}, - {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"}, -] - -[[package]] -name = "uritemplate" -version = "4.1.1" -description = "Implementation of RFC 6570 URI Templates" -optional = true -python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"gmail\" or extra == \"googledrive\"" -files = [ - {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, - {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, -] - -[[package]] -name = "urllib3" -version = "1.26.19" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, - {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "uuid-utils" -version = "0.14.1" -description = "Fast, drop-in replacement for Python's uuid module, powered by Rust." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0"}, - {file = "uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741"}, - {file = "uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1"}, - {file = "uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96"}, - {file = "uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae"}, - {file = "uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862"}, - {file = "uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b"}, - {file = "uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297"}, - {file = "uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3"}, - {file = "uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531"}, - {file = "uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43"}, - {file = "uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523"}, - {file = "uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba"}, - {file = "uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b197cd5424cf89fb019ca7f53641d05bfe34b1879614bed111c9c313b5574cd8"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:12c65020ba6cb6abe1d57fcbfc2d0ea0506c67049ee031714057f5caf0f9bc9c"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b5d2ad28063d422ccc2c28d46471d47b61a58de885d35113a8f18cb547e25bf"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da2234387b45fde40b0fedfee64a0ba591caeea9c48c7698ab6e2d85c7991533"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50fffc2827348c1e48972eed3d1c698959e63f9d030aa5dd82ba451113158a62"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dbe718765f70f5b7f9b7f66b6a937802941b1cc56bcf642ce0274169741e01"}, - {file = "uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:258186964039a8e36db10810c1ece879d229b01331e09e9030bc5dcabe231bd2"}, - {file = "uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69"}, -] - -[[package]] -name = "uvicorn" -version = "0.30.1" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, - {file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} -h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} - -[package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0) ; python_version >= \"3.12\"", "aiohttp (>=3.8.1) ; python_version < \"3.12\"", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - -[[package]] -name = "validators" -version = "0.32.0" -description = "Python Data Validation for Humans™" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"weaviate\"" -files = [ - {file = "validators-0.32.0-py3-none-any.whl", hash = "sha256:e9ce1703afb0adf7724b0f98e4081d9d10e88fa5d37254d21e41f27774c020cd"}, - {file = "validators-0.32.0.tar.gz", hash = "sha256:9ee6e6d7ac9292b9b755a3155d7c361d76bb2dce23def4f0627662da1e300676"}, -] - -[package.extras] -crypto-eth-addresses = ["eth-hash[pycryptodome] (>=0.7.0)"] - -[[package]] -name = "virtualenv" -version = "20.26.3" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - -[[package]] -name = "watchfiles" -version = "0.22.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, - {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, - {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, - {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, - {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, - {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, - {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, - {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, - {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, - {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, - {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, - {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, - {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, - {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, - {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "weaviate-client" -version = "3.26.5" -description = "A python native Weaviate client" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"weaviate\"" -files = [ - {file = "weaviate_client-3.26.5-py3-none-any.whl", hash = "sha256:76327ba93bdfff293e7e299e90ea28ad0e489cfff7c7a6be82c72d1159b60e4f"}, - {file = "weaviate_client-3.26.5.tar.gz", hash = "sha256:f9dc0e42656e3458b12aa59b73e08da0e0f6301f3cd368473c9f5242821854d6"}, -] - -[package.dependencies] -authlib = ">=1.2.1,<2.0.0" -requests = ">=2.30.0,<3.0.0" -validators = ">=0.21.2,<1.0.0" - -[package.extras] -grpc = ["grpcio (>=1.57.0,<2.0.0)", "grpcio-tools (>=1.57.0,<2.0.0)"] - -[[package]] -name = "websocket-client" -version = "1.8.0" -description = "WebSocket client for Python with low level API options" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, - {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, -] - -[package.extras] -docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "websockets" -version = "12.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, -] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "yarl" -version = "1.9.4" -description = "Yet another URL library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[[package]] -name = "zipp" -version = "3.19.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "zstandard" -version = "0.23.0" -description = "Zstandard bindings for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, - {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, - {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, - {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, - {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, - {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, - {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, - {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, - {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, - {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, - {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, - {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, - {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, - {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, - {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, - {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, - {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"}, - {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"}, - {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"}, - {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"}, - {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"}, - {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"}, - {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"}, - {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, - {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, -] - -[package.dependencies] -cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} - -[package.extras] -cffi = ["cffi (>=1.11)"] - -[extras] -aws = ["langchain-aws"] -elasticsearch = ["elasticsearch"] -gmail = ["google-api-core", "google-api-python-client", "google-auth", "google-auth-httplib2", "google-auth-oauthlib", "requests"] -google = ["google-generativeai"] -googledrive = ["google-api-python-client", "google-auth-httplib2", "google-auth-oauthlib"] -lancedb = ["lancedb"] -llama2 = ["replicate"] -milvus = ["pymilvus"] -mistralai = ["langchain-mistralai"] -mysql = ["mysql-connector-python"] -opensearch = ["opensearch-py"] -opensource = ["gpt4all", "sentence-transformers", "torch"] -postgres = ["psycopg", "psycopg-binary", "psycopg-pool"] -qdrant = ["qdrant-client"] -together = ["together"] -vertexai = ["langchain-google-vertexai"] -weaviate = ["weaviate-client"] - -[metadata] -lock-version = "2.1" -python-versions = ">=3.9,<=3.13.2" -content-hash = "2853314a806fa8337c1a0b2e0d61e444ba21605be7b3491b48a56ecf14c48b86" diff --git a/embedchain/poetry.toml b/embedchain/poetry.toml deleted file mode 100644 index 8eb0c8019..000000000 --- a/embedchain/poetry.toml +++ /dev/null @@ -1,3 +0,0 @@ -[virtualenvs] -in-project = true -path = "." \ No newline at end of file diff --git a/embedchain/pyproject.toml b/embedchain/pyproject.toml deleted file mode 100644 index b9a1ad5b1..000000000 --- a/embedchain/pyproject.toml +++ /dev/null @@ -1,189 +0,0 @@ -[tool.poetry] -name = "embedchain" -version = "0.1.128" -description = "Simplest open source retrieval (RAG) framework" -authors = [ - "Taranjeet Singh ", - "Deshraj Yadav ", -] -license = "Apache License" -readme = "README.md" -exclude = [ - "db", - "configs", - "notebooks" -] -packages = [ - { include = "embedchain" }, -] - -[build-system] -build-backend = "poetry.core.masonry.api" -requires = ["poetry-core"] - -[tool.ruff] -line-length = 120 -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".mypy_cache", - ".nox", - ".pants.d", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "venv" -] -target-version = "py38" - -[tool.ruff.lint] -select = ["ASYNC", "E", "F"] -ignore = [] -fixable = ["ALL"] -unfixable = [] -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - -# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. -[tool.ruff.lint.per-file-ignores] -"embedchain/__init__.py" = ["E401"] - -[tool.ruff.lint.mccabe] -max-complexity = 10 - -[tool.black] -line-length = 120 -target-version = ["py38", "py39", "py310", "py311"] -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.nox - | \.pants.d - | \.pytype - | \.ruff_cache - | \.svn - | \.tox - | \.venv - | __pypackages__ - | _build - | buck-out - | build - | dist - | node_modules - | venv -)/ -''' - -[tool.black.format] -color = true - -[tool.poetry.dependencies] -python = ">=3.9,<=3.13.2" -python-dotenv = "^1.0.0" -langchain = "^0.3.1" -requests = "^2.31.0" -openai = ">=1.1.1" -chromadb = "^0.5.10" -posthog = "^3.0.2" -rich = "^13.7.0" -beautifulsoup4 = "^4.12.2" -pypdf = "^5.0.0" -gptcache = "^0.1.43" -pysbd = "^0.3.4" -mem0ai = "^0.1.54" -tiktoken = { version = "^0.7.0", optional = true } -sentence-transformers = { version = "^2.2.2", optional = true } -torch = { version = ">=2.6.0,<3", optional = true } -# Torch 2.0.1 is not compatible with poetry (https://github.com/pytorch/pytorch/issues/100974) -gpt4all = { version = "2.0.2", optional = true } -# 1.0.9 is not working for some users (https://github.com/nomic-ai/gpt4all/issues/1394) -opensearch-py = { version = "2.3.1", optional = true } -elasticsearch = { version = "^8.9.0", optional = true } -cohere = { version = "^5.3", optional = true } -together = { version = "^1.2.1", optional = true } -lancedb = { version = "^0.6.2", optional = true } -weaviate-client = { version = "^3.24.1", optional = true } -qdrant-client = { version = "^1.6.3", optional = true } -pymilvus = { version = "2.4.3", optional = true } -google-cloud-aiplatform = { version = "^1.26.1", optional = true } -replicate = { version = "^0.15.4", optional = true } -schema = "^0.7.5" -psycopg = { version = "^3.1.12", optional = true } -psycopg-binary = { version = "^3.1.12", optional = true } -psycopg-pool = { version = "^3.1.8", optional = true } -mysql-connector-python = { version = "^8.1.0", optional = true } -google-generativeai = { version = "^0.3.0", optional = true } -google-api-python-client = { version = "^2.111.0", optional = true } -google-auth-oauthlib = { version = "^1.2.0", optional = true } -google-auth = { version = "^2.25.2", optional = true } -google-auth-httplib2 = { version = "^0.2.0", optional = true } -google-api-core = { version = "^2.15.0", optional = true } -langchain-mistralai = { version = "^0.2.0", optional = true } -langchain-openai = "^0.2.1" -langchain-google-vertexai = { version = "^2.0.2", optional = true } -sqlalchemy = "^2.0.27" -alembic = "^1.13.1" -langchain-cohere = "^0.3.0" -langchain-community = "^0.3.1" -langchain-aws = {version = "^0.2.1", optional = true} -langsmith = "^0.3.18" - -[tool.poetry.group.dev.dependencies] -black = "^23.3.0" -pre-commit = "^3.2.2" -ruff = "^0.1.11" -pytest = "^7.3.1" -pytest-mock = "^3.10.0" -pytest-env = "^0.8.1" -click = "^8.1.3" -isort = "^5.12.0" -pytest-cov = "^4.1.0" -responses = "^0.23.3" -mock = "^5.1.0" -pytest-asyncio = "^0.21.1" - -[tool.poetry.extras] -opensource = ["sentence-transformers", "torch", "gpt4all"] -lancedb = ["lancedb"] -elasticsearch = ["elasticsearch"] -opensearch = ["opensearch-py"] -weaviate = ["weaviate-client"] -qdrant = ["qdrant-client"] -together = ["together"] -milvus = ["pymilvus"] -vertexai = ["langchain-google-vertexai"] -llama2 = ["replicate"] -gmail = [ - "requests", - "google-api-python-client", - "google-auth", - "google-auth-oauthlib", - "google-auth-httplib2", - "google-api-core", -] -googledrive = ["google-api-python-client", "google-auth-oauthlib", "google-auth-httplib2"] -postgres = ["psycopg", "psycopg-binary", "psycopg-pool"] -mysql = ["mysql-connector-python"] -google = ["google-generativeai"] -mistralai = ["langchain-mistralai"] -aws = ["langchain-aws"] - -[tool.poetry.group.docs.dependencies] - -[tool.poetry.scripts] -ec = "embedchain.cli:cli" \ No newline at end of file diff --git a/embedchain/tests/__init__.py b/embedchain/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/embedchain/tests/chunkers/test_base_chunker.py b/embedchain/tests/chunkers/test_base_chunker.py deleted file mode 100644 index 23cf1e8ce..000000000 --- a/embedchain/tests/chunkers/test_base_chunker.py +++ /dev/null @@ -1,99 +0,0 @@ -import hashlib -from unittest.mock import MagicMock - -import pytest - -from embedchain.chunkers.base_chunker import BaseChunker -from embedchain.config.add_config import ChunkerConfig -from embedchain.models.data_type import DataType - - -@pytest.fixture -def text_splitter_mock(): - return MagicMock() - - -@pytest.fixture -def loader_mock(): - return MagicMock() - - -@pytest.fixture -def app_id(): - return "test_app" - - -@pytest.fixture -def data_type(): - return DataType.TEXT - - -@pytest.fixture -def chunker(text_splitter_mock, data_type): - text_splitter = text_splitter_mock - chunker = BaseChunker(text_splitter) - chunker.set_data_type(data_type) - return chunker - - -def test_create_chunks_with_config(chunker, text_splitter_mock, loader_mock, app_id, data_type): - text_splitter_mock.split_text.return_value = ["Chunk 1", "long chunk"] - loader_mock.load_data.return_value = { - "data": [{"content": "Content 1", "meta_data": {"url": "URL 1"}}], - "doc_id": "DocID", - } - config = ChunkerConfig(chunk_size=50, chunk_overlap=0, length_function=len, min_chunk_size=10) - result = chunker.create_chunks(loader_mock, "test_src", app_id, config) - - assert result["documents"] == ["long chunk"] - - -def test_create_chunks(chunker, text_splitter_mock, loader_mock, app_id, data_type): - text_splitter_mock.split_text.return_value = ["Chunk 1", "Chunk 2"] - loader_mock.load_data.return_value = { - "data": [{"content": "Content 1", "meta_data": {"url": "URL 1"}}], - "doc_id": "DocID", - } - - result = chunker.create_chunks(loader_mock, "test_src", app_id) - expected_ids = [ - f"{app_id}--" + hashlib.sha256(("Chunk 1" + "URL 1").encode()).hexdigest(), - f"{app_id}--" + hashlib.sha256(("Chunk 2" + "URL 1").encode()).hexdigest(), - ] - - assert result["documents"] == ["Chunk 1", "Chunk 2"] - assert result["ids"] == expected_ids - assert result["metadatas"] == [ - { - "url": "URL 1", - "data_type": data_type.value, - "doc_id": f"{app_id}--DocID", - }, - { - "url": "URL 1", - "data_type": data_type.value, - "doc_id": f"{app_id}--DocID", - }, - ] - assert result["doc_id"] == f"{app_id}--DocID" - - -def test_get_chunks(chunker, text_splitter_mock): - text_splitter_mock.split_text.return_value = ["Chunk 1", "Chunk 2"] - - content = "This is a test content." - result = chunker.get_chunks(content) - - assert len(result) == 2 - assert result == ["Chunk 1", "Chunk 2"] - - -def test_set_data_type(chunker): - chunker.set_data_type(DataType.MDX) - assert chunker.data_type == DataType.MDX - - -def test_get_word_count(chunker): - documents = ["This is a test.", "Another test."] - result = chunker.get_word_count(documents) - assert result == 6 diff --git a/embedchain/tests/chunkers/test_chunkers.py b/embedchain/tests/chunkers/test_chunkers.py deleted file mode 100644 index 8258e7764..000000000 --- a/embedchain/tests/chunkers/test_chunkers.py +++ /dev/null @@ -1,66 +0,0 @@ -from embedchain.chunkers.audio import AudioChunker -from embedchain.chunkers.common_chunker import CommonChunker -from embedchain.chunkers.discourse import DiscourseChunker -from embedchain.chunkers.docs_site import DocsSiteChunker -from embedchain.chunkers.docx_file import DocxFileChunker -from embedchain.chunkers.excel_file import ExcelFileChunker -from embedchain.chunkers.gmail import GmailChunker -from embedchain.chunkers.google_drive import GoogleDriveChunker -from embedchain.chunkers.json import JSONChunker -from embedchain.chunkers.mdx import MdxChunker -from embedchain.chunkers.notion import NotionChunker -from embedchain.chunkers.openapi import OpenAPIChunker -from embedchain.chunkers.pdf_file import PdfFileChunker -from embedchain.chunkers.postgres import PostgresChunker -from embedchain.chunkers.qna_pair import QnaPairChunker -from embedchain.chunkers.sitemap import SitemapChunker -from embedchain.chunkers.slack import SlackChunker -from embedchain.chunkers.table import TableChunker -from embedchain.chunkers.text import TextChunker -from embedchain.chunkers.web_page import WebPageChunker -from embedchain.chunkers.xml import XmlChunker -from embedchain.chunkers.youtube_video import YoutubeVideoChunker -from embedchain.config.add_config import ChunkerConfig - -chunker_config = ChunkerConfig(chunk_size=500, chunk_overlap=0, length_function=len) - -chunker_common_config = { - DocsSiteChunker: {"chunk_size": 500, "chunk_overlap": 50, "length_function": len}, - DocxFileChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - PdfFileChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - TextChunker: {"chunk_size": 300, "chunk_overlap": 0, "length_function": len}, - MdxChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - NotionChunker: {"chunk_size": 300, "chunk_overlap": 0, "length_function": len}, - QnaPairChunker: {"chunk_size": 300, "chunk_overlap": 0, "length_function": len}, - TableChunker: {"chunk_size": 300, "chunk_overlap": 0, "length_function": len}, - SitemapChunker: {"chunk_size": 500, "chunk_overlap": 0, "length_function": len}, - WebPageChunker: {"chunk_size": 2000, "chunk_overlap": 0, "length_function": len}, - XmlChunker: {"chunk_size": 500, "chunk_overlap": 50, "length_function": len}, - YoutubeVideoChunker: {"chunk_size": 2000, "chunk_overlap": 0, "length_function": len}, - JSONChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - OpenAPIChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - GmailChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - PostgresChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - SlackChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - DiscourseChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - CommonChunker: {"chunk_size": 2000, "chunk_overlap": 0, "length_function": len}, - GoogleDriveChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - ExcelFileChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, - AudioChunker: {"chunk_size": 1000, "chunk_overlap": 0, "length_function": len}, -} - - -def test_default_config_values(): - for chunker_class, config in chunker_common_config.items(): - chunker = chunker_class() - assert chunker.text_splitter._chunk_size == config["chunk_size"] - assert chunker.text_splitter._chunk_overlap == config["chunk_overlap"] - assert chunker.text_splitter._length_function == config["length_function"] - - -def test_custom_config_values(): - for chunker_class, _ in chunker_common_config.items(): - chunker = chunker_class(config=chunker_config) - assert chunker.text_splitter._chunk_size == 500 - assert chunker.text_splitter._chunk_overlap == 0 - assert chunker.text_splitter._length_function == len diff --git a/embedchain/tests/chunkers/test_text.py b/embedchain/tests/chunkers/test_text.py deleted file mode 100644 index 9a2873572..000000000 --- a/embedchain/tests/chunkers/test_text.py +++ /dev/null @@ -1,86 +0,0 @@ -# ruff: noqa: E501 - -from embedchain.chunkers.text import TextChunker -from embedchain.config import ChunkerConfig -from embedchain.models.data_type import DataType - - -class TestTextChunker: - def test_chunks_without_app_id(self): - """ - Test the chunks generated by TextChunker. - """ - chunker_config = ChunkerConfig(chunk_size=10, chunk_overlap=0, length_function=len, min_chunk_size=0) - chunker = TextChunker(config=chunker_config) - text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." - # Data type must be set manually in the test - chunker.set_data_type(DataType.TEXT) - result = chunker.create_chunks(MockLoader(), text, chunker_config) - documents = result["documents"] - assert len(documents) > 5 - - def test_chunks_with_app_id(self): - """ - Test the chunks generated by TextChunker with app_id - """ - chunker_config = ChunkerConfig(chunk_size=10, chunk_overlap=0, length_function=len, min_chunk_size=0) - chunker = TextChunker(config=chunker_config) - text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." - chunker.set_data_type(DataType.TEXT) - result = chunker.create_chunks(MockLoader(), text, chunker_config) - documents = result["documents"] - assert len(documents) > 5 - - def test_big_chunksize(self): - """ - Test that if an infinitely high chunk size is used, only one chunk is returned. - """ - chunker_config = ChunkerConfig(chunk_size=9999999999, chunk_overlap=0, length_function=len, min_chunk_size=0) - chunker = TextChunker(config=chunker_config) - text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." - # Data type must be set manually in the test - chunker.set_data_type(DataType.TEXT) - result = chunker.create_chunks(MockLoader(), text, chunker_config) - documents = result["documents"] - assert len(documents) == 1 - - def test_small_chunksize(self): - """ - Test that if a chunk size of one is used, every character is a chunk. - """ - chunker_config = ChunkerConfig(chunk_size=1, chunk_overlap=0, length_function=len, min_chunk_size=0) - chunker = TextChunker(config=chunker_config) - # We can't test with lorem ipsum because chunks are deduped, so would be recurring characters. - text = """0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c""" - # Data type must be set manually in the test - chunker.set_data_type(DataType.TEXT) - result = chunker.create_chunks(MockLoader(), text, chunker_config) - documents = result["documents"] - assert len(documents) == len(text) - - def test_word_count(self): - chunker_config = ChunkerConfig(chunk_size=1, chunk_overlap=0, length_function=len, min_chunk_size=0) - chunker = TextChunker(config=chunker_config) - chunker.set_data_type(DataType.TEXT) - - document = ["ab cd", "ef gh"] - result = chunker.get_word_count(document) - assert result == 4 - - -class MockLoader: - @staticmethod - def load_data(src) -> dict: - """ - Mock loader that returns a list of data dictionaries. - Adjust this method to return different data for testing. - """ - return { - "doc_id": "123", - "data": [ - { - "content": src, - "meta_data": {"url": "none"}, - } - ], - } diff --git a/embedchain/tests/conftest.py b/embedchain/tests/conftest.py deleted file mode 100644 index 0b5807a0d..000000000 --- a/embedchain/tests/conftest.py +++ /dev/null @@ -1,35 +0,0 @@ -import os - -import pytest -from sqlalchemy import MetaData, create_engine -from sqlalchemy.orm import sessionmaker - - -@pytest.fixture(autouse=True) -def clean_db(): - db_path = os.path.expanduser("~/.embedchain/embedchain.db") - db_url = f"sqlite:///{db_path}" - engine = create_engine(db_url) - metadata = MetaData() - metadata.reflect(bind=engine) # Reflect schema from the engine - Session = sessionmaker(bind=engine) - session = Session() - - try: - # Iterate over all tables in reversed order to respect foreign keys - for table in reversed(metadata.sorted_tables): - if table.name != "alembic_version": # Skip the Alembic version table - session.execute(table.delete()) - session.commit() - except Exception as e: - session.rollback() - print(f"Error cleaning database: {e}") - finally: - session.close() - - -@pytest.fixture(autouse=True) -def disable_telemetry(): - os.environ["EC_TELEMETRY"] = "false" - yield - del os.environ["EC_TELEMETRY"] \ No newline at end of file diff --git a/embedchain/tests/embedchain/test_add.py b/embedchain/tests/embedchain/test_add.py deleted file mode 100644 index b9d8437a9..000000000 --- a/embedchain/tests/embedchain/test_add.py +++ /dev/null @@ -1,52 +0,0 @@ -import os - -import pytest - -from embedchain import App -from embedchain.config import AddConfig, AppConfig, ChunkerConfig -from embedchain.models.data_type import DataType - -os.environ["OPENAI_API_KEY"] = "test_key" - - -@pytest.fixture -def app(mocker): - mocker.patch("chromadb.api.models.Collection.Collection.add") - return App(config=AppConfig(collect_metrics=False)) - - -def test_add(app): - app.add("https://example.com", metadata={"foo": "bar"}) - assert app.user_asks == [["https://example.com", "web_page", {"foo": "bar"}]] - - -# TODO: Make this test faster by generating a sitemap locally rather than using a remote one -# def test_add_sitemap(app): -# app.add("https://www.google.com/sitemap.xml", metadata={"foo": "bar"}) -# assert app.user_asks == [["https://www.google.com/sitemap.xml", "sitemap", {"foo": "bar"}]] - - -def test_add_forced_type(app): - data_type = "text" - app.add("https://example.com", data_type=data_type, metadata={"foo": "bar"}) - assert app.user_asks == [["https://example.com", data_type, {"foo": "bar"}]] - - -def test_dry_run(app): - chunker_config = ChunkerConfig(chunk_size=1, chunk_overlap=0, min_chunk_size=0) - text = """0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ""" - - result = app.add(source=text, config=AddConfig(chunker=chunker_config), dry_run=True) - - chunks = result["chunks"] - metadata = result["metadata"] - count = result["count"] - data_type = result["type"] - - assert len(chunks) == len(text) - assert count == len(text) - assert data_type == DataType.TEXT - for item in metadata: - assert isinstance(item, dict) - assert "local" in item["url"] - assert "text" in item["data_type"] diff --git a/embedchain/tests/embedchain/test_embedchain.py b/embedchain/tests/embedchain/test_embedchain.py deleted file mode 100644 index 7614b8deb..000000000 --- a/embedchain/tests/embedchain/test_embedchain.py +++ /dev/null @@ -1,75 +0,0 @@ -import os - -import pytest -from chromadb.api.models.Collection import Collection - -from embedchain import App -from embedchain.config import AppConfig, ChromaDbConfig -from embedchain.embedchain import EmbedChain -from embedchain.llm.base import BaseLlm -from embedchain.memory.base import ChatHistory -from embedchain.vectordb.chroma import ChromaDB - -os.environ["OPENAI_API_KEY"] = "test-api-key" - - -@pytest.fixture -def app_instance(): - config = AppConfig(log_level="DEBUG", collect_metrics=False) - return App(config=config) - - -def test_whole_app(app_instance, mocker): - knowledge = "lorem ipsum dolor sit amet, consectetur adipiscing" - - mocker.patch.object(EmbedChain, "add") - mocker.patch.object(EmbedChain, "_retrieve_from_database") - mocker.patch.object(BaseLlm, "get_answer_from_llm", return_value=knowledge) - mocker.patch.object(BaseLlm, "get_llm_model_answer", return_value=knowledge) - mocker.patch.object(BaseLlm, "generate_prompt") - mocker.patch.object(BaseLlm, "add_history") - mocker.patch.object(ChatHistory, "delete", autospec=True) - - app_instance.add(knowledge, data_type="text") - app_instance.query("What text did I give you?") - app_instance.chat("What text did I give you?") - - assert BaseLlm.generate_prompt.call_count == 2 - app_instance.reset() - - -def test_add_after_reset(app_instance, mocker): - mocker.patch("embedchain.vectordb.chroma.chromadb.Client") - - config = AppConfig(log_level="DEBUG", collect_metrics=False) - chroma_config = ChromaDbConfig(allow_reset=True) - db = ChromaDB(config=chroma_config) - app_instance = App(config=config, db=db) - - # mock delete chat history - mocker.patch.object(ChatHistory, "delete", autospec=True) - - app_instance.reset() - - app_instance.db.client.heartbeat() - - mocker.patch.object(Collection, "add") - - app_instance.db.collection.add( - embeddings=[[1.1, 2.3, 3.2], [4.5, 6.9, 4.4], [1.1, 2.3, 3.2]], - metadatas=[ - {"chapter": "3", "verse": "16"}, - {"chapter": "3", "verse": "5"}, - {"chapter": "29", "verse": "11"}, - ], - ids=["id1", "id2", "id3"], - ) - - app_instance.reset() - - -def test_add_with_incorrect_content(app_instance, mocker): - content = [{"foo": "bar"}] - - with pytest.raises(TypeError): - app_instance.add(content, data_type="json") diff --git a/embedchain/tests/embedchain/test_utils.py b/embedchain/tests/embedchain/test_utils.py deleted file mode 100644 index 22806b79f..000000000 --- a/embedchain/tests/embedchain/test_utils.py +++ /dev/null @@ -1,133 +0,0 @@ -import tempfile -import unittest -from unittest.mock import patch - -from embedchain.models.data_type import DataType -from embedchain.utils.misc import detect_datatype - - -class TestApp(unittest.TestCase): - """Test that the datatype detection is working, based on the input.""" - - def test_detect_datatype_youtube(self): - self.assertEqual(detect_datatype("https://www.youtube.com/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO) - self.assertEqual(detect_datatype("https://m.youtube.com/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO) - self.assertEqual( - detect_datatype("https://www.youtube-nocookie.com/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO - ) - self.assertEqual(detect_datatype("https://vid.plus/watch?v=dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO) - self.assertEqual(detect_datatype("https://youtu.be/dQw4w9WgXcQ"), DataType.YOUTUBE_VIDEO) - - def test_detect_datatype_local_file(self): - self.assertEqual(detect_datatype("file:///home/user/file.txt"), DataType.WEB_PAGE) - - def test_detect_datatype_pdf(self): - self.assertEqual(detect_datatype("https://www.example.com/document.pdf"), DataType.PDF_FILE) - - def test_detect_datatype_local_pdf(self): - self.assertEqual(detect_datatype("file:///home/user/document.pdf"), DataType.PDF_FILE) - - def test_detect_datatype_xml(self): - self.assertEqual(detect_datatype("https://www.example.com/sitemap.xml"), DataType.SITEMAP) - - def test_detect_datatype_local_xml(self): - self.assertEqual(detect_datatype("file:///home/user/sitemap.xml"), DataType.SITEMAP) - - def test_detect_datatype_docx(self): - self.assertEqual(detect_datatype("https://www.example.com/document.docx"), DataType.DOCX) - - def test_detect_datatype_local_docx(self): - self.assertEqual(detect_datatype("file:///home/user/document.docx"), DataType.DOCX) - - def test_detect_data_type_json(self): - self.assertEqual(detect_datatype("https://www.example.com/data.json"), DataType.JSON) - - def test_detect_data_type_local_json(self): - self.assertEqual(detect_datatype("file:///home/user/data.json"), DataType.JSON) - - @patch("os.path.isfile") - def test_detect_datatype_regular_filesystem_docx(self, mock_isfile): - with tempfile.NamedTemporaryFile(suffix=".docx", delete=True) as tmp: - mock_isfile.return_value = True - self.assertEqual(detect_datatype(tmp.name), DataType.DOCX) - - def test_detect_datatype_docs_site(self): - self.assertEqual(detect_datatype("https://docs.example.com"), DataType.DOCS_SITE) - - def test_detect_datatype_docs_sitein_path(self): - self.assertEqual(detect_datatype("https://www.example.com/docs/index.html"), DataType.DOCS_SITE) - self.assertNotEqual(detect_datatype("file:///var/www/docs/index.html"), DataType.DOCS_SITE) # NOT equal - - def test_detect_datatype_web_page(self): - self.assertEqual(detect_datatype("https://nav.al/agi"), DataType.WEB_PAGE) - - def test_detect_datatype_invalid_url(self): - self.assertEqual(detect_datatype("not a url"), DataType.TEXT) - - def test_detect_datatype_qna_pair(self): - self.assertEqual( - detect_datatype(("Question?", "Answer. Content of the string is irrelevant.")), DataType.QNA_PAIR - ) # - - def test_detect_datatype_qna_pair_types(self): - """Test that a QnA pair needs to be a tuple of length two, and both items have to be strings.""" - with self.assertRaises(TypeError): - self.assertNotEqual( - detect_datatype(("How many planets are in our solar system?", 8)), DataType.QNA_PAIR - ) # NOT equal - - def test_detect_datatype_text(self): - self.assertEqual(detect_datatype("Just some text."), DataType.TEXT) - - def test_detect_datatype_non_string_error(self): - """Test type error if the value passed is not a string, and not a valid non-string data_type""" - with self.assertRaises(TypeError): - detect_datatype(["foo", "bar"]) - - @patch("os.path.isfile") - def test_detect_datatype_regular_filesystem_file_txt(self, mock_isfile): - with tempfile.NamedTemporaryFile(suffix=".txt", delete=True) as tmp: - mock_isfile.return_value = True - self.assertEqual(detect_datatype(tmp.name), DataType.TEXT_FILE) - - def test_detect_datatype_regular_filesystem_no_file(self): - """Test that if a filepath is not actually an existing file, it is not handled as a file path.""" - self.assertEqual(detect_datatype("/var/not-an-existing-file.txt"), DataType.TEXT) - - def test_doc_examples_quickstart(self): - """Test examples used in the documentation.""" - self.assertEqual(detect_datatype("https://en.wikipedia.org/wiki/Elon_Musk"), DataType.WEB_PAGE) - self.assertEqual(detect_datatype("https://www.tesla.com/elon-musk"), DataType.WEB_PAGE) - - def test_doc_examples_introduction(self): - """Test examples used in the documentation.""" - self.assertEqual(detect_datatype("https://www.youtube.com/watch?v=3qHkcs3kG44"), DataType.YOUTUBE_VIDEO) - self.assertEqual( - detect_datatype( - "https://navalmanack.s3.amazonaws.com/Eric-Jorgenson_The-Almanack-of-Naval-Ravikant_Final.pdf" - ), - DataType.PDF_FILE, - ) - self.assertEqual(detect_datatype("https://nav.al/feedback"), DataType.WEB_PAGE) - - def test_doc_examples_app_types(self): - """Test examples used in the documentation.""" - self.assertEqual(detect_datatype("https://www.youtube.com/watch?v=Ff4fRgnuFgQ"), DataType.YOUTUBE_VIDEO) - self.assertEqual(detect_datatype("https://en.wikipedia.org/wiki/Mark_Zuckerberg"), DataType.WEB_PAGE) - - def test_doc_examples_configuration(self): - """Test examples used in the documentation.""" - import subprocess - import sys - - subprocess.check_call([sys.executable, "-m", "pip", "install", "wikipedia"]) - import wikipedia - - page = wikipedia.page("Albert Einstein") - # TODO: Add a wikipedia type, so wikipedia is a dependency and we don't need this slow test. - # (timings: import: 1.4s, fetch wiki: 0.7s) - self.assertEqual(detect_datatype(page.content), DataType.TEXT) - - -if __name__ == "__main__": - unittest.main() diff --git a/embedchain/tests/embedder/test_aws_bedrock_embedder.py b/embedchain/tests/embedder/test_aws_bedrock_embedder.py deleted file mode 100644 index f22b445b2..000000000 --- a/embedchain/tests/embedder/test_aws_bedrock_embedder.py +++ /dev/null @@ -1,21 +0,0 @@ -from unittest.mock import patch - -from embedchain.config.embedder.aws_bedrock import AWSBedrockEmbedderConfig -from embedchain.embedder.aws_bedrock import AWSBedrockEmbedder - - -def test_aws_bedrock_embedder_with_model(): - config = AWSBedrockEmbedderConfig( - model="test-model", - model_kwargs={"param": "value"}, - vector_dimension=1536, - ) - with patch("embedchain.embedder.aws_bedrock.BedrockEmbeddings") as mock_embeddings: - embedder = AWSBedrockEmbedder(config=config) - assert embedder.config.model == "test-model" - assert embedder.config.model_kwargs == {"param": "value"} - assert embedder.config.vector_dimension == 1536 - mock_embeddings.assert_called_once_with( - model_id="test-model", - model_kwargs={"param": "value"}, - ) diff --git a/embedchain/tests/embedder/test_azure_openai_embedder.py b/embedchain/tests/embedder/test_azure_openai_embedder.py deleted file mode 100644 index 2667d01f3..000000000 --- a/embedchain/tests/embedder/test_azure_openai_embedder.py +++ /dev/null @@ -1,52 +0,0 @@ -from unittest.mock import Mock, patch - -import httpx - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.azure_openai import AzureOpenAIEmbedder - - -def test_azure_openai_embedder_with_http_client(monkeypatch): - mock_http_client = Mock(spec=httpx.Client) - mock_http_client_instance = Mock(spec=httpx.Client) - mock_http_client.return_value = mock_http_client_instance - - with patch("embedchain.embedder.azure_openai.AzureOpenAIEmbeddings") as mock_embeddings, patch( - "httpx.Client", new=mock_http_client - ) as mock_http_client: - config = BaseEmbedderConfig( - deployment_name="text-embedding-ada-002", - http_client_proxies="http://testproxy.mem0.net:8000", - ) - - _ = AzureOpenAIEmbedder(config=config) - - mock_embeddings.assert_called_once_with( - deployment="text-embedding-ada-002", - http_client=mock_http_client_instance, - http_async_client=None, - ) - mock_http_client.assert_called_once_with(proxies="http://testproxy.mem0.net:8000") - - -def test_azure_openai_embedder_with_http_async_client(monkeypatch): - mock_http_async_client = Mock(spec=httpx.AsyncClient) - mock_http_async_client_instance = Mock(spec=httpx.AsyncClient) - mock_http_async_client.return_value = mock_http_async_client_instance - - with patch("embedchain.embedder.azure_openai.AzureOpenAIEmbeddings") as mock_embeddings, patch( - "httpx.AsyncClient", new=mock_http_async_client - ) as mock_http_async_client: - config = BaseEmbedderConfig( - deployment_name="text-embedding-ada-002", - http_async_client_proxies={"http://": "http://testproxy.mem0.net:8000"}, - ) - - _ = AzureOpenAIEmbedder(config=config) - - mock_embeddings.assert_called_once_with( - deployment="text-embedding-ada-002", - http_client=None, - http_async_client=mock_http_async_client_instance, - ) - mock_http_async_client.assert_called_once_with(proxies={"http://": "http://testproxy.mem0.net:8000"}) diff --git a/embedchain/tests/embedder/test_embedder.py b/embedchain/tests/embedder/test_embedder.py deleted file mode 100644 index 2797c2336..000000000 --- a/embedchain/tests/embedder/test_embedder.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest -from chromadb.api.types import Documents, Embeddings - -from embedchain.config.embedder.base import BaseEmbedderConfig -from embedchain.embedder.base import BaseEmbedder - - -@pytest.fixture -def base_embedder(): - return BaseEmbedder() - - -def test_initialization(base_embedder): - assert isinstance(base_embedder.config, BaseEmbedderConfig) - # not initialized - assert not hasattr(base_embedder, "embedding_fn") - assert not hasattr(base_embedder, "vector_dimension") - - -def test_set_embedding_fn(base_embedder): - def embedding_function(texts: Documents) -> Embeddings: - return [f"Embedding for {text}" for text in texts] - - base_embedder.set_embedding_fn(embedding_function) - assert hasattr(base_embedder, "embedding_fn") - assert callable(base_embedder.embedding_fn) - embeddings = base_embedder.embedding_fn(["text1", "text2"]) - assert embeddings == ["Embedding for text1", "Embedding for text2"] - - -def test_set_embedding_fn_when_not_a_function(base_embedder): - with pytest.raises(ValueError): - base_embedder.set_embedding_fn(None) - - -def test_set_vector_dimension(base_embedder): - base_embedder.set_vector_dimension(256) - assert hasattr(base_embedder, "vector_dimension") - assert base_embedder.vector_dimension == 256 - - -def test_set_vector_dimension_type_error(base_embedder): - with pytest.raises(TypeError): - base_embedder.set_vector_dimension(None) - - -def test_embedder_with_config(): - embedder = BaseEmbedder(BaseEmbedderConfig()) - assert isinstance(embedder.config, BaseEmbedderConfig) diff --git a/embedchain/tests/embedder/test_huggingface_embedder.py b/embedchain/tests/embedder/test_huggingface_embedder.py deleted file mode 100644 index ed97ccc91..000000000 --- a/embedchain/tests/embedder/test_huggingface_embedder.py +++ /dev/null @@ -1,19 +0,0 @@ - -from unittest.mock import patch - -from embedchain.config import BaseEmbedderConfig -from embedchain.embedder.huggingface import HuggingFaceEmbedder - - -def test_huggingface_embedder_with_model(monkeypatch): - config = BaseEmbedderConfig(model="test-model", model_kwargs={"param": "value"}) - with patch('embedchain.embedder.huggingface.HuggingFaceEmbeddings') as mock_embeddings: - embedder = HuggingFaceEmbedder(config=config) - assert embedder.config.model == "test-model" - assert embedder.config.model_kwargs == {"param": "value"} - mock_embeddings.assert_called_once_with( - model_name="test-model", - model_kwargs={"param": "value"} - ) - - diff --git a/embedchain/tests/evaluation/test_answer_relevancy_metric.py b/embedchain/tests/evaluation/test_answer_relevancy_metric.py deleted file mode 100644 index 03458ed38..000000000 --- a/embedchain/tests/evaluation/test_answer_relevancy_metric.py +++ /dev/null @@ -1,224 +0,0 @@ -import numpy as np -import pytest - -from embedchain.config.evaluation.base import AnswerRelevanceConfig -from embedchain.evaluation.metrics import AnswerRelevance -from embedchain.utils.evaluation import EvalData, EvalMetric - - -@pytest.fixture -def mock_data(): - return [ - EvalData( - contexts=[ - "This is a test context 1.", - ], - question="This is a test question 1.", - answer="This is a test answer 1.", - ), - EvalData( - contexts=[ - "This is a test context 2-1.", - "This is a test context 2-2.", - ], - question="This is a test question 2.", - answer="This is a test answer 2.", - ), - ] - - -@pytest.fixture -def mock_answer_relevance_metric(monkeypatch): - monkeypatch.setenv("OPENAI_API_KEY", "test_api_key") - monkeypatch.setenv("OPENAI_API_BASE", "test_api_base") - metric = AnswerRelevance() - return metric - - -def test_answer_relevance_init(monkeypatch): - monkeypatch.setenv("OPENAI_API_KEY", "test_api_key") - metric = AnswerRelevance() - assert metric.name == EvalMetric.ANSWER_RELEVANCY.value - assert metric.config.model == "gpt-4" - assert metric.config.embedder == "text-embedding-ada-002" - assert metric.config.api_key is None - assert metric.config.num_gen_questions == 1 - monkeypatch.delenv("OPENAI_API_KEY") - - -def test_answer_relevance_init_with_config(): - metric = AnswerRelevance(config=AnswerRelevanceConfig(api_key="test_api_key")) - assert metric.name == EvalMetric.ANSWER_RELEVANCY.value - assert metric.config.model == "gpt-4" - assert metric.config.embedder == "text-embedding-ada-002" - assert metric.config.api_key == "test_api_key" - assert metric.config.num_gen_questions == 1 - - -def test_answer_relevance_init_without_api_key(monkeypatch): - monkeypatch.delenv("OPENAI_API_KEY", raising=False) - with pytest.raises(ValueError): - AnswerRelevance() - - -def test_generate_prompt(mock_answer_relevance_metric, mock_data): - prompt = mock_answer_relevance_metric._generate_prompt(mock_data[0]) - assert "This is a test answer 1." in prompt - - prompt = mock_answer_relevance_metric._generate_prompt(mock_data[1]) - assert "This is a test answer 2." in prompt - - -def test_generate_questions(mock_answer_relevance_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_answer_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type( - "obj", - (object,), - {"message": type("obj", (object,), {"content": "This is a test question response.\n"})}, - ) - ] - }, - )(), - ) - prompt = mock_answer_relevance_metric._generate_prompt(mock_data[0]) - questions = mock_answer_relevance_metric._generate_questions(prompt) - assert len(questions) == 1 - - monkeypatch.setattr( - mock_answer_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type("obj", (object,), {"message": type("obj", (object,), {"content": "question 1?\nquestion2?"})}) - ] - }, - )(), - ) - prompt = mock_answer_relevance_metric._generate_prompt(mock_data[1]) - questions = mock_answer_relevance_metric._generate_questions(prompt) - assert len(questions) == 2 - - -def test_generate_embedding(mock_answer_relevance_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_answer_relevance_metric.client.embeddings, - "create", - lambda input, model: type("obj", (object,), {"data": [type("obj", (object,), {"embedding": [1, 2, 3]})]})(), - ) - embedding = mock_answer_relevance_metric._generate_embedding("This is a test question.") - assert len(embedding) == 3 - - -def test_compute_similarity(mock_answer_relevance_metric, mock_data): - original = np.array([1, 2, 3]) - generated = np.array([[1, 2, 3], [1, 2, 3]]) - similarity = mock_answer_relevance_metric._compute_similarity(original, generated) - assert len(similarity) == 2 - assert similarity[0] == 1.0 - assert similarity[1] == 1.0 - - -def test_compute_score(mock_answer_relevance_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_answer_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type( - "obj", - (object,), - {"message": type("obj", (object,), {"content": "This is a test question response.\n"})}, - ) - ] - }, - )(), - ) - monkeypatch.setattr( - mock_answer_relevance_metric.client.embeddings, - "create", - lambda input, model: type("obj", (object,), {"data": [type("obj", (object,), {"embedding": [1, 2, 3]})]})(), - ) - score = mock_answer_relevance_metric._compute_score(mock_data[0]) - assert score == 1.0 - - monkeypatch.setattr( - mock_answer_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type("obj", (object,), {"message": type("obj", (object,), {"content": "question 1?\nquestion2?"})}) - ] - }, - )(), - ) - monkeypatch.setattr( - mock_answer_relevance_metric.client.embeddings, - "create", - lambda input, model: type("obj", (object,), {"data": [type("obj", (object,), {"embedding": [1, 2, 3]})]})(), - ) - score = mock_answer_relevance_metric._compute_score(mock_data[1]) - assert score == 1.0 - - -def test_evaluate(mock_answer_relevance_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_answer_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type( - "obj", - (object,), - {"message": type("obj", (object,), {"content": "This is a test question response.\n"})}, - ) - ] - }, - )(), - ) - monkeypatch.setattr( - mock_answer_relevance_metric.client.embeddings, - "create", - lambda input, model: type("obj", (object,), {"data": [type("obj", (object,), {"embedding": [1, 2, 3]})]})(), - ) - score = mock_answer_relevance_metric.evaluate(mock_data) - assert score == 1.0 - - monkeypatch.setattr( - mock_answer_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type("obj", (object,), {"message": type("obj", (object,), {"content": "question 1?\nquestion2?"})}) - ] - }, - )(), - ) - monkeypatch.setattr( - mock_answer_relevance_metric.client.embeddings, - "create", - lambda input, model: type("obj", (object,), {"data": [type("obj", (object,), {"embedding": [1, 2, 3]})]})(), - ) - score = mock_answer_relevance_metric.evaluate(mock_data) - assert score == 1.0 diff --git a/embedchain/tests/evaluation/test_context_relevancy_metric.py b/embedchain/tests/evaluation/test_context_relevancy_metric.py deleted file mode 100644 index 6b4e13b10..000000000 --- a/embedchain/tests/evaluation/test_context_relevancy_metric.py +++ /dev/null @@ -1,100 +0,0 @@ -import pytest - -from embedchain.config.evaluation.base import ContextRelevanceConfig -from embedchain.evaluation.metrics import ContextRelevance -from embedchain.utils.evaluation import EvalData, EvalMetric - - -@pytest.fixture -def mock_data(): - return [ - EvalData( - contexts=[ - "This is a test context 1.", - ], - question="This is a test question 1.", - answer="This is a test answer 1.", - ), - EvalData( - contexts=[ - "This is a test context 2-1.", - "This is a test context 2-2.", - ], - question="This is a test question 2.", - answer="This is a test answer 2.", - ), - ] - - -@pytest.fixture -def mock_context_relevance_metric(monkeypatch): - monkeypatch.setenv("OPENAI_API_KEY", "test_api_key") - metric = ContextRelevance() - return metric - - -def test_context_relevance_init(monkeypatch): - monkeypatch.setenv("OPENAI_API_KEY", "test_api_key") - metric = ContextRelevance() - assert metric.name == EvalMetric.CONTEXT_RELEVANCY.value - assert metric.config.model == "gpt-4" - assert metric.config.api_key is None - assert metric.config.language == "en" - monkeypatch.delenv("OPENAI_API_KEY") - - -def test_context_relevance_init_with_config(): - metric = ContextRelevance(config=ContextRelevanceConfig(api_key="test_api_key")) - assert metric.name == EvalMetric.CONTEXT_RELEVANCY.value - assert metric.config.model == "gpt-4" - assert metric.config.api_key == "test_api_key" - assert metric.config.language == "en" - - -def test_context_relevance_init_without_api_key(monkeypatch): - monkeypatch.delenv("OPENAI_API_KEY", raising=False) - with pytest.raises(ValueError): - ContextRelevance() - - -def test_sentence_segmenter(mock_context_relevance_metric): - text = "This is a test sentence. This is another sentence." - assert mock_context_relevance_metric._sentence_segmenter(text) == [ - "This is a test sentence. ", - "This is another sentence.", - ] - - -def test_compute_score(mock_context_relevance_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_context_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type("obj", (object,), {"message": type("obj", (object,), {"content": "This is a test reponse."})}) - ] - }, - )(), - ) - assert mock_context_relevance_metric._compute_score(mock_data[0]) == 1.0 - assert mock_context_relevance_metric._compute_score(mock_data[1]) == 0.5 - - -def test_evaluate(mock_context_relevance_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_context_relevance_metric.client.chat.completions, - "create", - lambda model, messages: type( - "obj", - (object,), - { - "choices": [ - type("obj", (object,), {"message": type("obj", (object,), {"content": "This is a test reponse."})}) - ] - }, - )(), - ) - assert mock_context_relevance_metric.evaluate(mock_data) == 0.75 diff --git a/embedchain/tests/evaluation/test_groundedness_metric.py b/embedchain/tests/evaluation/test_groundedness_metric.py deleted file mode 100644 index 38c3fec8e..000000000 --- a/embedchain/tests/evaluation/test_groundedness_metric.py +++ /dev/null @@ -1,152 +0,0 @@ -import numpy as np -import pytest - -from embedchain.config.evaluation.base import GroundednessConfig -from embedchain.evaluation.metrics import Groundedness -from embedchain.utils.evaluation import EvalData, EvalMetric - - -@pytest.fixture -def mock_data(): - return [ - EvalData( - contexts=[ - "This is a test context 1.", - ], - question="This is a test question 1.", - answer="This is a test answer 1.", - ), - EvalData( - contexts=[ - "This is a test context 2-1.", - "This is a test context 2-2.", - ], - question="This is a test question 2.", - answer="This is a test answer 2.", - ), - ] - - -@pytest.fixture -def mock_groundedness_metric(monkeypatch): - monkeypatch.setenv("OPENAI_API_KEY", "test_api_key") - metric = Groundedness() - return metric - - -def test_groundedness_init(monkeypatch): - monkeypatch.setenv("OPENAI_API_KEY", "test_api_key") - metric = Groundedness() - assert metric.name == EvalMetric.GROUNDEDNESS.value - assert metric.config.model == "gpt-4" - assert metric.config.api_key is None - monkeypatch.delenv("OPENAI_API_KEY") - - -def test_groundedness_init_with_config(): - metric = Groundedness(config=GroundednessConfig(api_key="test_api_key")) - assert metric.name == EvalMetric.GROUNDEDNESS.value - assert metric.config.model == "gpt-4" - assert metric.config.api_key == "test_api_key" - - -def test_groundedness_init_without_api_key(monkeypatch): - monkeypatch.delenv("OPENAI_API_KEY", raising=False) - with pytest.raises(ValueError): - Groundedness() - - -def test_generate_answer_claim_prompt(mock_groundedness_metric, mock_data): - prompt = mock_groundedness_metric._generate_answer_claim_prompt(data=mock_data[0]) - assert "This is a test question 1." in prompt - assert "This is a test answer 1." in prompt - - -def test_get_claim_statements(mock_groundedness_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_groundedness_metric.client.chat.completions, - "create", - lambda *args, **kwargs: type( - "obj", - (object,), - { - "choices": [ - type( - "obj", - (object,), - { - "message": type( - "obj", - (object,), - { - "content": """This is a test answer 1. - This is a test answer 2. - This is a test answer 3.""" - }, - ) - }, - ) - ] - }, - )(), - ) - prompt = mock_groundedness_metric._generate_answer_claim_prompt(data=mock_data[0]) - claim_statements = mock_groundedness_metric._get_claim_statements(prompt=prompt) - assert len(claim_statements) == 3 - assert "This is a test answer 1." in claim_statements - - -def test_generate_claim_inference_prompt(mock_groundedness_metric, mock_data): - prompt = mock_groundedness_metric._generate_answer_claim_prompt(data=mock_data[0]) - claim_statements = [ - "This is a test claim 1.", - "This is a test claim 2.", - ] - prompt = mock_groundedness_metric._generate_claim_inference_prompt( - data=mock_data[0], claim_statements=claim_statements - ) - assert "This is a test context 1." in prompt - assert "This is a test claim 1." in prompt - - -def test_get_claim_verdict_scores(mock_groundedness_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_groundedness_metric.client.chat.completions, - "create", - lambda *args, **kwargs: type( - "obj", - (object,), - {"choices": [type("obj", (object,), {"message": type("obj", (object,), {"content": "1\n0\n-1"})})]}, - )(), - ) - prompt = mock_groundedness_metric._generate_answer_claim_prompt(data=mock_data[0]) - claim_statements = mock_groundedness_metric._get_claim_statements(prompt=prompt) - prompt = mock_groundedness_metric._generate_claim_inference_prompt( - data=mock_data[0], claim_statements=claim_statements - ) - claim_verdict_scores = mock_groundedness_metric._get_claim_verdict_scores(prompt=prompt) - assert len(claim_verdict_scores) == 3 - assert claim_verdict_scores[0] == 1 - assert claim_verdict_scores[1] == 0 - - -def test_compute_score(mock_groundedness_metric, mock_data, monkeypatch): - monkeypatch.setattr( - mock_groundedness_metric, - "_get_claim_statements", - lambda *args, **kwargs: np.array( - [ - "This is a test claim 1.", - "This is a test claim 2.", - ] - ), - ) - monkeypatch.setattr(mock_groundedness_metric, "_get_claim_verdict_scores", lambda *args, **kwargs: np.array([1, 0])) - score = mock_groundedness_metric._compute_score(data=mock_data[0]) - assert score == 0.5 - - -def test_evaluate(mock_groundedness_metric, mock_data, monkeypatch): - monkeypatch.setattr(mock_groundedness_metric, "_compute_score", lambda *args, **kwargs: 0.5) - score = mock_groundedness_metric.evaluate(dataset=mock_data) - assert score == 0.5 diff --git a/embedchain/tests/helper_classes/test_json_serializable.py b/embedchain/tests/helper_classes/test_json_serializable.py deleted file mode 100644 index e68e05075..000000000 --- a/embedchain/tests/helper_classes/test_json_serializable.py +++ /dev/null @@ -1,81 +0,0 @@ -import random -import unittest -from string import Template - -from embedchain import App -from embedchain.config import AppConfig, BaseLlmConfig -from embedchain.helpers.json_serializable import ( - JSONSerializable, - register_deserializable, -) - - -class TestJsonSerializable(unittest.TestCase): - """Test that the datatype detection is working, based on the input.""" - - def test_base_function(self): - """Test that the base premise of serialization and deserealization is working""" - - @register_deserializable - class TestClass(JSONSerializable): - def __init__(self): - self.rng = random.random() - - original_class = TestClass() - serial = original_class.serialize() - - # Negative test to show that a new class does not have the same random number. - negative_test_class = TestClass() - self.assertNotEqual(original_class.rng, negative_test_class.rng) - - # Test to show that a deserialized class has the same random number. - positive_test_class: TestClass = TestClass().deserialize(serial) - self.assertEqual(original_class.rng, positive_test_class.rng) - self.assertTrue(isinstance(positive_test_class, TestClass)) - - # Test that it works as a static method too. - positive_test_class: TestClass = TestClass.deserialize(serial) - self.assertEqual(original_class.rng, positive_test_class.rng) - - # TODO: There's no reason it shouldn't work, but serialization to and from file should be tested too. - - def test_registration_required(self): - """Test that registration is required, and that without registration the default class is returned.""" - - class SecondTestClass(JSONSerializable): - def __init__(self): - self.default = True - - app = SecondTestClass() - # Make not default - app.default = False - # Serialize - serial = app.serialize() - # Deserialize. Due to the way errors are handled, it will not fail but return a default class. - app: SecondTestClass = SecondTestClass().deserialize(serial) - self.assertTrue(app.default) - # If we register and try again with the same serial, it should work - SecondTestClass._register_class_as_deserializable(SecondTestClass) - app: SecondTestClass = SecondTestClass().deserialize(serial) - self.assertFalse(app.default) - - def test_recursive(self): - """Test recursiveness with the real app""" - random_id = str(random.random()) - config = AppConfig(id=random_id, collect_metrics=False) - # config class is set under app.config. - app = App(config=config) - s = app.serialize() - new_app: App = App.deserialize(s) - # The id of the new app is the same as the first one. - self.assertEqual(random_id, new_app.config.id) - # We have proven that a nested class (app.config) can be serialized and deserialized just the same. - # TODO: test deeper recursion - - def test_special_subclasses(self): - """Test special subclasses that are not serializable by default.""" - # Template - config = BaseLlmConfig(template=Template("My custom template with $query, $context and $history.")) - s = config.serialize() - new_config: BaseLlmConfig = BaseLlmConfig.deserialize(s) - self.assertEqual(config.prompt.template, new_config.prompt.template) diff --git a/embedchain/tests/llm/conftest.py b/embedchain/tests/llm/conftest.py deleted file mode 100644 index 6e3da3d5d..000000000 --- a/embedchain/tests/llm/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ - -from unittest import mock - -import pytest - - -@pytest.fixture(autouse=True) -def mock_alembic_command_upgrade(): - with mock.patch("alembic.command.upgrade"): - yield diff --git a/embedchain/tests/llm/test_anthrophic.py b/embedchain/tests/llm/test_anthrophic.py deleted file mode 100644 index fbf58d04d..000000000 --- a/embedchain/tests/llm/test_anthrophic.py +++ /dev/null @@ -1,54 +0,0 @@ -import os -from unittest.mock import patch - -import pytest -from langchain.schema import HumanMessage, SystemMessage - -from embedchain.config import BaseLlmConfig -from embedchain.llm.anthropic import AnthropicLlm - - -@pytest.fixture -def anthropic_llm(): - os.environ["ANTHROPIC_API_KEY"] = "test_api_key" - config = BaseLlmConfig(temperature=0.5, model="claude-instant-1", token_usage=False) - return AnthropicLlm(config) - - -def test_get_llm_model_answer(anthropic_llm): - with patch.object(AnthropicLlm, "_get_answer", return_value="Test Response") as mock_method: - prompt = "Test Prompt" - response = anthropic_llm.get_llm_model_answer(prompt) - assert response == "Test Response" - mock_method.assert_called_once_with(prompt, anthropic_llm.config) - - -def test_get_messages(anthropic_llm): - prompt = "Test Prompt" - system_prompt = "Test System Prompt" - messages = anthropic_llm._get_messages(prompt, system_prompt) - assert messages == [ - SystemMessage(content="Test System Prompt", additional_kwargs={}), - HumanMessage(content="Test Prompt", additional_kwargs={}, example=False), - ] - - -def test_get_llm_model_answer_with_token_usage(anthropic_llm): - test_config = BaseLlmConfig( - temperature=anthropic_llm.config.temperature, model=anthropic_llm.config.model, token_usage=True - ) - anthropic_llm.config = test_config - with patch.object( - AnthropicLlm, "_get_answer", return_value=("Test Response", {"input_tokens": 1, "output_tokens": 2}) - ) as mock_method: - prompt = "Test Prompt" - response, token_info = anthropic_llm.get_llm_model_answer(prompt) - assert response == "Test Response" - assert token_info == { - "prompt_tokens": 1, - "completion_tokens": 2, - "total_tokens": 3, - "total_cost": 1.265e-05, - "cost_currency": "USD", - } - mock_method.assert_called_once_with(prompt, anthropic_llm.config) diff --git a/embedchain/tests/llm/test_aws_bedrock.py b/embedchain/tests/llm/test_aws_bedrock.py deleted file mode 100644 index 440d8df3f..000000000 --- a/embedchain/tests/llm/test_aws_bedrock.py +++ /dev/null @@ -1,54 +0,0 @@ -import pytest -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler - -from embedchain.config import BaseLlmConfig -from embedchain.llm.aws_bedrock import AWSBedrockLlm - - -@pytest.fixture -def config(monkeypatch): - monkeypatch.setenv("AWS_ACCESS_KEY_ID", "test_access_key_id") - monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key") - config = BaseLlmConfig( - model="amazon.titan-text-express-v1", - model_kwargs={ - "temperature": 0.5, - "topP": 1, - "maxTokenCount": 1000, - }, - ) - yield config - monkeypatch.delenv("AWS_ACCESS_KEY_ID") - monkeypatch.delenv("AWS_SECRET_ACCESS_KEY") - - -def test_get_llm_model_answer(config, mocker): - mocked_get_answer = mocker.patch("embedchain.llm.aws_bedrock.AWSBedrockLlm._get_answer", return_value="Test answer") - - llm = AWSBedrockLlm(config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("Test query", config) - - -def test_get_llm_model_answer_empty_prompt(config, mocker): - mocked_get_answer = mocker.patch("embedchain.llm.aws_bedrock.AWSBedrockLlm._get_answer", return_value="Test answer") - - llm = AWSBedrockLlm(config) - answer = llm.get_llm_model_answer("") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("", config) - - -def test_get_llm_model_answer_with_streaming(config, mocker): - config.stream = True - mocked_bedrock_chat = mocker.patch("embedchain.llm.aws_bedrock.BedrockLLM") - - llm = AWSBedrockLlm(config) - llm.get_llm_model_answer("Test query") - - mocked_bedrock_chat.assert_called_once() - callbacks = [callback[1]["callbacks"] for callback in mocked_bedrock_chat.call_args_list] - assert any(isinstance(callback[0], StreamingStdOutCallbackHandler) for callback in callbacks) diff --git a/embedchain/tests/llm/test_azure_openai.py b/embedchain/tests/llm/test_azure_openai.py deleted file mode 100644 index 605b8f389..000000000 --- a/embedchain/tests/llm/test_azure_openai.py +++ /dev/null @@ -1,164 +0,0 @@ -from unittest.mock import MagicMock, Mock, patch - -import httpx -import pytest -from langchain.schema import HumanMessage, SystemMessage - -from embedchain.config import BaseLlmConfig -from embedchain.llm.azure_openai import AzureOpenAILlm - - -@pytest.fixture -def azure_openai_llm(): - config = BaseLlmConfig( - deployment_name="azure_deployment", - temperature=0.7, - model="gpt-4o-mini", - max_tokens=50, - system_prompt="System Prompt", - ) - return AzureOpenAILlm(config) - - -def test_get_llm_model_answer(azure_openai_llm): - with patch.object(AzureOpenAILlm, "_get_answer", return_value="Test Response") as mock_method: - prompt = "Test Prompt" - response = azure_openai_llm.get_llm_model_answer(prompt) - assert response == "Test Response" - mock_method.assert_called_once_with(prompt=prompt, config=azure_openai_llm.config) - - -def test_get_answer(azure_openai_llm): - with patch("langchain_openai.AzureChatOpenAI") as mock_chat: - mock_chat_instance = mock_chat.return_value - mock_chat_instance.invoke.return_value = MagicMock(content="Test Response") - - prompt = "Test Prompt" - response = azure_openai_llm._get_answer(prompt, azure_openai_llm.config) - - assert response == "Test Response" - mock_chat.assert_called_once_with( - deployment_name=azure_openai_llm.config.deployment_name, - openai_api_version="2024-02-01", - model_name=azure_openai_llm.config.model or "gpt-4o-mini", - temperature=azure_openai_llm.config.temperature, - max_tokens=azure_openai_llm.config.max_tokens, - streaming=azure_openai_llm.config.stream, - http_client=None, - http_async_client=None, - ) - - -def test_get_messages(azure_openai_llm): - prompt = "Test Prompt" - system_prompt = "Test System Prompt" - messages = azure_openai_llm._get_messages(prompt, system_prompt) - assert messages == [ - SystemMessage(content="Test System Prompt", additional_kwargs={}), - HumanMessage(content="Test Prompt", additional_kwargs={}, example=False), - ] - - -def test_when_no_deployment_name_provided(): - config = BaseLlmConfig(temperature=0.7, model="gpt-4o-mini", max_tokens=50, system_prompt="System Prompt") - with pytest.raises(ValueError): - llm = AzureOpenAILlm(config) - llm.get_llm_model_answer("Test Prompt") - - -def test_with_api_version(): - config = BaseLlmConfig( - deployment_name="azure_deployment", - temperature=0.7, - model="gpt-4o-mini", - max_tokens=50, - system_prompt="System Prompt", - api_version="2024-02-01", - ) - - with patch("langchain_openai.AzureChatOpenAI") as mock_chat: - llm = AzureOpenAILlm(config) - llm.get_llm_model_answer("Test Prompt") - - mock_chat.assert_called_once_with( - deployment_name="azure_deployment", - openai_api_version="2024-02-01", - model_name="gpt-4o-mini", - temperature=0.7, - max_tokens=50, - streaming=False, - http_client=None, - http_async_client=None, - ) - - -def test_get_llm_model_answer_with_http_client_proxies(): - mock_http_client = Mock(spec=httpx.Client) - mock_http_client_instance = Mock(spec=httpx.Client) - mock_http_client.return_value = mock_http_client_instance - - with patch("langchain_openai.AzureChatOpenAI") as mock_chat, patch( - "httpx.Client", new=mock_http_client - ) as mock_http_client: - mock_chat.return_value.invoke.return_value.content = "Mocked response" - - config = BaseLlmConfig( - deployment_name="azure_deployment", - temperature=0.7, - max_tokens=50, - stream=False, - system_prompt="System prompt", - model="gpt-4o-mini", - http_client_proxies="http://testproxy.mem0.net:8000", - ) - - llm = AzureOpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mock_chat.assert_called_once_with( - deployment_name="azure_deployment", - openai_api_version="2024-02-01", - model_name="gpt-4o-mini", - temperature=0.7, - max_tokens=50, - streaming=False, - http_client=mock_http_client_instance, - http_async_client=None, - ) - mock_http_client.assert_called_once_with(proxies="http://testproxy.mem0.net:8000") - - -def test_get_llm_model_answer_with_http_async_client_proxies(): - mock_http_async_client = Mock(spec=httpx.AsyncClient) - mock_http_async_client_instance = Mock(spec=httpx.AsyncClient) - mock_http_async_client.return_value = mock_http_async_client_instance - - with patch("langchain_openai.AzureChatOpenAI") as mock_chat, patch( - "httpx.AsyncClient", new=mock_http_async_client - ) as mock_http_async_client: - mock_chat.return_value.invoke.return_value.content = "Mocked response" - - config = BaseLlmConfig( - deployment_name="azure_deployment", - temperature=0.7, - max_tokens=50, - stream=False, - system_prompt="System prompt", - model="gpt-4o-mini", - http_async_client_proxies={"http://": "http://testproxy.mem0.net:8000"}, - ) - - llm = AzureOpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mock_chat.assert_called_once_with( - deployment_name="azure_deployment", - openai_api_version="2024-02-01", - model_name="gpt-4o-mini", - temperature=0.7, - max_tokens=50, - streaming=False, - http_client=None, - http_async_client=mock_http_async_client_instance, - ) - mock_http_async_client.assert_called_once_with(proxies={"http://": "http://testproxy.mem0.net:8000"}) diff --git a/embedchain/tests/llm/test_base_llm.py b/embedchain/tests/llm/test_base_llm.py deleted file mode 100644 index 61e0ee01a..000000000 --- a/embedchain/tests/llm/test_base_llm.py +++ /dev/null @@ -1,61 +0,0 @@ -from string import Template - -import pytest - -from embedchain.llm.base import BaseLlm, BaseLlmConfig - - -@pytest.fixture -def base_llm(): - config = BaseLlmConfig() - return BaseLlm(config=config) - - -def test_is_get_llm_model_answer_not_implemented(base_llm): - with pytest.raises(NotImplementedError): - base_llm.get_llm_model_answer() - - -def test_is_stream_bool(): - with pytest.raises(ValueError): - config = BaseLlmConfig(stream="test value") - BaseLlm(config=config) - - -def test_template_string_gets_converted_to_Template_instance(): - config = BaseLlmConfig(template="test value $query $context") - llm = BaseLlm(config=config) - assert isinstance(llm.config.prompt, Template) - - -def test_is_get_llm_model_answer_implemented(): - class TestLlm(BaseLlm): - def get_llm_model_answer(self): - return "Implemented" - - config = BaseLlmConfig() - llm = TestLlm(config=config) - assert llm.get_llm_model_answer() == "Implemented" - - -def test_stream_response(base_llm): - answer = ["Chunk1", "Chunk2", "Chunk3"] - result = list(base_llm._stream_response(answer)) - assert result == answer - - -def test_append_search_and_context(base_llm): - context = "Context" - web_search_result = "Web Search Result" - result = base_llm._append_search_and_context(context, web_search_result) - expected_result = "Context\nWeb Search Result: Web Search Result" - assert result == expected_result - - -def test_access_search_and_get_results(base_llm, mocker): - base_llm.access_search_and_get_results = mocker.patch.object( - base_llm, "access_search_and_get_results", return_value="Search Results" - ) - input_query = "Test query" - result = base_llm.access_search_and_get_results(input_query) - assert result == "Search Results" diff --git a/embedchain/tests/llm/test_chat.py b/embedchain/tests/llm/test_chat.py deleted file mode 100644 index 991a0adf0..000000000 --- a/embedchain/tests/llm/test_chat.py +++ /dev/null @@ -1,120 +0,0 @@ -import os -import unittest -from unittest.mock import MagicMock, patch - -from embedchain import App -from embedchain.config import AppConfig, BaseLlmConfig -from embedchain.llm.base import BaseLlm -from embedchain.memory.base import ChatHistory -from embedchain.memory.message import ChatMessage - - -class TestApp(unittest.TestCase): - def setUp(self): - os.environ["OPENAI_API_KEY"] = "test_key" - self.app = App(config=AppConfig(collect_metrics=False)) - - @patch.object(App, "_retrieve_from_database", return_value=["Test context"]) - @patch.object(BaseLlm, "get_answer_from_llm", return_value="Test answer") - def test_chat_with_memory(self, mock_get_answer, mock_retrieve): - """ - This test checks the functionality of the 'chat' method in the App class with respect to the chat history - memory. - The 'chat' method is called twice. The first call initializes the chat history memory. - The second call is expected to use the chat history from the first call. - - Key assumptions tested: - called with correct arguments, adding the correct chat history. - - After the first call, 'memory.chat_memory.add_user_message' and 'memory.chat_memory.add_ai_message' are - - During the second call, the 'chat' method uses the chat history from the first call. - - The test isolates the 'chat' method behavior by mocking out '_retrieve_from_database', 'get_answer_from_llm' and - 'memory' methods. - """ - config = AppConfig(collect_metrics=False) - app = App(config=config) - with patch.object(BaseLlm, "add_history") as mock_history: - first_answer = app.chat("Test query 1") - self.assertEqual(first_answer, "Test answer") - mock_history.assert_called_with(app.config.id, "Test query 1", "Test answer", session_id="default") - - second_answer = app.chat("Test query 2", session_id="test_session") - self.assertEqual(second_answer, "Test answer") - mock_history.assert_called_with(app.config.id, "Test query 2", "Test answer", session_id="test_session") - - @patch.object(App, "_retrieve_from_database", return_value=["Test context"]) - @patch.object(BaseLlm, "get_answer_from_llm", return_value="Test answer") - def test_template_replacement(self, mock_get_answer, mock_retrieve): - """ - Tests that if a default template is used and it doesn't contain history, - the default template is swapped in. - - Also tests that a dry run does not change the history - """ - with patch.object(ChatHistory, "get") as mock_memory: - mock_message = ChatMessage() - mock_message.add_user_message("Test query 1") - mock_message.add_ai_message("Test answer") - mock_memory.return_value = [mock_message] - - config = AppConfig(collect_metrics=False) - app = App(config=config) - first_answer = app.chat("Test query 1") - self.assertEqual(first_answer, "Test answer") - self.assertEqual(len(app.llm.history), 1) - history = app.llm.history - dry_run = app.chat("Test query 2", dry_run=True) - self.assertIn("Conversation history:", dry_run) - self.assertEqual(history, app.llm.history) - self.assertEqual(len(app.llm.history), 1) - - @patch("chromadb.api.models.Collection.Collection.add", MagicMock) - def test_chat_with_where_in_params(self): - """ - Test where filter - """ - with patch.object(self.app, "_retrieve_from_database") as mock_retrieve: - mock_retrieve.return_value = ["Test context"] - with patch.object(self.app.llm, "get_llm_model_answer") as mock_answer: - mock_answer.return_value = "Test answer" - answer = self.app.chat("Test query", where={"attribute": "value"}) - - self.assertEqual(answer, "Test answer") - _args, kwargs = mock_retrieve.call_args - self.assertEqual(kwargs.get("input_query"), "Test query") - self.assertEqual(kwargs.get("where"), {"attribute": "value"}) - mock_answer.assert_called_once() - - @patch("chromadb.api.models.Collection.Collection.add", MagicMock) - def test_chat_with_where_in_chat_config(self): - """ - This test checks the functionality of the 'chat' method in the App class. - It simulates a scenario where the '_retrieve_from_database' method returns a context list based on - a where filter and 'get_llm_model_answer' returns an expected answer string. - - The 'chat' method is expected to call '_retrieve_from_database' with the where filter specified - in the BaseLlmConfig and 'get_llm_model_answer' methods appropriately and return the right answer. - - Key assumptions tested: - - '_retrieve_from_database' method is called exactly once with arguments: "Test query" and an instance of - BaseLlmConfig. - - 'get_llm_model_answer' is called exactly once. The specific arguments are not checked in this test. - - 'chat' method returns the value it received from 'get_llm_model_answer'. - - The test isolates the 'chat' method behavior by mocking out '_retrieve_from_database' and - 'get_llm_model_answer' methods. - """ - with patch.object(self.app.llm, "get_llm_model_answer") as mock_answer: - mock_answer.return_value = "Test answer" - with patch.object(self.app.db, "query") as mock_database_query: - mock_database_query.return_value = ["Test context"] - llm_config = BaseLlmConfig(where={"attribute": "value"}) - answer = self.app.chat("Test query", llm_config) - - self.assertEqual(answer, "Test answer") - _args, kwargs = mock_database_query.call_args - self.assertEqual(kwargs.get("input_query"), "Test query") - where = kwargs.get("where") - assert "app_id" in where - assert "attribute" in where - mock_answer.assert_called_once() diff --git a/embedchain/tests/llm/test_clarifai.py b/embedchain/tests/llm/test_clarifai.py deleted file mode 100644 index 884fe63da..000000000 --- a/embedchain/tests/llm/test_clarifai.py +++ /dev/null @@ -1,23 +0,0 @@ - -import pytest - -from embedchain.config import BaseLlmConfig -from embedchain.llm.clarifai import ClarifaiLlm - - -@pytest.fixture -def clarifai_llm_config(monkeypatch): - monkeypatch.setenv("CLARIFAI_PAT","test_api_key") - config = BaseLlmConfig( - model="https://clarifai.com/openai/chat-completion/models/GPT-4", - model_kwargs={"temperature": 0.7, "max_tokens": 100}, - ) - yield config - monkeypatch.delenv("CLARIFAI_PAT") - -def test_clarifai__llm_get_llm_model_answer(clarifai_llm_config, mocker): - mocker.patch("embedchain.llm.clarifai.ClarifaiLlm._get_answer", return_value="Test answer") - llm = ClarifaiLlm(clarifai_llm_config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" diff --git a/embedchain/tests/llm/test_cohere.py b/embedchain/tests/llm/test_cohere.py deleted file mode 100644 index 20068f16c..000000000 --- a/embedchain/tests/llm/test_cohere.py +++ /dev/null @@ -1,73 +0,0 @@ -import os - -import pytest - -from embedchain.config import BaseLlmConfig -from embedchain.llm.cohere import CohereLlm - - -@pytest.fixture -def cohere_llm_config(): - os.environ["COHERE_API_KEY"] = "test_api_key" - config = BaseLlmConfig(model="command-r", max_tokens=100, temperature=0.7, top_p=0.8, token_usage=False) - yield config - os.environ.pop("COHERE_API_KEY") - - -def test_init_raises_value_error_without_api_key(mocker): - mocker.patch.dict(os.environ, clear=True) - with pytest.raises(ValueError): - CohereLlm() - - -def test_get_llm_model_answer_raises_value_error_for_system_prompt(cohere_llm_config): - llm = CohereLlm(cohere_llm_config) - llm.config.system_prompt = "system_prompt" - with pytest.raises(ValueError): - llm.get_llm_model_answer("prompt") - - -def test_get_llm_model_answer(cohere_llm_config, mocker): - mocker.patch("embedchain.llm.cohere.CohereLlm._get_answer", return_value="Test answer") - - llm = CohereLlm(cohere_llm_config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - - -def test_get_llm_model_answer_with_token_usage(cohere_llm_config, mocker): - test_config = BaseLlmConfig( - temperature=cohere_llm_config.temperature, - max_tokens=cohere_llm_config.max_tokens, - top_p=cohere_llm_config.top_p, - model=cohere_llm_config.model, - token_usage=True, - ) - mocker.patch( - "embedchain.llm.cohere.CohereLlm._get_answer", - return_value=("Test answer", {"input_tokens": 1, "output_tokens": 2}), - ) - - llm = CohereLlm(test_config) - answer, token_info = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - assert token_info == { - "prompt_tokens": 1, - "completion_tokens": 2, - "total_tokens": 3, - "total_cost": 3.5e-06, - "cost_currency": "USD", - } - - -def test_get_answer_mocked_cohere(cohere_llm_config, mocker): - mocked_cohere = mocker.patch("embedchain.llm.cohere.ChatCohere") - mocked_cohere.return_value.invoke.return_value.content = "Mocked answer" - - llm = CohereLlm(cohere_llm_config) - prompt = "Test query" - answer = llm.get_llm_model_answer(prompt) - - assert answer == "Mocked answer" diff --git a/embedchain/tests/llm/test_generate_prompt.py b/embedchain/tests/llm/test_generate_prompt.py deleted file mode 100644 index e283c3f81..000000000 --- a/embedchain/tests/llm/test_generate_prompt.py +++ /dev/null @@ -1,70 +0,0 @@ -import unittest -from string import Template - -from embedchain import App -from embedchain.config import AppConfig, BaseLlmConfig - - -class TestGeneratePrompt(unittest.TestCase): - def setUp(self): - self.app = App(config=AppConfig(collect_metrics=False)) - - def test_generate_prompt_with_template(self): - """ - Tests that the generate_prompt method correctly formats the prompt using - a custom template provided in the BaseLlmConfig instance. - - This test sets up a scenario with an input query and a list of contexts, - and a custom template, and then calls generate_prompt. It checks that the - returned prompt correctly incorporates all the contexts and the query into - the format specified by the template. - """ - # Setup - input_query = "Test query" - contexts = ["Context 1", "Context 2", "Context 3"] - template = "You are a bot. Context: ${context} - Query: ${query} - Helpful answer:" - config = BaseLlmConfig(template=Template(template)) - self.app.llm.config = config - - # Execute - result = self.app.llm.generate_prompt(input_query, contexts) - - # Assert - expected_result = ( - "You are a bot. Context: Context 1 | Context 2 | Context 3 - Query: Test query - Helpful answer:" - ) - self.assertEqual(result, expected_result) - - def test_generate_prompt_with_contexts_list(self): - """ - Tests that the generate_prompt method correctly handles a list of contexts. - - This test sets up a scenario with an input query and a list of contexts, - and then calls generate_prompt. It checks that the returned prompt - correctly includes all the contexts and the query. - """ - # Setup - input_query = "Test query" - contexts = ["Context 1", "Context 2", "Context 3"] - config = BaseLlmConfig() - - # Execute - self.app.llm.config = config - result = self.app.llm.generate_prompt(input_query, contexts) - - # Assert - expected_result = config.prompt.substitute(context="Context 1 | Context 2 | Context 3", query=input_query) - self.assertEqual(result, expected_result) - - def test_generate_prompt_with_history(self): - """ - Test the 'generate_prompt' method with BaseLlmConfig containing a history attribute. - """ - config = BaseLlmConfig() - config.prompt = Template("Context: $context | Query: $query | History: $history") - self.app.llm.config = config - self.app.llm.set_history(["Past context 1", "Past context 2"]) - prompt = self.app.llm.generate_prompt("Test query", ["Test context"]) - - expected_prompt = "Context: Test context | Query: Test query | History: Past context 1\nPast context 2" - self.assertEqual(prompt, expected_prompt) diff --git a/embedchain/tests/llm/test_google.py b/embedchain/tests/llm/test_google.py deleted file mode 100644 index d2ba301e6..000000000 --- a/embedchain/tests/llm/test_google.py +++ /dev/null @@ -1,43 +0,0 @@ -import pytest - -from embedchain.config import BaseLlmConfig -from embedchain.llm.google import GoogleLlm - - -@pytest.fixture -def google_llm_config(): - return BaseLlmConfig(model="gemini-pro", max_tokens=100, temperature=0.7, top_p=0.5, stream=False) - - -def test_google_llm_init_missing_api_key(monkeypatch): - monkeypatch.delenv("GOOGLE_API_KEY", raising=False) - with pytest.raises(ValueError, match="Please set the GOOGLE_API_KEY environment variable."): - GoogleLlm() - - -def test_google_llm_init(monkeypatch): - monkeypatch.setenv("GOOGLE_API_KEY", "fake_api_key") - with monkeypatch.context() as m: - m.setattr("importlib.import_module", lambda x: None) - google_llm = GoogleLlm() - assert google_llm is not None - - -def test_google_llm_get_llm_model_answer_with_system_prompt(monkeypatch): - monkeypatch.setenv("GOOGLE_API_KEY", "fake_api_key") - monkeypatch.setattr("importlib.import_module", lambda x: None) - google_llm = GoogleLlm(config=BaseLlmConfig(system_prompt="system prompt")) - with pytest.raises(ValueError, match="GoogleLlm does not support `system_prompt`"): - google_llm.get_llm_model_answer("test prompt") - - -def test_google_llm_get_llm_model_answer(monkeypatch, google_llm_config): - def mock_get_answer(prompt, config): - return "Generated Text" - - monkeypatch.setenv("GOOGLE_API_KEY", "fake_api_key") - monkeypatch.setattr(GoogleLlm, "_get_answer", mock_get_answer) - google_llm = GoogleLlm(config=google_llm_config) - result = google_llm.get_llm_model_answer("test prompt") - - assert result == "Generated Text" diff --git a/embedchain/tests/llm/test_gpt4all.py b/embedchain/tests/llm/test_gpt4all.py deleted file mode 100644 index a65f5aa5a..000000000 --- a/embedchain/tests/llm/test_gpt4all.py +++ /dev/null @@ -1,60 +0,0 @@ -import pytest -from langchain_community.llms.gpt4all import GPT4All as LangchainGPT4All - -from embedchain.config import BaseLlmConfig -from embedchain.llm.gpt4all import GPT4ALLLlm - - -@pytest.fixture -def config(): - config = BaseLlmConfig( - temperature=0.7, - max_tokens=50, - top_p=0.8, - stream=False, - system_prompt="System prompt", - model="orca-mini-3b-gguf2-q4_0.gguf", - ) - yield config - - -@pytest.fixture -def gpt4all_with_config(config): - return GPT4ALLLlm(config=config) - - -@pytest.fixture -def gpt4all_without_config(): - return GPT4ALLLlm() - - -def test_gpt4all_init_with_config(config, gpt4all_with_config): - assert gpt4all_with_config.config.temperature == config.temperature - assert gpt4all_with_config.config.max_tokens == config.max_tokens - assert gpt4all_with_config.config.top_p == config.top_p - assert gpt4all_with_config.config.stream == config.stream - assert gpt4all_with_config.config.system_prompt == config.system_prompt - assert gpt4all_with_config.config.model == config.model - - assert isinstance(gpt4all_with_config.instance, LangchainGPT4All) - - -def test_gpt4all_init_without_config(gpt4all_without_config): - assert gpt4all_without_config.config.model == "orca-mini-3b-gguf2-q4_0.gguf" - assert isinstance(gpt4all_without_config.instance, LangchainGPT4All) - - -def test_get_llm_model_answer(mocker, gpt4all_with_config): - test_query = "Test query" - test_answer = "Test answer" - - mocked_get_answer = mocker.patch("embedchain.llm.gpt4all.GPT4ALLLlm._get_answer", return_value=test_answer) - answer = gpt4all_with_config.get_llm_model_answer(test_query) - - assert answer == test_answer - mocked_get_answer.assert_called_once_with(prompt=test_query, config=gpt4all_with_config.config) - - -def test_gpt4all_model_switching(gpt4all_with_config): - with pytest.raises(RuntimeError, match="GPT4ALLLlm does not support switching models at runtime."): - gpt4all_with_config._get_answer("Test prompt", BaseLlmConfig(model="new_model")) diff --git a/embedchain/tests/llm/test_huggingface.py b/embedchain/tests/llm/test_huggingface.py deleted file mode 100644 index 754317f6b..000000000 --- a/embedchain/tests/llm/test_huggingface.py +++ /dev/null @@ -1,83 +0,0 @@ -import importlib -import os - -import pytest - -from embedchain.config import BaseLlmConfig -from embedchain.llm.huggingface import HuggingFaceLlm - - -@pytest.fixture -def huggingface_llm_config(): - os.environ["HUGGINGFACE_ACCESS_TOKEN"] = "test_access_token" - config = BaseLlmConfig(model="google/flan-t5-xxl", max_tokens=50, temperature=0.7, top_p=0.8) - yield config - os.environ.pop("HUGGINGFACE_ACCESS_TOKEN") - - -@pytest.fixture -def huggingface_endpoint_config(): - os.environ["HUGGINGFACE_ACCESS_TOKEN"] = "test_access_token" - config = BaseLlmConfig(endpoint="https://api-inference.huggingface.co/models/gpt2", model_kwargs={"device": "cpu"}) - yield config - os.environ.pop("HUGGINGFACE_ACCESS_TOKEN") - - -def test_init_raises_value_error_without_api_key(mocker): - mocker.patch.dict(os.environ, clear=True) - with pytest.raises(ValueError): - HuggingFaceLlm() - - -def test_get_llm_model_answer_raises_value_error_for_system_prompt(huggingface_llm_config): - llm = HuggingFaceLlm(huggingface_llm_config) - llm.config.system_prompt = "system_prompt" - with pytest.raises(ValueError): - llm.get_llm_model_answer("prompt") - - -def test_top_p_value_within_range(): - config = BaseLlmConfig(top_p=1.0) - with pytest.raises(ValueError): - HuggingFaceLlm._get_answer("test_prompt", config) - - -def test_dependency_is_imported(): - importlib_installed = True - try: - importlib.import_module("huggingface_hub") - except ImportError: - importlib_installed = False - assert importlib_installed - - -def test_get_llm_model_answer(huggingface_llm_config, mocker): - mocker.patch("embedchain.llm.huggingface.HuggingFaceLlm._get_answer", return_value="Test answer") - - llm = HuggingFaceLlm(huggingface_llm_config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - - -def test_hugging_face_mock(huggingface_llm_config, mocker): - mock_llm_instance = mocker.Mock(return_value="Test answer") - mock_hf_hub = mocker.patch("embedchain.llm.huggingface.HuggingFaceHub") - mock_hf_hub.return_value.invoke = mock_llm_instance - - llm = HuggingFaceLlm(huggingface_llm_config) - answer = llm.get_llm_model_answer("Test query") - assert answer == "Test answer" - mock_llm_instance.assert_called_once_with("Test query") - - -def test_custom_endpoint(huggingface_endpoint_config, mocker): - mock_llm_instance = mocker.Mock(return_value="Test answer") - mock_hf_endpoint = mocker.patch("embedchain.llm.huggingface.HuggingFaceEndpoint") - mock_hf_endpoint.return_value.invoke = mock_llm_instance - - llm = HuggingFaceLlm(huggingface_endpoint_config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - mock_llm_instance.assert_called_once_with("Test query") diff --git a/embedchain/tests/llm/test_jina.py b/embedchain/tests/llm/test_jina.py deleted file mode 100644 index 8df933222..000000000 --- a/embedchain/tests/llm/test_jina.py +++ /dev/null @@ -1,79 +0,0 @@ -import os - -import pytest -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler - -from embedchain.config import BaseLlmConfig -from embedchain.llm.jina import JinaLlm - - -@pytest.fixture -def config(): - os.environ["JINACHAT_API_KEY"] = "test_api_key" - config = BaseLlmConfig(temperature=0.7, max_tokens=50, top_p=0.8, stream=False, system_prompt="System prompt") - yield config - os.environ.pop("JINACHAT_API_KEY") - - -def test_init_raises_value_error_without_api_key(mocker): - mocker.patch.dict(os.environ, clear=True) - with pytest.raises(ValueError): - JinaLlm() - - -def test_get_llm_model_answer(config, mocker): - mocked_get_answer = mocker.patch("embedchain.llm.jina.JinaLlm._get_answer", return_value="Test answer") - - llm = JinaLlm(config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("Test query", config) - - -def test_get_llm_model_answer_with_system_prompt(config, mocker): - config.system_prompt = "Custom system prompt" - mocked_get_answer = mocker.patch("embedchain.llm.jina.JinaLlm._get_answer", return_value="Test answer") - - llm = JinaLlm(config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("Test query", config) - - -def test_get_llm_model_answer_empty_prompt(config, mocker): - mocked_get_answer = mocker.patch("embedchain.llm.jina.JinaLlm._get_answer", return_value="Test answer") - - llm = JinaLlm(config) - answer = llm.get_llm_model_answer("") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("", config) - - -def test_get_llm_model_answer_with_streaming(config, mocker): - config.stream = True - mocked_jinachat = mocker.patch("embedchain.llm.jina.JinaChat") - - llm = JinaLlm(config) - llm.get_llm_model_answer("Test query") - - mocked_jinachat.assert_called_once() - callbacks = [callback[1]["callbacks"] for callback in mocked_jinachat.call_args_list] - assert any(isinstance(callback[0], StreamingStdOutCallbackHandler) for callback in callbacks) - - -def test_get_llm_model_answer_without_system_prompt(config, mocker): - config.system_prompt = None - mocked_jinachat = mocker.patch("embedchain.llm.jina.JinaChat") - - llm = JinaLlm(config) - llm.get_llm_model_answer("Test query") - - mocked_jinachat.assert_called_once_with( - temperature=config.temperature, - max_tokens=config.max_tokens, - jinachat_api_key=os.environ["JINACHAT_API_KEY"], - model_kwargs={"top_p": config.top_p}, - ) diff --git a/embedchain/tests/llm/test_llama2.py b/embedchain/tests/llm/test_llama2.py deleted file mode 100644 index a9dd4049e..000000000 --- a/embedchain/tests/llm/test_llama2.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -import pytest - -from embedchain.llm.llama2 import Llama2Llm - - -@pytest.fixture -def llama2_llm(): - os.environ["REPLICATE_API_TOKEN"] = "test_api_token" - llm = Llama2Llm() - return llm - - -def test_init_raises_value_error_without_api_key(mocker): - mocker.patch.dict(os.environ, clear=True) - with pytest.raises(ValueError): - Llama2Llm() - - -def test_get_llm_model_answer_raises_value_error_for_system_prompt(llama2_llm): - llama2_llm.config.system_prompt = "system_prompt" - with pytest.raises(ValueError): - llama2_llm.get_llm_model_answer("prompt") - - -def test_get_llm_model_answer(llama2_llm, mocker): - mocked_replicate = mocker.patch("embedchain.llm.llama2.Replicate") - mocked_replicate_instance = mocker.MagicMock() - mocked_replicate.return_value = mocked_replicate_instance - mocked_replicate_instance.invoke.return_value = "Test answer" - - llama2_llm.config.model = "test_model" - llama2_llm.config.max_tokens = 50 - llama2_llm.config.temperature = 0.7 - llama2_llm.config.top_p = 0.8 - - answer = llama2_llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" diff --git a/embedchain/tests/llm/test_mistralai.py b/embedchain/tests/llm/test_mistralai.py deleted file mode 100644 index 9fc5e0873..000000000 --- a/embedchain/tests/llm/test_mistralai.py +++ /dev/null @@ -1,87 +0,0 @@ -import pytest - -from embedchain.config import BaseLlmConfig -from embedchain.llm.mistralai import MistralAILlm - - -@pytest.fixture -def mistralai_llm_config(monkeypatch): - monkeypatch.setenv("MISTRAL_API_KEY", "fake_api_key") - yield BaseLlmConfig(model="mistral-tiny", max_tokens=100, temperature=0.7, top_p=0.5, stream=False) - monkeypatch.delenv("MISTRAL_API_KEY", raising=False) - - -def test_mistralai_llm_init_missing_api_key(monkeypatch): - monkeypatch.delenv("MISTRAL_API_KEY", raising=False) - with pytest.raises(ValueError, match="Please set the MISTRAL_API_KEY environment variable."): - MistralAILlm() - - -def test_mistralai_llm_init(monkeypatch): - monkeypatch.setenv("MISTRAL_API_KEY", "fake_api_key") - llm = MistralAILlm() - assert llm is not None - - -def test_get_llm_model_answer(monkeypatch, mistralai_llm_config): - def mock_get_answer(self, prompt, config): - return "Generated Text" - - monkeypatch.setattr(MistralAILlm, "_get_answer", mock_get_answer) - llm = MistralAILlm(config=mistralai_llm_config) - result = llm.get_llm_model_answer("test prompt") - - assert result == "Generated Text" - - -def test_get_llm_model_answer_with_system_prompt(monkeypatch, mistralai_llm_config): - mistralai_llm_config.system_prompt = "Test system prompt" - monkeypatch.setattr(MistralAILlm, "_get_answer", lambda self, prompt, config: "Generated Text") - llm = MistralAILlm(config=mistralai_llm_config) - result = llm.get_llm_model_answer("test prompt") - - assert result == "Generated Text" - - -def test_get_llm_model_answer_empty_prompt(monkeypatch, mistralai_llm_config): - monkeypatch.setattr(MistralAILlm, "_get_answer", lambda self, prompt, config: "Generated Text") - llm = MistralAILlm(config=mistralai_llm_config) - result = llm.get_llm_model_answer("") - - assert result == "Generated Text" - - -def test_get_llm_model_answer_without_system_prompt(monkeypatch, mistralai_llm_config): - mistralai_llm_config.system_prompt = None - monkeypatch.setattr(MistralAILlm, "_get_answer", lambda self, prompt, config: "Generated Text") - llm = MistralAILlm(config=mistralai_llm_config) - result = llm.get_llm_model_answer("test prompt") - - assert result == "Generated Text" - - -def test_get_llm_model_answer_with_token_usage(monkeypatch, mistralai_llm_config): - test_config = BaseLlmConfig( - temperature=mistralai_llm_config.temperature, - max_tokens=mistralai_llm_config.max_tokens, - top_p=mistralai_llm_config.top_p, - model=mistralai_llm_config.model, - token_usage=True, - ) - monkeypatch.setattr( - MistralAILlm, - "_get_answer", - lambda self, prompt, config: ("Generated Text", {"prompt_tokens": 1, "completion_tokens": 2}), - ) - - llm = MistralAILlm(test_config) - answer, token_info = llm.get_llm_model_answer("Test query") - - assert answer == "Generated Text" - assert token_info == { - "prompt_tokens": 1, - "completion_tokens": 2, - "total_tokens": 3, - "total_cost": 7.5e-07, - "cost_currency": "USD", - } diff --git a/embedchain/tests/llm/test_ollama.py b/embedchain/tests/llm/test_ollama.py deleted file mode 100644 index b0d932635..000000000 --- a/embedchain/tests/llm/test_ollama.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler - -from embedchain.config import BaseLlmConfig -from embedchain.llm.ollama import OllamaLlm - - -@pytest.fixture -def ollama_llm_config(): - config = BaseLlmConfig(model="llama2", temperature=0.7, top_p=0.8, stream=True, system_prompt=None) - yield config - - -def test_get_llm_model_answer(ollama_llm_config, mocker): - mocker.patch("embedchain.llm.ollama.Client.list", return_value={"models": [{"name": "llama2"}]}) - mocker.patch("embedchain.llm.ollama.OllamaLlm._get_answer", return_value="Test answer") - - llm = OllamaLlm(ollama_llm_config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - - -def test_get_answer_mocked_ollama(ollama_llm_config, mocker): - mocker.patch("embedchain.llm.ollama.Client.list", return_value={"models": [{"name": "llama2"}]}) - mocked_ollama = mocker.patch("embedchain.llm.ollama.Ollama") - mock_instance = mocked_ollama.return_value - mock_instance.invoke.return_value = "Mocked answer" - - llm = OllamaLlm(ollama_llm_config) - prompt = "Test query" - answer = llm.get_llm_model_answer(prompt) - - assert answer == "Mocked answer" - - -def test_get_llm_model_answer_with_streaming(ollama_llm_config, mocker): - ollama_llm_config.stream = True - ollama_llm_config.callbacks = [StreamingStdOutCallbackHandler()] - mocker.patch("embedchain.llm.ollama.Client.list", return_value={"models": [{"name": "llama2"}]}) - mocked_ollama_chat = mocker.patch("embedchain.llm.ollama.OllamaLlm._get_answer", return_value="Test answer") - - llm = OllamaLlm(ollama_llm_config) - llm.get_llm_model_answer("Test query") - - mocked_ollama_chat.assert_called_once() - call_args = mocked_ollama_chat.call_args - config_arg = call_args[1]["config"] - callbacks = config_arg.callbacks - - assert len(callbacks) == 1 - assert isinstance(callbacks[0], StreamingStdOutCallbackHandler) diff --git a/embedchain/tests/llm/test_openai.py b/embedchain/tests/llm/test_openai.py deleted file mode 100644 index 5cff056ac..000000000 --- a/embedchain/tests/llm/test_openai.py +++ /dev/null @@ -1,267 +0,0 @@ -import os - -import httpx -import pytest -from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler - -from embedchain.config import BaseLlmConfig -from embedchain.llm.openai import OpenAILlm - - -@pytest.fixture() -def env_config(): - os.environ["OPENAI_API_KEY"] = "test_api_key" - os.environ["OPENAI_API_BASE"] = "https://api.openai.com/v1/engines/" - yield - os.environ.pop("OPENAI_API_KEY") - - -@pytest.fixture -def config(env_config): - config = BaseLlmConfig( - temperature=0.7, - max_tokens=50, - top_p=0.8, - stream=False, - system_prompt="System prompt", - model="gpt-4o-mini", - http_client_proxies=None, - http_async_client_proxies=None, - ) - yield config - - -def test_get_llm_model_answer(config, mocker): - mocked_get_answer = mocker.patch("embedchain.llm.openai.OpenAILlm._get_answer", return_value="Test answer") - - llm = OpenAILlm(config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("Test query", config) - - -def test_get_llm_model_answer_with_system_prompt(config, mocker): - config.system_prompt = "Custom system prompt" - mocked_get_answer = mocker.patch("embedchain.llm.openai.OpenAILlm._get_answer", return_value="Test answer") - - llm = OpenAILlm(config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("Test query", config) - - -def test_get_llm_model_answer_empty_prompt(config, mocker): - mocked_get_answer = mocker.patch("embedchain.llm.openai.OpenAILlm._get_answer", return_value="Test answer") - - llm = OpenAILlm(config) - answer = llm.get_llm_model_answer("") - - assert answer == "Test answer" - mocked_get_answer.assert_called_once_with("", config) - - -def test_get_llm_model_answer_with_token_usage(config, mocker): - test_config = BaseLlmConfig( - temperature=config.temperature, - max_tokens=config.max_tokens, - top_p=config.top_p, - stream=config.stream, - system_prompt=config.system_prompt, - model=config.model, - token_usage=True, - ) - mocked_get_answer = mocker.patch( - "embedchain.llm.openai.OpenAILlm._get_answer", - return_value=("Test answer", {"prompt_tokens": 1, "completion_tokens": 2}), - ) - - llm = OpenAILlm(test_config) - answer, token_info = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - assert token_info == { - "prompt_tokens": 1, - "completion_tokens": 2, - "total_tokens": 3, - "total_cost": 1.35e-06, - "cost_currency": "USD", - } - mocked_get_answer.assert_called_once_with("Test query", test_config) - - -def test_get_llm_model_answer_with_streaming(config, mocker): - config.stream = True - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - - llm = OpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once() - callbacks = [callback[1]["callbacks"] for callback in mocked_openai_chat.call_args_list] - assert any(isinstance(callback[0], StreamingStdOutCallbackHandler) for callback in callbacks) - - -def test_get_llm_model_answer_without_system_prompt(config, mocker): - config.system_prompt = None - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - - llm = OpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once_with( - model=config.model, - temperature=config.temperature, - max_tokens=config.max_tokens, - model_kwargs={}, - top_p= config.top_p, - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ["OPENAI_API_BASE"], - http_client=None, - http_async_client=None, - ) - - -def test_get_llm_model_answer_with_special_headers(config, mocker): - config.default_headers = {"test": "test"} - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - - llm = OpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once_with( - model=config.model, - temperature=config.temperature, - max_tokens=config.max_tokens, - model_kwargs={}, - top_p= config.top_p, - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ["OPENAI_API_BASE"], - default_headers={"test": "test"}, - http_client=None, - http_async_client=None, - ) - - -def test_get_llm_model_answer_with_model_kwargs(config, mocker): - config.model_kwargs = {"response_format": {"type": "json_object"}} - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - - llm = OpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once_with( - model=config.model, - temperature=config.temperature, - max_tokens=config.max_tokens, - model_kwargs={"response_format": {"type": "json_object"}}, - top_p=config.top_p, - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ["OPENAI_API_BASE"], - http_client=None, - http_async_client=None, - ) - - -@pytest.mark.parametrize( - "mock_return, expected", - [ - ([{"test": "test"}], '{"test": "test"}'), - ([], "Input could not be mapped to the function!"), - ], -) -def test_get_llm_model_answer_with_tools(config, mocker, mock_return, expected): - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - mocked_convert_to_openai_tool = mocker.patch("langchain_core.utils.function_calling.convert_to_openai_tool") - mocked_json_output_tools_parser = mocker.patch("langchain.output_parsers.openai_tools.JsonOutputToolsParser") - mocked_openai_chat.return_value.bind.return_value.pipe.return_value.invoke.return_value = mock_return - - llm = OpenAILlm(config, tools={"test": "test"}) - answer = llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once_with( - model=config.model, - temperature=config.temperature, - max_tokens=config.max_tokens, - model_kwargs={}, - top_p=config.top_p, - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ["OPENAI_API_BASE"], - http_client=None, - http_async_client=None, - ) - mocked_convert_to_openai_tool.assert_called_once_with({"test": "test"}) - mocked_json_output_tools_parser.assert_called_once() - - assert answer == expected - - -def test_get_llm_model_answer_with_http_client_proxies(env_config, mocker): - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - mock_http_client = mocker.Mock(spec=httpx.Client) - mock_http_client_instance = mocker.Mock(spec=httpx.Client) - mock_http_client.return_value = mock_http_client_instance - - mocker.patch("httpx.Client", new=mock_http_client) - - config = BaseLlmConfig( - temperature=0.7, - max_tokens=50, - top_p=0.8, - stream=False, - system_prompt="System prompt", - model="gpt-4o-mini", - http_client_proxies="http://testproxy.mem0.net:8000", - ) - - llm = OpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once_with( - model=config.model, - temperature=config.temperature, - max_tokens=config.max_tokens, - model_kwargs={}, - top_p=config.top_p, - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ["OPENAI_API_BASE"], - http_client=mock_http_client_instance, - http_async_client=None, - ) - mock_http_client.assert_called_once_with(proxies="http://testproxy.mem0.net:8000") - - -def test_get_llm_model_answer_with_http_async_client_proxies(env_config, mocker): - mocked_openai_chat = mocker.patch("embedchain.llm.openai.ChatOpenAI") - mock_http_async_client = mocker.Mock(spec=httpx.AsyncClient) - mock_http_async_client_instance = mocker.Mock(spec=httpx.AsyncClient) - mock_http_async_client.return_value = mock_http_async_client_instance - - mocker.patch("httpx.AsyncClient", new=mock_http_async_client) - - config = BaseLlmConfig( - temperature=0.7, - max_tokens=50, - top_p=0.8, - stream=False, - system_prompt="System prompt", - model="gpt-4o-mini", - http_async_client_proxies={"http://": "http://testproxy.mem0.net:8000"}, - ) - - llm = OpenAILlm(config) - llm.get_llm_model_answer("Test query") - - mocked_openai_chat.assert_called_once_with( - model=config.model, - temperature=config.temperature, - max_tokens=config.max_tokens, - model_kwargs={}, - top_p=config.top_p, - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ["OPENAI_API_BASE"], - http_client=None, - http_async_client=mock_http_async_client_instance, - ) - mock_http_async_client.assert_called_once_with(proxies={"http://": "http://testproxy.mem0.net:8000"}) diff --git a/embedchain/tests/llm/test_query.py b/embedchain/tests/llm/test_query.py deleted file mode 100644 index 2472110e8..000000000 --- a/embedchain/tests/llm/test_query.py +++ /dev/null @@ -1,79 +0,0 @@ -import os -from unittest.mock import MagicMock, patch - -import pytest - -from embedchain import App -from embedchain.config import AppConfig, BaseLlmConfig -from embedchain.llm.openai import OpenAILlm - - -@pytest.fixture -def app(): - os.environ["OPENAI_API_KEY"] = "test_api_key" - app = App(config=AppConfig(collect_metrics=False)) - return app - - -@patch("chromadb.api.models.Collection.Collection.add", MagicMock) -def test_query(app): - with patch.object(app, "_retrieve_from_database") as mock_retrieve: - mock_retrieve.return_value = ["Test context"] - with patch.object(app.llm, "get_llm_model_answer") as mock_answer: - mock_answer.return_value = "Test answer" - answer = app.query(input_query="Test query") - assert answer == "Test answer" - - mock_retrieve.assert_called_once() - _, kwargs = mock_retrieve.call_args - input_query_arg = kwargs.get("input_query") - assert input_query_arg == "Test query" - mock_answer.assert_called_once() - - -@patch("embedchain.llm.openai.OpenAILlm._get_answer") -def test_query_config_app_passing(mock_get_answer): - mock_get_answer.return_value = MagicMock() - mock_get_answer.return_value = "Test answer" - - config = AppConfig(collect_metrics=False) - chat_config = BaseLlmConfig(system_prompt="Test system prompt") - llm = OpenAILlm(config=chat_config) - app = App(config=config, llm=llm) - answer = app.llm.get_llm_model_answer("Test query") - - assert app.llm.config.system_prompt == "Test system prompt" - assert answer == "Test answer" - - -@patch("chromadb.api.models.Collection.Collection.add", MagicMock) -def test_query_with_where_in_params(app): - with patch.object(app, "_retrieve_from_database") as mock_retrieve: - mock_retrieve.return_value = ["Test context"] - with patch.object(app.llm, "get_llm_model_answer") as mock_answer: - mock_answer.return_value = "Test answer" - answer = app.query("Test query", where={"attribute": "value"}) - - assert answer == "Test answer" - _, kwargs = mock_retrieve.call_args - assert kwargs.get("input_query") == "Test query" - assert kwargs.get("where") == {"attribute": "value"} - mock_answer.assert_called_once() - - -@patch("chromadb.api.models.Collection.Collection.add", MagicMock) -def test_query_with_where_in_query_config(app): - with patch.object(app.llm, "get_llm_model_answer") as mock_answer: - mock_answer.return_value = "Test answer" - with patch.object(app.db, "query") as mock_database_query: - mock_database_query.return_value = ["Test context"] - llm_config = BaseLlmConfig(where={"attribute": "value"}) - answer = app.query("Test query", llm_config) - - assert answer == "Test answer" - _, kwargs = mock_database_query.call_args - assert kwargs.get("input_query") == "Test query" - where = kwargs.get("where") - assert "app_id" in where - assert "attribute" in where - mock_answer.assert_called_once() diff --git a/embedchain/tests/llm/test_together.py b/embedchain/tests/llm/test_together.py deleted file mode 100644 index 3e8b566dd..000000000 --- a/embedchain/tests/llm/test_together.py +++ /dev/null @@ -1,74 +0,0 @@ -import os - -import pytest - -from embedchain.config import BaseLlmConfig -from embedchain.llm.together import TogetherLlm - - -@pytest.fixture -def together_llm_config(): - os.environ["TOGETHER_API_KEY"] = "test_api_key" - config = BaseLlmConfig(model="together-ai-up-to-3b", max_tokens=50, temperature=0.7, top_p=0.8) - yield config - os.environ.pop("TOGETHER_API_KEY") - - -def test_init_raises_value_error_without_api_key(mocker): - mocker.patch.dict(os.environ, clear=True) - with pytest.raises(ValueError): - TogetherLlm() - - -def test_get_llm_model_answer_raises_value_error_for_system_prompt(together_llm_config): - llm = TogetherLlm(together_llm_config) - llm.config.system_prompt = "system_prompt" - with pytest.raises(ValueError): - llm.get_llm_model_answer("prompt") - - -def test_get_llm_model_answer(together_llm_config, mocker): - mocker.patch("embedchain.llm.together.TogetherLlm._get_answer", return_value="Test answer") - - llm = TogetherLlm(together_llm_config) - answer = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - - -def test_get_llm_model_answer_with_token_usage(together_llm_config, mocker): - test_config = BaseLlmConfig( - temperature=together_llm_config.temperature, - max_tokens=together_llm_config.max_tokens, - top_p=together_llm_config.top_p, - model=together_llm_config.model, - token_usage=True, - ) - mocker.patch( - "embedchain.llm.together.TogetherLlm._get_answer", - return_value=("Test answer", {"prompt_tokens": 1, "completion_tokens": 2}), - ) - - llm = TogetherLlm(test_config) - answer, token_info = llm.get_llm_model_answer("Test query") - - assert answer == "Test answer" - assert token_info == { - "prompt_tokens": 1, - "completion_tokens": 2, - "total_tokens": 3, - "total_cost": 3e-07, - "cost_currency": "USD", - } - - -def test_get_answer_mocked_together(together_llm_config, mocker): - mocked_together = mocker.patch("embedchain.llm.together.ChatTogether") - mock_instance = mocked_together.return_value - mock_instance.invoke.return_value.content = "Mocked answer" - - llm = TogetherLlm(together_llm_config) - prompt = "Test query" - answer = llm.get_llm_model_answer(prompt) - - assert answer == "Mocked answer" diff --git a/embedchain/tests/llm/test_vertex_ai.py b/embedchain/tests/llm/test_vertex_ai.py deleted file mode 100644 index 602c85a65..000000000 --- a/embedchain/tests/llm/test_vertex_ai.py +++ /dev/null @@ -1,76 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pytest -from langchain.schema import HumanMessage, SystemMessage - -from embedchain.config import BaseLlmConfig -from embedchain.core.db.database import database_manager -from embedchain.llm.vertex_ai import VertexAILlm - - -@pytest.fixture(autouse=True) -def setup_database(): - database_manager.setup_engine() - - -@pytest.fixture -def vertexai_llm(): - config = BaseLlmConfig(temperature=0.6, model="chat-bison") - return VertexAILlm(config) - - -def test_get_llm_model_answer(vertexai_llm): - with patch.object(VertexAILlm, "_get_answer", return_value="Test Response") as mock_method: - prompt = "Test Prompt" - response = vertexai_llm.get_llm_model_answer(prompt) - assert response == "Test Response" - mock_method.assert_called_once_with(prompt, vertexai_llm.config) - - -def test_get_llm_model_answer_with_token_usage(vertexai_llm): - test_config = BaseLlmConfig( - temperature=vertexai_llm.config.temperature, - max_tokens=vertexai_llm.config.max_tokens, - top_p=vertexai_llm.config.top_p, - model=vertexai_llm.config.model, - token_usage=True, - ) - vertexai_llm.config = test_config - with patch.object( - VertexAILlm, - "_get_answer", - return_value=("Test Response", {"prompt_token_count": 1, "candidates_token_count": 2}), - ): - response, token_info = vertexai_llm.get_llm_model_answer("Test Query") - assert response == "Test Response" - assert token_info == { - "prompt_tokens": 1, - "completion_tokens": 2, - "total_tokens": 3, - "total_cost": 3.75e-07, - "cost_currency": "USD", - } - - -@patch("embedchain.llm.vertex_ai.ChatVertexAI") -def test_get_answer(mock_chat_vertexai, vertexai_llm, caplog): - mock_chat_vertexai.return_value.invoke.return_value = MagicMock(content="Test Response") - - config = vertexai_llm.config - prompt = "Test Prompt" - messages = vertexai_llm._get_messages(prompt) - response = vertexai_llm._get_answer(prompt, config) - mock_chat_vertexai.return_value.invoke.assert_called_once_with(messages) - - assert response == "Test Response" # Assertion corrected - assert "Config option `top_p` is not supported by this model." not in caplog.text - - -def test_get_messages(vertexai_llm): - prompt = "Test Prompt" - system_prompt = "Test System Prompt" - messages = vertexai_llm._get_messages(prompt, system_prompt) - assert messages == [ - SystemMessage(content="Test System Prompt", additional_kwargs={}), - HumanMessage(content="Test Prompt", additional_kwargs={}, example=False), - ] diff --git a/embedchain/tests/loaders/test_audio.py b/embedchain/tests/loaders/test_audio.py deleted file mode 100644 index c62ec1393..000000000 --- a/embedchain/tests/loaders/test_audio.py +++ /dev/null @@ -1,100 +0,0 @@ -import hashlib -import os -import sys -from unittest.mock import mock_open, patch - -import pytest - -if sys.version_info > (3, 10): # as `match` statement was introduced in python 3.10 - from deepgram import PrerecordedOptions - - from embedchain.loaders.audio import AudioLoader - - -@pytest.fixture -def setup_audio_loader(mocker): - mock_dropbox = mocker.patch("deepgram.DeepgramClient") - mock_dbx = mocker.MagicMock() - mock_dropbox.return_value = mock_dbx - - os.environ["DEEPGRAM_API_KEY"] = "test_key" - loader = AudioLoader() - loader.client = mock_dbx - - yield loader, mock_dbx - - if "DEEPGRAM_API_KEY" in os.environ: - del os.environ["DEEPGRAM_API_KEY"] - - -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="Test skipped for Python 3.9 or lower" -) # as `match` statement was introduced in python 3.10 -def test_initialization(setup_audio_loader): - """Test initialization of AudioLoader.""" - loader, _ = setup_audio_loader - assert loader is not None - - -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="Test skipped for Python 3.9 or lower" -) # as `match` statement was introduced in python 3.10 -def test_load_data_from_url(setup_audio_loader): - loader, mock_dbx = setup_audio_loader - url = "https://example.com/audio.mp3" - expected_content = "This is a test audio transcript." - - mock_response = {"results": {"channels": [{"alternatives": [{"transcript": expected_content}]}]}} - mock_dbx.listen.prerecorded.v.return_value.transcribe_url.return_value = mock_response - - result = loader.load_data(url) - - doc_id = hashlib.sha256((expected_content + url).encode()).hexdigest() - expected_result = { - "doc_id": doc_id, - "data": [ - { - "content": expected_content, - "meta_data": {"url": url}, - } - ], - } - - assert result == expected_result - mock_dbx.listen.prerecorded.v.assert_called_once_with("1") - mock_dbx.listen.prerecorded.v.return_value.transcribe_url.assert_called_once_with( - {"url": url}, PrerecordedOptions(model="nova-2", smart_format=True) - ) - - -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="Test skipped for Python 3.9 or lower" -) # as `match` statement was introduced in python 3.10 -def test_load_data_from_file(setup_audio_loader): - loader, mock_dbx = setup_audio_loader - file_path = "local_audio.mp3" - expected_content = "This is a test audio transcript." - - mock_response = {"results": {"channels": [{"alternatives": [{"transcript": expected_content}]}]}} - mock_dbx.listen.prerecorded.v.return_value.transcribe_file.return_value = mock_response - - # Mock the file reading functionality - with patch("builtins.open", mock_open(read_data=b"some data")) as mock_file: - result = loader.load_data(file_path) - - doc_id = hashlib.sha256((expected_content + file_path).encode()).hexdigest() - expected_result = { - "doc_id": doc_id, - "data": [ - { - "content": expected_content, - "meta_data": {"url": file_path}, - } - ], - } - - assert result == expected_result - mock_dbx.listen.prerecorded.v.assert_called_once_with("1") - mock_dbx.listen.prerecorded.v.return_value.transcribe_file.assert_called_once_with( - {"buffer": mock_file.return_value}, PrerecordedOptions(model="nova-2", smart_format=True) - ) diff --git a/embedchain/tests/loaders/test_csv.py b/embedchain/tests/loaders/test_csv.py deleted file mode 100644 index 9cdcff394..000000000 --- a/embedchain/tests/loaders/test_csv.py +++ /dev/null @@ -1,113 +0,0 @@ -import csv -import os -import pathlib -import tempfile -from unittest.mock import MagicMock, patch - -import pytest - -from embedchain.loaders.csv import CsvLoader - - -@pytest.mark.parametrize("delimiter", [",", "\t", ";", "|"]) -def test_load_data(delimiter): - """ - Test csv loader - - Tests that file is loaded, metadata is correct and content is correct - """ - # Creating temporary CSV file - with tempfile.NamedTemporaryFile(mode="w+", newline="", delete=False) as tmpfile: - writer = csv.writer(tmpfile, delimiter=delimiter) - writer.writerow(["Name", "Age", "Occupation"]) - writer.writerow(["Alice", "28", "Engineer"]) - writer.writerow(["Bob", "35", "Doctor"]) - writer.writerow(["Charlie", "22", "Student"]) - - tmpfile.seek(0) - filename = tmpfile.name - - # Loading CSV using CsvLoader - loader = CsvLoader() - result = loader.load_data(filename) - data = result["data"] - - # Assertions - assert len(data) == 3 - assert data[0]["content"] == "Name: Alice, Age: 28, Occupation: Engineer" - assert data[0]["meta_data"]["url"] == filename - assert data[0]["meta_data"]["row"] == 1 - assert data[1]["content"] == "Name: Bob, Age: 35, Occupation: Doctor" - assert data[1]["meta_data"]["url"] == filename - assert data[1]["meta_data"]["row"] == 2 - assert data[2]["content"] == "Name: Charlie, Age: 22, Occupation: Student" - assert data[2]["meta_data"]["url"] == filename - assert data[2]["meta_data"]["row"] == 3 - - # Cleaning up the temporary file - os.unlink(filename) - - -@pytest.mark.parametrize("delimiter", [",", "\t", ";", "|"]) -def test_load_data_with_file_uri(delimiter): - """ - Test csv loader with file URI - - Tests that file is loaded, metadata is correct and content is correct - """ - # Creating temporary CSV file - with tempfile.NamedTemporaryFile(mode="w+", newline="", delete=False) as tmpfile: - writer = csv.writer(tmpfile, delimiter=delimiter) - writer.writerow(["Name", "Age", "Occupation"]) - writer.writerow(["Alice", "28", "Engineer"]) - writer.writerow(["Bob", "35", "Doctor"]) - writer.writerow(["Charlie", "22", "Student"]) - - tmpfile.seek(0) - filename = pathlib.Path(tmpfile.name).as_uri() # Convert path to file URI - - # Loading CSV using CsvLoader - loader = CsvLoader() - result = loader.load_data(filename) - data = result["data"] - - # Assertions - assert len(data) == 3 - assert data[0]["content"] == "Name: Alice, Age: 28, Occupation: Engineer" - assert data[0]["meta_data"]["url"] == filename - assert data[0]["meta_data"]["row"] == 1 - assert data[1]["content"] == "Name: Bob, Age: 35, Occupation: Doctor" - assert data[1]["meta_data"]["url"] == filename - assert data[1]["meta_data"]["row"] == 2 - assert data[2]["content"] == "Name: Charlie, Age: 22, Occupation: Student" - assert data[2]["meta_data"]["url"] == filename - assert data[2]["meta_data"]["row"] == 3 - - # Cleaning up the temporary file - os.unlink(tmpfile.name) - - -@pytest.mark.parametrize("content", ["ftp://example.com", "sftp://example.com", "mailto://example.com"]) -def test_get_file_content(content): - with pytest.raises(ValueError): - loader = CsvLoader() - loader._get_file_content(content) - - -@pytest.mark.parametrize("content", ["http://example.com", "https://example.com"]) -def test_get_file_content_http(content): - """ - Test _get_file_content method of CsvLoader for http and https URLs - """ - - with patch("requests.get") as mock_get: - mock_response = MagicMock() - mock_response.text = "Name,Age,Occupation\nAlice,28,Engineer\nBob,35,Doctor\nCharlie,22,Student" - mock_get.return_value = mock_response - - loader = CsvLoader() - file_content = loader._get_file_content(content) - - mock_get.assert_called_once_with(content) - mock_response.raise_for_status.assert_called_once() - assert file_content.read() == mock_response.text diff --git a/embedchain/tests/loaders/test_discourse.py b/embedchain/tests/loaders/test_discourse.py deleted file mode 100644 index 71635b377..000000000 --- a/embedchain/tests/loaders/test_discourse.py +++ /dev/null @@ -1,104 +0,0 @@ -import pytest -import requests - -from embedchain.loaders.discourse import DiscourseLoader - - -@pytest.fixture -def discourse_loader_config(): - return { - "domain": "https://example.com/", - } - - -@pytest.fixture -def discourse_loader(discourse_loader_config): - return DiscourseLoader(config=discourse_loader_config) - - -def test_discourse_loader_init_with_valid_config(): - config = {"domain": "https://example.com/"} - loader = DiscourseLoader(config=config) - assert loader.domain == "https://example.com/" - - -def test_discourse_loader_init_with_missing_config(): - with pytest.raises(ValueError, match="DiscourseLoader requires a config"): - DiscourseLoader() - - -def test_discourse_loader_init_with_missing_domain(): - config = {"another_key": "value"} - with pytest.raises(ValueError, match="DiscourseLoader requires a domain"): - DiscourseLoader(config=config) - - -def test_discourse_loader_check_query_with_valid_query(discourse_loader): - discourse_loader._check_query("sample query") - - -def test_discourse_loader_check_query_with_empty_query(discourse_loader): - with pytest.raises(ValueError, match="DiscourseLoader requires a query"): - discourse_loader._check_query("") - - -def test_discourse_loader_check_query_with_invalid_query_type(discourse_loader): - with pytest.raises(ValueError, match="DiscourseLoader requires a query"): - discourse_loader._check_query(123) - - -def test_discourse_loader_load_post_with_valid_post_id(discourse_loader, monkeypatch): - def mock_get(*args, **kwargs): - class MockResponse: - def json(self): - return {"raw": "Sample post content"} - - def raise_for_status(self): - pass - - return MockResponse() - - monkeypatch.setattr(requests, "get", mock_get) - - post_data = discourse_loader._load_post(123) - - assert post_data["content"] == "Sample post content" - assert "meta_data" in post_data - - -def test_discourse_loader_load_data_with_valid_query(discourse_loader, monkeypatch): - def mock_get(*args, **kwargs): - class MockResponse: - def json(self): - return {"grouped_search_result": {"post_ids": [123, 456, 789]}} - - def raise_for_status(self): - pass - - return MockResponse() - - monkeypatch.setattr(requests, "get", mock_get) - - def mock_load_post(*args, **kwargs): - return { - "content": "Sample post content", - "meta_data": { - "url": "https://example.com/posts/123.json", - "created_at": "2021-01-01", - "username": "test_user", - "topic_slug": "test_topic", - "score": 10, - }, - } - - monkeypatch.setattr(discourse_loader, "_load_post", mock_load_post) - - data = discourse_loader.load_data("sample query") - - assert len(data["data"]) == 3 - assert data["data"][0]["content"] == "Sample post content" - assert data["data"][0]["meta_data"]["url"] == "https://example.com/posts/123.json" - assert data["data"][0]["meta_data"]["created_at"] == "2021-01-01" - assert data["data"][0]["meta_data"]["username"] == "test_user" - assert data["data"][0]["meta_data"]["topic_slug"] == "test_topic" - assert data["data"][0]["meta_data"]["score"] == 10 diff --git a/embedchain/tests/loaders/test_docs_site.py b/embedchain/tests/loaders/test_docs_site.py deleted file mode 100644 index 31d03f67a..000000000 --- a/embedchain/tests/loaders/test_docs_site.py +++ /dev/null @@ -1,130 +0,0 @@ -import hashlib -from unittest.mock import Mock, patch - -import pytest -from requests import Response - -from embedchain.loaders.docs_site_loader import DocsSiteLoader - - -@pytest.fixture -def mock_requests_get(): - with patch("requests.get") as mock_get: - yield mock_get - - -@pytest.fixture -def docs_site_loader(): - return DocsSiteLoader() - - -def test_get_child_links_recursive(mock_requests_get, docs_site_loader): - mock_response = Mock() - mock_response.status_code = 200 - mock_response.text = """ - - Page 1 - Page 2 - - """ - mock_requests_get.return_value = mock_response - - docs_site_loader._get_child_links_recursive("https://example.com") - - assert len(docs_site_loader.visited_links) == 2 - assert "https://example.com/page1" in docs_site_loader.visited_links - assert "https://example.com/page2" in docs_site_loader.visited_links - - -def test_get_child_links_recursive_status_not_200(mock_requests_get, docs_site_loader): - mock_response = Mock() - mock_response.status_code = 404 - mock_requests_get.return_value = mock_response - - docs_site_loader._get_child_links_recursive("https://example.com") - - assert len(docs_site_loader.visited_links) == 0 - - -def test_get_all_urls(mock_requests_get, docs_site_loader): - mock_response = Mock() - mock_response.status_code = 200 - mock_response.text = """ - - Page 1 - Page 2 - External - - """ - mock_requests_get.return_value = mock_response - - all_urls = docs_site_loader._get_all_urls("https://example.com") - - assert len(all_urls) == 3 - assert "https://example.com/page1" in all_urls - assert "https://example.com/page2" in all_urls - assert "https://example.com/external" in all_urls - - -def test_load_data_from_url(mock_requests_get, docs_site_loader): - mock_response = Mock() - mock_response.status_code = 200 - mock_response.content = """ - - -
-

Article Content

-
- - """.encode() - mock_requests_get.return_value = mock_response - - data = docs_site_loader._load_data_from_url("https://example.com/page1") - - assert len(data) == 1 - assert data[0]["content"] == "Article Content" - assert data[0]["meta_data"]["url"] == "https://example.com/page1" - - -def test_load_data_from_url_status_not_200(mock_requests_get, docs_site_loader): - mock_response = Mock() - mock_response.status_code = 404 - mock_requests_get.return_value = mock_response - - data = docs_site_loader._load_data_from_url("https://example.com/page1") - - assert data == [] - assert len(data) == 0 - - -def test_load_data(mock_requests_get, docs_site_loader): - mock_response = Response() - mock_response.status_code = 200 - mock_response._content = """ - - Page 1 - Page 2 - """.encode() - mock_requests_get.return_value = mock_response - - url = "https://example.com" - data = docs_site_loader.load_data(url) - expected_doc_id = hashlib.sha256((" ".join(docs_site_loader.visited_links) + url).encode()).hexdigest() - - assert len(data["data"]) == 2 - assert data["doc_id"] == expected_doc_id - - -def test_if_response_status_not_200(mock_requests_get, docs_site_loader): - mock_response = Response() - mock_response.status_code = 404 - mock_requests_get.return_value = mock_response - - url = "https://example.com" - data = docs_site_loader.load_data(url) - expected_doc_id = hashlib.sha256((" ".join(docs_site_loader.visited_links) + url).encode()).hexdigest() - - assert len(data["data"]) == 0 - assert data["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_docs_site_loader.py b/embedchain/tests/loaders/test_docs_site_loader.py deleted file mode 100644 index 16b503b9f..000000000 --- a/embedchain/tests/loaders/test_docs_site_loader.py +++ /dev/null @@ -1,218 +0,0 @@ -import pytest -import responses -from bs4 import BeautifulSoup - - -@pytest.mark.parametrize( - "ignored_tag", - [ - "", - "", - "
This is a form.
", - "
This is a header.
", - "", - "This is an SVG.", - "This is a canvas.", - "
This is a footer.
", - "", - "", - ], - ids=["nav", "aside", "form", "header", "noscript", "svg", "canvas", "footer", "script", "style"], -) -@pytest.mark.parametrize( - "selectee", - [ - """ -
-

Article Title

-

Article content goes here.

- {ignored_tag} -
-""", - """ -
-

Main Article Title

-

Main article content goes here.

- {ignored_tag} -
-""", - """ -
-

Markdown Content

-

Markdown content goes here.

- {ignored_tag} -
-""", - """ -
-

Main Content

-

Main content goes here.

- {ignored_tag} -
-""", - """ -
-

Container

-

Container content goes here.

- {ignored_tag} -
- """, - """ -
-

Section

-

Section content goes here.

- {ignored_tag} -
- """, - """ -
-

Generic Article

-

Generic article content goes here.

- {ignored_tag} -
- """, - """ -
-

Main Content

-

Main content goes here.

- {ignored_tag} -
-""", - ], - ids=[ - "article.bd-article", - 'article[role="main"]', - "div.md-content", - 'div[role="main"]', - "div.container", - "div.section", - "article", - "main", - ], -) -def test_load_data_gets_by_selectors_and_ignored_tags(selectee, ignored_tag, loader, mocked_responses, mocker): - child_url = "https://docs.embedchain.ai/quickstart" - selectee = selectee.format(ignored_tag=ignored_tag) - html_body = """ - - - - {selectee} - - -""" - html_body = html_body.format(selectee=selectee) - mocked_responses.get(child_url, body=html_body, status=200, content_type="text/html") - - url = "https://docs.embedchain.ai/" - html_body = """ - - - -
  • Quickstart
  • - - -""" - mocked_responses.get(url, body=html_body, status=200, content_type="text/html") - - mock_sha256 = mocker.patch("embedchain.loaders.docs_site_loader.hashlib.sha256") - doc_id = "mocked_hash" - mock_sha256.return_value.hexdigest.return_value = doc_id - - result = loader.load_data(url) - selector_soup = BeautifulSoup(selectee, "html.parser") - expected_content = " ".join((selector_soup.select_one("h2").get_text(), selector_soup.select_one("p").get_text())) - assert result["doc_id"] == doc_id - assert result["data"] == [ - { - "content": expected_content, - "meta_data": {"url": "https://docs.embedchain.ai/quickstart"}, - } - ] - - -def test_load_data_gets_child_links_recursively(loader, mocked_responses, mocker): - child_url = "https://docs.embedchain.ai/quickstart" - html_body = """ - - - -
  • ..
  • -
  • .
  • - - -""" - mocked_responses.get(child_url, body=html_body, status=200, content_type="text/html") - - child_url = "https://docs.embedchain.ai/introduction" - html_body = """ - - - -
  • ..
  • -
  • .
  • - - -""" - mocked_responses.get(child_url, body=html_body, status=200, content_type="text/html") - - url = "https://docs.embedchain.ai/" - html_body = """ - - - -
  • Quickstart
  • -
  • Introduction
  • - - -""" - mocked_responses.get(url, body=html_body, status=200, content_type="text/html") - - mock_sha256 = mocker.patch("embedchain.loaders.docs_site_loader.hashlib.sha256") - doc_id = "mocked_hash" - mock_sha256.return_value.hexdigest.return_value = doc_id - - result = loader.load_data(url) - assert result["doc_id"] == doc_id - expected_data = [ - {"content": "..\n.", "meta_data": {"url": "https://docs.embedchain.ai/quickstart"}}, - {"content": "..\n.", "meta_data": {"url": "https://docs.embedchain.ai/introduction"}}, - ] - assert all(item in expected_data for item in result["data"]) - - -def test_load_data_fails_to_fetch_website(loader, mocked_responses, mocker): - child_url = "https://docs.embedchain.ai/introduction" - mocked_responses.get(child_url, status=404) - - url = "https://docs.embedchain.ai/" - html_body = """ - - - -
  • Introduction
  • - - -""" - mocked_responses.get(url, body=html_body, status=200, content_type="text/html") - - mock_sha256 = mocker.patch("embedchain.loaders.docs_site_loader.hashlib.sha256") - doc_id = "mocked_hash" - mock_sha256.return_value.hexdigest.return_value = doc_id - - result = loader.load_data(url) - assert result["doc_id"] is doc_id - assert result["data"] == [] - - -@pytest.fixture -def loader(): - from embedchain.loaders.docs_site_loader import DocsSiteLoader - - return DocsSiteLoader() - - -@pytest.fixture -def mocked_responses(): - with responses.RequestsMock() as rsps: - yield rsps diff --git a/embedchain/tests/loaders/test_docx_file.py b/embedchain/tests/loaders/test_docx_file.py deleted file mode 100644 index b7deffcb2..000000000 --- a/embedchain/tests/loaders/test_docx_file.py +++ /dev/null @@ -1,39 +0,0 @@ -import hashlib -from unittest.mock import MagicMock, patch - -import pytest - -from embedchain.loaders.docx_file import DocxFileLoader - - -@pytest.fixture -def mock_docx2txt_loader(): - with patch("embedchain.loaders.docx_file.Docx2txtLoader") as mock_loader: - yield mock_loader - - -@pytest.fixture -def docx_file_loader(): - return DocxFileLoader() - - -def test_load_data(mock_docx2txt_loader, docx_file_loader): - mock_url = "mock_docx_file.docx" - - mock_loader = MagicMock() - mock_loader.load.return_value = [MagicMock(page_content="Sample Docx Content", metadata={"url": "local"})] - - mock_docx2txt_loader.return_value = mock_loader - - result = docx_file_loader.load_data(mock_url) - - assert "doc_id" in result - assert "data" in result - - expected_content = "Sample Docx Content" - assert result["data"][0]["content"] == expected_content - - assert result["data"][0]["meta_data"]["url"] == "local" - - expected_doc_id = hashlib.sha256((expected_content + mock_url).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_dropbox.py b/embedchain/tests/loaders/test_dropbox.py deleted file mode 100644 index c7e816731..000000000 --- a/embedchain/tests/loaders/test_dropbox.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -from unittest.mock import MagicMock - -import pytest -from dropbox.files import FileMetadata - -from embedchain.loaders.dropbox import DropboxLoader - - -@pytest.fixture -def setup_dropbox_loader(mocker): - mock_dropbox = mocker.patch("dropbox.Dropbox") - mock_dbx = mocker.MagicMock() - mock_dropbox.return_value = mock_dbx - - os.environ["DROPBOX_ACCESS_TOKEN"] = "test_token" - loader = DropboxLoader() - - yield loader, mock_dbx - - if "DROPBOX_ACCESS_TOKEN" in os.environ: - del os.environ["DROPBOX_ACCESS_TOKEN"] - - -def test_initialization(setup_dropbox_loader): - """Test initialization of DropboxLoader.""" - loader, _ = setup_dropbox_loader - assert loader is not None - - -def test_download_folder(setup_dropbox_loader, mocker): - """Test downloading a folder.""" - loader, mock_dbx = setup_dropbox_loader - mocker.patch("os.makedirs") - mocker.patch("os.path.join", return_value="mock/path") - - mock_file_metadata = mocker.MagicMock(spec=FileMetadata) - mock_dbx.files_list_folder.return_value.entries = [mock_file_metadata] - - entries = loader._download_folder("path/to/folder", "local_root") - assert entries is not None - - -def test_generate_dir_id_from_all_paths(setup_dropbox_loader, mocker): - """Test directory ID generation.""" - loader, mock_dbx = setup_dropbox_loader - mock_file_metadata = mocker.MagicMock(spec=FileMetadata, name="file.txt") - mock_dbx.files_list_folder.return_value.entries = [mock_file_metadata] - - dir_id = loader._generate_dir_id_from_all_paths("path/to/folder") - assert dir_id is not None - assert len(dir_id) == 64 - - -def test_clean_directory(setup_dropbox_loader, mocker): - """Test cleaning up a directory.""" - loader, _ = setup_dropbox_loader - mocker.patch("os.listdir", return_value=["file1", "file2"]) - mocker.patch("os.remove") - mocker.patch("os.rmdir") - - loader._clean_directory("path/to/folder") - - -def test_load_data(mocker, setup_dropbox_loader, tmp_path): - loader = setup_dropbox_loader[0] - - mock_file_metadata = MagicMock(spec=FileMetadata, name="file.txt") - mocker.patch.object(loader.dbx, "files_list_folder", return_value=MagicMock(entries=[mock_file_metadata])) - mocker.patch.object(loader.dbx, "files_download_to_file") - - # Mock DirectoryLoader - mock_data = {"data": "test_data"} - mocker.patch("embedchain.loaders.directory_loader.DirectoryLoader.load_data", return_value=mock_data) - - test_dir = tmp_path / "dropbox_test" - test_dir.mkdir() - test_file = test_dir / "file.txt" - test_file.write_text("dummy content") - mocker.patch.object(loader, "_generate_dir_id_from_all_paths", return_value=str(test_dir)) - - result = loader.load_data("path/to/folder") - - assert result == {"doc_id": mocker.ANY, "data": "test_data"} - loader.dbx.files_list_folder.assert_called_once_with("path/to/folder") diff --git a/embedchain/tests/loaders/test_excel_file.py b/embedchain/tests/loaders/test_excel_file.py deleted file mode 100644 index c0865ed5e..000000000 --- a/embedchain/tests/loaders/test_excel_file.py +++ /dev/null @@ -1,33 +0,0 @@ -import hashlib -from unittest.mock import patch - -import pytest - -from embedchain.loaders.excel_file import ExcelFileLoader - - -@pytest.fixture -def excel_file_loader(): - return ExcelFileLoader() - - -def test_load_data(excel_file_loader): - mock_url = "mock_excel_file.xlsx" - expected_content = "Sample Excel Content" - - # Mock the load_data method of the excel_file_loader instance - with patch.object( - excel_file_loader, - "load_data", - return_value={ - "doc_id": hashlib.sha256((expected_content + mock_url).encode()).hexdigest(), - "data": [{"content": expected_content, "meta_data": {"url": mock_url}}], - }, - ): - result = excel_file_loader.load_data(mock_url) - - assert result["data"][0]["content"] == expected_content - assert result["data"][0]["meta_data"]["url"] == mock_url - - expected_doc_id = hashlib.sha256((expected_content + mock_url).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_github.py b/embedchain/tests/loaders/test_github.py deleted file mode 100644 index fe728a89b..000000000 --- a/embedchain/tests/loaders/test_github.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest - -from embedchain.loaders.github import GithubLoader - - -@pytest.fixture -def mock_github_loader_config(): - return { - "token": "your_mock_token", - } - - -@pytest.fixture -def mock_github_loader(mocker, mock_github_loader_config): - mock_github = mocker.patch("github.Github") - _ = mock_github.return_value - return GithubLoader(config=mock_github_loader_config) - - -def test_github_loader_init(mocker, mock_github_loader_config): - mock_github = mocker.patch("github.Github") - GithubLoader(config=mock_github_loader_config) - mock_github.assert_called_once_with("your_mock_token") - - -def test_github_loader_init_empty_config(mocker): - with pytest.raises(ValueError, match="requires a personal access token"): - GithubLoader() - - -def test_github_loader_init_missing_token(): - with pytest.raises(ValueError, match="requires a personal access token"): - GithubLoader(config={}) diff --git a/embedchain/tests/loaders/test_gmail.py b/embedchain/tests/loaders/test_gmail.py deleted file mode 100644 index 1b7834b87..000000000 --- a/embedchain/tests/loaders/test_gmail.py +++ /dev/null @@ -1,43 +0,0 @@ -import pytest - -from embedchain.loaders.gmail import GmailLoader - - -@pytest.fixture -def mock_beautifulsoup(mocker): - return mocker.patch("embedchain.loaders.gmail.BeautifulSoup", return_value=mocker.MagicMock()) - - -@pytest.fixture -def gmail_loader(mock_beautifulsoup): - return GmailLoader() - - -def test_load_data_file_not_found(gmail_loader, mocker): - with pytest.raises(FileNotFoundError): - with mocker.patch("os.path.isfile", return_value=False): - gmail_loader.load_data("your_query") - - -@pytest.mark.skip(reason="TODO: Fix this test. Failing due to some googleapiclient import issue.") -def test_load_data(gmail_loader, mocker): - mock_gmail_reader_instance = mocker.MagicMock() - text = "your_test_email_text" - metadata = { - "id": "your_test_id", - "snippet": "your_test_snippet", - } - mock_gmail_reader_instance.load_data.return_value = [ - { - "text": text, - "extra_info": metadata, - } - ] - - with mocker.patch("os.path.isfile", return_value=True): - response_data = gmail_loader.load_data("your_query") - - assert "doc_id" in response_data - assert "data" in response_data - assert isinstance(response_data["doc_id"], str) - assert isinstance(response_data["data"], list) diff --git a/embedchain/tests/loaders/test_google_drive.py b/embedchain/tests/loaders/test_google_drive.py deleted file mode 100644 index 00d8bb1c1..000000000 --- a/embedchain/tests/loaders/test_google_drive.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - -from embedchain.loaders.google_drive import GoogleDriveLoader - - -@pytest.fixture -def google_drive_folder_loader(): - return GoogleDriveLoader() - - -def test_load_data_invalid_drive_url(google_drive_folder_loader): - mock_invalid_drive_url = "https://example.com" - with pytest.raises( - ValueError, - match="The url provided https://example.com does not match a google drive folder url. Example " - "drive url: https://drive.google.com/drive/u/0/folders/xxxx", - ): - google_drive_folder_loader.load_data(mock_invalid_drive_url) - - -@pytest.mark.skip(reason="This test won't work unless google api credentials are properly setup.") -def test_load_data_incorrect_drive_url(google_drive_folder_loader): - mock_invalid_drive_url = "https://drive.google.com/drive/u/0/folders/xxxx" - with pytest.raises( - FileNotFoundError, match="Unable to locate folder or files, check provided drive URL and try again" - ): - google_drive_folder_loader.load_data(mock_invalid_drive_url) - - -@pytest.mark.skip(reason="This test won't work unless google api credentials are properly setup.") -def test_load_data(google_drive_folder_loader): - mock_valid_url = "YOUR_VALID_URL" - result = google_drive_folder_loader.load_data(mock_valid_url) - assert "doc_id" in result - assert "data" in result - assert "content" in result["data"][0] - assert "meta_data" in result["data"][0] diff --git a/embedchain/tests/loaders/test_json.py b/embedchain/tests/loaders/test_json.py deleted file mode 100644 index ba2361407..000000000 --- a/embedchain/tests/loaders/test_json.py +++ /dev/null @@ -1,131 +0,0 @@ -import hashlib - -import pytest - -from embedchain.loaders.json import JSONLoader - - -def test_load_data(mocker): - content = "temp.json" - - mock_document = { - "doc_id": hashlib.sha256((content + ", ".join(["content1", "content2"])).encode()).hexdigest(), - "data": [ - {"content": "content1", "meta_data": {"url": content}}, - {"content": "content2", "meta_data": {"url": content}}, - ], - } - - mocker.patch("embedchain.loaders.json.JSONLoader.load_data", return_value=mock_document) - - json_loader = JSONLoader() - - result = json_loader.load_data(content) - - assert "doc_id" in result - assert "data" in result - - expected_data = [ - {"content": "content1", "meta_data": {"url": content}}, - {"content": "content2", "meta_data": {"url": content}}, - ] - - assert result["data"] == expected_data - - expected_doc_id = hashlib.sha256((content + ", ".join(["content1", "content2"])).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id - - -def test_load_data_url(mocker): - content = "https://example.com/posts.json" - - mocker.patch("os.path.isfile", return_value=False) - mocker.patch( - "embedchain.loaders.json.JSONReader.load_data", - return_value=[ - { - "text": "content1", - }, - { - "text": "content2", - }, - ], - ) - - mock_response = mocker.Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {"document1": "content1", "document2": "content2"} - - mocker.patch("requests.get", return_value=mock_response) - - result = JSONLoader.load_data(content) - - assert "doc_id" in result - assert "data" in result - - expected_data = [ - {"content": "content1", "meta_data": {"url": content}}, - {"content": "content2", "meta_data": {"url": content}}, - ] - - assert result["data"] == expected_data - - expected_doc_id = hashlib.sha256((content + ", ".join(["content1", "content2"])).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id - - -def test_load_data_invalid_string_content(mocker): - mocker.patch("os.path.isfile", return_value=False) - mocker.patch("requests.get") - - content = "123: 345}" - - with pytest.raises(ValueError, match="Invalid content to load json data from"): - JSONLoader.load_data(content) - - -def test_load_data_invalid_url(mocker): - mocker.patch("os.path.isfile", return_value=False) - - mock_response = mocker.Mock() - mock_response.status_code = 404 - mocker.patch("requests.get", return_value=mock_response) - - content = "http://invalid-url.com/" - - with pytest.raises(ValueError, match=f"Invalid content to load json data from: {content}"): - JSONLoader.load_data(content) - - -def test_load_data_from_json_string(mocker): - content = '{"foo": "bar"}' - - content_url_str = hashlib.sha256((content).encode("utf-8")).hexdigest() - - mocker.patch("os.path.isfile", return_value=False) - mocker.patch( - "embedchain.loaders.json.JSONReader.load_data", - return_value=[ - { - "text": "content1", - }, - { - "text": "content2", - }, - ], - ) - - result = JSONLoader.load_data(content) - - assert "doc_id" in result - assert "data" in result - - expected_data = [ - {"content": "content1", "meta_data": {"url": content_url_str}}, - {"content": "content2", "meta_data": {"url": content_url_str}}, - ] - - assert result["data"] == expected_data - - expected_doc_id = hashlib.sha256((content_url_str + ", ".join(["content1", "content2"])).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_local_qna_pair.py b/embedchain/tests/loaders/test_local_qna_pair.py deleted file mode 100644 index 5bdfd2caf..000000000 --- a/embedchain/tests/loaders/test_local_qna_pair.py +++ /dev/null @@ -1,32 +0,0 @@ -import hashlib - -import pytest - -from embedchain.loaders.local_qna_pair import LocalQnaPairLoader - - -@pytest.fixture -def qna_pair_loader(): - return LocalQnaPairLoader() - - -def test_load_data(qna_pair_loader): - question = "What is the capital of France?" - answer = "The capital of France is Paris." - - content = (question, answer) - result = qna_pair_loader.load_data(content) - - assert "doc_id" in result - assert "data" in result - url = "local" - - expected_content = f"Q: {question}\nA: {answer}" - assert result["data"][0]["content"] == expected_content - - assert result["data"][0]["meta_data"]["url"] == url - - assert result["data"][0]["meta_data"]["question"] == question - - expected_doc_id = hashlib.sha256((expected_content + url).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_local_text.py b/embedchain/tests/loaders/test_local_text.py deleted file mode 100644 index 58b6ec8fe..000000000 --- a/embedchain/tests/loaders/test_local_text.py +++ /dev/null @@ -1,27 +0,0 @@ -import hashlib - -import pytest - -from embedchain.loaders.local_text import LocalTextLoader - - -@pytest.fixture -def text_loader(): - return LocalTextLoader() - - -def test_load_data(text_loader): - mock_content = "This is a sample text content." - - result = text_loader.load_data(mock_content) - - assert "doc_id" in result - assert "data" in result - - url = "local" - assert result["data"][0]["content"] == mock_content - - assert result["data"][0]["meta_data"]["url"] == url - - expected_doc_id = hashlib.sha256((mock_content + url).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_mdx.py b/embedchain/tests/loaders/test_mdx.py deleted file mode 100644 index d4826209b..000000000 --- a/embedchain/tests/loaders/test_mdx.py +++ /dev/null @@ -1,30 +0,0 @@ -import hashlib -from unittest.mock import mock_open, patch - -import pytest - -from embedchain.loaders.mdx import MdxLoader - - -@pytest.fixture -def mdx_loader(): - return MdxLoader() - - -def test_load_data(mdx_loader): - mock_content = "Sample MDX Content" - - # Mock open function to simulate file reading - with patch("builtins.open", mock_open(read_data=mock_content)): - url = "mock_file.mdx" - result = mdx_loader.load_data(url) - - assert "doc_id" in result - assert "data" in result - - assert result["data"][0]["content"] == mock_content - - assert result["data"][0]["meta_data"]["url"] == url - - expected_doc_id = hashlib.sha256((mock_content + url).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id diff --git a/embedchain/tests/loaders/test_mysql.py b/embedchain/tests/loaders/test_mysql.py deleted file mode 100644 index 976d30ff8..000000000 --- a/embedchain/tests/loaders/test_mysql.py +++ /dev/null @@ -1,77 +0,0 @@ -import hashlib -from unittest.mock import MagicMock - -import pytest - -from embedchain.loaders.mysql import MySQLLoader - - -@pytest.fixture -def mysql_loader(mocker): - with mocker.patch("mysql.connector.connection.MySQLConnection"): - config = { - "host": "localhost", - "port": "3306", - "user": "your_username", - "password": "your_password", - "database": "your_database", - } - loader = MySQLLoader(config=config) - yield loader - - -def test_mysql_loader_initialization(mysql_loader): - assert mysql_loader.config is not None - assert mysql_loader.connection is not None - assert mysql_loader.cursor is not None - - -def test_mysql_loader_invalid_config(): - with pytest.raises(ValueError, match="Invalid sql config: None"): - MySQLLoader(config=None) - - -def test_mysql_loader_setup_loader_successful(mysql_loader): - assert mysql_loader.connection is not None - assert mysql_loader.cursor is not None - - -def test_mysql_loader_setup_loader_connection_error(mysql_loader, mocker): - mocker.patch("mysql.connector.connection.MySQLConnection", side_effect=IOError("Mocked connection error")) - with pytest.raises(ValueError, match="Unable to connect with the given config:"): - mysql_loader._setup_loader(config={}) - - -def test_mysql_loader_check_query_successful(mysql_loader): - query = "SELECT * FROM table" - mysql_loader._check_query(query=query) - - -def test_mysql_loader_check_query_invalid(mysql_loader): - with pytest.raises(ValueError, match="Invalid mysql query: 123"): - mysql_loader._check_query(query=123) - - -def test_mysql_loader_load_data_successful(mysql_loader, mocker): - mock_cursor = MagicMock() - mocker.patch.object(mysql_loader, "cursor", mock_cursor) - mock_cursor.fetchall.return_value = [(1, "data1"), (2, "data2")] - - query = "SELECT * FROM table" - result = mysql_loader.load_data(query) - - assert "doc_id" in result - assert "data" in result - assert len(result["data"]) == 2 - assert result["data"][0]["meta_data"]["url"] == query - assert result["data"][1]["meta_data"]["url"] == query - - doc_id = hashlib.sha256((query + ", ".join([d["content"] for d in result["data"]])).encode()).hexdigest() - - assert result["doc_id"] == doc_id - assert mock_cursor.execute.called_with(query) - - -def test_mysql_loader_load_data_invalid_query(mysql_loader): - with pytest.raises(ValueError, match="Invalid mysql query: 123"): - mysql_loader.load_data(query=123) diff --git a/embedchain/tests/loaders/test_notion.py b/embedchain/tests/loaders/test_notion.py deleted file mode 100644 index c9849c36f..000000000 --- a/embedchain/tests/loaders/test_notion.py +++ /dev/null @@ -1,36 +0,0 @@ -import hashlib -import os -from unittest.mock import Mock, patch - -import pytest - -from embedchain.loaders.notion import NotionLoader - - -@pytest.fixture -def notion_loader(): - with patch.dict(os.environ, {"NOTION_INTEGRATION_TOKEN": "test_notion_token"}): - yield NotionLoader() - - -def test_load_data(notion_loader): - source = "https://www.notion.so/Test-Page-1234567890abcdef1234567890abcdef" - mock_text = "This is a test page." - expected_doc_id = hashlib.sha256((mock_text + source).encode()).hexdigest() - expected_data = [ - { - "content": mock_text, - "meta_data": {"url": "notion-12345678-90ab-cdef-1234-567890abcdef"}, # formatted_id - } - ] - - mock_page = Mock() - mock_page.text = mock_text - mock_documents = [mock_page] - - with patch("embedchain.loaders.notion.NotionPageLoader") as mock_reader: - mock_reader.return_value.load_data.return_value = mock_documents - result = notion_loader.load_data(source) - - assert result["doc_id"] == expected_doc_id - assert result["data"] == expected_data diff --git a/embedchain/tests/loaders/test_openapi.py b/embedchain/tests/loaders/test_openapi.py deleted file mode 100644 index b39462c23..000000000 --- a/embedchain/tests/loaders/test_openapi.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from embedchain.loaders.openapi import OpenAPILoader - - -@pytest.fixture -def openapi_loader(): - return OpenAPILoader() - - -def test_load_data(openapi_loader, mocker): - mocker.patch("builtins.open", mocker.mock_open(read_data="key1: value1\nkey2: value2")) - - mocker.patch("hashlib.sha256", return_value=mocker.Mock(hexdigest=lambda: "mock_hash")) - - file_path = "configs/openai_openapi.yaml" - result = openapi_loader.load_data(file_path) - - expected_doc_id = "mock_hash" - expected_data = [ - {"content": "key1: value1", "meta_data": {"url": file_path, "row": 1}}, - {"content": "key2: value2", "meta_data": {"url": file_path, "row": 2}}, - ] - - assert result["doc_id"] == expected_doc_id - assert result["data"] == expected_data diff --git a/embedchain/tests/loaders/test_pdf_file.py b/embedchain/tests/loaders/test_pdf_file.py deleted file mode 100644 index 6e6dda6e5..000000000 --- a/embedchain/tests/loaders/test_pdf_file.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from langchain.schema import Document - - -def test_load_data(loader, mocker): - mocked_pypdfloader = mocker.patch("embedchain.loaders.pdf_file.PyPDFLoader") - mocked_pypdfloader.return_value.load_and_split.return_value = [ - Document(page_content="Page 0 Content", metadata={"source": "example.pdf", "page": 0}), - Document(page_content="Page 1 Content", metadata={"source": "example.pdf", "page": 1}), - ] - - mock_sha256 = mocker.patch("embedchain.loaders.docs_site_loader.hashlib.sha256") - doc_id = "mocked_hash" - mock_sha256.return_value.hexdigest.return_value = doc_id - - result = loader.load_data("dummy_url") - assert result["doc_id"] is doc_id - assert result["data"] == [ - {"content": "Page 0 Content", "meta_data": {"source": "example.pdf", "page": 0, "url": "dummy_url"}}, - {"content": "Page 1 Content", "meta_data": {"source": "example.pdf", "page": 1, "url": "dummy_url"}}, - ] - - -def test_load_data_fails_to_find_data(loader, mocker): - mocked_pypdfloader = mocker.patch("embedchain.loaders.pdf_file.PyPDFLoader") - mocked_pypdfloader.return_value.load_and_split.return_value = [] - - with pytest.raises(ValueError): - loader.load_data("dummy_url") - - -@pytest.fixture -def loader(): - from embedchain.loaders.pdf_file import PdfFileLoader - - return PdfFileLoader() diff --git a/embedchain/tests/loaders/test_postgres.py b/embedchain/tests/loaders/test_postgres.py deleted file mode 100644 index 72a7d2a7f..000000000 --- a/embedchain/tests/loaders/test_postgres.py +++ /dev/null @@ -1,60 +0,0 @@ -from unittest.mock import MagicMock - -import psycopg -import pytest - -from embedchain.loaders.postgres import PostgresLoader - - -@pytest.fixture -def postgres_loader(mocker): - with mocker.patch.object(psycopg, "connect"): - config = {"url": "postgres://user:password@localhost:5432/database"} - loader = PostgresLoader(config=config) - yield loader - - -def test_postgres_loader_initialization(postgres_loader): - assert postgres_loader.connection is not None - assert postgres_loader.cursor is not None - - -def test_postgres_loader_invalid_config(): - with pytest.raises(ValueError, match="Must provide the valid config. Received: None"): - PostgresLoader(config=None) - - -def test_load_data(postgres_loader, monkeypatch): - mock_cursor = MagicMock() - monkeypatch.setattr(postgres_loader, "cursor", mock_cursor) - - query = "SELECT * FROM table" - mock_cursor.fetchall.return_value = [(1, "data1"), (2, "data2")] - - result = postgres_loader.load_data(query) - - assert "doc_id" in result - assert "data" in result - assert len(result["data"]) == 2 - assert result["data"][0]["meta_data"]["url"] == query - assert result["data"][1]["meta_data"]["url"] == query - assert mock_cursor.execute.called_with(query) - - -def test_load_data_exception(postgres_loader, monkeypatch): - mock_cursor = MagicMock() - monkeypatch.setattr(postgres_loader, "cursor", mock_cursor) - - _ = "SELECT * FROM table" - mock_cursor.execute.side_effect = Exception("Mocked exception") - - with pytest.raises( - ValueError, match=r"Failed to load data using query=SELECT \* FROM table with: Mocked exception" - ): - postgres_loader.load_data("SELECT * FROM table") - - -def test_close_connection(postgres_loader): - postgres_loader.close_connection() - assert postgres_loader.cursor is None - assert postgres_loader.connection is None diff --git a/embedchain/tests/loaders/test_slack.py b/embedchain/tests/loaders/test_slack.py deleted file mode 100644 index 8a2831f0e..000000000 --- a/embedchain/tests/loaders/test_slack.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest - -from embedchain.loaders.slack import SlackLoader - - -@pytest.fixture -def slack_loader(mocker, monkeypatch): - # Mocking necessary dependencies - mocker.patch("slack_sdk.WebClient") - mocker.patch("ssl.create_default_context") - mocker.patch("certifi.where") - - monkeypatch.setenv("SLACK_USER_TOKEN", "slack_user_token") - - return SlackLoader() - - -def test_slack_loader_initialization(slack_loader): - assert slack_loader.client is not None - assert slack_loader.config == {"base_url": "https://www.slack.com/api/"} - - -def test_slack_loader_setup_loader(slack_loader): - slack_loader._setup_loader({"base_url": "https://custom.slack.api/"}) - - assert slack_loader.client is not None - - -def test_slack_loader_check_query(slack_loader): - valid_json_query = "test_query" - invalid_query = 123 - - slack_loader._check_query(valid_json_query) - - with pytest.raises(ValueError): - slack_loader._check_query(invalid_query) - - -def test_slack_loader_load_data(slack_loader, mocker): - valid_json_query = "in:random" - - mocker.patch.object(slack_loader.client, "search_messages", return_value={"messages": {}}) - - result = slack_loader.load_data(valid_json_query) - - assert "doc_id" in result - assert "data" in result diff --git a/embedchain/tests/loaders/test_web_page.py b/embedchain/tests/loaders/test_web_page.py deleted file mode 100644 index 46036ee20..000000000 --- a/embedchain/tests/loaders/test_web_page.py +++ /dev/null @@ -1,148 +0,0 @@ -import hashlib -from unittest.mock import Mock, patch - -import pytest -import requests - -from embedchain.loaders.web_page import WebPageLoader - - -@pytest.fixture -def web_page_loader(): - return WebPageLoader() - - -def test_load_data(web_page_loader): - page_url = "https://example.com/page" - mock_response = Mock() - mock_response.status_code = 200 - mock_response.content = """ - - - Test Page - - -
    -

    This is some test content.

    -
    - - - """ - with patch("embedchain.loaders.web_page.WebPageLoader._session.get", return_value=mock_response): - result = web_page_loader.load_data(page_url) - - content = web_page_loader._get_clean_content(mock_response.content, page_url) - expected_doc_id = hashlib.sha256((content + page_url).encode()).hexdigest() - assert result["doc_id"] == expected_doc_id - - expected_data = [ - { - "content": content, - "meta_data": { - "url": page_url, - }, - } - ] - - assert result["data"] == expected_data - - -def test_get_clean_content_excludes_unnecessary_info(web_page_loader): - mock_html = """ - - - Sample HTML - - - - - - -
    Form Content
    -
    Main Content
    -
    Footer Content
    - - - SVG Content - Canvas Content - - - - - -
    Header Sidebar Wrapper Content
    -
    Blog Sidebar Wrapper Content
    - - - - """ - - tags_to_exclude = [ - "nav", - "aside", - "form", - "header", - "noscript", - "svg", - "canvas", - "footer", - "script", - "style", - ] - ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"] - classes_to_exclude = [ - "elementor-location-header", - "navbar-header", - "nav", - "header-sidebar-wrapper", - "blog-sidebar-wrapper", - "related-posts", - ] - - content = web_page_loader._get_clean_content(mock_html, "https://example.com/page") - - for tag in tags_to_exclude: - assert tag not in content - - for id in ids_to_exclude: - assert id not in content - - for class_name in classes_to_exclude: - assert class_name not in content - - assert len(content) > 0 - - -def test_fetch_reference_links_success(web_page_loader): - # Mock a successful response - response = Mock(spec=requests.Response) - response.status_code = 200 - response.content = b""" - - - Example - Another Example - Relative Link - - - """ - - expected_links = ["http://example.com", "https://another-example.com"] - result = web_page_loader.fetch_reference_links(response) - assert result == expected_links - - -def test_fetch_reference_links_failure(web_page_loader): - # Mock a failed response - response = Mock(spec=requests.Response) - response.status_code = 404 - response.content = b"" - - expected_links = [] - result = web_page_loader.fetch_reference_links(response) - assert result == expected_links diff --git a/embedchain/tests/loaders/test_xml.py b/embedchain/tests/loaders/test_xml.py deleted file mode 100644 index d1ff5daad..000000000 --- a/embedchain/tests/loaders/test_xml.py +++ /dev/null @@ -1,62 +0,0 @@ -import tempfile - -import pytest - -from embedchain.loaders.xml import XmlLoader - -# Taken from https://github.com/langchain-ai/langchain/blob/master/libs/langchain/tests/integration_tests/examples/factbook.xml -SAMPLE_XML = """ - - - United States - Washington, DC - Joe Biden - Baseball - - - Canada - Ottawa - Justin Trudeau - Hockey - - - France - Paris - Emmanuel Macron - Soccer - - - Trinidad & Tobado - Port of Spain - Keith Rowley - Track & Field - -""" - - -@pytest.mark.parametrize("xml", [SAMPLE_XML]) -def test_load_data(xml: str): - """ - Test XML loader - - Tests that XML file is loaded, metadata is correct and content is correct - """ - # Creating temporary XML file - with tempfile.NamedTemporaryFile(mode="w+") as tmpfile: - tmpfile.write(xml) - - tmpfile.seek(0) - filename = tmpfile.name - - # Loading CSV using XmlLoader - loader = XmlLoader() - result = loader.load_data(filename) - data = result["data"] - - # Assertions - assert len(data) == 1 - assert "United States Washington, DC Joe Biden" in data[0]["content"] - assert "Canada Ottawa Justin Trudeau" in data[0]["content"] - assert "France Paris Emmanuel Macron" in data[0]["content"] - assert "Trinidad & Tobado Port of Spain Keith Rowley" in data[0]["content"] - assert data[0]["meta_data"]["url"] == filename diff --git a/embedchain/tests/loaders/test_youtube_video.py b/embedchain/tests/loaders/test_youtube_video.py deleted file mode 100644 index b8184a6e5..000000000 --- a/embedchain/tests/loaders/test_youtube_video.py +++ /dev/null @@ -1,53 +0,0 @@ -import hashlib -from unittest.mock import MagicMock, Mock, patch - -import pytest - -from embedchain.loaders.youtube_video import YoutubeVideoLoader - - -@pytest.fixture -def youtube_video_loader(): - return YoutubeVideoLoader() - - -def test_load_data(youtube_video_loader): - video_url = "https://www.youtube.com/watch?v=VIDEO_ID" - mock_loader = Mock() - mock_page_content = "This is a YouTube video content." - mock_loader.load.return_value = [ - MagicMock( - page_content=mock_page_content, - metadata={"url": video_url, "title": "Test Video"}, - ) - ] - - mock_transcript = [{"text": "sample text", "start": 0.0, "duration": 5.0}] - - with patch("embedchain.loaders.youtube_video.YoutubeLoader.from_youtube_url", return_value=mock_loader), patch( - "embedchain.loaders.youtube_video.YouTubeTranscriptApi.get_transcript", return_value=mock_transcript - ): - result = youtube_video_loader.load_data(video_url) - - expected_doc_id = hashlib.sha256((mock_page_content + video_url).encode()).hexdigest() - - assert result["doc_id"] == expected_doc_id - - expected_data = [ - { - "content": "This is a YouTube video content.", - "meta_data": {"url": video_url, "title": "Test Video", "transcript": "Unavailable"}, - } - ] - - assert result["data"] == expected_data - - -def test_load_data_with_empty_doc(youtube_video_loader): - video_url = "https://www.youtube.com/watch?v=VIDEO_ID" - mock_loader = Mock() - mock_loader.load.return_value = [] - - with patch("embedchain.loaders.youtube_video.YoutubeLoader.from_youtube_url", return_value=mock_loader): - with pytest.raises(ValueError): - youtube_video_loader.load_data(video_url) diff --git a/embedchain/tests/memory/test_chat_memory.py b/embedchain/tests/memory/test_chat_memory.py deleted file mode 100644 index 6fac2a643..000000000 --- a/embedchain/tests/memory/test_chat_memory.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest - -from embedchain.memory.base import ChatHistory -from embedchain.memory.message import ChatMessage - - -# Fixture for creating an instance of ChatHistory -@pytest.fixture -def chat_memory_instance(): - return ChatHistory() - - -def test_add_chat_memory(chat_memory_instance): - app_id = "test_app" - session_id = "test_session" - human_message = "Hello, how are you?" - ai_message = "I'm fine, thank you!" - - chat_message = ChatMessage() - chat_message.add_user_message(human_message) - chat_message.add_ai_message(ai_message) - - chat_memory_instance.add(app_id, session_id, chat_message) - - assert chat_memory_instance.count(app_id, session_id) == 1 - chat_memory_instance.delete(app_id, session_id) - - -def test_get(chat_memory_instance): - app_id = "test_app" - session_id = "test_session" - - for i in range(1, 7): - human_message = f"Question {i}" - ai_message = f"Answer {i}" - - chat_message = ChatMessage() - chat_message.add_user_message(human_message) - chat_message.add_ai_message(ai_message) - - chat_memory_instance.add(app_id, session_id, chat_message) - - recent_memories = chat_memory_instance.get(app_id, session_id, num_rounds=5) - - assert len(recent_memories) == 5 - - all_memories = chat_memory_instance.get(app_id, fetch_all=True) - - assert len(all_memories) == 6 - - -def test_delete_chat_history(chat_memory_instance): - app_id = "test_app" - session_id = "test_session" - - for i in range(1, 6): - human_message = f"Question {i}" - ai_message = f"Answer {i}" - - chat_message = ChatMessage() - chat_message.add_user_message(human_message) - chat_message.add_ai_message(ai_message) - - chat_memory_instance.add(app_id, session_id, chat_message) - - session_id_2 = "test_session_2" - - for i in range(1, 6): - human_message = f"Question {i}" - ai_message = f"Answer {i}" - - chat_message = ChatMessage() - chat_message.add_user_message(human_message) - chat_message.add_ai_message(ai_message) - - chat_memory_instance.add(app_id, session_id_2, chat_message) - - chat_memory_instance.delete(app_id, session_id) - - assert chat_memory_instance.count(app_id, session_id) == 0 - assert chat_memory_instance.count(app_id) == 5 - - chat_memory_instance.delete(app_id) - - assert chat_memory_instance.count(app_id) == 0 - - -@pytest.fixture -def close_connection(chat_memory_instance): - yield - chat_memory_instance.close_connection() diff --git a/embedchain/tests/memory/test_memory_messages.py b/embedchain/tests/memory/test_memory_messages.py deleted file mode 100644 index 23f7b53b9..000000000 --- a/embedchain/tests/memory/test_memory_messages.py +++ /dev/null @@ -1,37 +0,0 @@ -from embedchain.memory.message import BaseMessage, ChatMessage - - -def test_ec_base_message(): - content = "Hello, how are you?" - created_by = "human" - metadata = {"key": "value"} - - message = BaseMessage(content=content, created_by=created_by, metadata=metadata) - - assert message.content == content - assert message.created_by == created_by - assert message.metadata == metadata - assert message.type is None - assert message.is_lc_serializable() is True - assert str(message) == f"{created_by}: {content}" - - -def test_ec_base_chat_message(): - human_message_content = "Hello, how are you?" - ai_message_content = "I'm fine, thank you!" - human_metadata = {"user": "John"} - ai_metadata = {"response_time": 0.5} - - chat_message = ChatMessage() - chat_message.add_user_message(human_message_content, metadata=human_metadata) - chat_message.add_ai_message(ai_message_content, metadata=ai_metadata) - - assert chat_message.human_message.content == human_message_content - assert chat_message.human_message.created_by == "human" - assert chat_message.human_message.metadata == human_metadata - - assert chat_message.ai_message.content == ai_message_content - assert chat_message.ai_message.created_by == "ai" - assert chat_message.ai_message.metadata == ai_metadata - - assert str(chat_message) == f"human: {human_message_content}\nai: {ai_message_content}" diff --git a/embedchain/tests/models/test_data_type.py b/embedchain/tests/models/test_data_type.py deleted file mode 100644 index 60d66282c..000000000 --- a/embedchain/tests/models/test_data_type.py +++ /dev/null @@ -1,34 +0,0 @@ -from embedchain.models.data_type import ( - DataType, - DirectDataType, - IndirectDataType, - SpecialDataType, -) - - -def test_subclass_types_in_data_type(): - """Test that all data type category subclasses are contained in the composite data type""" - # Check if DirectDataType values are in DataType - for data_type in DirectDataType: - assert data_type.value in DataType._value2member_map_ - - # Check if IndirectDataType values are in DataType - for data_type in IndirectDataType: - assert data_type.value in DataType._value2member_map_ - - # Check if SpecialDataType values are in DataType - for data_type in SpecialDataType: - assert data_type.value in DataType._value2member_map_ - - -def test_data_type_in_subclasses(): - """Test that all data types in the composite data type are categorized in a subclass""" - for data_type in DataType: - if data_type.value in DirectDataType._value2member_map_: - assert data_type.value in DirectDataType._value2member_map_ - elif data_type.value in IndirectDataType._value2member_map_: - assert data_type.value in IndirectDataType._value2member_map_ - elif data_type.value in SpecialDataType._value2member_map_: - assert data_type.value in SpecialDataType._value2member_map_ - else: - assert False, f"{data_type.value} not found in any subclass enums" diff --git a/embedchain/tests/telemetry/test_posthog.py b/embedchain/tests/telemetry/test_posthog.py deleted file mode 100644 index 8efd150ea..000000000 --- a/embedchain/tests/telemetry/test_posthog.py +++ /dev/null @@ -1,65 +0,0 @@ -import logging -import os - -from embedchain.telemetry.posthog import AnonymousTelemetry - - -class TestAnonymousTelemetry: - def test_init(self, mocker): - # Enable telemetry specifically for this test - os.environ["EC_TELEMETRY"] = "true" - mock_posthog = mocker.patch("embedchain.telemetry.posthog.Posthog") - telemetry = AnonymousTelemetry() - assert telemetry.project_api_key == "phc_PHQDA5KwztijnSojsxJ2c1DuJd52QCzJzT2xnSGvjN2" - assert telemetry.host == "https://app.posthog.com" - assert telemetry.enabled is True - assert telemetry.user_id - mock_posthog.assert_called_once_with(project_api_key=telemetry.project_api_key, host=telemetry.host) - - def test_init_with_disabled_telemetry(self, mocker): - mocker.patch("embedchain.telemetry.posthog.Posthog") - telemetry = AnonymousTelemetry() - assert telemetry.enabled is False - assert telemetry.posthog.disabled is True - - def test_get_user_id(self, mocker, tmpdir): - mock_uuid = mocker.patch("embedchain.telemetry.posthog.uuid.uuid4") - mock_uuid.return_value = "unique_user_id" - config_file = tmpdir.join("config.json") - mocker.patch("embedchain.telemetry.posthog.CONFIG_FILE", str(config_file)) - telemetry = AnonymousTelemetry() - - user_id = telemetry._get_user_id() - assert user_id == "unique_user_id" - assert config_file.read() == '{"user_id": "unique_user_id"}' - - def test_capture(self, mocker): - # Enable telemetry specifically for this test - os.environ["EC_TELEMETRY"] = "true" - mock_posthog = mocker.patch("embedchain.telemetry.posthog.Posthog") - telemetry = AnonymousTelemetry() - event_name = "test_event" - properties = {"key": "value"} - telemetry.capture(event_name, properties) - - mock_posthog.assert_called_once_with( - project_api_key=telemetry.project_api_key, - host=telemetry.host, - ) - mock_posthog.return_value.capture.assert_called_once_with( - telemetry.user_id, - event_name, - properties, - ) - - def test_capture_with_exception(self, mocker, caplog): - os.environ["EC_TELEMETRY"] = "true" - mock_posthog = mocker.patch("embedchain.telemetry.posthog.Posthog") - mock_posthog.return_value.capture.side_effect = Exception("Test Exception") - telemetry = AnonymousTelemetry() - event_name = "test_event" - properties = {"key": "value"} - with caplog.at_level(logging.ERROR): - telemetry.capture(event_name, properties) - assert "Failed to send telemetry event" in caplog.text - caplog.clear() diff --git a/embedchain/tests/test_app.py b/embedchain/tests/test_app.py deleted file mode 100644 index 370503d7e..000000000 --- a/embedchain/tests/test_app.py +++ /dev/null @@ -1,111 +0,0 @@ -import os - -import pytest -import yaml - -from embedchain import App -from embedchain.config import ChromaDbConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.llm.base import BaseLlm -from embedchain.vectordb.base import BaseVectorDB -from embedchain.vectordb.chroma import ChromaDB - - -@pytest.fixture -def app(): - os.environ["OPENAI_API_KEY"] = "test-api-key" - os.environ["OPENAI_API_BASE"] = "test-api-base" - return App() - - -def test_app(app): - assert isinstance(app.llm, BaseLlm) - assert isinstance(app.db, BaseVectorDB) - assert isinstance(app.embedding_model, BaseEmbedder) - - -class TestConfigForAppComponents: - def test_constructor_config(self): - collection_name = "my-test-collection" - db = ChromaDB(config=ChromaDbConfig(collection_name=collection_name)) - app = App(db=db) - assert app.db.config.collection_name == collection_name - - def test_component_config(self): - collection_name = "my-test-collection" - database = ChromaDB(config=ChromaDbConfig(collection_name=collection_name)) - app = App(db=database) - assert app.db.config.collection_name == collection_name - - -class TestAppFromConfig: - def load_config_data(self, yaml_path): - with open(yaml_path, "r") as file: - return yaml.safe_load(file) - - def test_from_chroma_config(self, mocker): - mocker.patch("embedchain.vectordb.chroma.chromadb.Client") - - yaml_path = "configs/chroma.yaml" - config_data = self.load_config_data(yaml_path) - - app = App.from_config(config_path=yaml_path) - - # Check if the App instance and its components were created correctly - assert isinstance(app, App) - - # Validate the AppConfig values - assert app.config.id == config_data["app"]["config"]["id"] - # Even though not present in the config, the default value is used - assert app.config.collect_metrics is True - - # Validate the LLM config values - llm_config = config_data["llm"]["config"] - assert app.llm.config.temperature == llm_config["temperature"] - assert app.llm.config.max_tokens == llm_config["max_tokens"] - assert app.llm.config.top_p == llm_config["top_p"] - assert app.llm.config.stream == llm_config["stream"] - - # Validate the VectorDB config values - db_config = config_data["vectordb"]["config"] - assert app.db.config.collection_name == db_config["collection_name"] - assert app.db.config.dir == db_config["dir"] - assert app.db.config.allow_reset == db_config["allow_reset"] - - # Validate the Embedder config values - embedder_config = config_data["embedder"]["config"] - assert app.embedding_model.config.model == embedder_config["model"] - assert app.embedding_model.config.deployment_name == embedder_config.get("deployment_name") - - def test_from_opensource_config(self, mocker): - mocker.patch("embedchain.vectordb.chroma.chromadb.Client") - - yaml_path = "configs/opensource.yaml" - config_data = self.load_config_data(yaml_path) - - app = App.from_config(yaml_path) - - # Check if the App instance and its components were created correctly - assert isinstance(app, App) - - # Validate the AppConfig values - assert app.config.id == config_data["app"]["config"]["id"] - assert app.config.collect_metrics == config_data["app"]["config"]["collect_metrics"] - - # Validate the LLM config values - llm_config = config_data["llm"]["config"] - assert app.llm.config.model == llm_config["model"] - assert app.llm.config.temperature == llm_config["temperature"] - assert app.llm.config.max_tokens == llm_config["max_tokens"] - assert app.llm.config.top_p == llm_config["top_p"] - assert app.llm.config.stream == llm_config["stream"] - - # Validate the VectorDB config values - db_config = config_data["vectordb"]["config"] - assert app.db.config.collection_name == db_config["collection_name"] - assert app.db.config.dir == db_config["dir"] - assert app.db.config.allow_reset == db_config["allow_reset"] - - # Validate the Embedder config values - embedder_config = config_data["embedder"]["config"] - assert app.embedding_model.config.deployment_name == embedder_config["deployment_name"] diff --git a/embedchain/tests/test_client.py b/embedchain/tests/test_client.py deleted file mode 100644 index 5259ecd69..000000000 --- a/embedchain/tests/test_client.py +++ /dev/null @@ -1,53 +0,0 @@ -import pytest - -from embedchain import Client - - -class TestClient: - @pytest.fixture - def mock_requests_post(self, mocker): - return mocker.patch("embedchain.client.requests.post") - - def test_valid_api_key(self, mock_requests_post): - mock_requests_post.return_value.status_code = 200 - client = Client(api_key="valid_api_key") - assert client.check("valid_api_key") is True - - def test_invalid_api_key(self, mock_requests_post): - mock_requests_post.return_value.status_code = 401 - with pytest.raises(ValueError): - Client(api_key="invalid_api_key") - - def test_update_valid_api_key(self, mock_requests_post): - mock_requests_post.return_value.status_code = 200 - client = Client(api_key="valid_api_key") - client.update("new_valid_api_key") - assert client.get() == "new_valid_api_key" - - def test_clear_api_key(self, mock_requests_post): - mock_requests_post.return_value.status_code = 200 - client = Client(api_key="valid_api_key") - client.clear() - assert client.get() is None - - def test_save_api_key(self, mock_requests_post): - mock_requests_post.return_value.status_code = 200 - api_key_to_save = "valid_api_key" - client = Client(api_key=api_key_to_save) - client.save() - assert client.get() == api_key_to_save - - def test_load_api_key_from_config(self, mocker): - mocker.patch("embedchain.Client.load_config", return_value={"api_key": "test_api_key"}) - client = Client() - assert client.get() == "test_api_key" - - def test_load_invalid_api_key_from_config(self, mocker): - mocker.patch("embedchain.Client.load_config", return_value={}) - with pytest.raises(ValueError): - Client() - - def test_load_missing_api_key_from_config(self, mocker): - mocker.patch("embedchain.Client.load_config", return_value={}) - with pytest.raises(ValueError): - Client() diff --git a/embedchain/tests/test_factory.py b/embedchain/tests/test_factory.py deleted file mode 100644 index c6e1ea0e5..000000000 --- a/embedchain/tests/test_factory.py +++ /dev/null @@ -1,66 +0,0 @@ -import os - -import pytest - -import embedchain -import embedchain.embedder.gpt4all -import embedchain.embedder.huggingface -import embedchain.embedder.openai -import embedchain.embedder.vertexai -import embedchain.llm.anthropic -import embedchain.llm.openai -import embedchain.vectordb.chroma -import embedchain.vectordb.elasticsearch -import embedchain.vectordb.opensearch -from embedchain.factory import EmbedderFactory, LlmFactory, VectorDBFactory - - -class TestFactories: - @pytest.mark.parametrize( - "provider_name, config_data, expected_class", - [ - ("openai", {}, embedchain.llm.openai.OpenAILlm), - ("anthropic", {}, embedchain.llm.anthropic.AnthropicLlm), - ], - ) - def test_llm_factory_create(self, provider_name, config_data, expected_class): - os.environ["ANTHROPIC_API_KEY"] = "test_api_key" - os.environ["OPENAI_API_KEY"] = "test_api_key" - os.environ["OPENAI_API_BASE"] = "test_api_base" - llm_instance = LlmFactory.create(provider_name, config_data) - assert isinstance(llm_instance, expected_class) - - @pytest.mark.parametrize( - "provider_name, config_data, expected_class", - [ - ("gpt4all", {}, embedchain.embedder.gpt4all.GPT4AllEmbedder), - ( - "huggingface", - {"model": "sentence-transformers/all-mpnet-base-v2", "vector_dimension": 768}, - embedchain.embedder.huggingface.HuggingFaceEmbedder, - ), - ("vertexai", {"model": "textembedding-gecko"}, embedchain.embedder.vertexai.VertexAIEmbedder), - ("openai", {}, embedchain.embedder.openai.OpenAIEmbedder), - ], - ) - def test_embedder_factory_create(self, mocker, provider_name, config_data, expected_class): - mocker.patch("embedchain.embedder.vertexai.VertexAIEmbedder", autospec=True) - embedder_instance = EmbedderFactory.create(provider_name, config_data) - assert isinstance(embedder_instance, expected_class) - - @pytest.mark.parametrize( - "provider_name, config_data, expected_class", - [ - ("chroma", {}, embedchain.vectordb.chroma.ChromaDB), - ( - "opensearch", - {"opensearch_url": "http://localhost:9200", "http_auth": ("admin", "admin")}, - embedchain.vectordb.opensearch.OpenSearchDB, - ), - ("elasticsearch", {"es_url": "http://localhost:9200"}, embedchain.vectordb.elasticsearch.ElasticsearchDB), - ], - ) - def test_vectordb_factory_create(self, mocker, provider_name, config_data, expected_class): - mocker.patch("embedchain.vectordb.opensearch.OpenSearchDB", autospec=True) - vectordb_instance = VectorDBFactory.create(provider_name, config_data) - assert isinstance(vectordb_instance, expected_class) diff --git a/embedchain/tests/test_utils.py b/embedchain/tests/test_utils.py deleted file mode 100644 index 3e50e1e16..000000000 --- a/embedchain/tests/test_utils.py +++ /dev/null @@ -1,38 +0,0 @@ -import yaml - -from embedchain.utils.misc import validate_config - -CONFIG_YAMLS = [ - "configs/anthropic.yaml", - "configs/azure_openai.yaml", - "configs/chroma.yaml", - "configs/chunker.yaml", - "configs/cohere.yaml", - "configs/together.yaml", - "configs/ollama.yaml", - "configs/full-stack.yaml", - "configs/gpt4.yaml", - "configs/gpt4all.yaml", - "configs/huggingface.yaml", - "configs/jina.yaml", - "configs/llama2.yaml", - "configs/opensearch.yaml", - "configs/opensource.yaml", - "configs/pinecone.yaml", - "configs/vertexai.yaml", - "configs/weaviate.yaml", -] - - -def test_all_config_yamls(): - """Test that all config yamls are valid.""" - for config_yaml in CONFIG_YAMLS: - with open(config_yaml, "r") as f: - config = yaml.safe_load(f) - assert config is not None - - try: - validate_config(config) - except Exception as e: - print(f"Error in {config_yaml}: {e}") - raise e diff --git a/embedchain/tests/vectordb/test_chroma_db.py b/embedchain/tests/vectordb/test_chroma_db.py deleted file mode 100644 index 1e2659e3e..000000000 --- a/embedchain/tests/vectordb/test_chroma_db.py +++ /dev/null @@ -1,253 +0,0 @@ -import os -import shutil -from unittest.mock import patch - -import pytest -from chromadb.config import Settings - -from embedchain import App -from embedchain.config import AppConfig, ChromaDbConfig -from embedchain.vectordb.chroma import ChromaDB - -os.environ["OPENAI_API_KEY"] = "test-api-key" - - -@pytest.fixture -def chroma_db(): - return ChromaDB(config=ChromaDbConfig(host="test-host", port="1234")) - - -@pytest.fixture -def app_with_settings(): - chroma_config = ChromaDbConfig(allow_reset=True, dir="test-db") - chroma_db = ChromaDB(config=chroma_config) - app_config = AppConfig(collect_metrics=False) - return App(config=app_config, db=chroma_db) - - -@pytest.fixture(scope="session", autouse=True) -def cleanup_db(): - yield - try: - shutil.rmtree("test-db") - except OSError as e: - print("Error: %s - %s." % (e.filename, e.strerror)) - - -@patch("embedchain.vectordb.chroma.chromadb.Client") -def test_chroma_db_init_with_host_and_port(mock_client): - chroma_db = ChromaDB(config=ChromaDbConfig(host="test-host", port="1234")) # noqa - called_settings: Settings = mock_client.call_args[0][0] - assert called_settings.chroma_server_host == "test-host" - assert called_settings.chroma_server_http_port == "1234" - - -@patch("embedchain.vectordb.chroma.chromadb.Client") -def test_chroma_db_init_with_basic_auth(mock_client): - chroma_config = { - "host": "test-host", - "port": "1234", - "chroma_settings": { - "chroma_client_auth_provider": "chromadb.auth.basic.BasicAuthClientProvider", - "chroma_client_auth_credentials": "admin:admin", - }, - } - - ChromaDB(config=ChromaDbConfig(**chroma_config)) - called_settings: Settings = mock_client.call_args[0][0] - assert called_settings.chroma_server_host == "test-host" - assert called_settings.chroma_server_http_port == "1234" - assert ( - called_settings.chroma_client_auth_provider == chroma_config["chroma_settings"]["chroma_client_auth_provider"] - ) - assert ( - called_settings.chroma_client_auth_credentials - == chroma_config["chroma_settings"]["chroma_client_auth_credentials"] - ) - - -@patch("embedchain.vectordb.chroma.chromadb.Client") -def test_app_init_with_host_and_port(mock_client): - host = "test-host" - port = "1234" - config = AppConfig(collect_metrics=False) - db_config = ChromaDbConfig(host=host, port=port) - db = ChromaDB(config=db_config) - _app = App(config=config, db=db) - - called_settings: Settings = mock_client.call_args[0][0] - assert called_settings.chroma_server_host == host - assert called_settings.chroma_server_http_port == port - - -@patch("embedchain.vectordb.chroma.chromadb.Client") -def test_app_init_with_host_and_port_none(mock_client): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - _app = App(config=AppConfig(collect_metrics=False), db=db) - - called_settings: Settings = mock_client.call_args[0][0] - assert called_settings.chroma_server_host is None - assert called_settings.chroma_server_http_port is None - - -def test_chroma_db_duplicates_throw_warning(caplog): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"]) - app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"]) - assert "Insert of existing embedding ID: 0" in caplog.text - assert "Add of existing embedding ID: 0" in caplog.text - app.db.reset() - - -def test_chroma_db_duplicates_collections_no_warning(caplog): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"]) - app.set_collection_name("test_collection_2") - app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"]) - assert "Insert of existing embedding ID: 0" not in caplog.text - assert "Add of existing embedding ID: 0" not in caplog.text - app.db.reset() - app.set_collection_name("test_collection_1") - app.db.reset() - - -def test_chroma_db_collection_init_with_default_collection(): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - assert app.db.collection.name == "embedchain_store" - - -def test_chroma_db_collection_init_with_custom_collection(): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name(name="test_collection") - assert app.db.collection.name == "test_collection" - - -def test_chroma_db_collection_set_collection_name(): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection") - assert app.db.collection.name == "test_collection" - - -def test_chroma_db_collection_changes_encapsulated(): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - assert app.db.count() == 0 - - app.db.collection.add(embeddings=[0, 0, 0], ids=["0"]) - assert app.db.count() == 1 - - app.set_collection_name("test_collection_2") - assert app.db.count() == 0 - - app.db.collection.add(embeddings=[0, 0, 0], ids=["0"]) - app.set_collection_name("test_collection_1") - assert app.db.count() == 1 - app.db.reset() - app.set_collection_name("test_collection_2") - app.db.reset() - - -def test_chroma_db_collection_collections_are_persistent(): - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"]) - del app - - db = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - assert app.db.count() == 1 - - app.db.reset() - - -def test_chroma_db_collection_parallel_collections(): - db1 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db", collection_name="test_collection_1")) - app1 = App( - config=AppConfig(collect_metrics=False), - db=db1, - ) - db2 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db", collection_name="test_collection_2")) - app2 = App( - config=AppConfig(collect_metrics=False), - db=db2, - ) - - # cleanup if any previous tests failed or were interrupted - app1.db.reset() - app2.db.reset() - - app1.db.collection.add(embeddings=[0, 0, 0], ids=["0"]) - assert app1.db.count() == 1 - assert app2.db.count() == 0 - - app1.db.collection.add(embeddings=[[0, 0, 0], [1, 1, 1]], ids=["1", "2"]) - app2.db.collection.add(embeddings=[0, 0, 0], ids=["0"]) - - app1.set_collection_name("test_collection_2") - assert app1.db.count() == 1 - app2.set_collection_name("test_collection_1") - assert app2.db.count() == 3 - - # cleanup - app1.db.reset() - app2.db.reset() - - -def test_chroma_db_collection_ids_share_collections(): - db1 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app1 = App(config=AppConfig(collect_metrics=False), db=db1) - app1.set_collection_name("one_collection") - db2 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app2 = App(config=AppConfig(collect_metrics=False), db=db2) - app2.set_collection_name("one_collection") - - app1.db.collection.add(embeddings=[[0, 0, 0], [1, 1, 1]], ids=["0", "1"]) - app2.db.collection.add(embeddings=[0, 0, 0], ids=["2"]) - - assert app1.db.count() == 3 - assert app2.db.count() == 3 - - # cleanup - app1.db.reset() - app2.db.reset() - - -def test_chroma_db_collection_reset(): - db1 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app1 = App(config=AppConfig(collect_metrics=False), db=db1) - app1.set_collection_name("one_collection") - db2 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app2 = App(config=AppConfig(collect_metrics=False), db=db2) - app2.set_collection_name("two_collection") - db3 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app3 = App(config=AppConfig(collect_metrics=False), db=db3) - app3.set_collection_name("three_collection") - db4 = ChromaDB(config=ChromaDbConfig(allow_reset=True, dir="test-db")) - app4 = App(config=AppConfig(collect_metrics=False), db=db4) - app4.set_collection_name("four_collection") - - app1.db.collection.add(embeddings=[0, 0, 0], ids=["1"]) - app2.db.collection.add(embeddings=[0, 0, 0], ids=["2"]) - app3.db.collection.add(embeddings=[0, 0, 0], ids=["3"]) - app4.db.collection.add(embeddings=[0, 0, 0], ids=["4"]) - - app1.db.reset() - - assert app1.db.count() == 0 - assert app2.db.count() == 1 - assert app3.db.count() == 1 - assert app4.db.count() == 1 - - # cleanup - app2.db.reset() - app3.db.reset() - app4.db.reset() diff --git a/embedchain/tests/vectordb/test_elasticsearch_db.py b/embedchain/tests/vectordb/test_elasticsearch_db.py deleted file mode 100644 index 2cb42c424..000000000 --- a/embedchain/tests/vectordb/test_elasticsearch_db.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -import unittest -from unittest.mock import patch - -from embedchain import App -from embedchain.config import AppConfig, ElasticsearchDBConfig -from embedchain.embedder.gpt4all import GPT4AllEmbedder -from embedchain.vectordb.elasticsearch import ElasticsearchDB - - -class TestEsDB(unittest.TestCase): - @patch("embedchain.vectordb.elasticsearch.Elasticsearch") - def test_setUp(self, mock_client): - self.db = ElasticsearchDB(config=ElasticsearchDBConfig(es_url="https://localhost:9200")) - self.vector_dim = 384 - app_config = AppConfig(collect_metrics=False) - self.app = App(config=app_config, db=self.db) - - # Assert that the Elasticsearch client is stored in the ElasticsearchDB class. - self.assertEqual(self.db.client, mock_client.return_value) - - @patch("embedchain.vectordb.elasticsearch.Elasticsearch") - def test_query(self, mock_client): - self.db = ElasticsearchDB(config=ElasticsearchDBConfig(es_url="https://localhost:9200")) - app_config = AppConfig(collect_metrics=False) - self.app = App(config=app_config, db=self.db, embedding_model=GPT4AllEmbedder()) - - # Assert that the Elasticsearch client is stored in the ElasticsearchDB class. - self.assertEqual(self.db.client, mock_client.return_value) - - # Create some dummy data - documents = ["This is a document.", "This is another document."] - metadatas = [{"url": "url_1", "doc_id": "doc_id_1"}, {"url": "url_2", "doc_id": "doc_id_2"}] - ids = ["doc_1", "doc_2"] - - # Add the data to the database. - self.db.add(documents, metadatas, ids) - - search_response = { - "hits": { - "hits": [ - { - "_source": {"text": "This is a document.", "metadata": {"url": "url_1", "doc_id": "doc_id_1"}}, - "_score": 0.9, - }, - { - "_source": { - "text": "This is another document.", - "metadata": {"url": "url_2", "doc_id": "doc_id_2"}, - }, - "_score": 0.8, - }, - ] - } - } - - # Configure the mock client to return the mocked response. - mock_client.return_value.search.return_value = search_response - - # Query the database for the documents that are most similar to the query "This is a document". - query = "This is a document" - results_without_citations = self.db.query(query, n_results=2, where={}) - expected_results_without_citations = ["This is a document.", "This is another document."] - self.assertEqual(results_without_citations, expected_results_without_citations) - - results_with_citations = self.db.query(query, n_results=2, where={}, citations=True) - expected_results_with_citations = [ - ("This is a document.", {"url": "url_1", "doc_id": "doc_id_1", "score": 0.9}), - ("This is another document.", {"url": "url_2", "doc_id": "doc_id_2", "score": 0.8}), - ] - self.assertEqual(results_with_citations, expected_results_with_citations) - - def test_init_without_url(self): - # Make sure it's not loaded from env - try: - del os.environ["ELASTICSEARCH_URL"] - except KeyError: - pass - # Test if an exception is raised when an invalid es_config is provided - with self.assertRaises(AttributeError): - ElasticsearchDB() - - def test_init_with_invalid_es_config(self): - # Test if an exception is raised when an invalid es_config is provided - with self.assertRaises(TypeError): - ElasticsearchDB(es_config={"ES_URL": "some_url", "valid es_config": False}) diff --git a/embedchain/tests/vectordb/test_lancedb.py b/embedchain/tests/vectordb/test_lancedb.py deleted file mode 100644 index 91885bdd5..000000000 --- a/embedchain/tests/vectordb/test_lancedb.py +++ /dev/null @@ -1,215 +0,0 @@ -import os -import shutil - -import pytest - -from embedchain import App -from embedchain.config import AppConfig -from embedchain.config.vector_db.lancedb import LanceDBConfig -from embedchain.vectordb.lancedb import LanceDB - -os.environ["OPENAI_API_KEY"] = "test-api-key" - - -@pytest.fixture -def lancedb(): - return LanceDB(config=LanceDBConfig(dir="test-db", collection_name="test-coll")) - - -@pytest.fixture -def app_with_settings(): - lancedb_config = LanceDBConfig(allow_reset=True, dir="test-db-reset") - lancedb = LanceDB(config=lancedb_config) - app_config = AppConfig(collect_metrics=False) - return App(config=app_config, db=lancedb) - - -@pytest.fixture(scope="session", autouse=True) -def cleanup_db(): - yield - try: - shutil.rmtree("test-db.lance") - shutil.rmtree("test-db-reset.lance") - except OSError as e: - print("Error: %s - %s." % (e.filename, e.strerror)) - - -def test_lancedb_duplicates_throw_warning(caplog): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - assert "Insert of existing doc ID: 0" not in caplog.text - assert "Add of existing doc ID: 0" not in caplog.text - app.db.reset() - - -def test_lancedb_duplicates_collections_no_warning(caplog): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - app.set_collection_name("test_collection_2") - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - assert "Insert of existing doc ID: 0" not in caplog.text - assert "Add of existing doc ID: 0" not in caplog.text - app.db.reset() - app.set_collection_name("test_collection_1") - app.db.reset() - - -def test_lancedb_collection_init_with_default_collection(): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - assert app.db.collection.name == "embedchain_store" - - -def test_lancedb_collection_init_with_custom_collection(): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name(name="test_collection") - assert app.db.collection.name == "test_collection" - - -def test_lancedb_collection_set_collection_name(): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection") - assert app.db.collection.name == "test_collection" - - -def test_lancedb_collection_changes_encapsulated(): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - assert app.db.count() == 0 - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - assert app.db.count() == 1 - - app.set_collection_name("test_collection_2") - assert app.db.count() == 0 - - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - app.set_collection_name("test_collection_1") - assert app.db.count() == 1 - app.db.reset() - app.set_collection_name("test_collection_2") - app.db.reset() - - -def test_lancedb_collection_collections_are_persistent(): - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - app.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - del app - - db = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app = App(config=AppConfig(collect_metrics=False), db=db) - app.set_collection_name("test_collection_1") - assert app.db.count() == 1 - - app.db.reset() - - -def test_lancedb_collection_parallel_collections(): - db1 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db", collection_name="test_collection_1")) - app1 = App( - config=AppConfig(collect_metrics=False), - db=db1, - ) - db2 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db", collection_name="test_collection_2")) - app2 = App( - config=AppConfig(collect_metrics=False), - db=db2, - ) - - # cleanup if any previous tests failed or were interrupted - app1.db.reset() - app2.db.reset() - - app1.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - - assert app1.db.count() == 1 - assert app2.db.count() == 0 - - app1.db.add(ids=["1", "2"], documents=["doc1", "doc2"], metadatas=["test", "test"]) - app2.db.add(ids=["0"], documents=["doc1"], metadatas=["test"]) - - app1.set_collection_name("test_collection_2") - assert app1.db.count() == 1 - app2.set_collection_name("test_collection_1") - assert app2.db.count() == 3 - - # cleanup - app1.db.reset() - app2.db.reset() - - -def test_lancedb_collection_ids_share_collections(): - db1 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app1 = App(config=AppConfig(collect_metrics=False), db=db1) - app1.set_collection_name("one_collection") - db2 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app2 = App(config=AppConfig(collect_metrics=False), db=db2) - app2.set_collection_name("one_collection") - - # cleanup - app1.db.reset() - app2.db.reset() - - app1.db.add(ids=["0", "1"], documents=["doc1", "doc2"], metadatas=["test", "test"]) - app2.db.add(ids=["2"], documents=["doc3"], metadatas=["test"]) - - assert app1.db.count() == 2 - assert app2.db.count() == 3 - - # cleanup - app1.db.reset() - app2.db.reset() - - -def test_lancedb_collection_reset(): - db1 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app1 = App(config=AppConfig(collect_metrics=False), db=db1) - app1.set_collection_name("one_collection") - db2 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app2 = App(config=AppConfig(collect_metrics=False), db=db2) - app2.set_collection_name("two_collection") - db3 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app3 = App(config=AppConfig(collect_metrics=False), db=db3) - app3.set_collection_name("three_collection") - db4 = LanceDB(config=LanceDBConfig(allow_reset=True, dir="test-db")) - app4 = App(config=AppConfig(collect_metrics=False), db=db4) - app4.set_collection_name("four_collection") - - # cleanup if any previous tests failed or were interrupted - app1.db.reset() - app2.db.reset() - app3.db.reset() - app4.db.reset() - - app1.db.add(ids=["1"], documents=["doc1"], metadatas=["test"]) - app2.db.add(ids=["2"], documents=["doc2"], metadatas=["test"]) - app3.db.add(ids=["3"], documents=["doc3"], metadatas=["test"]) - app4.db.add(ids=["4"], documents=["doc4"], metadatas=["test"]) - - app1.db.reset() - - assert app1.db.count() == 0 - assert app2.db.count() == 1 - assert app3.db.count() == 1 - assert app4.db.count() == 1 - - # cleanup - app2.db.reset() - app3.db.reset() - app4.db.reset() - - -def generate_embeddings(dummy_embed, embed_size): - generated_embedding = [] - for i in range(embed_size): - generated_embedding.append(dummy_embed) - - return generated_embedding diff --git a/embedchain/tests/vectordb/test_pinecone.py b/embedchain/tests/vectordb/test_pinecone.py deleted file mode 100644 index 00051ed94..000000000 --- a/embedchain/tests/vectordb/test_pinecone.py +++ /dev/null @@ -1,225 +0,0 @@ -import pytest - -from embedchain.config.vector_db.pinecone import PineconeDBConfig -from embedchain.vectordb.pinecone import PineconeDB - - -@pytest.fixture -def pinecone_pod_config(): - return PineconeDBConfig( - index_name="test_collection", - api_key="test_api_key", - vector_dimension=3, - pod_config={"environment": "test_environment", "metadata_config": {"indexed": ["*"]}}, - ) - - -@pytest.fixture -def pinecone_serverless_config(): - return PineconeDBConfig( - index_name="test_collection", - api_key="test_api_key", - vector_dimension=3, - serverless_config={ - "cloud": "test_cloud", - "region": "test_region", - }, - ) - - -def test_pinecone_init_without_config(monkeypatch): - monkeypatch.setenv("PINECONE_API_KEY", "test_api_key") - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._setup_pinecone_index", lambda x: x) - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._get_or_create_db", lambda x: x) - pinecone_db = PineconeDB() - - assert isinstance(pinecone_db, PineconeDB) - assert isinstance(pinecone_db.config, PineconeDBConfig) - assert pinecone_db.config.pod_config == {"environment": "gcp-starter", "metadata_config": {"indexed": ["*"]}} - monkeypatch.delenv("PINECONE_API_KEY") - - -def test_pinecone_init_with_config(pinecone_pod_config, monkeypatch): - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._setup_pinecone_index", lambda x: x) - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._get_or_create_db", lambda x: x) - pinecone_db = PineconeDB(config=pinecone_pod_config) - - assert isinstance(pinecone_db, PineconeDB) - assert isinstance(pinecone_db.config, PineconeDBConfig) - - assert pinecone_db.config.pod_config == pinecone_pod_config.pod_config - - pinecone_db = PineconeDB(config=pinecone_pod_config) - - assert isinstance(pinecone_db, PineconeDB) - assert isinstance(pinecone_db.config, PineconeDBConfig) - - assert pinecone_db.config.serverless_config == pinecone_pod_config.serverless_config - - -class MockListIndexes: - def names(self): - return ["test_collection"] - - -class MockPineconeIndex: - db = [] - - def __init__(*args, **kwargs): - pass - - def upsert(self, chunk, **kwargs): - self.db.extend([c for c in chunk]) - return - - def delete(self, *args, **kwargs): - pass - - def query(self, *args, **kwargs): - return { - "matches": [ - { - "metadata": { - "key": "value", - "text": "text_1", - }, - "score": 0.1, - }, - { - "metadata": { - "key": "value", - "text": "text_2", - }, - "score": 0.2, - }, - ] - } - - def fetch(self, *args, **kwargs): - return { - "vectors": { - "key_1": { - "metadata": { - "source": "1", - } - }, - "key_2": { - "metadata": { - "source": "2", - } - }, - } - } - - def describe_index_stats(self, *args, **kwargs): - return {"total_vector_count": len(self.db)} - - -class MockPineconeClient: - def __init__(*args, **kwargs): - pass - - def list_indexes(self): - return MockListIndexes() - - def create_index(self, *args, **kwargs): - pass - - def Index(self, *args, **kwargs): - return MockPineconeIndex() - - def delete_index(self, *args, **kwargs): - pass - - -class MockPinecone: - def __init__(*args, **kwargs): - pass - - def Pinecone(*args, **kwargs): - return MockPineconeClient() - - def PodSpec(*args, **kwargs): - pass - - def ServerlessSpec(*args, **kwargs): - pass - - -class MockEmbedder: - def embedding_fn(self, documents): - return [[1, 1, 1] for d in documents] - - -def test_setup_pinecone_index(pinecone_pod_config, pinecone_serverless_config, monkeypatch): - monkeypatch.setattr("embedchain.vectordb.pinecone.pinecone", MockPinecone) - monkeypatch.setenv("PINECONE_API_KEY", "test_api_key") - pinecone_db = PineconeDB(config=pinecone_pod_config) - pinecone_db._setup_pinecone_index() - - assert pinecone_db.client is not None - assert pinecone_db.config.index_name == "test_collection" - assert pinecone_db.client.list_indexes().names() == ["test_collection"] - assert pinecone_db.pinecone_index is not None - - pinecone_db = PineconeDB(config=pinecone_serverless_config) - pinecone_db._setup_pinecone_index() - - assert pinecone_db.client is not None - assert pinecone_db.config.index_name == "test_collection" - assert pinecone_db.client.list_indexes().names() == ["test_collection"] - assert pinecone_db.pinecone_index is not None - - -def test_get(monkeypatch): - def mock_pinecone_db(): - monkeypatch.setenv("PINECONE_API_KEY", "test_api_key") - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._setup_pinecone_index", lambda x: x) - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._get_or_create_db", lambda x: x) - db = PineconeDB() - db.pinecone_index = MockPineconeIndex() - return db - - pinecone_db = mock_pinecone_db() - ids = pinecone_db.get(["key_1", "key_2"]) - assert ids == {"ids": ["key_1", "key_2"], "metadatas": [{"source": "1"}, {"source": "2"}]} - - -def test_add(monkeypatch): - def mock_pinecone_db(): - monkeypatch.setenv("PINECONE_API_KEY", "test_api_key") - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._setup_pinecone_index", lambda x: x) - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._get_or_create_db", lambda x: x) - db = PineconeDB() - db.pinecone_index = MockPineconeIndex() - db._set_embedder(MockEmbedder()) - return db - - pinecone_db = mock_pinecone_db() - pinecone_db.add(["text_1", "text_2"], [{"key_1": "value_1"}, {"key_2": "value_2"}], ["key_1", "key_2"]) - assert pinecone_db.count() == 2 - - pinecone_db.add(["text_3", "text_4"], [{"key_3": "value_3"}, {"key_4": "value_4"}], ["key_3", "key_4"]) - assert pinecone_db.count() == 4 - - -def test_query(monkeypatch): - def mock_pinecone_db(): - monkeypatch.setenv("PINECONE_API_KEY", "test_api_key") - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._setup_pinecone_index", lambda x: x) - monkeypatch.setattr("embedchain.vectordb.pinecone.PineconeDB._get_or_create_db", lambda x: x) - db = PineconeDB() - db.pinecone_index = MockPineconeIndex() - db._set_embedder(MockEmbedder()) - return db - - pinecone_db = mock_pinecone_db() - # without citations - results = pinecone_db.query(["text_1", "text_2"], n_results=2, where={}) - assert results == ["text_1", "text_2"] - # with citations - results = pinecone_db.query(["text_1", "text_2"], n_results=2, where={}, citations=True) - assert results == [ - ("text_1", {"key": "value", "text": "text_1", "score": 0.1}), - ("text_2", {"key": "value", "text": "text_2", "score": 0.2}), - ] diff --git a/embedchain/tests/vectordb/test_qdrant.py b/embedchain/tests/vectordb/test_qdrant.py deleted file mode 100644 index b2b3dfa07..000000000 --- a/embedchain/tests/vectordb/test_qdrant.py +++ /dev/null @@ -1,167 +0,0 @@ -import unittest -import uuid - -from mock import patch -from qdrant_client.http import models -from qdrant_client.http.models import Batch - -from embedchain import App -from embedchain.config import AppConfig -from embedchain.config.vector_db.pinecone import PineconeDBConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.vectordb.qdrant import QdrantDB - - -def mock_embedding_fn(texts: list[str]) -> list[list[float]]: - """A mock embedding function.""" - return [[1, 2, 3], [4, 5, 6]] - - -class TestQdrantDB(unittest.TestCase): - TEST_UUIDS = ["abc", "def", "ghi"] - - def test_incorrect_config_throws_error(self): - """Test the init method of the Qdrant class throws error for incorrect config""" - with self.assertRaises(TypeError): - QdrantDB(config=PineconeDBConfig()) - - @patch("embedchain.vectordb.qdrant.QdrantClient") - def test_initialize(self, qdrant_client_mock): - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Qdrant instance - db = QdrantDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - self.assertEqual(db.collection_name, "embedchain-store-1536") - self.assertEqual(db.client, qdrant_client_mock.return_value) - qdrant_client_mock.return_value.get_collections.assert_called_once() - - @patch("embedchain.vectordb.qdrant.QdrantClient") - def test_get(self, qdrant_client_mock): - qdrant_client_mock.return_value.scroll.return_value = ([], None) - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Qdrant instance - db = QdrantDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - resp = db.get(ids=[], where={}) - self.assertEqual(resp, {"ids": [], "metadatas": []}) - resp2 = db.get(ids=["123", "456"], where={"url": "https://ai.ai"}) - self.assertEqual(resp2, {"ids": [], "metadatas": []}) - - @patch("embedchain.vectordb.qdrant.QdrantClient") - @patch.object(uuid, "uuid4", side_effect=TEST_UUIDS) - def test_add(self, uuid_mock, qdrant_client_mock): - qdrant_client_mock.return_value.scroll.return_value = ([], None) - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Qdrant instance - db = QdrantDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - documents = ["This is a test document.", "This is another test document."] - metadatas = [{}, {}] - ids = ["123", "456"] - db.add(documents, metadatas, ids) - qdrant_client_mock.return_value.upsert.assert_called_once_with( - collection_name="embedchain-store-1536", - points=Batch( - ids=["123", "456"], - payloads=[ - { - "identifier": "123", - "text": "This is a test document.", - "metadata": {"text": "This is a test document."}, - }, - { - "identifier": "456", - "text": "This is another test document.", - "metadata": {"text": "This is another test document."}, - }, - ], - vectors=[[1, 2, 3], [4, 5, 6]], - ), - ) - - @patch("embedchain.vectordb.qdrant.QdrantClient") - def test_query(self, qdrant_client_mock): - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Qdrant instance - db = QdrantDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - # Query for the document. - db.query(input_query="This is a test document.", n_results=1, where={"doc_id": "123"}) - - qdrant_client_mock.return_value.search.assert_called_once_with( - collection_name="embedchain-store-1536", - query_filter=models.Filter( - must=[ - models.FieldCondition( - key="metadata.doc_id", - match=models.MatchValue( - value="123", - ), - ) - ] - ), - query_vector=[1, 2, 3], - limit=1, - ) - - @patch("embedchain.vectordb.qdrant.QdrantClient") - def test_count(self, qdrant_client_mock): - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Qdrant instance - db = QdrantDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - db.count() - qdrant_client_mock.return_value.get_collection.assert_called_once_with(collection_name="embedchain-store-1536") - - @patch("embedchain.vectordb.qdrant.QdrantClient") - def test_reset(self, qdrant_client_mock): - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Qdrant instance - db = QdrantDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - db.reset() - qdrant_client_mock.return_value.delete_collection.assert_called_once_with( - collection_name="embedchain-store-1536" - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/embedchain/tests/vectordb/test_weaviate.py b/embedchain/tests/vectordb/test_weaviate.py deleted file mode 100644 index a51870d44..000000000 --- a/embedchain/tests/vectordb/test_weaviate.py +++ /dev/null @@ -1,237 +0,0 @@ -import unittest -from unittest.mock import patch - -from embedchain import App -from embedchain.config import AppConfig -from embedchain.config.vector_db.pinecone import PineconeDBConfig -from embedchain.embedder.base import BaseEmbedder -from embedchain.vectordb.weaviate import WeaviateDB - - -def mock_embedding_fn(texts: list[str]) -> list[list[float]]: - """A mock embedding function.""" - return [[1, 2, 3], [4, 5, 6]] - - -class TestWeaviateDb(unittest.TestCase): - def test_incorrect_config_throws_error(self): - """Test the init method of the WeaviateDb class throws error for incorrect config""" - with self.assertRaises(TypeError): - WeaviateDB(config=PineconeDBConfig()) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_initialize(self, weaviate_mock): - """Test the init method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - weaviate_client_schema_mock = weaviate_client_mock.schema - - # Mock that schema doesn't already exist so that a new schema is created - weaviate_client_schema_mock.exists.return_value = False - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - expected_class_obj = { - "classes": [ - { - "class": "Embedchain_store_1536", - "vectorizer": "none", - "properties": [ - { - "name": "identifier", - "dataType": ["text"], - }, - { - "name": "text", - "dataType": ["text"], - }, - { - "name": "metadata", - "dataType": ["Embedchain_store_1536_metadata"], - }, - ], - }, - { - "class": "Embedchain_store_1536_metadata", - "vectorizer": "none", - "properties": [ - { - "name": "data_type", - "dataType": ["text"], - }, - { - "name": "doc_id", - "dataType": ["text"], - }, - { - "name": "url", - "dataType": ["text"], - }, - { - "name": "hash", - "dataType": ["text"], - }, - { - "name": "app_id", - "dataType": ["text"], - }, - ], - }, - ] - } - - # Assert that the Weaviate client was initialized - weaviate_mock.Client.assert_called_once() - self.assertEqual(db.index_name, "Embedchain_store_1536") - weaviate_client_schema_mock.create.assert_called_once_with(expected_class_obj) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_get_or_create_db(self, weaviate_mock): - """Test the _get_or_create_db method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - expected_client = db._get_or_create_db() - self.assertEqual(expected_client, weaviate_client_mock) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_add(self, weaviate_mock): - """Test the add method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - weaviate_client_batch_mock = weaviate_client_mock.batch - weaviate_client_batch_enter_mock = weaviate_client_mock.batch.__enter__.return_value - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - documents = ["This is test document"] - metadatas = [None] - ids = ["id_1"] - db.add(documents, metadatas, ids) - - # Check if the document was added to the database. - weaviate_client_batch_mock.configure.assert_called_once_with(batch_size=100, timeout_retries=3) - weaviate_client_batch_enter_mock.add_data_object.assert_any_call( - data_object={"text": documents[0]}, class_name="Embedchain_store_1536_metadata", vector=[1, 2, 3] - ) - - weaviate_client_batch_enter_mock.add_data_object.assert_any_call( - data_object={"text": documents[0]}, - class_name="Embedchain_store_1536_metadata", - vector=[1, 2, 3], - ) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_query_without_where(self, weaviate_mock): - """Test the query method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - weaviate_client_query_mock = weaviate_client_mock.query - weaviate_client_query_get_mock = weaviate_client_query_mock.get.return_value - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - # Query for the document. - db.query(input_query="This is a test document.", n_results=1, where={}) - - weaviate_client_query_mock.get.assert_called_once_with("Embedchain_store_1536", ["text"]) - weaviate_client_query_get_mock.with_near_vector.assert_called_once_with({"vector": [1, 2, 3]}) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_query_with_where(self, weaviate_mock): - """Test the query method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - weaviate_client_query_mock = weaviate_client_mock.query - weaviate_client_query_get_mock = weaviate_client_query_mock.get.return_value - weaviate_client_query_get_where_mock = weaviate_client_query_get_mock.with_where.return_value - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - # Query for the document. - db.query(input_query="This is a test document.", n_results=1, where={"doc_id": "123"}) - - weaviate_client_query_mock.get.assert_called_once_with("Embedchain_store_1536", ["text"]) - weaviate_client_query_get_mock.with_where.assert_called_once_with( - {"operator": "Equal", "path": ["metadata", "Embedchain_store_1536_metadata", "doc_id"], "valueText": "123"} - ) - weaviate_client_query_get_where_mock.with_near_vector.assert_called_once_with({"vector": [1, 2, 3]}) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_reset(self, weaviate_mock): - """Test the reset method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - weaviate_client_batch_mock = weaviate_client_mock.batch - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - # Reset the database. - db.reset() - - weaviate_client_batch_mock.delete_objects.assert_called_once_with( - "Embedchain_store_1536", where={"path": ["identifier"], "operator": "Like", "valueText": ".*"} - ) - - @patch("embedchain.vectordb.weaviate.weaviate") - def test_count(self, weaviate_mock): - """Test the reset method of the WeaviateDb class.""" - weaviate_client_mock = weaviate_mock.Client.return_value - weaviate_client_query = weaviate_client_mock.query - - # Set the embedder - embedder = BaseEmbedder() - embedder.set_vector_dimension(1536) - embedder.set_embedding_fn(mock_embedding_fn) - - # Create a Weaviate instance - db = WeaviateDB() - app_config = AppConfig(collect_metrics=False) - App(config=app_config, db=db, embedding_model=embedder) - - # Reset the database. - db.count() - - weaviate_client_query.aggregate.assert_called_once_with("Embedchain_store_1536") diff --git a/embedchain/tests/vectordb/test_zilliz_db.py b/embedchain/tests/vectordb/test_zilliz_db.py deleted file mode 100644 index 7d6360442..000000000 --- a/embedchain/tests/vectordb/test_zilliz_db.py +++ /dev/null @@ -1,168 +0,0 @@ -# ruff: noqa: E501 - -import os -from unittest import mock -from unittest.mock import Mock, patch - -import pytest - -from embedchain.config import ZillizDBConfig -from embedchain.vectordb.zilliz import ZillizVectorDB - - -# to run tests, provide the URI and TOKEN in .env file -class TestZillizVectorDBConfig: - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def test_init_with_uri_and_token(self): - """ - Test if the `ZillizVectorDBConfig` instance is initialized with the correct uri and token values. - """ - # Create a ZillizDBConfig instance with mocked values - expected_uri = "mocked_uri" - expected_token = "mocked_token" - db_config = ZillizDBConfig() - - # Assert that the values in the ZillizVectorDB instance match the mocked values - assert db_config.uri == expected_uri - assert db_config.token == expected_token - - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def test_init_without_uri(self): - """ - Test if the `ZillizVectorDBConfig` instance throws an error when no URI found. - """ - try: - del os.environ["ZILLIZ_CLOUD_URI"] - except KeyError: - pass - - with pytest.raises(AttributeError): - ZillizDBConfig() - - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def test_init_without_token(self): - """ - Test if the `ZillizVectorDBConfig` instance throws an error when no Token found. - """ - try: - del os.environ["ZILLIZ_CLOUD_TOKEN"] - except KeyError: - pass - # Test if an exception is raised when ZILLIZ_CLOUD_TOKEN is missing - with pytest.raises(AttributeError): - ZillizDBConfig() - - -class TestZillizVectorDB: - @pytest.fixture - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def mock_config(self, mocker): - return mocker.Mock(spec=ZillizDBConfig()) - - @patch("embedchain.vectordb.zilliz.MilvusClient", autospec=True) - @patch("embedchain.vectordb.zilliz.connections.connect", autospec=True) - def test_zilliz_vector_db_setup(self, mock_connect, mock_client, mock_config): - """ - Test if the `ZillizVectorDB` instance is initialized with the correct uri and token values. - """ - # Create an instance of ZillizVectorDB with the mock config - # zilliz_db = ZillizVectorDB(config=mock_config) - ZillizVectorDB(config=mock_config) - - # Assert that the MilvusClient and connections.connect were called - mock_client.assert_called_once_with(uri=mock_config.uri, token=mock_config.token) - mock_connect.assert_called_once_with(uri=mock_config.uri, token=mock_config.token) - - -class TestZillizDBCollection: - @pytest.fixture - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def mock_config(self, mocker): - return mocker.Mock(spec=ZillizDBConfig()) - - @pytest.fixture - def mock_embedder(self, mocker): - return mocker.Mock() - - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def test_init_with_default_collection(self): - """ - Test if the `ZillizVectorDB` instance is initialized with the correct default collection name. - """ - # Create a ZillizDBConfig instance - db_config = ZillizDBConfig() - - assert db_config.collection_name == "embedchain_store" - - @mock.patch.dict(os.environ, {"ZILLIZ_CLOUD_URI": "mocked_uri", "ZILLIZ_CLOUD_TOKEN": "mocked_token"}) - def test_init_with_custom_collection(self): - """ - Test if the `ZillizVectorDB` instance is initialized with the correct custom collection name. - """ - # Create a ZillizDBConfig instance with mocked values - - expected_collection = "test_collection" - db_config = ZillizDBConfig(collection_name="test_collection") - - assert db_config.collection_name == expected_collection - - @patch("embedchain.vectordb.zilliz.MilvusClient", autospec=True) - @patch("embedchain.vectordb.zilliz.connections", autospec=True) - def test_query(self, mock_connect, mock_client, mock_embedder, mock_config): - # Create an instance of ZillizVectorDB with mock config - zilliz_db = ZillizVectorDB(config=mock_config) - - # Add a 'embedder' attribute to the ZillizVectorDB instance for testing - zilliz_db.embedder = mock_embedder # Mock the 'collection' object - - # Add a 'collection' attribute to the ZillizVectorDB instance for testing - zilliz_db.collection = Mock(is_empty=False) # Mock the 'collection' object - - assert zilliz_db.client == mock_client() - - # Mock the MilvusClient search method - with patch.object(zilliz_db.client, "search") as mock_search: - # Mock the embedding function - mock_embedder.embedding_fn.return_value = ["query_vector"] - - # Mock the search result - mock_search.return_value = [ - [ - { - "distance": 0.0, - "entity": { - "text": "result_doc", - "embeddings": [1, 2, 3], - "metadata": {"url": "url_1", "doc_id": "doc_id_1"}, - }, - } - ] - ] - - query_result = zilliz_db.query(input_query="query_text", n_results=1, where={}) - - # Assert that MilvusClient.search was called with the correct parameters - mock_search.assert_called_with( - collection_name=mock_config.collection_name, - data=["query_vector"], - filter="", - limit=1, - output_fields=["*"], - ) - - # Assert that the query result matches the expected result - assert query_result == ["result_doc"] - - query_result_with_citations = zilliz_db.query( - input_query="query_text", n_results=1, where={}, citations=True - ) - - mock_search.assert_called_with( - collection_name=mock_config.collection_name, - data=["query_vector"], - filter="", - limit=1, - output_fields=["*"], - ) - - assert query_result_with_citations == [("result_doc", {"url": "url_1", "doc_id": "doc_id_1", "score": 0.0})] diff --git a/marketplace.json b/marketplace.json new file mode 100644 index 000000000..6e0e240a1 --- /dev/null +++ b/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "mem0-plugins", + "interface": { + "displayName": "Mem0 Plugins" + }, + "plugins": [ + { + "name": "mem0", + "source": { + "source": "local", + "path": "./mem0-plugin" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/mem0-plugin/.claude-plugin/plugin.json b/mem0-plugin/.claude-plugin/plugin.json index d82722125..30add9c39 100644 --- a/mem0-plugin/.claude-plugin/plugin.json +++ b/mem0-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "mem0", - "version": "0.2.0", - "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows using the Mem0 Platform MCP server.", + "version": "0.2.7", + "description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", "email": "support@mem0.ai" @@ -9,5 +9,14 @@ "homepage": "https://mem0.ai", "repository": "https://github.com/mem0ai/mem0", "license": "Apache-2.0", - "keywords": ["memory", "personalization", "mcp", "semantic-search"] + "keywords": ["memory", "personalization", "mcp", "semantic-search"], + "userConfig": { + "api_key": { + "type": "string", + "title": "Mem0 API Key", + "description": "Your Mem0 Platform API key (starts with m0-). Get one at https://app.mem0.ai/dashboard/api-keys", + "sensitive": true, + "required": true + } + } } diff --git a/mem0-plugin/.codex-plugin/plugin.json b/mem0-plugin/.codex-plugin/plugin.json index 89d01c3be..9de0e2f04 100644 --- a/mem0-plugin/.codex-plugin/plugin.json +++ b/mem0-plugin/.codex-plugin/plugin.json @@ -1,26 +1,26 @@ { "name": "mem0", - "version": "0.2.0", - "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Codex workflows using the Mem0 Platform MCP server.", + "version": "0.2.7", + "description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", - "email": "support@mem0.ai" + "email": "support@mem0.ai", + "url": "https://mem0.ai" }, "homepage": "https://mem0.ai", "repository": "https://github.com/mem0ai/mem0", "license": "Apache-2.0", + "keywords": ["memory", "personalization", "mcp", "semantic-search"], "skills": "./skills/", "mcpServers": "./.codex-mcp.json", + "hooks": "./hooks/codex-hooks.json", "interface": { "displayName": "Mem0", "shortDescription": "Persistent memory layer for AI coding workflows", "longDescription": "Mem0 adds long-term memory to Codex. Store decisions, user preferences, project context, and session state across conversations. Memories are automatically retrieved via semantic search so Codex always has the right context.", "developerName": "Mem0", "category": "Productivity", - "capabilities": [ - "Read", - "Write" - ], + "capabilities": ["Read", "Write"], "websiteURL": "https://mem0.ai", "privacyPolicyURL": "https://mem0.ai/privacy", "termsOfServiceURL": "https://mem0.ai/terms", diff --git a/mem0-plugin/.cursor-plugin/plugin.json b/mem0-plugin/.cursor-plugin/plugin.json index 153b73786..e9d361600 100644 --- a/mem0-plugin/.cursor-plugin/plugin.json +++ b/mem0-plugin/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.0", + "version": "0.2.7", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.", "author": { "name": "Mem0", diff --git a/embedchain/LICENSE b/mem0-plugin/.opencode-plugin/LICENSE similarity index 99% rename from embedchain/LICENSE rename to mem0-plugin/.opencode-plugin/LICENSE index d20d5102c..bfa36bdd4 100644 --- a/embedchain/LICENSE +++ b/mem0-plugin/.opencode-plugin/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2023] [Taranjeet Singh] + Copyright [2026] [Taranjeet Singh] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/mem0-plugin/.opencode-plugin/README.md b/mem0-plugin/.opencode-plugin/README.md new file mode 100644 index 000000000..fac980ad7 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/README.md @@ -0,0 +1,84 @@ +# @mem0/opencode-plugin + +Persistent memory for [OpenCode](https://opencode.ai). Your agent remembers decisions, preferences, and learnings across sessions automatically. + +## Install + +```bash +bunx @mem0/opencode-plugin@latest install +``` + +Or using OpenCode's built-in CLI: + +```bash +opencode plugin @mem0/opencode-plugin +``` + +**Or let your agent do it** — paste this into OpenCode: + +``` +Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/mem0-plugin/.opencode-plugin/README.md +``` + +All commands auto-add the plugin and MCP server to your `~/.config/opencode/opencode.json`. No manual config needed. + +Get your API key (free): [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys) + +```bash +echo 'export MEM0_API_KEY="m0-your-key"' >> ~/.zshrc && source ~/.zshrc +``` + +Restart OpenCode. + +## What's included + +| Component | Description | +|-----------|-------------| +| **MCP Server** | 9 memory tools — add, search, get, update, delete memories | +| **Lifecycle Hooks** | Auto-search on session start and every prompt, metadata enforcement, error memory lookup, compaction context | +| **16 Slash Commands** | `/mem0:remember`, `/mem0:tour`, `/mem0:stats`, `/mem0:health`, `/mem0:dream`, and more | + +## Hooks + +Pure TypeScript — no Python, no shell scripts. Uses the [mem0ai](https://www.npmjs.com/package/mem0ai) SDK directly. + +| Hook | Event | What it does | +|------|-------|-------------| +| **Chat message** | `chat.message` | Loads prior memories on session start, searches relevant memories before each prompt, auto-captures learnings periodically | +| **Pre-tool** | `tool.execute.before` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools | +| **Post-tool** | `tool.execute.after` | Tracks stats, scans bash errors for related memories | +| **System transform** | `experimental.chat.system.transform` | Injects memory context (session memories, search results, error lookups) into system prompt | +| **Compaction** | `experimental.session.compacting` | Stores session state memory, then injects prior memories into compaction context so nothing is lost | +| **Shell env** | `shell.env` | Exports `MEM0_USER_ID`, `MEM0_APP_ID`, `MEM0_SESSION_ID`, and `MEM0_BRANCH` to shell | + +## MCP Tools + +| Tool | Description | +|------|-------------| +| `add_memory` | Save text or conversation history | +| `search_memories` | Semantic search across memories | +| `get_memories` | List memories with filters and pagination | +| `get_memory` | Retrieve a specific memory by ID | +| `update_memory` | Overwrite a memory's text by ID | +| `delete_memory` | Delete a single memory by ID | +| `delete_all_memories` | Bulk delete all memories in scope | +| `delete_entities` | Delete an entity and its memories | +| `list_entities` | List users/agents/apps stored in Mem0 | + +## Verify + +Start OpenCode and ask: *"Search my memories for recent decisions"* + +If the `mem0` tools respond, you're all set. + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| No tools appearing | Restart OpenCode after installing | +| 401 Unauthorized | `echo $MEM0_API_KEY` must print your `m0-` key | +| Plugin not loading | Run `opencode plugin @mem0/opencode-plugin` again | + +## License + +Apache-2.0 diff --git a/mem0-plugin/.opencode-plugin/bun.lock b/mem0-plugin/.opencode-plugin/bun.lock new file mode 100644 index 000000000..2ce24e0eb --- /dev/null +++ b/mem0-plugin/.opencode-plugin/bun.lock @@ -0,0 +1,765 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@mem0/opencode-plugin", + "dependencies": { + "@opencode-ai/plugin": "^1.0.162", + "mem0ai": "^3.0.5", + }, + "devDependencies": { + "bun-types": ">=1.3.14", + "typescript": "^5.7.3", + }, + "peerDependencies": { + "bun": ">=1.0.0", + }, + }, + }, + "packages": { + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.40.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-DJMWm8lTEM9Lk/MSFL+V+ugF7jKOn0M2Ujvb5fN8r2nY14aHbGPZ1k6sgjL+tpJ3VuOGJNG+4R83jEpOuYPv8w=="], + + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], + + "@azure/core-http-compat": ["@azure/core-http-compat@2.4.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2" }, "peerDependencies": { "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-f1P96IB399YiN2ARYHP7EpZi3Bf3wH4SN2lGzrw7JVwm7bbsVYtf2iKSBwTywD2P62NOPZGHFSZi+6jjb75JuA=="], + + "@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@azure/msal-browser": ["@azure/msal-browser@5.11.0", "", { "dependencies": { "@azure/msal-common": "16.6.2" } }, "sha512-zkGNYS3TwY8lUpPIafAmsFCYZbgFixY9y/LZB9GUg0IILoHTqpN26j5OrkL1AQThh/YdZsawe4iWXfp85lFVxg=="], + + "@azure/msal-common": ["@azure/msal-common@16.6.2", "", {}, "sha512-hQjjsekAjB00cM1EmatWJlzhEoK2Qhz7Rj5gvM6tYf8iL7RM3tkxlpU9fG0+ofkulzg9AEEA6dIEnSmDr5ZqUA=="], + + "@azure/msal-node": ["@azure/msal-node@5.2.2", "", { "dependencies": { "@azure/msal-common": "16.6.2", "jsonwebtoken": "^9.0.0" } }, "sha512-toS+2AePxqyzb0YOKttDOOiSl3jrkK9aiqIvpurpis0O34QcIS5gToqrgT39p04Dpxw3YoUU0lxJKTpSFFfA6Q=="], + + "@azure/search-documents": ["@azure/search-documents@12.2.0", "", { "dependencies": { "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-http-compat": "^2.1.2", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.18.0", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-4+Qw+qaGqnkdUCq/vEFzk/bkROogTvdbPb1fmI8poxNfDDN1q2WHxBmhI7CYwesrBj1yXC4i5E0aISBxZqZi0g=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="], + + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260527.1", "", {}, "sha512-GK+C05N4fGptp1uG+pbjC/7Udonz8EhMEYvYFnVKdLLnEGS9nsmVOFi/RLQr7tq9l/0UEzcWjudzeY2ssuqyIA=="], + + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + + "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], + + "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], + + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], + + "@langchain/core": ["@langchain/core@1.1.48", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "zod": "^3.25.76 || ^4" } }, "sha512-fQU6Guyb1pwc2fEplmA8FPbKfOMAofjnyJzExevro0FxEiuGHE18Ov/ZHmT9trWCDTZRI9eW1VIc6aChxV8pAQ=="], + + "@mistralai/mistralai": ["@mistralai/mistralai@1.15.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-fb995eiz3r0KsBGtRjFV+/iLbX+UpfalxpF+YitT3R6ukrPD4PN+FGwwmYcRFhNAzVzDUtTVxQYnjQWEnwV5nw=="], + + "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.11", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], + + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.15.11", "", { "dependencies": { "@opencode-ai/sdk": "1.15.11", "effect": "4.0.0-beta.66", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.2.15", "@opentui/keymap": ">=0.2.15", "@opentui/solid": ">=0.2.15" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-RDvYDCHO0+3OAGD590oDQqtryrENrUW04SjtoA4sgRV2efpZeBFjx3TnonBsXKq6nWqYg172nhltUEZsihGnXQ=="], + + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.15.11", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-IyYyDVsO8SKbKbkSadHpDuYnYC+2vmEeLU+rW+rH2M54Sigq6l3gDHno16+U6SRut+lowbph7v/ry3WbV67V3w=="], + + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w=="], + + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g=="], + + "@oven/bun-freebsd-aarch64": ["@oven/bun-freebsd-aarch64@1.3.14", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw=="], + + "@oven/bun-freebsd-x64": ["@oven/bun-freebsd-x64@1.3.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw=="], + + "@oven/bun-linux-aarch64-android": ["@oven/bun-linux-aarch64-android@1.3.14", "", { "os": "android", "cpu": "arm64" }, "sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw=="], + + "@oven/bun-linux-x64-android": ["@oven/bun-linux-x64-android@1.3.14", "", { "os": "android", "cpu": "x64" }, "sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg=="], + + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg=="], + + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw=="], + + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.3.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg=="], + + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + + "@qdrant/js-client-rest": ["@qdrant/js-client-rest@1.13.0", "", { "dependencies": { "@qdrant/openapi-typescript-fetch": "1.2.6", "@sevinf/maybe": "0.5.0", "undici": "~5.28.4" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-bewMtnXlGvhhnfXsp0sLoLXOGvnrCM15z9lNlG0Snp021OedNAnRtKkerjk5vkOcbQWUmJHXYCuxDfcT93aSkA=="], + + "@qdrant/openapi-typescript-fetch": ["@qdrant/openapi-typescript-fetch@1.2.6", "", {}, "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA=="], + + "@redis/bloom": ["@redis/bloom@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="], + + "@redis/client": ["@redis/client@1.6.1", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw=="], + + "@redis/graph": ["@redis/graph@1.1.1", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw=="], + + "@redis/json": ["@redis/json@1.0.7", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ=="], + + "@redis/search": ["@redis/search@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw=="], + + "@redis/time-series": ["@redis/time-series@1.1.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g=="], + + "@sevinf/maybe": ["@sevinf/maybe@0.5.0", "", {}, "sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@supabase/auth-js": ["@supabase/auth-js@2.106.2", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-VcAjUErkHkhC5Jaf+g/G1qbkQrFh8edaCdHa7pxJmHUjkWKjT7UnYCtPA89XV0N0GIYRkEqJZw5V62CtOxTmBQ=="], + + "@supabase/functions-js": ["@supabase/functions-js@2.106.2", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-oRnr0QrL8H+zTO1YyQ1QjiHZU/957jvubbxSJTUm2XLAgzoGGV9Tahfyd+uvLsBLRVmXLtpU3oyCjdQIvkGMOA=="], + + "@supabase/phoenix": ["@supabase/phoenix@0.4.2", "", {}, "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A=="], + + "@supabase/postgrest-js": ["@supabase/postgrest-js@2.106.2", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-tDOzyPgp9pIRMR2x6C9+uDSJrnXSzxLtt3d7nC+Lrsy3jnJDHYfdQC/xcRyhJE/TOBJ0heSqRKR3UmejDjZxsw=="], + + "@supabase/realtime-js": ["@supabase/realtime-js@2.106.2", "", { "dependencies": { "@supabase/phoenix": "^0.4.2", "tslib": "2.8.1" } }, "sha512-LdRGT7DNhyZkPjubUv5bSdAZ0jSEX8wTHvx7htj7+K59TOZRvz4TuQK7tL2RWxyIZVeFMRluL04SzWS61rKnUA=="], + + "@supabase/storage-js": ["@supabase/storage-js@2.106.2", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-xgKCSYuev1YarV+iVqr+zlfgSyremnJtn8T0NCT8L4XmMv1CLtESc0Q6kNp8+mKWdX/8ND0nzm7OMKx08kwNAw=="], + + "@supabase/supabase-js": ["@supabase/supabase-js@2.106.2", "", { "dependencies": { "@supabase/auth-js": "2.106.2", "@supabase/functions-js": "2.106.2", "@supabase/postgrest-js": "2.106.2", "@supabase/realtime-js": "2.106.2", "@supabase/storage-js": "2.106.2" } }, "sha512-2/RZ/1fmJx/MRSEDG2Xk8+J4JVk5clM9V0uSI6kUTrcS32KA89DtqI5RUOC9r6mzY3WBC9qexLjssIHjbLyVJA=="], + + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], + + "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], + + "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], + + "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="], + + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + + "@types/pg": ["@types/pg@8.11.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^4.0.1" } }, "sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw=="], + + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], + + "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + + "@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="], + + "@types/whatwg-url": ["@types/whatwg-url@13.0.0", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q=="], + + "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], + + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.5", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + + "afinn-165": ["afinn-165@2.0.2", "", {}, "sha512-mJ/RLUfpXfQA6bzugv+bBsc/QYkVrKaLYeS8fWBpKbTCsonv4iuV9ET0fgReEunm9vKLkaNgnekuSNlTC3WQ1Q=="], + + "afinn-165-financialmarketnews": ["afinn-165-financialmarketnews@3.0.0", "", {}, "sha512-0g9A1S3ZomFIGDTzZ0t6xmv4AuokBvBmpes8htiyHpH7N4xDmvSQL6UxL/Zcs2ypRb3VwgCscaD8Q3zEawKYhw=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "apparatus": ["apparatus@0.0.10", "", { "dependencies": { "sylvester": ">= 0.0.8" } }, "sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], + + "base-64": ["base-64@0.1.0", "", {}, "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "better-sqlite3": ["better-sqlite3@12.10.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bson": ["bson@7.2.0", "", {}, "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "buffer-writer": ["buffer-writer@2.0.0", "", {}, "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="], + + "bun": ["bun@1.3.14", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.14", "@oven/bun-darwin-x64": "1.3.14", "@oven/bun-darwin-x64-baseline": "1.3.14", "@oven/bun-freebsd-aarch64": "1.3.14", "@oven/bun-freebsd-x64": "1.3.14", "@oven/bun-linux-aarch64": "1.3.14", "@oven/bun-linux-aarch64-android": "1.3.14", "@oven/bun-linux-aarch64-musl": "1.3.14", "@oven/bun-linux-x64": "1.3.14", "@oven/bun-linux-x64-android": "1.3.14", "@oven/bun-linux-x64-baseline": "1.3.14", "@oven/bun-linux-x64-musl": "1.3.14", "@oven/bun-linux-x64-musl-baseline": "1.3.14", "@oven/bun-windows-aarch64": "1.3.14", "@oven/bun-windows-x64": "1.3.14", "@oven/bun-windows-x64-baseline": "1.3.14" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + + "cloudflare": ["cloudflare@4.5.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "compromise": ["compromise@14.15.1", "", { "dependencies": { "efrt": "2.7.0", "grad-school": "0.0.5", "suffix-thumb": "5.0.2" } }, "sha512-9F3UkUaEU1PPz2fgStkE/TI4tk++0wHxS8xfWq9PQWL/v28dy8bEcPVVSLh3dISIRD7PEhJ8YTzHRKF8y9tnLA=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "crypt": ["crypt@0.0.2", "", {}, "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + + "digest-fetch": ["digest-fetch@1.3.0", "", { "dependencies": { "base-64": "^0.1.0", "md5": "^2.3.0" } }, "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA=="], + + "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "effect": ["effect@4.0.0-beta.66", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-4arEr62cziFa8BBVDUwJCJJmaVepXf/kRg7KtC0h8+bufngscrHbwWFhr9c+HonwOF+31U3iD3xUJmw9KzX7Dw=="], + + "efrt": ["efrt@2.7.0", "", {}, "sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], + + "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + + "generic-pool": ["generic-pool@3.9.0", "", {}, "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "grad-school": ["grad-school@0.0.5", "", {}, "sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ=="], + + "groq-sdk": ["groq-sdk@0.3.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "digest-fetch": "^1.3.0", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7", "web-streams-polyfill": "^3.2.1" } }, "sha512-Cdgjh4YoSBE2X4S9sxPGXaAy1dlN4bRtAaDZ3cnq+XsxhhN9WSBeHF64l7LWwuD5ntmw7YC5Vf4Ff1oHCg1LOg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + + "iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], + + "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], + + "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="], + + "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], + + "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], + + "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "kareem": ["kareem@3.3.0", "", {}, "sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw=="], + + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + + "langsmith": ["langsmith@0.7.2", "", { "dependencies": { "p-queue": "6.6.2" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-3dZUwQDJluxi2ih5eIygFODtlrQKrs3Tua0Ck3l+DAUkSGFkB1Dc0JPqkGbqiVSN+TQDElnkev/ydAOAz6jndA=="], + + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="], + + "mem0ai": ["mem0ai@3.0.5", "", { "dependencies": { "axios": "^1.15.2", "openai": "^4.93.0", "uuid": "9.0.1", "zod": "^3.24.1" }, "peerDependencies": { "@anthropic-ai/sdk": "^0.40.1", "@azure/identity": "^4.0.0", "@azure/search-documents": "^12.0.0", "@cloudflare/workers-types": "^4.20250504.0", "@google/genai": "^1.2.0", "@langchain/core": "^1.1.47", "@mistralai/mistralai": "^1.5.2", "@qdrant/js-client-rest": "1.13.0", "@supabase/supabase-js": "^2.49.1", "@types/jest": "29.5.14", "@types/pg": "8.11.0", "better-sqlite3": "^12.6.2", "cloudflare": "^4.2.0", "compromise": "^14.0.0", "groq-sdk": "0.3.0", "natural": "^8.0.1", "ollama": "^0.5.14", "pg": "8.11.3", "redis": "^4.6.13" } }, "sha512-W/R59d5fMpUGHhPEnyoo36GSz5NFJbAs+vS4BxoIvE+t19mIJfoz/2FJSKIw80mT8AkeNYpDfzc/DYRu2IzYIw=="], + + "memjs": ["memjs@1.3.2", "", {}, "sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg=="], + + "memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "mongodb": ["mongodb@7.2.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.2.0", "mongodb-connection-string-url": "^7.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.806.0", "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", "mongodb-client-encryption": ">=7.0.0 <7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw=="], + + "mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.1", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ=="], + + "mongoose": ["mongoose@9.6.3", "", { "dependencies": { "kareem": "3.3.0", "mongodb": "~7.2", "mpath": "0.9.0", "mquery": "6.0.0", "ms": "2.1.3", "sift": "17.1.3" } }, "sha512-vI6dTTlQnfMCyyQ5TrvhG0bCRs4dq5e1uFNPtOOWsOhn0fSg8AoIHjfyyCYr8aybyvPs845dRHGxsC3w/fHcBA=="], + + "mpath": ["mpath@0.9.0", "", {}, "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="], + + "mquery": ["mquery@6.0.0", "", {}, "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "msgpackr": ["msgpackr@1.11.12", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], + + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "natural": ["natural@8.1.1", "", { "dependencies": { "afinn-165": "^2.0.2", "afinn-165-financialmarketnews": "^3.0.0", "apparatus": "^0.0.10", "dotenv": "^17.3.1", "memjs": "^1.3.2", "mongoose": "^9.2.1", "pg": "^8.18.0", "redis": "^5.11.0", "safe-stable-stringify": "^2.5.0", "stopwords-iso": "^1.1.0", "sylvester": "^0.0.21", "underscore": "^1.13.0", "uuid": "^13.0.0", "wordnet-db": "^3.1.14" } }, "sha512-Ucb+lsUcGxUqu3rn8cwHjT6gJQosO63nIX/aBQXB3+IDkNbFV7PuviysO+Rzz3aKn7PZhPj3bNF4PS9gDVjYCQ=="], + + "node-abi": ["node-abi@3.92.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="], + + "ollama": ["ollama@0.5.18", "", { "dependencies": { "whatwg-fetch": "^3.6.20" } }, "sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="], + + "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="], + + "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], + + "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + + "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], + + "packet-reader": ["packet-reader@1.0.0", "", {}, "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pg": ["pg@8.11.3", "", { "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "^2.6.2", "pg-pool": "^3.6.1", "pg-protocol": "^1.6.0", "pg-types": "^2.1.0", "pgpass": "1.x" }, "optionalDependencies": { "pg-cloudflare": "^1.1.1" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g=="], + + "pg-cloudflare": ["pg-cloudflare@1.4.0", "", {}, "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A=="], + + "pg-connection-string": ["pg-connection-string@2.13.0", "", {}, "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-numeric": ["pg-numeric@1.0.2", "", {}, "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw=="], + + "pg-pool": ["pg-pool@3.14.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw=="], + + "pg-protocol": ["pg-protocol@1.14.0", "", {}, "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA=="], + + "pg-types": ["pg-types@4.1.0", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="], + + "postgres-bytea": ["postgres-bytea@3.0.0", "", { "dependencies": { "obuf": "~1.1.2" } }, "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw=="], + + "postgres-date": ["postgres-date@2.1.0", "", {}, "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA=="], + + "postgres-interval": ["postgres-interval@3.0.0", "", {}, "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw=="], + + "postgres-range": ["postgres-range@1.1.4", "", {}, "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + + "protobufjs": ["protobufjs@7.6.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "redis": ["redis@4.7.1", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.1", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ=="], + + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "sift": ["sift@17.1.3", "", {}, "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "stopwords-iso": ["stopwords-iso@1.1.0", "", {}, "sha512-I6GPS/E0zyieHehMRPQcqkiBMJKGgLta+1hREixhoLPqEA0AlVFiC43dl8uPpmkkeRdDMzYRWFWk5/l9x7nmNg=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "suffix-thumb": ["suffix-thumb@5.0.2", "", {}, "sha512-I5PWXAFKx3FYnI9a+dQMWNqTxoRt6vdBdb0O+BJ1sxXCWtSoQCusc13E58f+9p4MYx/qCnEMkD5jac6K2j3dgA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "sylvester": ["sylvester@0.0.21", "", {}, "sha512-yUT0ukFkFEt4nb+NY+n2ag51aS/u9UHXoZw+A4jgD77/jzZsBoSDHuqysrVCBC4CYR4TYvUJq54ONpXgDBH8tA=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="], + + "undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wordnet-db": ["wordnet-db@3.1.14", "", {}, "sha512-zVyFsvE+mq9MCmwXUWHIcpfbrHHClZWZiVOzKSxNJruIcFn2RbY55zkhiAMMxM8zCVSmtNiViq8FsAZSFpMYag=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + + "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "cloudflare/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + + "effect/uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + + "formdata-node/web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], + + "gaxios/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "groq-sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + + "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "mem0ai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "mongodb-connection-string-url/whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], + + "natural/pg": ["pg@8.21.0", "", { "dependencies": { "pg-connection-string": "^2.13.0", "pg-pool": "^3.14.0", "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA=="], + + "natural/redis": ["redis@5.12.1", "", { "dependencies": { "@redis/bloom": "5.12.1", "@redis/client": "5.12.1", "@redis/json": "5.12.1", "@redis/search": "5.12.1", "@redis/time-series": "5.12.1" } }, "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g=="], + + "natural/uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + + "openai/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + + "pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "@typespec/ts-http-runtime/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "cloudflare/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "groq-sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "mongodb-connection-string-url/whatwg-url/tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], + + "mongodb-connection-string-url/whatwg-url/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + + "natural/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "natural/redis/@redis/bloom": ["@redis/bloom@5.12.1", "", { "peerDependencies": { "@redis/client": "^5.12.1" } }, "sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA=="], + + "natural/redis/@redis/client": ["@redis/client@5.12.1", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0", "@opentelemetry/api": ">=1 <2" }, "optionalPeers": ["@node-rs/xxhash", "@opentelemetry/api"] }, "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA=="], + + "natural/redis/@redis/json": ["@redis/json@5.12.1", "", { "peerDependencies": { "@redis/client": "^5.12.1" } }, "sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg=="], + + "natural/redis/@redis/search": ["@redis/search@5.12.1", "", { "peerDependencies": { "@redis/client": "^5.12.1" } }, "sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w=="], + + "natural/redis/@redis/time-series": ["@redis/time-series@5.12.1", "", { "peerDependencies": { "@redis/client": "^5.12.1" } }, "sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ=="], + + "openai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "natural/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "natural/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "natural/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "natural/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + } +} diff --git a/mem0-plugin/.opencode-plugin/cli.ts b/mem0-plugin/.opencode-plugin/cli.ts new file mode 100644 index 000000000..6d02f2178 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/cli.ts @@ -0,0 +1,254 @@ +#!/usr/bin/env bun +import { + readFileSync, + writeFileSync, + existsSync, + mkdirSync, + copyFileSync, + readdirSync, + rmSync, + statSync, +} from "fs"; +import { join, dirname } from "path"; +import { homedir } from "os"; + +const PLUGIN_NAME = "@mem0/opencode-plugin"; +const MCP_CONFIG = { + mem0: { + type: "remote", + url: "https://mcp.mem0.ai/mcp/", + headers: { + Authorization: "Token {env:MEM0_API_KEY}", + }, + oauth: false, + }, +}; + +const SKILLS_NAMESPACE = "mem0"; + +function getConfigDir(): string { + const dir = join(homedir(), ".config", "opencode"); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + return dir; +} + +function getConfigPath(): string { + const configDir = getConfigDir(); + const jsonc = join(configDir, "opencode.jsonc"); + if (existsSync(jsonc)) return jsonc; + return join(configDir, "opencode.json"); +} + +function stripJsonComments(text: string): string { + let result = ""; + let i = 0; + let inString = false; + let escape = false; + while (i < text.length) { + const ch = text[i]; + if (escape) { + result += ch; + escape = false; + i++; + continue; + } + if (inString) { + if (ch === "\\") escape = true; + else if (ch === '"') inString = false; + result += ch; + i++; + continue; + } + if (ch === '"') { + inString = true; + result += ch; + i++; + continue; + } + if (ch === "/" && text[i + 1] === "/") { + while (i < text.length && text[i] !== "\n") i++; + continue; + } + if (ch === "/" && text[i + 1] === "*") { + i += 2; + while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++; + i += 2; + continue; + } + result += ch; + i++; + } + return result; +} + +function resolvePluginDir(): string { + try { + return dirname(new URL(import.meta.url).pathname); + } catch {} + return __dirname ?? process.cwd(); +} + +function findSkillsDir(): string { + const base = resolvePluginDir(); + const candidates = [ + join(base, "opencode-skills"), + join(dirname(base), "opencode-skills"), + join(base, "..", "opencode-skills"), + ]; + for (const c of candidates) { + try { + if (existsSync(c) && statSync(c).isDirectory()) return c; + } catch {} + } + return ""; +} + +function installSkills(): number { + const skillsSource = findSkillsDir(); + if (!skillsSource) { + console.log(" ! Skills directory not found — skipping slash command install"); + return 0; + } + + const skillsTarget = join(getConfigDir(), "skills"); + if (!existsSync(skillsTarget)) mkdirSync(skillsTarget, { recursive: true }); + + let count = 0; + const entries = readdirSync(skillsSource); + + for (const name of entries) { + const skillDir = join(skillsSource, name); + try { + if (!statSync(skillDir).isDirectory()) continue; + } catch { + continue; + } + + const skillFile = join(skillDir, "SKILL.md"); + if (!existsSync(skillFile)) continue; + + const targetDir = join(skillsTarget, `${SKILLS_NAMESPACE}-${name}`); + if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true }); + + copyFileSync(skillFile, join(targetDir, "SKILL.md")); + count++; + } + + return count; +} + +function uninstallSkills(): number { + const skillsTarget = join(getConfigDir(), "skills"); + if (!existsSync(skillsTarget)) return 0; + + let count = 0; + const entries = readdirSync(skillsTarget); + + for (const name of entries) { + if (!name.startsWith(`${SKILLS_NAMESPACE}-`)) continue; + const fullPath = join(skillsTarget, name); + try { + if (!statSync(fullPath).isDirectory()) continue; + rmSync(fullPath, { recursive: true }); + count++; + } catch {} + } + + return count; +} + +function install() { + console.log("Installing Mem0 plugin for OpenCode...\n"); + + const configPath = getConfigPath(); + let config: any = {}; + + if (existsSync(configPath)) { + try { + const raw = readFileSync(configPath, "utf-8"); + config = JSON.parse(stripJsonComments(raw)); + } catch { + console.log(` ! Could not parse ${configPath}, creating fresh config`); + config = {}; + } + } + + if (!Array.isArray(config.plugin)) config.plugin = []; + if (!config.plugin.includes(PLUGIN_NAME)) { + config.plugin.push(PLUGIN_NAME); + console.log(` + Added "${PLUGIN_NAME}" to plugin array`); + } else { + console.log(` ~ "${PLUGIN_NAME}" already in plugin array`); + } + + if (!config.mcp) config.mcp = {}; + if (!config.mcp.mem0) { + config.mcp.mem0 = MCP_CONFIG.mem0; + console.log(" + Added mem0 MCP server config"); + } else { + console.log(" ~ mem0 MCP server already configured"); + } + + writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n"); + console.log(`\n Wrote ${configPath}`); + + const skillCount = installSkills(); + if (skillCount > 0) { + console.log(` + Installed ${skillCount} slash commands to ~/.config/opencode/skills/`); + } + + console.log(""); + + if (!process.env.MEM0_API_KEY) { + console.log(" ! MEM0_API_KEY is not set in your environment."); + console.log( + ' Run: echo \'export MEM0_API_KEY="m0-your-key"\' >> ~/.zshrc && source ~/.zshrc', + ); + console.log( + " Get a free key at: https://app.mem0.ai/dashboard/api-keys\n", + ); + } else { + console.log(" MEM0_API_KEY detected\n"); + } + + console.log("Done! Restart OpenCode to activate Mem0."); + console.log(" Then run /mem0-onboard in the TUI to complete setup.\n"); +} + +function uninstall() { + const configPath = getConfigPath(); + if (!existsSync(configPath)) { + console.log("No OpenCode config found. Nothing to remove."); + return; + } + + const raw = readFileSync(configPath, "utf-8"); + const config = JSON.parse(stripJsonComments(raw)); + + if (Array.isArray(config.plugin)) { + config.plugin = config.plugin.filter((p: string) => p !== PLUGIN_NAME); + if (config.plugin.length === 0) delete config.plugin; + } + + if (config.mcp?.mem0) { + delete config.mcp.mem0; + if (Object.keys(config.mcp).length === 0) delete config.mcp; + } + + writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n"); + console.log(` Removed plugin + MCP from ${configPath}`); + + const skillCount = uninstallSkills(); + if (skillCount > 0) { + console.log(` Removed ${skillCount} slash commands from ~/.config/opencode/skills/`); + } + + console.log("Restart OpenCode to complete removal."); +} + +const cmd = process.argv[2]; +if (cmd === "uninstall" || cmd === "remove") { + uninstall(); +} else { + install(); +} diff --git a/mem0-plugin/.opencode-plugin/opencode-mem0.ts b/mem0-plugin/.opencode-plugin/opencode-mem0.ts new file mode 100644 index 000000000..e46f90049 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-mem0.ts @@ -0,0 +1,585 @@ +// Mem0 memory plugin for OpenCode: captures and recalls memories across sessions +// (add / search / manage) via the Mem0 platform, wired through OpenCode plugin hooks. +import type { Plugin } from "@opencode-ai/plugin"; +import { MemoryClient } from "mem0ai"; +import { userInfo } from "os"; +import { basename, resolve, dirname } from "path"; +import { randomBytes } from "crypto"; +import { existsSync, readdirSync, cpSync, mkdirSync, readFileSync, writeFileSync } from "fs"; + +async function getUserId(): Promise { + if (process.env.MEM0_USER_ID) return process.env.MEM0_USER_ID; + try { + return userInfo().username; + } catch {} + return process.env.USER || process.env.USERNAME || "unknown"; +} + +async function getProjectId($: any): Promise { + if (process.env.MEM0_APP_ID) return process.env.MEM0_APP_ID; + try { + const r = await $`git remote get-url origin`.quiet(); + const remote = r.stdout.toString().trim(); + const m = remote.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/); + if (m) return m[1].replace("/", "-"); + } catch {} + return basename(process.cwd()); +} + +async function getBranch($: any): Promise { + try { + const r = await $`git branch --show-current`.quiet(); + return r.stdout.toString().trim() || "main"; + } catch {} + return "main"; +} + +function extractMemories(res: any): Array<{ memory: string; id: string }> { + const arr = res?.results ?? res; + if (!Array.isArray(arr)) return []; + return arr.map((m: any) => ({ memory: m.memory ?? "", id: m.id ?? "" })); +} + +function generateSessionId(): string { + const ts = Math.floor(Date.now() / 1000); + const rnd = randomBytes(3).toString("hex"); + return `ses_${ts}_${rnd}`; +} + +const SECRET_PATTERNS = [ + /sk-[A-Za-z0-9]{20,}/g, + /m0-[A-Za-z0-9]{20,}/g, + /AKIA[0-9A-Z]{16}/g, + /xox[baprs]-[A-Za-z0-9-]{20,}/g, + /ghp_[A-Za-z0-9]{36,}/g, + /gho_[A-Za-z0-9]{36,}/g, +]; + +function redact(text: string): string { + let out = text; + for (const re of SECRET_PATTERNS) { + out = out.replace(re, "[REDACTED]"); + } + return out; +} + +const NUDGE_RE = + /\b(remember\s+(this|that)|memorize|save\s+this|note\s+(this|that)|don'?t\s+forget|always\s+remember|never\s+forget|keep\s+(this|that)\s+in\s+(mind|memory)|store\s+(this|that))\b/i; + +const RESUME_RE = + /where\s+(did\s+)?(we|I)\s+(leave|left)\s+off|continue\s+(from\s+)?(where|last)|what\s+were\s+we\s+(working|doing)|pick\s+up\s+where|resume\s+(from\s+|where\s+)|what.s\s+the\s+(current|latest)\s+(state|status)|catch\s+me\s+up|where\s+are\s+we/i; + +const ERROR_STRONG_RE = + /Traceback \(most recent call last\)|panic: |FATAL:|error\[E\d+\]/; +const ERROR_MULTI_RE = /(Error:|Exception:)/g; + +const MEM0_MCP_RE = /mem0.*(?:add_memory|search_memories|get_memor|delete_memor|update_memory|delete_entities|list_entities)/i; +const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "write", "edit", "multiEdit"]); + +function isMem0Tool(name: string): boolean { + return MEM0_MCP_RE.test(name); +} + +function isMem0AddMemory(name: string): boolean { + return /mem0.*add_memory/i.test(name); +} + +function isMem0SearchOrGet(name: string): boolean { + return /mem0.*(search_memories|get_memories)/i.test(name); +} + +function isMem0DeleteAll(name: string): boolean { + return /mem0.*delete_all_memories/i.test(name); +} + +function extractUserText(input: any, output: any): string { + const parts: any[] = output?.parts; + if (Array.isArray(parts)) { + return parts + .filter((p: any) => p.type === "text" && !p.synthetic) + .map((p: any) => p.text ?? "") + .join("\n"); + } + const msg = output?.message ?? input?.message; + if (typeof msg?.content === "string") return msg.content; + if (typeof msg?.text === "string") return msg.text; + return ""; +} + +function installSkills(projectDir: string): void { + const pluginDir = dirname(dirname(import.meta.filename)); + const srcSkills = resolve(pluginDir, "opencode-skills"); + if (!existsSync(srcSkills)) return; + + const destSkills = resolve(projectDir, ".opencode", "skills"); + const destCommands = resolve(projectDir, ".opencode", "commands"); + try { + const skills = readdirSync(srcSkills, { withFileTypes: true }); + for (const entry of skills) { + if (!entry.isDirectory()) continue; + const dest = resolve(destSkills, `mem0-${entry.name}`); + if (existsSync(dest)) continue; + cpSync(resolve(srcSkills, entry.name), dest, { recursive: true }); + } + + for (const entry of skills) { + if (!entry.isDirectory()) continue; + const cmdFile = resolve(destCommands, `mem0-${entry.name}.md`); + if (existsSync(cmdFile)) continue; + const skillMd = resolve(srcSkills, entry.name, "SKILL.md"); + if (!existsSync(skillMd)) continue; + let desc = "Mem0 " + entry.name + " skill"; + try { + const content = readFileSync(skillMd, "utf8"); + const m = content.match(/^description:\s*(.+)$/m); + if (m) desc = m[1].trim(); + } catch {} + const cmdContent = `---\ndescription: ${desc}\n---\nLoad and follow the skill at .opencode/skills/mem0-${entry.name}/SKILL.md\n\nUse the mem0 MCP tools (search_memories, get_memories, add_memory, delete_memory, update_memory, list_entities, delete_entities, get_event_status) to execute the skill instructions.\n\nIdentity context (from environment):\n- user_id: Use MEM0_USER_ID env var, or fall back to $USER\n- app_id: Use MEM0_APP_ID env var\n- session_id: Use MEM0_SESSION_ID env var\n- branch: Use MEM0_BRANCH env var\n`; + try { + mkdirSync(destCommands, { recursive: true }); + writeFileSync(cmdFile, cmdContent, "utf8"); + } catch {} + } + } catch {} +} + +const Mem0Plugin: Plugin = async (ctx) => { + const { $, client } = ctx; + + try { + const projectDir = (ctx as any).directory ?? process.cwd(); + installSkills(projectDir); + } catch {} + + const apiKey = process.env.MEM0_API_KEY; + + if (!apiKey) { + try { + await client.app.log({ + body: { + service: "mem0", + level: "warn", + message: + "MEM0_API_KEY not set. Get one at https://app.mem0.ai/dashboard/api-keys", + }, + }); + } catch {} + return {}; + } + + const mem0 = new MemoryClient({ apiKey }); + const userId = await getUserId(); + const appId = await getProjectId($); + const branch = await getBranch($); + const stats = { adds: 0, searches: 0, messages: 0 }; + const sessionId = generateSessionId(); + + let initialized = false; + let memoryCount = 0; + let msgCount = 0; + + const systemContext: string[] = []; + + return { + "chat.message": async (input: any, output: any) => { + const userText = extractUserText(input, output); + if (!userText || userText.length < 10) return; + + const safeText = redact(userText); + msgCount++; + stats.messages++; + + if (!initialized) { + initialized = true; + + try { + const all = await mem0.getAll({ + filters: { + AND: [{ user_id: userId }, { app_id: appId }], + }, + page: 1, + pageSize: 1, + }); + memoryCount = + (all as any)?.count ?? + (all as any)?.results?.length ?? + 0; + + systemContext.push( + `Always include user_id="${userId}" and app_id="${appId}" in every search_memories filter and add_memory call.`, + ); + + if (memoryCount === 0) { + systemContext.push( + "New project with 0 memories. Suggest running /mem0:onboard to import project files and install coding categories.", + ); + } + + if (memoryCount > 0) { + systemContext.push( + "Search mem0 for recent decisions and task learnings before responding. Run 2 parallel searches: one for decision type, one for task_learning type.", + ); + try { + const res = await mem0.search( + "recent session state decisions and learnings", + { + filters: { + AND: [{ user_id: userId }, { app_id: appId }], + }, + topK: 5, + }, + ); + stats.searches++; + const memories = extractMemories(res); + if (memories.length > 0) { + const memLines = memories + .map((m) => `- ${m.memory}`) + .join("\n"); + systemContext.push(`Prior context from mem0:\n${memLines}`); + } + } catch {} + } + + systemContext.push( + "Mem0 searches apply when user references past work, decision questions, errors, or non-trivial tasks. Queries use noun-phrases, 2-4 parallel calls with different metadata.type filters, and include user_id + app_id.", + ); + } catch (err: any) { + try { + await client.app.log({ + body: { + service: "mem0", + level: "error", + message: `Session init error: ${err?.message}`, + }, + }); + } catch {} + } + } + + if (NUDGE_RE.test(safeText)) { + systemContext.push( + "[MEMORY TRIGGER] User asked to remember something. Call add_memory with the user's statement, confidence=1.0, infer=false.", + ); + } + + const hasResume = RESUME_RE.test(safeText); + if (hasResume) { + try { + const [stateRes, decisionsRes] = await Promise.all([ + mem0.search("session state current task", { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "session_state" } }, + ], + }, + topK: 3, + }), + mem0.search("recent decisions and learnings", { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "decision" } }, + ], + }, + topK: 3, + }), + ]); + stats.searches += 2; + const all = [ + ...extractMemories(stateRes), + ...extractMemories(decisionsRes), + ]; + const seen = new Set(); + const unique = all.filter((m) => { + if (seen.has(m.id)) return false; + seen.add(m.id); + return true; + }); + if (unique.length > 0) { + const memLines = unique.map((m) => `- ${m.memory}`).join("\n"); + systemContext.push( + `Session resume context:\n${memLines}\n\nThese memories provide context for resuming work.`, + ); + } + } catch {} + } + + if (!hasResume && memoryCount > 0) { + try { + const res = await mem0.search(safeText, { + filters: { AND: [{ user_id: userId }, { app_id: appId }] }, + topK: 5, + }); + stats.searches++; + const memories = extractMemories(res); + if (memories.length > 0) { + const memLines = memories.map((m) => `- ${m.memory}`).join("\n"); + systemContext.push(`Relevant memories:\n${memLines}`); + } + } catch {} + } + + if (msgCount % 3 === 0) { + Promise.resolve().then(async () => { + try { + await mem0.add([{ role: "user", content: safeText }], { + user_id: userId, + app_id: appId, + metadata: { + type: "auto_capture", + source: "opencode", + confidence: 0.7, + session_id: sessionId, + branch, + }, + infer: true, + } as any); + stats.adds++; + } catch {} + }); + } + + if (msgCount % 5 === 0 && stats.adds < Math.floor(msgCount / 3)) { + systemContext.push( + "After responding, store any new decisions, learnings, or preferences from this exchange via add_memory. Keep it to 1 sentence per memory.", + ); + } + }, + + "experimental.chat.messages.transform": async ( + _input: any, + output: { messages: { info: any; parts: any[] }[] }, + ) => { + if (systemContext.length === 0 || !output?.messages?.length) return; + + const firstUser = output.messages.find( + (m) => m.info.role === "user", + ); + if (!firstUser || !firstUser.parts.length) return; + + const marker = "## Mem0 Memory Context"; + if (firstUser.parts.some((p: any) => p.type === "text" && p.text?.includes(marker))) return; + + const block = `${marker}\n\n${systemContext.join("\n\n")}`; + const ref = firstUser.parts[0]; + firstUser.parts.unshift({ ...ref, type: "text", text: block }); + }, + + "tool.execute.before": async (input: any, output: any) => { + const toolName: string = input?.tool ?? ""; + + if (WRITE_TOOLS.has(toolName)) { + const fp = String( + output?.args?.file_path ?? output?.args?.filePath ?? "", + ); + if (/MEMORY\.md|\.claude\/memory/i.test(fp)) { + throw new Error( + "Use the add_memory MCP tool instead of writing to MEMORY.md", + ); + } + } + + if (isMem0Tool(toolName) && output?.args) { + if (!output.args.user_id) output.args.user_id = userId; + if (!output.args.app_id) output.args.app_id = appId; + + if (isMem0AddMemory(toolName)) { + if (!output.args.metadata) output.args.metadata = {}; + const meta = output.args.metadata; + if (meta.confidence === undefined) meta.confidence = 0.7; + if (!meta.source) meta.source = "opencode"; + if (!meta.type) meta.type = "task_learning"; + if (!meta.session_id) meta.session_id = sessionId; + if (!meta.files) meta.files = ["*"]; + if (!meta.branch) meta.branch = branch; + if (meta.confidence >= 1.0 && output.args.infer === undefined) { + output.args.infer = false; + } + } + + if (isMem0SearchOrGet(toolName)) { + const existingFilters = output.args.filters; + if (existingFilters === undefined || existingFilters === null) { + output.args.filters = { + AND: [{ user_id: userId }, { app_id: appId }], + }; + } else if (typeof existingFilters === "object") { + const andClauses: any[] = existingFilters.AND; + if (Array.isArray(andClauses)) { + const hasUid = andClauses.some( + (c: any) => c && typeof c === "object" && "user_id" in c, + ); + const hasAid = andClauses.some( + (c: any) => c && typeof c === "object" && "app_id" in c, + ); + if (!hasUid) andClauses.push({ user_id: userId }); + if (!hasAid) andClauses.push({ app_id: appId }); + } else if (andClauses === undefined) { + const hasUid = "user_id" in existingFilters; + const hasAid = "app_id" in existingFilters; + if (!hasUid || !hasAid) { + const existing = Object.entries(existingFilters).map( + ([k, v]) => ({ [k]: v }), + ); + if (!hasUid) existing.push({ user_id: userId }); + if (!hasAid) existing.push({ app_id: appId }); + output.args.filters = { AND: existing }; + } + } + } + } + + if (isMem0DeleteAll(toolName)) { + if (!output.args.user_id) output.args.user_id = userId; + if (!output.args.app_id) output.args.app_id = appId; + } + + if (!isMem0AddMemory(toolName) && !output.args.metadata) { + output.args.metadata = { source: "opencode", branch }; + } + } + }, + + "tool.execute.after": async (input: any, _output: any) => { + const toolName: string = input?.tool ?? ""; + const toolOutput: string = input?.output ?? _output?.output ?? ""; + + if (MEM0_MCP_RE.test(toolName)) { + if (toolName.includes("add_memory")) stats.adds++; + if (toolName.includes("search")) stats.searches++; + } + + if (toolName === "bash" && toolOutput.length >= 50) { + const command: string = input?.args?.command ?? ""; + if (/git\s+(commit|merge|rebase)/.test(command)) return; + + const hasStrongError = ERROR_STRONG_RE.test(toolOutput); + const multiErrors = (toolOutput.match(ERROR_MULTI_RE) ?? []).length; + if (!hasStrongError && multiErrors < 2) return; + + try { + const errorLine = + toolOutput + .split("\n") + .find((l: string) => + /Error:|Exception:|panic:|FAIL:|fatal:/i.test(l), + ) + ?.replace(/^\s+/, "") + .slice(0, 120) ?? ""; + + const traceFiles = [ + ...new Set( + toolOutput.match( + /[a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh)(:\d+)?/g, + ) ?? [], + ), + ].slice(0, 5); + + const errorQuery = errorLine.slice(0, 80); + if (errorQuery.length < 10) return; + + const [antiPatternRes, bugFixRes] = await Promise.all([ + mem0.search(`error: ${errorQuery}`, { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "anti_pattern" } }, + ], + }, + topK: 3, + }), + mem0.search(`error: ${errorQuery}`, { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "bug_fix" } }, + ], + }, + topK: 3, + }), + ]); + stats.searches += 2; + + const allResults = [ + ...extractMemories(antiPatternRes), + ...extractMemories(bugFixRes), + ]; + const seen = new Set(); + const unique = allResults.filter((m) => { + if (seen.has(m.id)) return false; + seen.add(m.id); + return true; + }); + + let ctx = `Error detected: \`${command.slice(0, 100)}\` produced:\n> ${errorLine}`; + if (traceFiles.length > 0) { + ctx += `\nFiles in stack trace: ${traceFiles.join(", ")}`; + } + if (unique.length > 0) { + const lines = unique.map((m) => `- ${m.memory}`).join("\n"); + ctx += `\nPrior error memories:\n${lines}`; + } + ctx += + "\nStore resolved errors as anti_pattern or bug_fix memories for future reference."; + systemContext.push(ctx); + } catch {} + } + }, + + "experimental.session.compacting": async ( + input: { sessionID?: string }, + output: { context: string[]; prompt?: string }, + ) => { + try { + const compactSessionId = input?.sessionID ?? sessionId; + const summaryContent = `Session compacting. Project: ${appId}. Branch: ${branch}. Session: ${compactSessionId}. Stats: ${stats.adds} memories stored, ${stats.searches} searches, ${stats.messages} messages.`; + Promise.resolve().then(async () => { + try { + await mem0.add([{ role: "user", content: summaryContent }], { + user_id: userId, + app_id: appId, + metadata: { + type: "session_state", + source: "pre-compaction", + session_id: compactSessionId, + branch, + }, + infer: true, + } as any); + } catch {} + }); + + const res = await mem0.search("session state decisions learnings", { + filters: { AND: [{ user_id: userId }, { app_id: appId }] }, + topK: 10, + }); + const memories = extractMemories(res); + if (memories.length > 0 && output?.context) { + const lines = memories.map((m) => `- ${m.memory}`).join("\n"); + output.context.push( + `## Mem0 Memories (preserve across compaction)\n\n${lines}\n\nIMPORTANT: After compaction, store any key decisions or learnings using the add_memory MCP tool.`, + ); + } + } catch {} + }, + + "shell.env": async ( + _input: { cwd: string; sessionID?: string }, + output: { env: Record }, + ) => { + if (output?.env) { + output.env.MEM0_USER_ID = userId; + output.env.MEM0_APP_ID = appId; + output.env.MEM0_SESSION_ID = sessionId; + output.env.MEM0_BRANCH = branch; + } + }, + }; +}; + +export default Mem0Plugin; diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/context-loader/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/context-loader/SKILL.md new file mode 100644 index 000000000..312b90584 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/context-loader/SKILL.md @@ -0,0 +1,52 @@ +--- +name: context-loader +description: Searches and injects relevant memories into context before starting work on a task. Use when beginning a new task, switching context, or when project history, past decisions, or coding conventions need to be loaded. +--- + +# Context Loader + +Pre-fetches relevant memories to prime context before working on a task. + +## When to use + +- Session start (invoke manually or auto-triggered by skill description matching) +- User starts work on a specific feature or file set +- Complex multi-step task begins +- User says "what do we know about X" or "context for X" + +## Steps + +1. **Extract topics** from current message/task. Identify: file paths, module names, feature areas, error patterns. + +2. **Run 2-4 parallel `search_memories` calls** with different angles: + + | Query angle | Filter | Purpose | + |---|---|---| + | Feature/module name | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}` | Architecture decisions | + | File paths mentioned | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "convention"}}]}` | Coding patterns | + | Error keywords (if any) | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "anti_pattern"}}]}` | Known pitfalls | + | Broad project context | `{"AND": [{"user_id": ""}, {"app_id": ""}]}` | Catch-all | + +3. **Deduplicate** results by memory ID across all search responses. + +4. **Output compact context block** (max 10 memories): + +``` +context-loader: loaded memories for "" + - [decision] [mem0:] + - [convention] [mem0:] + - [anti_pattern] [mem0:] +``` + +5. If **zero results**: output nothing. Don't announce empty context. + +## Constraints + +- **Read-only** — never modify or delete memories +- **Max 10 memories** returned (most relevant only) +- **Silent on empty** — only surfaces findings if relevant context exists +- Skip memories already visible in current session context + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/dream/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/dream/SKILL.md new file mode 100644 index 000000000..83d9cb92f --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/dream/SKILL.md @@ -0,0 +1,237 @@ +--- +name: dream +description: Consolidates stored memories by merging duplicates, resolving contradictions, and pruning stale entries. Use when memory count is high, search results feel noisy or repetitive, or periodic cleanup is needed to maintain memory quality. +--- + +# Mem0 Dream — Memory Consolidation + +This skill performs a memory consolidation pass: it fetches all project memories, +identifies near-duplicates, flags contradictions, and prunes stale entries based on +configured retention policies. All proposed changes are shown as a diff for user +approval before anything is modified. + +**IMPORTANT: Execute steps strictly in order (1 → 2 → 3 → 4 → 5 → 6). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead.** + +## Step 1: Load Retention Policies + +Check for a project config file in the project root (current working directory): + +1. Look for `.mem0.json` first. If it exists, parse it as JSON and read the + `retention` field (a dict of `category → days | null`). +2. If `.mem0.json` is not present, look for `.mem0.md`. If it exists, scan it + for a `retention:` section or YAML front matter with retention settings and + parse what you find. +3. If neither file exists, skip config loading entirely. + +If no config is found or the config contains no retention settings, fall back to +these built-in defaults: + +| `metadata.type` | Default retention | +|---|---| +| `session_state` | 90 days | +| `compact_summary` | 90 days | +| all others | no pruning | + +Store the resolved policies for use in Step 3. + +--- + +## Step 2: Fetch ALL Project Memories + +Call `get_memories` to retrieve every memory for the active project: + +```python +get_memories( + filters={"AND": [{"user_id": ""}, {"app_id": ""}]}, + page_size=200, +) +``` + +If the response indicates more pages exist, paginate until all memories are fetched. +Collect the full list before proceeding. If zero memories are found, print: + +``` +No memories found for project . Nothing to consolidate. +``` + +…and stop. + +--- + +## Step 3: Analyze — Find Issues + +Work entirely in-memory; do not modify anything yet. + +Group memories by `metadata.type` (use `"unknown"` when the field is absent). +For each group, identify the following: + +### 3a. Near-duplicate pairs (merge candidates) + +Two memories are near-duplicates when they express the same fact or decision but +phrased differently (e.g., "Use PostgreSQL for auth" and "Auth DB is PostgreSQL"). + +Heuristics — two memories are near-duplicates if **all** of these hold: +- Similarity threshold: estimated cosine similarity > 0.9 (use noun/keyword overlap as proxy — if >60% of significant nouns overlap, treat as >0.9 similarity). +- Same `metadata.type`. +- Neither memory is pinned (`metadata.pinned != true`). + +For each qualifying pair, draft a merged version that is more complete and specific +than either original. + +### 3b. Contradictions + +Two memories contradict when they assert opposing facts about the same topic +(e.g., "Deploy to ECS" vs. "Deploy to Vercel"). + +Identify the likely winner: the more recent memory with higher confidence wins. +Store both IDs and their content for user review. + +### 3c. Prune candidates + +A memory is a prune candidate when **any** of the following is true: + +1. Its `metadata.type` has a retention policy and the memory is older than the + configured number of days (compare `created_at` to today). +2. Its confidence score is below 0.3 AND it contains no information unique to + this project (no file paths, identifiers, or domain-specific nouns). + +**Always skip memories where `metadata.pinned == true`**, regardless of age or +confidence. + +--- + +## Step 4: Print Diff Report + +Print a structured diff to the terminal before making any changes. Use exactly +this format: + +``` +## dream — consolidation report + +Merges (): + [mem0:] + [mem0:] → "" + +Conflicts (): + [mem0:] vs [mem0:] — "" [A/B/skip] + +Prune (): + [mem0:] — , d old + +Proposed: merges, prunes, conflicts. Apply? [Y/n] +``` + +If there are zero items in any category, omit that section entirely. + +If there are zero total proposals (no merges, no prunes, no conflicts), print: + +``` +Dream complete. No duplicate, contradictory, or stale memories found. +``` + +…and stop. + +--- + +## Step 5: Wait for User Input and Apply + +### 5a. Contradictions + +For each `CONFLICT` pair in the report, wait for the user to type `A`, `B`, or +`skip` (case-insensitive). If they enter nothing (empty), treat as `skip`. + +Record the winner for each pair before proceeding to the final apply confirmation. + +### 5b. Final confirmation + +After all conflict resolutions are collected, prompt: + +``` +Apply? [Y/n] +``` + +If the user types `n` or `no` (case-insensitive), print `Cancelled. No changes made.` +and stop. + +If the user confirms (`Y`, `yes`, or empty / Enter), apply all changes in this order: + +#### Merges + +For each approved merge pair: +1. `delete_memory()` +2. `delete_memory()` +3. `add_memory` with: + - `text=""` + - `user_id=` + - `app_id=` (top-level, not in metadata) + - `metadata={"type": "", "branch": "", "confidence": , "source": "mem0-dream"}` + - `infer=False` + +#### Contradictions (resolved) + +For each resolved conflict where the user chose A or B: +- Delete the loser (the non-chosen memory): `delete_memory(memory_id=)` + +Contradictions where the user chose `skip` are left untouched. + +#### Prunes + +For each prune candidate: +- `delete_memory()` + +--- + +## Step 6: Print Summary + +After all changes are applied, print: + +``` +Dream complete — merged: , pruned: , conflicts resolved: , skipped: +``` + +--- + +## Auto mode + +When invoked with `--auto` (e.g., `/mem0:dream --auto`), run non-interactively: + +- **Merges**: applied automatically (no contradiction, both are compatible). +- **Prunes**: applied automatically (age/confidence-based, no ambiguity). +- **Contradictions**: skipped — they require human judgment. + +### Concurrency guard + +Before doing any work, check for a lock file at `/tmp/mem0_dream_auto.lock`: +- If the lock file exists and is less than 10 minutes old, print `[mem0-dream --auto] Another run in progress — skipping.` and stop. +- Otherwise, create the lock file (write the current timestamp). Delete it when done (in all exit paths). + +### Execution + +In auto mode: +1. Load policies and fetch memories (Steps 1–3) as normal. +2. Apply merges and prunes silently without printing the diff or prompting. +3. Print a compact summary: + ``` + [mem0-dream --auto] project= merged= pruned= conflicts_skipped= + ``` +4. If contradictions were detected but skipped, check if a `mem0-dream-auto` reminder already exists before storing one: + - Search for existing reminders: `search_memories(query="mem0-dream contradictions manual review", filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"source": "mem0-dream-auto"}}]}, top_k=1)` + - If a result exists with similarity > 0.9, skip storing the reminder (one already exists). + - If no match, store the reminder: + ```python + add_memory( + text="mem0-dream detected contradiction(s) requiring manual review. Run /mem0:dream to resolve them interactively.", + user_id="", + app_id="", + metadata={"type": "task_learning", "source": "mem0-dream-auto", "branch": ""}, + infer=False, + ) + ``` + +## See also + +- `/mem0:forget` — targeted deletion of specific memories (search + confirm + delete) +- `/mem0:health --deep` — quick quality scan without applying changes + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/export/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/export/SKILL.md new file mode 100644 index 000000000..96460cae6 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/export/SKILL.md @@ -0,0 +1,80 @@ +--- +name: export +description: Exports all project memories to a portable Markdown file for backup or migration. Use when backing up memories, migrating to another project, sharing memory state with teammates, or archiving before cleanup. +--- + +# Mem0 Export + +Export all memories for the current project to a portable Markdown file. + +## Execution + +### Step 1: Resolve identity + +Determine the active identity: +- `user_id` from `MEM0_USER_ID` env var, else `$USER`, else `"default"` +- `project_id` (used as `app_id`) from `MEM0_PROJECT_ID` env var, or via the project resolver + +### Step 2: Fetch all memories + +Call `get_memories` with: +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` +- `page_size=200` + +If the response is paginated (i.e. the result contains a `next` cursor or the count equals `page_size`), continue fetching pages until all memories are retrieved. + +### Step 3: Format each memory as a YAML-frontmatter block + +For each memory record, produce a block in this exact format: + +``` +--- +id: +created_at: +type: +confidence: +branch: +files: +categories: +--- + + +``` + +Notes: +- The `---` delimiters must be on their own lines with no extra whitespace. +- `files` and `categories` are written as comma-separated values on a single line. +- Leave a blank line after the content before the next `---` (for readability). +- If a field is missing or null, write an empty string (not "null"). + +### Step 4: Write the export file + +Determine the output filename: + +``` +mem0-export--.md +``` + +Where `` is today's date in UTC. + +Write all formatted blocks to this file using the Write tool (or equivalent). The file is written to the current working directory. + +### Step 5: Print summary + +``` +Exported memories to +``` + +Where `` is the total number of memory blocks written. + +## Error Handling + +- If `get_memories` returns an error or zero memories, print: + ``` + No memories found for project . Nothing exported. + ``` +- If the write fails, report the error to the user. + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/forget/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/forget/SKILL.md new file mode 100644 index 000000000..d83004de8 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/forget/SKILL.md @@ -0,0 +1,74 @@ +--- +name: forget +description: Deletes memories by search query or memory ID with confirmation before removal. Use when removing outdated decisions, incorrect memories, sensitive data, or cleaning up after experiments. Also handles undo of recent additions. +--- + +# Mem0 Forget + +Delete specific memories from mem0. + +## Execution + +### Step 1: Parse input + +The user provides either: +- A search query: `/mem0:forget auth module decisions` +- A memory ID: `/mem0:forget ` + +If no argument, ask: "What should I forget? Provide a search query or memory ID." + +### Step 2: Find memories + +**If memory ID provided** (looks like a UUID or hex string): +- Call `get_memory` with the ID to verify it exists. +- Show: `Found: "" (created )` + +**If search query provided:** +- Call `search_memories` with: + - `query=` + - `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` + - `top_k=10` +- Show numbered list: + ``` + Found memories matching "": + 1. (type: , created: ) [ID: ] + 2. ... + ``` + +### Step 3: Confirm + +Ask: "Delete which memories? Enter numbers (e.g., 1,3,5), 'all', or 'cancel'." + +For a single memory ID, ask: "Delete this memory? [y/N]" + +**Never delete without confirmation.** This is destructive. + +### Step 4: Delete + +For each confirmed memory, call `delete_memory` with the memory ID. + +### Step 5: Report + +``` +Deleted memories. +``` + +If any deletions failed, report which ones and why. + +## Undo recent writes + +If the user says "undo last N memories" or "undo last write": + +1. Check the `MEM0_SESSION_ID` environment variable (set by the plugin's shell.env hook). +2. If `MEM0_SESSION_ID` is set, call `search_memories` with: + - `query="recently added"` + - `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"session_id": ""}}]}` + - `top_k=20` +3. Sort results by creation time descending and show the last N entries (default 1). Ask for confirmation. +4. Delete confirmed entries via `delete_memory`. + +If `MEM0_SESSION_ID` is not set or the search returns no results, tell the user: "No recent memory IDs tracked this session. Try `/mem0:tour` to browse recent memories, or `/mem0:forget ` to find specific ones." + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/health/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/health/SKILL.md new file mode 100644 index 000000000..e5f6015d9 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/health/SKILL.md @@ -0,0 +1,159 @@ +--- +name: health +description: Diagnoses mem0 connectivity, API key validity, and memory read/write functionality. Use when memory operations fail, searches return empty, add_memory errors occur, MCP connection drops, or to verify the plugin is working correctly. +--- + +# Mem0 Health Check + +Run a diagnostic check on the mem0 plugin. Useful for troubleshooting. + +## Execution + +Run ALL checks, then display a single summary. Do not stop on the first failure. + +### Check 1: API key + +```bash +_KEY="${MEM0_API_KEY:-}" +[ -n "$_KEY" ] && echo "${_KEY:0:6}..." || echo "NOT_SET" +``` + +- If `NOT_SET`: FAIL — "No API key configured" +- If set: PASS — the command already prints only the first 6 chars + +### Check 2: Identity resolution + +Resolve identity from environment variables set by the plugin's `shell.env` hook: + +```bash +echo "user_id=${MEM0_USER_ID:-${USER:-}}" +echo "project_id=${MEM0_APP_ID:-}" +echo "branch=$(git branch --show-current 2>/dev/null || echo '')" +``` + +- `user_id`: from `MEM0_USER_ID`, falling back to `$USER` +- `project_id`: from `MEM0_APP_ID` +- `branch`: from `git branch --show-current` + +PASS if all three are non-empty. WARN if any falls back to defaults. + +### Check 3: MCP server connectivity + +Call `search_memories` with: +- `query="health check"` +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` +- `top_k=1` + +- If returns successfully (even empty): PASS +- If errors: FAIL — show the error message + +### Check 4: Memory write capability + +Call `add_memory` with: +- `text="Health check probe — safe to delete."` +- `user_id=` +- `app_id=` +- `metadata={"type": "health_check", "probe": true}` +- `infer=False` + +The response returns `event_id` (v3 writes are async). Call `get_event_status(event_id=)` to check processing. + +- If status is `SUCCEEDED`: PASS — extract the memory ID from the event result, then call `delete_memory` with that ID to clean up. +- If status is `PENDING` after 5 seconds: PASS (write accepted, processing delayed) +- If errors: FAIL — show the error. + +### Check 5: Session context + +Check that the plugin's `shell.env` hook has injected session context into the environment: + +```bash +echo "session_id=${MEM0_SESSION_ID:-}" +echo "app_id=${MEM0_APP_ID:-}" +echo "branch=${MEM0_BRANCH:-}" +``` + +- If all three are non-empty: PASS — "Session active" +- If any are missing: WARN — "Plugin env vars not set; shell.env hook may not have fired" + +### Display + +``` +## mem0 health + +PASS API Key m0-dVe... +PASS Identity user=kartik, project=mem0, branch=main +PASS MCP Connection 142ms +PASS Write/Read write + delete OK +PASS Session session_id=abc123, app_id=mem0, branch=main + +All checks passed. +``` + +If any check fails, add a `## Troubleshooting` section with specific fix steps for each failure. + +## Extended mode: Memory Quality Analysis + +When invoked with `--deep` (e.g., `/mem0:health --deep`), run the standard 5 checks above **plus** a memory quality scan. + +### Quality Check 1: Duplicates + +Call `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200`. Compare all pairs within the same `metadata.type` group for high textual overlap (shared nouns/keywords > 60%). Report: + +``` +Potential duplicates: pairs + [mem0:] ≈ [mem0:] — both about "" +``` + +### Quality Check 2: Stale memories + +Flag memories where: +- `metadata.type` is `session_state` or `compact_summary` AND older than 90 days +- `metadata.confidence` < 0.3 AND older than 30 days + +``` +Stale candidates: + [mem0:] — session_state, 142d old +``` + +### Quality Check 2b: Low-confidence memories + +Flag memories where `metadata.confidence` < 0.5 (regardless of age). Report separately from stale: + +``` +Low-confidence memories: + [mem0:] — confidence=0.3, "" +``` + +### Quality Check 3: Contradictions + +Within each `metadata.type` group, flag pairs that assert opposing facts about the same topic. Use semantic judgment — look for negation patterns, conflicting tool/framework choices, or reversed decisions. + +``` +Possible contradictions: + [mem0:] vs [mem0:] — conflicting on "" +``` + +### Quality Check 4: Orphan memories + +Memories with no `metadata.type` set, or with `metadata.type` not in the 17 known coding categories. These were likely written without proper tagging. + +``` +Untagged/orphan memories: +``` + +### Quality summary + +``` +## Memory Quality + +Duplicates: · Stale: · Contradictions: · Orphans: +``` + +If all counts are 0: `Memory quality: clean.` +If any non-zero: append `Run /mem0:dream to fix.` + +To fix issues found by `--deep`, run `/mem0:dream` for automated consolidation (merges, prunes, conflict resolution). + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/import/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/import/SKILL.md new file mode 100644 index 000000000..b982351ad --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/import/SKILL.md @@ -0,0 +1,185 @@ +--- +name: import +description: Imports memories from an exported Markdown file or MEMORY.md into the current project. Use when migrating from another project, restoring from backup, importing Claude Code native MEMORY.md content, or setting up a new project with existing knowledge. +--- + +# Mem0 Import + +Import memories from a mem0 export file into the current project. + +## Execution + +### Step 1: Determine the export file to import + +If the user provided a filename as an argument to `/mem0:import `, use that file. + +Otherwise, list `.md` files in the current directory whose names contain `mem0-export`: + +```bash +ls -1 *.md 2>/dev/null | grep mem0-export || echo "No export files found" +``` + +If multiple files are found, ask the user which one to import. If none are found, print: +``` +No mem0-export files found in the current directory. +Run /mem0:export first, or provide the filename: /mem0:import +``` + +### Step 2: Parse the export file + +Read the export file directly. It is a JSON file containing a top-level `memories` array. Each element has: +- `id` — original memory ID (for reference only; a new ID will be assigned on import) +- `type` — metadata type +- `confidence` — metadata confidence value +- `branch` — metadata branch +- `files` — list of associated files +- `categories` — list of categories +- `content` — the memory text + +Parse the JSON in-memory (do not run any external script). If the file cannot be read or parsed, or if the `memories` array is missing or empty, print: +``` +Failed to parse or file contains no valid memory blocks. +``` +and stop. + +### Step 3: Resolve identity + +Determine the active identity: +- `user_id` from `MEM0_USER_ID` env var, else `$USER`, else `"default"` +- `project_id` (used as `app_id`) from `MEM0_PROJECT_ID` env var, or via the project resolver + +### Step 4: Import each memory + +For each record in the parsed JSON array, call `add_memory` (MCP tool) with: + +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={` + - `"type": ""` (if non-empty) + - `"confidence": ""` (if non-empty) + - `"branch": ""` (if non-empty) + - `"files": ` (the list, if non-empty) + - `"source": "import"` + - `}` +- `infer=False` + +Notes: +- Do NOT pass the original `id` — the platform assigns a new ID. +- Skip records where `content` is empty. +- Continue importing even if individual records fail; track the count of successes. + +### Step 5: Print results + +``` +Imported memories into project +``` + +Where `` is the number of successfully imported memories. + +If any failed: +``` +Imported / memories into project ( failed) +``` + +## Importing from competing AI tools (`--tools`) + +When invoked with `--tools` (e.g., `/mem0:import --tools`), detect and import +from competing AI tool configuration files: + +### Supported tools + +| Tool | File/directory | +|------|---------------| +| Cursor | `.cursorrules` | +| GitHub Copilot | `.github/copilot-instructions.md` | +| Cline | `memory-bank/` (directory of `.md` files) | +| Continue | `.continue/rules.md` | + +### T1: Detect + +```bash +test -f .cursorrules && echo "cursor: .cursorrules" +test -f .github/copilot-instructions.md && echo "copilot: .github/copilot-instructions.md" +test -d memory-bank/ && echo "cline: memory-bank/" +test -f .continue/rules.md && echo "continue: .continue/rules.md" +``` + +### T2: Ask user + +List found files, ask which to import (numbers, comma-separated, or "all"). +If none found: +``` +No competing tool configuration files found. +Checked: .cursorrules, .github/copilot-instructions.md, memory-bank/, .continue/rules.md +``` + +### T3: Import + +For each selected tool, read the file(s) directly and split the content into logical +chunks to import as individual memories. No external scripts are used — all parsing +and importing is done via MCP tools. + +Chunking rules per tool: + +- **cursorrules / copilot / continue**: Read the file as plain text. Split on blank + lines or section headers (`#`, `##`). Each non-empty chunk becomes one memory. + Skip chunks shorter than 50 characters. + +- **cline (memory-bank/)**: List all `.md` files in the directory. Read each file. + Split each file on blank lines or headers. Each non-empty chunk becomes one memory. + Skip chunks shorter than 50 characters. + +For each chunk, call `add_memory` (MCP tool) with: +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={"type": "task_learning", "source": "-import", "confidence": 0.8}` +- `infer=False` + +Where `` is `cursorrules`, `copilot`, `cline`, or `continue`. + +Notes: chunks longer than 10,000 characters are truncated before import. Safe to +re-run — deduplication handles repeated entries. + +### T4: Report + +``` +Imported memories into (cursor: , copilot: ) +``` + +--- + +## Importing Claude Code's native MEMORY.md + +When invoked with a path to Claude Code's native `MEMORY.md` file (typically +`~/.claude/projects//memory/MEMORY.md`), or when `on_session_start.sh` +detects native auto-memory and the user chooses to import: + +1. Read the file directly. It contains newline-separated memory entries (one fact per + line, sometimes with `- ` bullet prefix). +2. Split by non-empty lines. Each line becomes one memory. +3. Skip lines shorter than 20 characters or lines that are just headers (`#`). +4. For each line, call `add_memory` (MCP tool) with: + - `text=""` + - `user_id=` + - `app_id=` + - `metadata={"type": "task_learning", "source": "memory-md-import", "confidence": 0.8}` + - `infer=False` +5. Report: `Imported memories from MEMORY.md into project ` +6. Suggest disabling native auto-memory: + ``` + To avoid duplicate memory systems, add to ~/.claude/settings.json: + "autoMemoryEnabled": false + ``` + +This handles the cold-start gap when a user has been using Claude Code's native +memory and switches to mem0. + +## Error Handling + +- If `add_memory` calls fail consistently (e.g. auth error), report the issue and stop early. + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/list-projects/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/list-projects/SKILL.md new file mode 100644 index 000000000..85a39116c --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/list-projects/SKILL.md @@ -0,0 +1,64 @@ +--- +name: list-projects +description: Lists all projects with stored memories for the current user, showing memory counts and last activity dates. Use when checking which projects have memories, comparing memory distribution across repos, or finding a specific project scope. +--- + +# Mem0 List Projects + +Show all known project scopes for the current user. + +## Execution + +### Step 1: Fetch memories to discover app_ids + +There is no dedicated "list projects" API endpoint. Discover projects by fetching +the user's memories across all scopes. + +**Important:** A filter with only `user_id` triggers implicit null scoping — it +excludes memories that have a non-null `app_id`. Run two queries and merge: + +1. **Null-scoped:** `get_memories` with `filters={"AND": [{"user_id": ""}]}`, `page_size=200` + — catches memories without `app_id` +2. **App-scoped:** `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": {"exists": true}}]}`, `page_size=200` + — catches memories with any `app_id` + +Run both calls in parallel. Merge results, deduplicate by memory `id`. + +If either response indicates more pages, paginate (up to 1000 total). + +### Step 2: Extract distinct projects + +For each memory, determine project by: +1. Top-level `app_id` field (preferred) +2. `metadata.project_id` (legacy memories) +3. `metadata.project` (oldest format) +4. `"(unscoped)"` if none found + +Group by resolved project name. For each project, count: +- Total memories +- Most recent `created_at` date +- Top 3 `metadata.type` values by frequency + +### Step 3: Display + +``` +## mem0 projects + + memories (last: ) ← current + memories (last: ) + + projects, total memories +``` + +Mark current project with `← current`. Sort by memory count descending. + +### Step 4: Empty state + +If zero memories found: +``` +No projects found. Run /mem0:onboard to get started. +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/LICENSE b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/LICENSE new file mode 100644 index 000000000..78c99ae28 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/LICENSE @@ -0,0 +1,189 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but not + limited to compiled object code, generated documentation, and + conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work. + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2024 Mem0.ai + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/README.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/README.md new file mode 100644 index 000000000..4ed29d162 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/README.md @@ -0,0 +1,73 @@ +# Mem0 Skill for Claude + +Add persistent memory to any AI application in minutes using [Mem0 Platform](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-skill-readme). + +## What This Skill Does + +When installed, Claude can: + +- **Set up Mem0** in your Python or TypeScript project +- **Integrate memory** into your existing AI app (LangChain, CrewAI, Vercel AI, OpenAI Agents, LangGraph, LlamaIndex, etc.) +- **Generate working code** using real API references and tested patterns +- **Search live docs** on demand for the latest Mem0 documentation + +## Installation + +This skill is included automatically when you install the Mem0 plugin: + +``` +/plugin marketplace add mem0ai/mem0 +/plugin install mem0@mem0-plugins +``` + +See the [plugin README](../../README.md) for full setup instructions. + +### Prerequisites + +- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill-readme)) +- Python 3.10+ or Node.js 18+ +- Set the environment variable: + + ```bash + export MEM0_API_KEY="m0-your-api-key" + ``` + +## Quick Start + +After installing, just ask Claude: + +- "Set up mem0 in my project" +- "Add memory to my chatbot" +- "Help me search user memories with filters" +- "Integrate mem0 with my LangChain app" +- "Add graph memory to track entity relationships" + +## What's Inside + +```text +skills/mem0/ +├── SKILL.md # Skill definition and instructions +├── README.md # This file +├── LICENSE # Apache-2.0 +├── scripts/ +│ └── mem0_doc_search.py # Search live Mem0 docs on demand +└── references/ # Documentation (loaded on demand) + ├── quickstart.md # Full quickstart (Python, TS, cURL) + ├── sdk-guide.md # All SDK methods (Python + TypeScript) + ├── api-reference.md # REST endpoints, filters, memory object + ├── architecture.md # Processing pipeline, lifecycle, scoping, performance + ├── features.md # Retrieval, graph, categories, MCP, webhooks, multimodal + ├── integration-patterns.md # LangChain, CrewAI, Vercel AI, LangGraph, LlamaIndex, etc. + └── use-cases.md # 7 real-world patterns with Python + TypeScript code +``` + +## Links + +- [Mem0 Platform Dashboard](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-skill-readme) +- [Mem0 Documentation](https://docs.mem0.ai) +- [Mem0 GitHub](https://github.com/mem0ai/mem0) +- [API Reference](https://docs.mem0.ai/api-reference) + +## License + +Apache-2.0 diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/SKILL.md new file mode 100644 index 000000000..4da64160a --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/SKILL.md @@ -0,0 +1,191 @@ +--- +name: mem0 +description: Mem0 SDK reference covering Python and TypeScript APIs, memory client methods, configuration, and framework integrations. Use when writing code that calls mem0 APIs, configuring memory providers, or integrating mem0 into an application. +license: Apache-2.0 +metadata: + author: mem0ai + version: "0.1.1" + category: ai-memory + tags: "memory, personalization, ai, python, typescript, vector-search" +compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm install mem0ai, MEM0_API_KEY env var (Platform), and internet access to api.mem0.ai. Uses Mem0 v3 API. +--- + +# Mem0 Platform Integration + +> **Skill Graph:** This skill is part of the Mem0 skill graph: +> - **mem0** (this skill) -- Platform Client SDK + OSS (Python + TypeScript) +> - **[mem0-vercel-ai-sdk](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk)** -- Vercel AI SDK provider + +Mem0 is a managed memory layer for AI applications. It stores, retrieves, and manages user memories via API — no infrastructure to deploy. For self-hosted usage, see the OSS section in the client references below. + +## Step 1: Install and authenticate + +**Python:** +```bash +pip install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +**TypeScript/JavaScript:** +```bash +npm install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +Get an API key at: https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill + +> **Don't have a `MEM0_API_KEY`?** Sign up at https://app.mem0.ai and create one from the dashboard. Keys start with `m0-`. + +## Step 2: Initialize the client + +**Python:** +```python +from mem0 import MemoryClient +client = MemoryClient(api_key="m0-xxx") +``` + +**TypeScript:** +```typescript +import MemoryClient from 'mem0ai'; +const client = new MemoryClient({ apiKey: 'm0-xxx' }); +``` + +For async Python, use `AsyncMemoryClient`. + +## Step 3: Core operations + +Every Mem0 integration follows the same pattern: **retrieve → generate → store**. + +### Add memories +```python +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember that."} +] +client.add(messages, user_id="alice") +``` + +### Search memories +```python +results = client.search("dietary preferences", filters={"user_id": "alice"}) +for mem in results.get("results", []): + print(mem["memory"]) +``` + +### Get all memories +```python +all_memories = client.get_all(filters={"user_id": "alice"}) +``` + +### Update a memory +```python +client.update("memory-uuid", text="Updated: vegetarian, nut allergy, prefers organic") +``` + +### Delete a memory +```python +client.delete("memory-uuid") +client.delete_all(user_id="alice") # delete all for a user +``` + +## Common integration pattern + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai = OpenAI() + +def chat(user_input: str, user_id: str) -> str: + # 1. Retrieve relevant memories + memories = mem0.search(user_input, filters={"user_id": user_id}) + context = "\n".join([m["memory"] for m in memories.get("results", [])]) + + # 2. Generate response with memory context + response = openai.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"User context:\n{context}"}, + {"role": "user", "content": user_input}, + ] + ) + reply = response.choices[0].message.content + + # 3. Store interaction for future context + mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": reply}], + user_id=user_id + ) + return reply +``` + +## Common edge cases + +- **Search returns empty:** v3 processes `add()` asynchronously — returns an event ID immediately. Wait 2-3s before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax. +- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. `{"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}` returns nothing. Use `OR` instead, or query each separately. +- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. `infer=True` extracts facts via LLM with dedup. `infer=False` stores raw — same text can be stored twice. +- **Implicit null scoping:** `filters={"user_id": "alice"}` only returns memories where `agent_id`, `app_id`, `run_id` are ALL null. Wrap in `{"OR": [...]}` to include memories with non-null scoping fields. +- **Platform vs OSS imports:** Platform: `from mem0 import MemoryClient`. OSS: `from mem0 import Memory`. Don't mix them — `MemoryClient` talks to `api.mem0.ai`, `Memory` runs locally. +- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed. + +## v3 API (Current) + +Mem0 v3 uses single-pass extraction, entity linking, and multi-signal retrieval. + +**Key v3 changes from v2:** +- **Endpoints:** `POST /v3/memories/add/`, `POST /v3/memories/search/`, `POST /v3/memories/` (paginated list) +- **Extraction:** Single ADD-only pass — no more UPDATE/DELETE operations during extraction. Memories accumulate rather than consolidate. +- **Entity linking:** Replaces graph memory. Auto-extracted during `add()`, no config needed. Remove `enable_graph` and `graph_store` from any old config. +- **Defaults:** `top_k=20`, `threshold=0.1`, `rerank=False` +- **Removed params:** `org_id`, `project_id`, `enable_graph` — all removed from SDK +- **TypeScript:** Exclusively camelCase (`userId`, `agentId`, `appId`, `topK`) +- **Add response:** Async — returns event ID immediately, poll via `GET /v1/event/{event_id}/` + +See the [migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details. + +## Live documentation search + +For the latest docs beyond what's in the references, use the doc search tool: + +```bash +bun ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.ts --query "topic" +bun ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.ts --page "/platform/features/graph-memory" +bun ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.ts --index +``` + +No API key needed — searches docs.mem0.ai directly. + +## Client SDK References + +Language-specific deep references (Platform + OSS): + +| Language | File | +|----------|------| +| Python (MemoryClient + AsyncMemoryClient + Memory OSS) | [client/python.md](client/python.md) | +| TypeScript/Node.js (MemoryClient + Memory OSS) | [client/node.md](client/node.md) | +| Python vs TypeScript differences | [client/differences.md](client/differences.md) | + +## Platform References + +Load these on demand for deeper detail: + +| Topic | File | +|-------|------| +| Quickstart (Python, TS, cURL) | [references/quickstart.md](references/quickstart.md) | +| SDK guide (all methods, both languages) | [references/sdk-guide.md](references/sdk-guide.md) | +| API reference (endpoints, filters, object schema) | [references/api-reference.md](references/api-reference.md) | +| Architecture (pipeline, lifecycle, scoping, performance) | [references/architecture.md](references/architecture.md) | +| Platform features (retrieval, graph, categories, MCP, etc.) | [references/features.md](references/features.md) | +| Framework integrations (LangChain, CrewAI, OpenAI Agents, etc.) | [references/integration-patterns.md](references/integration-patterns.md) | +| Use cases & examples (real-world patterns with code) | [references/use-cases.md](references/use-cases.md) | + +## Related Mem0 Skills + +| Skill | When to use | Link | +|-------|-------------|------| +| mem0-vercel-ai-sdk | Vercel AI SDK provider with automatic memory | [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk) | + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/differences.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/differences.md new file mode 100644 index 000000000..e5e80e250 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/differences.md @@ -0,0 +1,129 @@ +# Python vs TypeScript SDK Differences + +Quick-reference cheatsheet for developers working across both Mem0 SDKs. + +## Constructor + +| Aspect | Python | TypeScript | +|--------|--------|------------| +| Import (Platform) | `from mem0 import MemoryClient` | `import MemoryClient from 'mem0ai'` | +| Import (OSS) | `from mem0 import Memory` | `import { Memory } from 'mem0ai/oss'` | +| Constructor | `MemoryClient(api_key="m0-xxx")` | `new MemoryClient({ apiKey: 'm0-xxx' })` | +| Required param | `api_key` (positional or kwarg) | `apiKey` (in options object) | + +Both read from `MEM0_API_KEY` env var if no key provided. + +## Method Naming + +| Operation | Python | TypeScript | +|-----------|--------|------------| +| Add | `add()` | `add()` | +| Search | `search()` | `search()` | +| Get | `get()` | `get()` | +| Get all | `get_all()` | `getAll()` | +| Update | `update()` | `update()` | +| Delete | `delete()` | `delete()` | +| Delete all | `delete_all()` | `deleteAll()` | +| History | `history()` | `history()` | +| Batch update | `batch_update()` | `batchUpdate()` | +| Batch delete | `batch_delete()` | `batchDelete()` | +| List users | `users()` | `users()` | +| Delete users | `delete_users()` | `deleteUsers()` | +| Get project | `project.get()` | `getProject()` | +| Update project | `project.update()` | `updateProject()` | +| Create webhook | `create_webhook()` | `createWebhook()` | +| Get webhooks | `get_webhooks()` | `getWebhooks()` | +| Update webhook | `update_webhook()` | `updateWebhook()` | +| Delete webhook | `delete_webhook()` | `deleteWebhook()` | +| Create export | `create_memory_export()` | `createMemoryExport()` | +| Get export | `get_memory_export()` | `getMemoryExport()` | +| Feedback | `feedback()` | `feedback()` | + +**Rule:** Python uses `snake_case`, TypeScript uses `camelCase` for method names. + +## Parameter Passing + +```python +# Python: kwargs +client.add(messages, user_id="alice", metadata={"source": "chat"}) +client.search("query", filters={"user_id": "alice"}, top_k=5, rerank=True) +``` + +```typescript +// TypeScript: options object with camelCase for top-level params, snake_case for filter keys +await client.add(messages, { userId: 'alice', metadata: { source: 'chat' } }); +await client.search('query', { filters: { user_id: 'alice' }, topK: 5, rerank: true }); +``` + +**v3:** Python uses `snake_case` everywhere. TypeScript uses `camelCase` for top-level params (`userId`, `topK`) but `snake_case` for filter keys (`user_id`, `agent_id`). + +## Architectural Differences + +| Aspect | Python | TypeScript | +|--------|--------|------------| +| HTTP library | httpx | axios | +| Default timeout | 300s | 60s | +| Sync support | Yes (`MemoryClient`) | No (all async) | +| Async support | Yes (`AsyncMemoryClient`) | All methods are async | +| Project management | `client.project.*` (separate class) | `client.getProject()` / `client.updateProject()` | +| Context manager | `async with AsyncMemoryClient()` | Not supported | + +## Platform Features: Python-only + +These methods exist in Python but not TypeScript: + +| Method | Description | +|--------|-------------| +| `get_summary(filters)` | Get summary of memories | +| `reset()` | Delete ALL data (users + memories) | +| `project.create(name)` | Create a new project | +| `project.delete()` | Delete current project | +| `project.get_members()` | List project members | +| `project.add_member(email, role)` | Add member to project | +| `project.update_member(email, role)` | Change member role | +| `project.remove_member(email)` | Remove member | + +## Platform Features: TypeScript-only + +| Method | Description | +|--------|-------------| +| `deleteUser(data)` | Convenience method for single entity deletion | +| `ping()` | Health check endpoint | + +## OSS Config Naming + +| Python config key | TypeScript config key | +|-------------------|----------------------| +| `vector_store` | `vectorStore` | +| `history_db_path` | `historyDbPath` | +| `custom_instructions` | `customInstructions` | + +## OSS Scope Parameter Naming + +| Python | TypeScript | +|--------|------------| +| `user_id="alice"` | `userId: 'alice'` | +| `agent_id="bot"` | `agentId: 'bot'` | +| `run_id="session"` | `runId: 'session'` | + +## Entity ID Passing (v3) + +| Method | Python | TypeScript | +|--------|--------|------------| +| add() | Top-level: `user_id="alice"` | Top-level: `{ userId: 'alice' }` | +| search() | In filters: `filters={"user_id": "alice"}` | In filters: `{ filters: { user_id: 'alice' } }` | +| get_all() | In filters: `filters={"user_id": "alice"}` | In filters: `{ filters: { user_id: 'alice' } }` | + +## Common Gotcha + +When searching/filtering, both Python and TypeScript use `snake_case` for filter keys. TypeScript only uses `camelCase` for top-level method parameters: + +```python +# Python - snake_case in filters +results = client.search("query", filters={"user_id": "alice"}) +``` + +```typescript +// TypeScript - snake_case in filters, camelCase for top-level params +const results = await client.search('query', { filters: { user_id: 'alice' }, topK: 20 }); +``` diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/node.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/node.md new file mode 100644 index 000000000..ca85ba7ba --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/node.md @@ -0,0 +1,418 @@ +# Mem0 Node.js / TypeScript SDK Reference + +Complete reference for the `mem0ai` npm package. Covers both the Platform client (managed API) and the Open Source self-hosted variant. + +--- + +## Platform Client + +### Installation + +```bash +npm install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +### MemoryClient + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: 'm0-xxx' }); +``` + +**Constructor:** `new MemoryClient({ apiKey })`. If `apiKey` is not provided, reads from `MEM0_API_KEY` environment variable. + +- HTTP library: `axios` +- Timeout: 60 seconds +- Base URL: `https://api.mem0.ai` +- All methods are async (return `Promise`) + +--- + +### Memory Methods + +#### add(messages, options?) + +Store new memories from messages. + +```typescript +const messages = [ + { role: 'user', content: "I'm a vegetarian and allergic to nuts." }, + { role: 'assistant', content: "Got it! I'll remember that." }, +]; +await client.add(messages, { userId: 'alice' }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `messages` | `Message[]` | Array of `{role, content}` objects | +| `options.userId` | string | User identifier | +| `options.agentId` | string | Agent identifier | +| `options.appId` | string | Application identifier | +| `options.runId` | string | Session identifier | +| `options.metadata` | object | Custom key-value pairs | +| `options.infer` | boolean | If false, store raw text (default: true) | + +**Returns:** `Promise` -- list of events + +#### search(query, options?) + +Search memories by semantic similarity. + +```typescript +const results = await client.search('dietary preferences', { filters: { user_id: 'alice' }, topK: 20 }); +for (const mem of results.results) { + console.log(mem.memory, mem.score); +} +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `query` | string | Natural language search query | +| `options.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, etc.) and/or `AND`/`OR`/`NOT` conditions | +| `options.topK` | number | Number of results (default: 20) | +| `options.rerank` | boolean | Enable semantic reranking (default: false) | +| `options.threshold` | number | Minimum similarity (default: 0.1) | + +**Returns:** `Promise` -- `{results: [{id, memory, score, ...}]}` + +#### get(memoryId) + +```typescript +const memory = await client.get('ea925981-...'); +``` + +#### getAll(options?) + +Retrieve all memories. Requires at least one entity identifier in filters. + +```typescript +const memories = await client.getAll({ filters: { user_id: 'alice' } }); +// With filters +const filtered = await client.getAll({ + filters: { AND: [{ user_id: 'alice' }, { categories: { contains: 'health' } }] }, +}); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `options.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, etc.) and/or `AND`/`OR`/`NOT` conditions | +| `options.page` | number | Page number | +| `options.pageSize` | number | Results per page | + +#### update(memoryId, data) + +```typescript +await client.update('ea925981-...', { text: 'Updated: vegan since 2024' }); +await client.update('ea925981-...', { text: 'Updated', metadata: { verified: true } }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `memoryId` | string | Memory ID | +| `data.text` | string | New content | +| `data.metadata` | object | New metadata | +| `data.timestamp` | string | New timestamp | + +#### delete(memoryId) + +```typescript +await client.delete('ea925981-...'); +``` + +#### deleteAll(options?) + +```typescript +await client.deleteAll({ userId: 'alice' }); +``` + +#### history(memoryId) + +```typescript +const history = await client.history('ea925981-...'); +// Returns: [{previousValue, newValue, action, timestamps}] +``` + +--- + +### Batch Methods + +#### batchUpdate(memories) + +```typescript +await client.batchUpdate([ + { memoryId: 'uuid-1', text: 'Updated text' }, + { memoryId: 'uuid-2', text: 'Another update' }, +]); +``` + +#### batchDelete(memories) + +```typescript +await client.batchDelete(['uuid-1', 'uuid-2', 'uuid-3']); +``` + +--- + +### User/Entity Management + +#### users() + +```typescript +const users = await client.users(); +// Returns: {results: [{type: "user", name: "alice"}, ...]} +``` + +#### deleteUser(data) / deleteUsers(data) + +```typescript +await client.deleteUser({ userId: 'alice' }); // Single entity +await client.deleteUsers({ agentId: 'bot-1' }); // Flexible +``` + +--- + +### Project Management + +```typescript +// Get project config +const config = await client.getProject({ fields: ['customCategories'] }); + +// Update project settings +await client.updateProject({ + customInstructions: 'Extract dietary preferences and health info', + customCategories: [{ health: 'Medical and dietary info' }], +}); +``` + +--- + +### Webhooks + +```typescript +// List +const webhooks = await client.getWebhooks({ projectId: 'proj_123' }); + +// Create +const webhook = await client.createWebhook({ + url: 'https://your-app.com/webhook', + name: 'Memory Logger', + projectId: 'proj_123', + eventTypes: ['memory_add', 'memory_update'], +}); + +// Update +await client.updateWebhook({ + webhookId: 'wh_123', + name: 'Updated Logger', + url: 'https://new-url.com', +}); + +// Delete +await client.deleteWebhook({ webhookId: 'wh_123' }); +``` + +--- + +### Feedback + +```typescript +await client.feedback({ + memoryId: 'mem-123', + feedback: 'POSITIVE', + feedbackReason: 'Accurately captured preference', +}); +``` + +--- + +### Export + +```typescript +const exportReq = await client.createMemoryExport({ + schema: JSON.stringify({ type: 'object', properties: { name: { type: 'string' } } }), + filters: { user_id: 'alice' }, +}); + +const result = await client.getMemoryExport({ memoryExportId: exportReq.id }); +``` + +--- + +### TypeScript Types + +Key interfaces from `mem0.types.ts`: + +```typescript +interface Message { role: string; content: string; } +interface Memory { id: string; memory: string; userId: string; categories: string[]; score?: number; /* ... */ } +interface MemoryOptions { userId?: string; agentId?: string; appId?: string; runId?: string; metadata?: object; /* ... */ } +interface SearchOptions { filters?: object; topK?: number; rerank?: boolean; threshold?: number; /* ... */ } +interface MemoryHistory { id: string; memoryId: string; previousValue: string; newValue: string; action: string; /* ... */ } +interface FeedbackPayload { memoryId: string; feedback: string; feedbackReason?: string; } +interface WebhookCreatePayload { url: string; name: string; projectId: string; eventTypes: string[]; } +``` + +--- + +## Open Source / Self-Hosted + +### Installation + +```bash +npm install mem0ai +``` + +### Memory Class + +```typescript +import { Memory } from 'mem0ai/oss'; + +const m = new Memory(); // Uses default config +``` + +**Import:** `from 'mem0ai/oss'` (NOT the default export -- that is `MemoryClient` for Platform) + +### Configuration + +```typescript +const config = { + llm: { + provider: 'openai', // openai, groq, anthropic, google, ollama, lmstudio, mistral, azure + config: { + model: 'gpt-5-mini', + apiKey: 'sk-xxx', + }, + }, + embedder: { + provider: 'openai', // openai, ollama, lmstudio, google, azure, langchain, anthropic + config: { + model: 'text-embedding-3-small', + apiKey: 'sk-xxx', + }, + }, + vectorStore: { + provider: 'qdrant', // memory, qdrant, redis, supabase, langchain, azure_ai_search, pgvector + config: { + collectionName: 'my_memories', + host: 'localhost', + port: 6333, + }, + }, + historyDbPath: 'history.db', + customInstructions: '...', + disableHistory: false, +}; + +const m = new Memory(config); +// Or from dict with validation: +const m2 = Memory.fromConfig(config); +``` + +### Methods + +All methods are async (return `Promise`): + +#### add(messages, config) + +```typescript +await m.add('I prefer dark mode', { userId: 'alice' }); +await m.add([ + { role: 'user', content: 'I like hiking' }, + { role: 'assistant', content: 'Great outdoor activity!' }, +], { userId: 'alice' }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `messages` | `string \| Message[]` | Content to store | +| `config.userId` | string | User identifier (at least one scope required) | +| `config.agentId` | string | Agent identifier | +| `config.runId` | string | Session identifier | +| `config.metadata` | object | Custom key-value pairs | +| `config.filters` | object | Additional filters | +| `config.infer` | boolean | LLM inference (default: true) | + +**Returns:** `Promise<{results: [...], relations?: [...]}>` + +#### search(query, config) + +```typescript +const results = await m.search('dietary preferences', { filters: { user_id: 'alice' }, topK: 5 }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `query` | string | Search query | +| `config.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, `run_id`, etc.) | +| `config.topK` | number | Max results (default: 20) | + +#### get(memoryId) / getAll(config) / update(memoryId, data) / delete(memoryId) / deleteAll(config) / history(memoryId) + +Same interface patterns. Note: OSS `update` takes a string for data, not an object. + +```typescript +await m.update('mem-id', 'new content'); +``` + +#### reset() + +Clear the entire vector store and history. + +```typescript +await m.reset(); +``` + +--- + +## Key Differences: Platform vs OSS + +| Aspect | Platform (`MemoryClient`) | OSS (`Memory`) | +|--------|--------------------------|----------------| +| **Import** | `import MemoryClient from 'mem0ai'` | `import { Memory } from 'mem0ai/oss'` | +| **Auth** | API key required (`MEM0_API_KEY`) | No API key -- config-based | +| **Execution** | API calls to `api.mem0.ai` | Local execution | +| **Infrastructure** | Fully managed | Self-managed vector DB, embedder, LLM | +| **Param style** | Top-level: `camelCase` (`userId`, `topK`), filter keys: `snake_case` (`user_id`) | Top-level: `camelCase` (`userId`, `topK`), filter keys: `snake_case` (`user_id`) | +| **Batch ops** | `batchUpdate`, `batchDelete` | Not available | +| **Webhooks** | Full CRUD | Not available | +| **Export** | `createMemoryExport` | Not available | +| **Feedback** | `feedback()` | Not available | +| **Project mgmt** | `getProject`, `updateProject` | Not available | +| **User listing** | `users()`, `deleteUser()` | Not available | +| **History** | Platform-managed | SQLite (configurable) | + +--- + +## v2 Compatibility + +If you're using SDK v2.x: + +**Naming Changes:** +- Top-level params now use camelCase: `topK`, `rerank` (not `top_k`) +- Filter keys use snake_case: `user_id`, `agent_id` +- OSS: `limit` renamed to `topK` + +**API Changes:** +```typescript +// v2 - top-level entity IDs, snake_case +await client.search("query", { user_id: "alice", top_k: 20 }); + +// v3 - filters object with snake_case keys, camelCase top-level params +await client.search("query", { filters: { user_id: "alice" }, topK: 20 }); +``` + +**Default Changes:** +| Param | v2 | v3 | +|-------|----|----| +| `topK` | 100 | 20 | +| `threshold` | none | 0.1 | +| `rerank` | true | false | + +**Removed:** +- `OutputFormat` and `API_VERSION` enums +- `organizationId`, `projectId` from constructor +- `enableGraph`, `asyncMode`, `outputFormat`, `immutable`, `expirationDate`, `filterMemories`, `batchSize`, `forceAddOnly`, `includes`, `excludes`, `keywordSearch` + +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/python.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/python.md new file mode 100644 index 000000000..0cf35a550 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/python.md @@ -0,0 +1,487 @@ +# Mem0 Python SDK Reference + +Complete reference for the `mem0ai` Python package. Covers both the Platform client (managed API) and the Open Source self-hosted variant. + +--- + +## Platform Client + +### Installation + +```bash +pip install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +### MemoryClient (Synchronous) + +```python +from mem0 import MemoryClient + +client = MemoryClient(api_key="m0-xxx") +``` + +**Constructor:** `MemoryClient(api_key=None)`. If `api_key` is not provided, reads from `MEM0_API_KEY` environment variable. Raises `ValueError` if no key found. + +- HTTP library: `httpx` +- Timeout: 300 seconds +- Base URL: `https://api.mem0.ai` + +### AsyncMemoryClient (Asynchronous) + +```python +from mem0 import AsyncMemoryClient + +client = AsyncMemoryClient(api_key="m0-xxx") + +# Or use as context manager +async with AsyncMemoryClient(api_key="m0-xxx") as client: + results = await client.search("query", filters={"user_id": "alice"}) +``` + +Same methods as `MemoryClient`, all `async`/`await`. Supports async context manager. + +--- + +### Memory Methods + +#### add(messages, **kwargs) + +Store new memories from messages. + +```python +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember that."} +] +client.add(messages, user_id="alice") +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `messages` | str \| dict \| list[dict] | required | Message content. Strings auto-convert to user messages | +| `user_id` | str | None | User identifier | +| `agent_id` | str | None | Agent identifier | +| `app_id` | str | None | Application identifier | +| `run_id` | str | None | Session/run identifier | +| `metadata` | dict | None | Custom key-value pairs | +| `infer` | bool | True | If False, store raw text without LLM inference | +| `custom_categories` | list | None | Override project categories | +| `custom_instructions` | str | None | Override extraction instructions | +| `timestamp` | int \| float \| str | None | Custom timestamp (Unix epoch or ISO 8601) | + +**Returns:** `dict` -- list of events: `[{"id": "...", "event": "ADD", "data": {"memory": "..."}}]` + +#### search(query, **kwargs) + +Search memories by semantic similarity. + +```python +results = client.search("dietary preferences", filters={"user_id": "alice"}) +for mem in results.get("results", []): + print(mem["memory"], mem["score"]) +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `query` | str | required | Natural language search query | +| `filters` | dict | None | Filter object with entity IDs and/or `AND`/`OR`/`NOT` conditions (e.g., `{"user_id": "alice"}`) | +| `top_k` | int | 10 | Number of results | +| `rerank` | bool | False | Enable deep semantic reranking (+150-200ms) | +| `threshold` | float | 0.1 | Minimum similarity score | +| `fields` | list | None | Specific fields to return | +| `categories` | list | None | Filter by category | + +**Returns:** `dict` -- `{"results": [{id, memory, user_id, categories, score, created_at, ...}]}` + +#### get(memory_id) + +Retrieve a single memory by ID. + +```python +memory = client.get(memory_id="ea925981-...") +``` + +**Returns:** `dict` -- full memory object + +#### get_all(**kwargs) + +Retrieve all memories with optional filtering. Requires at least one entity identifier. + +```python +memories = client.get_all(filters={"user_id": "alice"}) +# With compound filters +memories = client.get_all(filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "health"}}]}) +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `filters` | dict | None | Filter object with entity IDs and/or `AND`/`OR`/`NOT` conditions | +| `top_k` | int | None | Limit results | +| `page` | int | None | Page number | +| `page_size` | int | None | Results per page | + +**Returns:** `dict` -- `{"results": [...]}` + +#### update(memory_id, text=None, metadata=None, timestamp=None) + +Update a memory's content, metadata, or timestamp. At least one parameter required. + +```python +client.update("ea925981-...", text="Updated: vegan since 2024") +client.update("ea925981-...", metadata={"verified": True}) +``` + +**Returns:** `dict` -- updated memory + +#### delete(memory_id) + +Permanently delete a single memory. + +```python +client.delete("ea925981-...") +``` + +#### delete_all(**kwargs) + +Delete all memories matching filters. Irreversible. + +```python +client.delete_all(user_id="alice") +``` + +#### history(memory_id) + +Get the change history of a memory. + +```python +history = client.history("ea925981-...") +# Returns: [{previous_value, new_value, action, timestamps}] +``` + +--- + +### Batch Methods + +#### batch_update(memories) + +Update up to 1000 memories in a single request. + +```python +client.batch_update([ + {"memory_id": "uuid-1", "text": "Updated text"}, + {"memory_id": "uuid-2", "text": "Another update", "metadata": {"verified": True}}, +]) +``` + +#### batch_delete(memories) + +Delete up to 1000 memories in a single request. + +```python +client.batch_delete([ + {"memory_id": "uuid-1"}, + {"memory_id": "uuid-2"}, +]) +``` + +--- + +### User/Entity Management + +#### users() + +List all users, agents, and sessions that have memories. + +```python +users = client.users() +# Returns: {"results": [{"type": "user", "name": "alice"}, ...]} +``` + +#### delete_users(user_id=None, agent_id=None, app_id=None, run_id=None) + +Delete a specific entity and all its memories. + +```python +client.delete_users(user_id="alice") +``` + +#### reset() + +Delete ALL users, agents, sessions, and memories. Complete data reset. + +```python +client.reset() +``` + +--- + +### Export & Summary + +#### create_memory_export(schema, **kwargs) + +Create a structured export of memories. + +```python +import json + +schema = json.dumps({ + "type": "object", + "properties": { + "name": {"type": "string"}, + "preferences": {"type": "array", "items": {"type": "string"}}, + } +}) +export = client.create_memory_export(schema=schema, user_id="alice") +``` + +#### get_memory_export(**kwargs) + +Retrieve a previously created export. + +```python +result = client.get_memory_export(memory_export_id=export["id"]) +``` + +#### get_summary(filters=None) + +Get a summary of memories. + +```python +summary = client.get_summary(filters={"user_id": "alice"}) +``` + +--- + +### Feedback + +#### feedback(memory_id, feedback=None, feedback_reason=None) + +Provide quality feedback on a memory. + +```python +client.feedback( + memory_id="mem-123", + feedback="POSITIVE", # POSITIVE | NEGATIVE | VERY_NEGATIVE | None (clear) + feedback_reason="Accurately captured preference" +) +``` + +--- + +### Webhooks + +```python +# List +webhooks = client.get_webhooks(project_id="proj_123") + +# Create +webhook = client.create_webhook( + url="https://your-app.com/webhook", + name="Memory Logger", + project_id="proj_123", + event_types=["memory_add", "memory_update"] +) + +# Update +client.update_webhook(webhook_id=123, name="Updated", url="https://new-url.com") + +# Delete +client.delete_webhook(webhook_id=123) +``` + +--- + +### Project Management + +Access via `client.project.*`: + +```python +# Get project config +config = client.project.get(fields=["custom_categories", "custom_instructions"]) + +# Update project settings +client.project.update( + custom_instructions="Extract dietary preferences and health info", + custom_categories=[{"health": "Medical and dietary info"}], + multilingual=True, +) + +# Create/delete project +client.project.create(name="My Project", description="...") +client.project.delete() + +# Member management +members = client.project.get_members() +client.project.add_member(email="user@example.com", role="READER") # READER or OWNER +client.project.update_member(email="user@example.com", role="OWNER") +client.project.remove_member(email="user@example.com") +``` + +--- + +## Open Source / Self-Hosted + +### Installation + +```bash +pip install mem0ai +``` + +### Memory Class + +```python +from mem0 import Memory + +m = Memory() # Uses default config (OpenAI embedder + in-memory vector store) +``` + +**Import:** `from mem0 import Memory` (NOT `MemoryClient` -- that is the Platform client) + +### Configuration + +```python +config = { + "llm": { + "provider": "openai", # openai, groq, azure, ollama, lmstudio, google, anthropic, mistral + "config": { + "model": "gpt-5-mini", + "api_key": "sk-xxx", + } + }, + "embedder": { + "provider": "openai", # openai, ollama, azure, lmstudio, google, huggingface + "config": { + "model": "text-embedding-3-small", + "api_key": "sk-xxx", + } + }, + "vector_store": { + "provider": "qdrant", # faiss, qdrant, pgvector, redis, supabase, azure_ai_search, memory + "config": { + "collection_name": "my_memories", + "host": "localhost", + "port": 6333, + } + }, + "history_db_path": "history.db", # SQLite path for change history + "custom_instructions": "...", # Custom LLM prompt for extraction +} + +m = Memory.from_config(config) +``` + +### Context Manager + +```python +with Memory(config) as m: + m.add("I prefer dark mode", user_id="alice") + results = m.search("preferences", filters={"user_id": "alice"}) +# SQLite connections released automatically +``` + +### Methods + +All methods mirror the Platform client but run locally: + +#### add(messages, *, user_id, agent_id, run_id, metadata, infer=True) + +```python +m.add("I'm a vegetarian", user_id="alice") +m.add([ + {"role": "user", "content": "I like hiking"}, + {"role": "assistant", "content": "Great outdoor activity!"} +], user_id="alice") +``` + +At least one of `user_id`, `agent_id`, `run_id` required. + +**Returns:** `{"results": [...], "relations": [...]}` + +#### search(query, *, filters=None, top_k=20, threshold=0.1, rerank=False) + +```python +results = m.search("dietary preferences", filters={"user_id": "alice"}, top_k=5) +``` + +Entity IDs (`user_id`, `agent_id`, `run_id`) must be passed inside the `filters` dict. + +Supports filter operators: `eq`, `ne`, `in`, `nin`, `gt`, `gte`, `lt`, `lte`, `contains`, `not_contains`. + +#### get(memory_id) / get_all(**kwargs) / update(memory_id, data, metadata=None) / delete(memory_id) / delete_all(**kwargs) / history(memory_id) + +Same interface as Platform client. + +#### reset() + +Clear the entire vector store collection and history database. Recreates the vector store. + +```python +m.reset() +``` + +#### close() + +Release SQLite connections. Called automatically when using context manager. + +### AsyncMemory + +```python +from mem0 import AsyncMemory + +m = AsyncMemory(config) +await m.add("text", user_id="alice") +results = await m.search("query", filters={"user_id": "alice"}) +``` + +--- + +## Key Differences: Platform vs OSS + +| Aspect | Platform (`MemoryClient`) | OSS (`Memory`) | +|--------|--------------------------|----------------| +| **Import** | `from mem0 import MemoryClient` | `from mem0 import Memory` | +| **Auth** | API key required (`MEM0_API_KEY`) | No API key -- config-based | +| **Execution** | API calls to `api.mem0.ai` | Local execution | +| **Infrastructure** | Fully managed | Self-managed vector DB, embedder, LLM | +| **Entity filtering** | `filters={"user_id": "..."}` | `filters={"user_id": "..."}` | +| **Batch ops** | `batch_update`, `batch_delete` | Not available | +| **Webhooks** | Full CRUD | Not available | +| **Export** | `create_memory_export`, `get_memory_export` | Not available | +| **Feedback** | `feedback()` | Not available | +| **Project mgmt** | `client.project.*` | Not available | +| **User listing** | `users()`, `delete_users()` | Not available | +| **Custom prompts** | Via project settings | Direct config (`custom_instructions`) | +| **History** | Platform-managed | SQLite (configurable) | +| **Async** | `AsyncMemoryClient` | `AsyncMemory` | + +--- + +## v2 Compatibility + +If you're using SDK v2.x or the v2 API: + +**API Changes:** +- **Entity IDs in search/get_all:** Pass `user_id`, `agent_id` as top-level kwargs instead of inside `filters` + ```python + # v2 + results = client.search("query", user_id="alice") + # v3 + results = client.search("query", filters={"user_id": "alice"}) + ``` +- **add() returns:** v2 returns ADD, UPDATE, DELETE events; v3 returns ADD only + +**Default Changes:** +| Param | v2 | v3 | +|-------|----|----| +| `top_k` | 100 | 20 | +| `threshold` | None | 0.1 | +| `rerank` | True | False | + +**Removed Parameters:** +- Constructor: `org_id`, `project_id` +- add(): `async_mode`, `output_format`, `enable_graph`, `immutable`, `expiration_date`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search` +- search()/get_all(): `enable_graph` +- Config: `enable_graph`, `graph_store`, `custom_fact_extraction_prompt` (renamed to `custom_instructions`) + +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for full details. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/api-reference.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/api-reference.md new file mode 100644 index 000000000..4ab8548ca --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/api-reference.md @@ -0,0 +1,150 @@ +# Mem0 Platform API Reference + +REST API endpoints for the Mem0 Platform. Base URL: `https://api.mem0.ai` + +All endpoints require: `Authorization: Token ` + +## Endpoints + +| Operation | Method | URL | +|-----------|--------|-----| +| Add Memories | `POST` | `/v3/memories/add/` | +| Search Memories | `POST` | `/v3/memories/search/` | +| Get All Memories | `POST` | `/v3/memories/` | +| Get Single Memory | `GET` | `/v1/memories/{memory_id}/` | +| Update Memory | `PUT` | `/v1/memories/{memory_id}/` | +| Delete Memory | `DELETE` | `/v1/memories/{memory_id}/` | +| Delete All Memories | `DELETE` | `/v1/memories/?user_id=X&app_id=Y` | +| Get Event Status | `GET` | `/v1/event/{event_id}/` | + +## Memory Object Structure + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string (UUID) | Unique memory identifier | +| `memory` | string | Text content of the memory | +| `user_id` | string | Associated user | +| `agent_id` | string (nullable) | Agent identifier | +| `app_id` | string (nullable) | Application identifier | +| `run_id` | string (nullable) | Run/session identifier | +| `metadata` | object | Custom key-value pairs | +| `categories` | array of strings | Auto-assigned category tags | +| `hash` | string | Content hash | +| `created_at` | datetime | Creation timestamp | +| `updated_at` | datetime | Last modification timestamp | + +Search results additionally include `score` (relevance metric). + +## Scoping Identifiers + +Memories can be scoped to different levels: + +| Scope | Parameter | Use Case | +|-------|-----------|----------| +| User | `user_id` | Per-user memory isolation | +| Agent | `agent_id` | Per-agent memory partitioning | +| Application | `app_id` | Cross-agent app-level memory | +| Run/Session | `run_id` | Session-scoped temporary memory | + +**Critical:** Combining `user_id` and `agent_id` in a single AND filter yields empty results. Entities are stored separately. Use `OR` logic or separate queries. + +## Processing Model + +- Memories are processed **asynchronously** (v3 default) +- Add responses return queued `ADD` events only (v3 is ADD-only, no UPDATE/DELETE) +- Poll status via `GET /v1/event/{event_id}/` + +## Filter System + +Filters use nested JSON with a logical operator at the root: + +```json +{ + "AND": [ + {"user_id": "alice"}, + {"categories": {"contains": "finance"}}, + {"created_at": {"gte": "2024-01-01"}} + ] +} +``` + +Root must be `AND`, `OR`, or `NOT`. Simple shorthand `{"user_id": "alice"}` also works. + +### Supported Operators + +| Operator | Description | +|----------|-------------| +| `eq` | Equal to (default) | +| `ne` | Not equal to | +| `in` | Matches any value in array | +| `gt`, `gte` | Greater than / greater than or equal | +| `lt`, `lte` | Less than / less than or equal | +| `contains` | Case-sensitive containment | +| `icontains` | Case-insensitive containment | +| `*` | Wildcard -- matches any non-null value | + +### Filterable Fields + +| Field | Valid Operators | +|-------|-----------------| +| `user_id`, `agent_id`, `app_id`, `run_id` | `eq`, `ne`, `in`, `*` | +| `created_at`, `updated_at`, `timestamp` | `gt`, `gte`, `lt`, `lte`, `eq`, `ne` | +| `categories` | `eq`, `ne`, `in`, `contains` | +| `metadata` | `eq`, `ne`, `contains` (top-level keys only) | +| `keywords` | `contains`, `icontains` | +| `memory_ids` | `in` | + +### Filter Constraints + +1. **Entity scope partitioning:** `user_id` AND `agent_id` in one `AND` block yields empty results. +2. **Metadata limitations:** Only top-level keys. Only `eq`, `contains`, `ne`. No `in` or `gt`. +3. **Operator syntax:** Use `gte`, `lt`, `ne`. SQL-style (`>=`, `!=`) rejected. +4. **Entity filter required for get-all:** At least one of `user_id`, `agent_id`, `app_id`, or `run_id`. +5. **Wildcard excludes null:** `*` matches only non-null values. +6. **Date format:** ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). Timezone-naive defaults to UTC. + +## Response Formats + +### Add Response (v3) + +```json +{ + "message": "Memory processing has been queued for background execution", + "status": "PENDING", + "event_id": "evt-uuid" +} +``` + +v3 is ADD-only. No UPDATE or DELETE events. + +### Search Response + +```json +{ + "results": [ + { + "id": "ea925981-...", + "memory": "Is a vegetarian and allergic to nuts.", + "user_id": "user123", + "categories": ["food", "health"], + "score": 0.89, + "created_at": "2024-07-26T10:29:36.630547-07:00" + } + ] +} +``` + +In v3, `score` is a combined multi-signal relevance score. + +### Get All Response (v3) + +```json +{ + "count": 123, + "next": "https://api.mem0.ai/v3/memories/?page=2&page_size=50", + "previous": null, + "results": [...] +} +``` + +v3 returns paginated envelope. Use `page` and `page_size` query params. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/architecture.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/architecture.md new file mode 100644 index 000000000..4a04c820b --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/architecture.md @@ -0,0 +1,330 @@ +# Mem0 Platform Architecture + +How Mem0 processes, stores, and retrieves memories under the hood. + +## Table of Contents + +- [Core Concept](#core-concept) +- [Memory Processing Pipeline](#memory-processing-pipeline) +- [Retrieval Pipeline](#retrieval-pipeline) +- [Memory Lifecycle](#memory-lifecycle) +- [Memory Object Structure](#memory-object-structure) +- [Scoping & Multi-Tenancy](#scoping--multi-tenancy) +- [Memory Layers](#memory-layers) +- [Performance Characteristics](#performance-characteristics) + +--- + +## Core Concept + +Mem0 is a managed memory layer that sits between your AI application and users. Every integration follows the same 3-step loop: + +``` +User Input → Retrieve relevant memories → Enrich LLM prompt → Generate response → Store new memories +``` + +Mem0 handles the complexity of extraction, deduplication, conflict resolution, and semantic retrieval so your application only needs to call `search()` and `add()`. + +**Storage architecture:** +- **Vector store**: Embeddings for semantic similarity search +- **Entity store**: Automatic entity linking for relationship-aware retrieval + +--- + +## Memory Processing Pipeline + +### What happens when you call `client.add()` + +``` +Messages In + │ + ▼ +┌─────────────────────┐ +│ 1. EXTRACTION │ Single LLM call extracts all distinct new facts +│ (infer=True) │ If infer=False, stores raw text as-is +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 2. DEDUPLICATION │ Hash-based dedup (MD5 prevents exact duplicates) +│ │ No UPDATE/DELETE - v3 is ADD-only +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 3. STORAGE │ Batch embed → vector store +│ │ Entity extraction → entity store +└─────────┬───────────┘ + │ + ▼ + Memory Object +``` + +### Processing (v3) + +v3 processes memories asynchronously by default: +- API returns immediately: `{"status": "PENDING", "event_id": "evt-..."}` +- Poll status via `GET /v1/event/{event_id}/` +- Use webhooks for completion notifications + +### Extraction modes + +**Inferred (`infer=True`, default):** +- LLM extracts structured facts from conversation +- Conflict resolution deduplicates and resolves contradictions +- Best for: natural conversation → memory + +**Raw (`infer=False`):** +- Stores text exactly as provided, no LLM processing +- Skips conflict resolution — same fact can be stored twice +- Only `user` role messages are stored; `assistant` messages ignored +- Best for: bulk imports, pre-structured data, migrations + +**Warning:** Don't mix `infer=True` and `infer=False` for the same data — the same fact will be stored twice. + +--- + +## Retrieval Pipeline (v3) + +### What happens when you call `client.search()` + +``` +Query In + │ + ▼ +┌─────────────────────┐ +│ 1. PREPROCESSING │ Lemmatize keywords, extract entities +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 2. PARALLEL SCORING │ Semantic search (vector similarity) +│ │ BM25 keyword search (term matching) +│ │ Entity matching (entity graph boost) +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 3. SCORE FUSION │ Combine signals into single score +│ │ Optional: rerank=True for deep reordering +└─────────┬───────────┘ + │ + ▼ + Results (combined score per memory) +``` + +### v3 Search Defaults + +| Parameter | Default | Notes | +|-----------|---------|-------| +| `top_k` | 20 | Was 100 in v2 | +| `threshold` | 0.1 | Was None in v2 | +| `rerank` | False | Was True in v2 | + +### Implicit null scoping + +When you search with `filters={"user_id": "alice"}` only, Mem0 returns memories where `agent_id`, `app_id`, and `run_id` are all null. This prevents cross-scope leakage by default. + +To include memories with non-null fields, use explicit filters: +```python +# Gets memories for alice regardless of agent/app/run +filters={"OR": [{"user_id": "alice"}]} +``` + +--- + +## Memory Lifecycle (v3) + +v3 uses ADD-only extraction. Memories accumulate over time rather than being consolidated. + +### Creation +- `client.add(messages, user_id="...")` +- Single-pass extraction → deduplication → storage +- Returns `{"event_id": "...", "status": "PENDING"}` + +### Updates +- `client.update(memory_id, text="...")` replaces text +- Batch: `client.batch_update([...])` + +### Deletion +- Single: `client.delete(memory_id)` +- Batch: `client.batch_delete([...])` +- Bulk: `client.delete_all(filters={"user_id": "alice"})` + +--- + +## Memory Object Structure + +```json +{ + "id": "uuid-string", + "memory": "Extracted memory text", + "user_id": "user-identifier", + "agent_id": null, + "app_id": null, + "run_id": null, + "metadata": { "source": "chat", "priority": "high" }, + "categories": ["health", "preferences"], + "created_at": "2025-03-12T12:34:56Z", + "updated_at": "2025-03-12T12:34:56Z", + "structured_attributes": { + "day": 12, "month": 3, "year": 2025, + "hour": 12, "minute": 34, + "day_of_week": "wednesday", + "is_weekend": false, + "quarter": 1, "week_of_year": 11 + }, + "score": 0.85 +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `id` | UUID | Unique identifier, used for update/delete | +| `memory` | string | Extracted or stored text content | +| `user_id` | string | Primary entity scope | +| `agent_id` | string | Agent scope | +| `app_id` | string | Application scope | +| `run_id` | string | Session/run scope | +| `metadata` | object | Custom key-value pairs for filtering | +| `categories` | array | Auto-assigned or custom category tags | +| `created_at` | datetime | Creation timestamp | +| `updated_at` | datetime | Last modification timestamp | +| `structured_attributes` | object | Temporal breakdown for time-based queries | +| `score` | float | Semantic similarity (search results only, 0-1) | + +--- + +## Scoping & Multi-Tenancy + +Mem0 separates memories across four dimensions to prevent data mixing: + +| Dimension | Field | Purpose | Example | +|-----------|-------|---------|---------| +| User | `user_id` | Persistent persona or account | `"customer_6412"` | +| Agent | `agent_id` | Distinct agent or tool | `"meal_planner"` | +| App | `app_id` | Product surface or deployment | `"ios_retail_app"` | +| Session | `run_id` | Short-lived flow or thread | `"ticket-9241"` | + +### Storage model + +Each entity combination creates separate records. A memory with `user_id="alice"` is stored separately from one with `user_id="alice"` + `agent_id="bot"`. + +### Critical: cross-entity queries + +```python +# This returns NOTHING — user and agent memories are stored separately +filters={"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]} + +# Use OR to query multiple scopes +filters={"OR": [{"user_id": "alice"}, {"agent_id": "bot"}]} + +# Use wildcard to include any non-null value +filters={"AND": [{"user_id": "*"}]} # All users (excludes null) +``` + +### Recommended scoping patterns + +```python +# User-level: persistent preferences +client.add(messages, user_id="alice") + +# Session-level: temporary context +client.add(messages, user_id="alice", run_id="session_123") +# Clean up when done: client.delete_all(run_id="session_123") + +# Agent-level: agent-specific knowledge +client.add(messages, agent_id="support_bot", app_id="helpdesk") + +# Multi-tenant: full isolation +client.add(messages, user_id="alice", agent_id="bot", app_id="acme_corp", run_id="ticket_42") +``` + +--- + +## Memory Layers + +Mem0 supports three layers of memory, from shortest to longest lived: + +### Conversation memory +- In-flight messages within a single turn +- Tool calls, chain-of-thought reasoning +- **Lifetime:** Single response — lost after turn finishes +- **Managed by:** Your application, not Mem0 + +### Session memory +- Short-lived facts for current task or channel +- Multi-step flows (onboarding, debugging, support tickets) +- **Lifetime:** Minutes to hours +- **Managed by:** Mem0 via `run_id` parameter +- Clean up with `client.delete_all(run_id="session_id")` + +### User memory +- Long-lived knowledge tied to a person or account +- Personal preferences, account state, compliance details +- **Lifetime:** Weeks to forever +- **Managed by:** Mem0 via `user_id` parameter +- Persists across all sessions and interactions + +### How layering works in practice + +```python +def chat(user_input: str, user_id: str, session_id: str) -> str: + # 1. Retrieve user memories (long-term preferences) + user_mems = mem0.search(user_input, filters={"user_id": user_id}) + + # 2. Retrieve session memories (current task context) + session_mems = mem0.search(user_input, filters={ + "AND": [{"user_id": user_id}, {"run_id": session_id}] + }) + + # 3. Combine both layers for LLM context + context = format_memories(user_mems) + format_memories(session_mems) + + # 4. Generate response + response = llm.generate(context=context, input=user_input) + + # 5. Store in session scope (temporary) + user scope (persistent) + messages = [{"role": "user", "content": user_input}, {"role": "assistant", "content": response}] + mem0.add(messages, user_id=user_id, run_id=session_id) + + return response +``` + +--- + +## Performance Characteristics + +### Latency + +| Operation | Typical Latency | +|-----------|----------------| +| Hybrid search (v3 default) | ~100-150ms | +| + reranking | +150-200ms | +| Add (async) | < 50ms response | + +### Processing + +- **Async (default):** Returns immediately, processes in background +- **Batch operations:** Up to 1000 memories per batch_update/batch_delete +- **Webhooks:** Real-time notifications when async processing completes + +### Scoping strategy for performance + +- Use `user_id` for all user-facing queries (most common, fastest) +- Add `run_id` for session isolation (narrows search space) +- Avoid wildcard `"*"` filters on large datasets (scans all non-null records) +- Use `top_k` to limit result count when you only need a few memories + +--- + +## Comparison with Alternatives + +| Approach | Pros | Cons | +|----------|------|------| +| **Raw vector DB** | Fast, full control | No extraction, no dedup, no conflict resolution | +| **In-memory chat history** | Zero latency | Lost on restart, no cross-session, grows unbounded | +| **RAG over documents** | Good for static knowledge | No personalization, no memory updates | +| **Mem0 Platform** | Managed extraction + dedup + graph + scoping | External dependency, async processing delay | + +Mem0 combines the best of vector search (semantic retrieval) with automatic extraction (LLM-powered), conflict resolution (deduplication), and structured scoping (multi-tenancy) — in a single managed API. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/features.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/features.md new file mode 100644 index 000000000..66875ee75 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/features.md @@ -0,0 +1,425 @@ +# Platform Features -- Mem0 Platform + +Additional platform capabilities beyond core CRUD operations. + +## Table of Contents + +- [Advanced Retrieval](#advanced-retrieval) +- [Entity Linking](#entity-linking) +- [Custom Categories](#custom-categories) +- [Custom Instructions](#custom-instructions) +- [Criteria Retrieval](#criteria-retrieval) +- [Feedback Mechanism](#feedback-mechanism) +- [Memory Export](#memory-export) +- [Group Chat](#group-chat) +- [MCP Integration](#mcp-integration) +- [Webhooks](#webhooks) +- [Multimodal Support](#multimodal-support) + +## Advanced Retrieval + +### Hybrid Search (v3 Default) + +v3 uses multi-signal hybrid search combining: +- **Semantic search** (vector similarity) +- **BM25 keyword search** (normalized term matching) +- **Entity matching** (entity graph boost) + +This is automatic — no configuration needed. + +### Reranking (`rerank=True`) + +Deep semantic reordering of results — most relevant first. + +- Latency: +150-200ms +- Default: `False` (was `True` in v2) +- Best for: user-facing results, top-N precision + +**Python:** +```python +results = client.search(query, filters={"user_id": "user123"}, rerank=True) +``` + +**TypeScript:** +```typescript +const results = await client.search(query, { + filters: { user_id: 'user123' }, + rerank: true, +}); +``` + +--- + +## Entity Linking + +v3 replaces graph memory with built-in entity linking. Entities (proper nouns, quoted text, compound noun phrases) are automatically extracted and linked across memories. + +### How It Works + +1. **Extraction**: During `add()`, entities are automatically extracted from memory text +2. **Storage**: Entities are stored in a parallel collection (`{collection}_entities`) +3. **Retrieval**: During `search()`, query entities are matched and used to boost relevant memories + +Entity linking is automatic — no configuration required. The boost is folded into the combined `score` on each result. + +### v2 Migration Note + +If you were using `enable_graph=True` in v2: +- Remove `enable_graph` from all API calls +- Remove `graph_store` from OSS configuration +- Entity relationships are now consumed through retrieval ranking, not exposed as a separate `relations` array + +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details. + +--- + +## Custom Categories + +Replace Mem0's default 15 labels with domain-specific categories. The system automatically tags memories to the closest matching category. + +### Default Categories (15) + +`personal_details`, `family`, `professional_details`, `sports`, `travel`, `food`, `music`, `health`, `technology`, `hobbies`, `fashion`, `entertainment`, `milestones`, `user_preferences`, `misc` + +### Configuration + +**Set project-level categories:** +```python +new_categories = [ + {"lifestyle_management": "Tracks daily routines, habits, wellness activities"}, + {"seeking_structure": "Documents goals around creating routines and systems"}, + {"personal_information": "Basic information about the user"} +] +client.project.update(custom_categories=new_categories) +``` + +```javascript +await client.updateProject({ customCategories: newCategories }); +``` + +**Retrieve active categories:** +```python +categories = client.project.get(fields=["custom_categories"]) +``` + +### Key Constraint + +Per-request overrides (`custom_categories=...` on `client.add`) are **not supported** on the managed API. Only project-level configuration works. Workaround: store ad-hoc labels in `metadata` field. + +--- + +## Custom Instructions + +Natural language filters that control what information Mem0 extracts when creating memories. + +### Set Instructions + +```python +client.project.update(custom_instructions="Your guidelines here...") +``` + +```javascript +await client.updateProject({ customInstructions: "Your guidelines here..." }); +``` + +### Template Structure + +1. **Task Description** -- brief extraction overview +2. **Information Categories** -- numbered sections with specific details to capture +3. **Processing Guidelines** -- quality and handling rules +4. **Exclusion List** -- sensitive/irrelevant data to filter out + +### Domain Examples + +**E-commerce:** Capture product issues, preferences, service experience; exclude payment data. + +**Education:** Extract learning progress, student preferences, performance patterns; exclude specific grades. + +**Finance:** Track financial goals, life events, investment interests; exclude account numbers and SSNs. + +### Best Practices + +- Start simply, test with sample messages, iterate based on results +- Avoid overly lengthy instructions +- Be specific about what to include AND exclude + +--- + +## Criteria Retrieval + +Custom attribute-based memory ranking using LLM-evaluated criteria with weights. Goes beyond semantic similarity to prioritize memories based on domain-specific signals. + +### Configuration + +```python +# Define criteria at project level +retrieval_criteria = [ + {"name": "joy", "description": "Positive emotions like happiness and excitement", "weight": 3}, + {"name": "curiosity", "description": "Inquisitiveness and desire to learn", "weight": 2}, + {"name": "urgency", "description": "Time-sensitive or high-priority items", "weight": 4}, +] +client.project.update(retrieval_criteria=retrieval_criteria) +``` + +```typescript +await client.updateProject({ + retrievalCriteria: [ + { name: 'joy', description: 'Positive emotions', weight: 3 }, + { name: 'urgency', description: 'Time-sensitive items', weight: 4 }, + ], +}); +``` + +### Usage + +Once configured, `client.search()` automatically applies criteria ranking: + +```python +# Criteria-weighted results returned automatically +results = client.search("Why am I feeling happy?", filters={"user_id": "alice"}) +``` + +**Best for:** Wellness assistants, tutoring platforms, productivity tools — any app needing intent-aware retrieval. + +--- + +## Feedback Mechanism + +Provide feedback on extracted memories to improve system quality over time. + +### Feedback Types + +| Type | Meaning | +|------|---------| +| `POSITIVE` | Memory is useful and accurate | +| `NEGATIVE` | Memory is not useful | +| `VERY_NEGATIVE` | Memory is harmful or completely wrong | +| `None` | Clear existing feedback | + +### Usage + +**Python:** +```python +client.feedback( + memory_id="mem-123", + feedback="POSITIVE", + feedback_reason="Accurately captured dietary preference" +) + +# Bulk feedback +for item in feedback_data: + client.feedback(**item) +``` + +**TypeScript:** +```typescript +await client.feedback('mem-123', { + feedback: 'POSITIVE', + feedbackReason: 'Accurately captured dietary preference', +}); +``` + +--- + +## Memory Export + +Create structured exports of memories using customizable schemas with filters. + +### Usage + +```python +import json + +# Define export schema +schema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "preferences": {"type": "array", "items": {"type": "string"}}, + "health_info": {"type": "string"}, + } +} + +# Create export +response = client.create_memory_export( + schema=json.dumps(schema), + filters={"user_id": "alice"}, + export_instructions="Create comprehensive profile based on all memories" +) + +# Retrieve export (may take a moment to process) +result = client.get_memory_export(memory_export_id=response["id"]) +``` + +**Best for:** Data analytics, user profile generation, compliance audits, CRM sync. + +--- + +## Group Chat + +Process multi-participant conversations and automatically attribute memories to individual speakers. + +### Usage + +```python +messages = [ + {"role": "user", "name": "Alice", "content": "I think we should use React for the frontend"}, + {"role": "user", "name": "Bob", "content": "I prefer Vue.js, it's simpler for our use case"}, + {"role": "assistant", "content": "Both are great choices. Let me note your preferences."}, +] + +# Mem0 automatically attributes memories to each speaker +response = client.add(messages, run_id="team_meeting_1") + +# Retrieve Alice's memories from that session +alice_mems = client.get_all( + filters={"AND": [{"user_id": "alice"}, {"run_id": "team_meeting_1"}]} +) +``` + +Use the `name` field in messages to identify speakers. Mem0 maps names to entity scopes automatically. + +--- + +## MCP Integration + +Model Context Protocol integration enables AI clients (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode) to manage Mem0 memory autonomously. + +### Setup + +Add Mem0 MCP to your clients with a single command: + +```bash +npx mcp-add \ + --name mem0-mcp \ + --type http \ + --url "https://mcp.mem0.ai/mcp" \ + --clients "claude,claude code,cursor,windsurf,vscode,opencode" +``` + +### Available MCP Tools + +The MCP server exposes 9 memory tools that AI agents can use autonomously: +- Add, search, get, update, delete memories +- Get history, list users, delete users +- Search Mem0 documentation + +### How It Works + +1. Add Mem0 MCP to your AI client using the setup command above +2. The agent autonomously decides when to store/retrieve memories +3. No manual API calls needed — the agent manages memory as part of its reasoning + +**Best for:** Universal AI client integration — one protocol works everywhere. + +--- + +## Webhooks + +Real-time event notifications for memory operations. + +### Supported Events + +| Event | Trigger | +|-------|---------| +| `memory_add` | Memory created | +| `memory_update` | Memory modified | +| `memory_delete` | Memory removed | +| `memory_categorize` | Memory tagged | + +### Create Webhook + +Note: `project_id` here refers to the Mem0 dashboard project scope for webhooks — not the deprecated client init parameter. + +```python +webhook = client.create_webhook( + url="https://your-app.com/webhook", + name="Memory Logger", + project_id="proj_123", + event_types=["memory_add", "memory_categorize"] +) +``` + +### Manage Webhooks + +```python +# Retrieve +webhooks = client.get_webhooks(project_id="proj_123") + +# Update +client.update_webhook( + name="Updated Logger", + url="https://your-app.com/new-webhook", + event_types=["memory_update", "memory_add"], + webhook_id="wh_123" +) + +# Delete +client.delete_webhook(webhook_id="wh_123") +``` + +### Payload Structure + +Memory events contain: ID, data object with memory content, event type (`ADD`/`UPDATE`/`DELETE`). +Categorization events contain: memory ID, event type (`CATEGORIZE`), assigned category labels. + +--- + +## Multimodal Support + +Mem0 can process images and documents alongside text. + +### Supported Media Types + +- Images: JPG, PNG +- Documents: MDX, TXT, PDF + +### Image via URL + +```python +image_message = { + "role": "user", + "content": { + "type": "image_url", + "image_url": {"url": "https://example.com/image.jpg"} + } +} +client.add([image_message], user_id="alice") +``` + +### Image via Base64 + +```python +import base64 +with open("photo.jpg", "rb") as f: + base64_image = base64.b64encode(f.read()).decode("utf-8") + +image_message = { + "role": "user", + "content": { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"} + } +} +client.add([image_message], user_id="alice") +``` + +### Document (MDX/TXT) + +```python +doc_message = { + "role": "user", + "content": {"type": "mdx_url", "mdx_url": {"url": document_url}} +} +client.add([doc_message], user_id="alice") +``` + +### PDF Document + +```python +pdf_message = { + "role": "user", + "content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}} +} +client.add([pdf_message], user_id="alice") +``` diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/integration-patterns.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/integration-patterns.md new file mode 100644 index 000000000..71cfa981c --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/integration-patterns.md @@ -0,0 +1,395 @@ +# Mem0 Integration Patterns + +Working code examples for integrating Mem0 Platform with popular AI frameworks. +All examples use `MemoryClient` (Platform API key). + +Code examples are sourced from official Mem0 integration docs at docs.mem0.ai, simplified for quick reference. + +--- + +## Common Pattern + +Every integration follows the same 3-step loop: + +1. **Retrieve** -- search relevant memories before generating a response +2. **Generate** -- include memories as context in the LLM prompt +3. **Store** -- save the interaction back to Mem0 for future use + +--- + +## LangChain + +Source: [docs.mem0.ai/integrations/langchain](https://docs.mem0.ai/integrations/langchain) + +```python +from langchain_openai import ChatOpenAI +from langchain_core.messages import SystemMessage, HumanMessage +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from mem0 import MemoryClient + +llm = ChatOpenAI(model="gpt-5-mini") +mem0 = MemoryClient() + +prompt = ChatPromptTemplate.from_messages([ + SystemMessage(content="You are a helpful travel agent AI. Use the provided context to personalize your responses."), + MessagesPlaceholder(variable_name="context"), + HumanMessage(content="{input}") +]) + +def retrieve_context(query: str, user_id: str): + """Retrieve relevant memories from Mem0""" + memories = mem0.search(query, filters={"user_id": user_id}) + memory_list = memories['results'] + serialized = ' '.join([m["memory"] for m in memory_list]) + return [ + {"role": "system", "content": f"Relevant information: {serialized}"}, + {"role": "user", "content": query} + ] + +def chat_turn(user_input: str, user_id: str) -> str: + # 1. Retrieve + context = retrieve_context(user_input, user_id) + # 2. Generate + chain = prompt | llm + response = chain.invoke({"context": context, "input": user_input}) + # 3. Store + mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": response.content}], + user_id=user_id + ) + return response.content +``` + +--- + +## CrewAI + +Source: [docs.mem0.ai/integrations/crewai](https://docs.mem0.ai/integrations/crewai) + +CrewAI has native Mem0 integration via `memory_config`: + +```python +from crewai import Agent, Task, Crew, Process +from mem0 import MemoryClient + +client = MemoryClient() + +# Store user preferences first +messages = [ + {"role": "user", "content": "I am more of a beach person than a mountain person."}, + {"role": "assistant", "content": "Noted! I'll recommend beach destinations."}, + {"role": "user", "content": "I like Airbnb more than hotels."}, +] +client.add(messages, user_id="crew_user_1") + +# Create agent +travel_agent = Agent( + role="Personalized Travel Planner", + goal="Plan personalized travel itineraries", + backstory="You are a seasoned travel planner.", + memory=True, +) + +# Create task +task = Task( + description="Find places to live, eat, and visit in San Francisco.", + expected_output="A detailed list of places to live, eat, and visit.", + agent=travel_agent, +) + +# Setup crew with Mem0 memory +crew = Crew( + agents=[travel_agent], + tasks=[task], + process=Process.sequential, + memory=True, + memory_config={ + "provider": "mem0", + "config": {"user_id": "crew_user_1"}, + } +) + +result = crew.kickoff() +``` + +--- + +## Vercel AI SDK + +> **Dedicated skill available.** For comprehensive Vercel AI SDK documentation, see the [mem0-vercel-ai-sdk skill](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk). + +Install: `npm install @mem0/vercel-ai-provider` + +Quick example (wrapped model with automatic memory): + +```typescript +import { generateText } from "ai"; +import { createMem0 } from "@mem0/vercel-ai-provider"; + +const mem0 = createMem0(); +const { text } = await generateText({ + model: mem0("gpt-5-mini", { user_id: "borat" }), + prompt: "Suggest me a good car to buy!", +}); +``` + +Supported providers: `openai`, `anthropic`, `google`, `groq`, `cohere` + +--- + +## OpenAI Agents SDK + +Source: [docs.mem0.ai/integrations/openai-agents-sdk](https://docs.mem0.ai/integrations/openai-agents-sdk) + +```python +from agents import Agent, Runner, function_tool +from mem0 import MemoryClient + +mem0 = MemoryClient() + +@function_tool +def search_memory(query: str, user_id: str) -> str: + """Search through past conversations and memories""" + memories = mem0.search(query, filters={"user_id": user_id}, top_k=3) + if memories and memories.get('results'): + return "\n".join([f"- {mem['memory']}" for mem in memories['results']]) + return "No relevant memories found." + +@function_tool +def save_memory(content: str, user_id: str) -> str: + """Save important information to memory""" + mem0.add([{"role": "user", "content": content}], user_id=user_id) + return "Information saved to memory." + +agent = Agent( + name="Personal Assistant", + instructions="""You are a helpful personal assistant with memory capabilities. + Use search_memory to recall past conversations. + Use save_memory to store important information.""", + tools=[search_memory, save_memory], + model="gpt-5-mini" +) + +result = Runner.run_sync(agent, "I love Italian food and I'm planning a trip to Rome next month") +print(result.final_output) +``` + +### Multi-Agent with Handoffs + +```python +from agents import Agent, Runner, function_tool + +travel_agent = Agent( + name="Travel Planner", + instructions="You are a travel planning specialist. Use search_memory and save_memory tools.", + tools=[search_memory, save_memory], + model="gpt-5-mini" +) + +health_agent = Agent( + name="Health Advisor", + instructions="You are a health and wellness advisor. Use search_memory and save_memory tools.", + tools=[search_memory, save_memory], + model="gpt-5-mini" +) + +triage_agent = Agent( + name="Personal Assistant", + instructions="""Route travel questions to Travel Planner, health questions to Health Advisor.""", + handoffs=[travel_agent, health_agent], + model="gpt-5-mini" +) + +result = Runner.run_sync(triage_agent, "Plan a healthy meal for my Italy trip") +``` + +--- + +## Pipecat (Voice / Real-Time) + +Source: [docs.mem0.ai/integrations/pipecat](https://docs.mem0.ai/integrations/pipecat) + +```python +from pipecat.services.mem0 import Mem0MemoryService + +memory = Mem0MemoryService( + api_key=os.getenv("MEM0_API_KEY"), + user_id="alice", + agent_id="voice_bot", + params={ + "search_limit": 10, + "search_threshold": 0.1, + "system_prompt": "Here are your past memories:", + "add_as_system_message": True, + } +) + +# Use in pipeline +pipeline = Pipeline([ + transport.input(), + stt, + user_context, + memory, # Memory enhances context automatically + llm, + transport.output(), + assistant_context +]) +``` + + + +--- + +## LangGraph + +Source: [docs.mem0.ai/integrations/langgraph](https://docs.mem0.ai/integrations/langgraph) + +State-based agent workflows with memory persistence. Best for complex conversation flows with branching logic. + +```python +from typing import Annotated, TypedDict, List +from langgraph.graph import StateGraph, START +from langgraph.graph.message import add_messages +from langchain_openai import ChatOpenAI +from mem0 import MemoryClient +from langchain_core.messages import SystemMessage, HumanMessage, AIMessage + +llm = ChatOpenAI(model="gpt-5-mini") +mem0 = MemoryClient() + +class State(TypedDict): + messages: Annotated[List[HumanMessage | AIMessage], add_messages] + mem0_user_id: str + +def chatbot(state: State): + messages = state["messages"] + user_id = state["mem0_user_id"] + + # Retrieve relevant memories + memories = mem0.search(messages[-1].content, filters={"user_id": user_id}) + context = "Relevant context:\n" + for memory in memories["results"]: + context += f"- {memory['memory']}\n" + + system_message = SystemMessage(content=f"""You are a helpful support assistant. +{context}""") + + response = llm.invoke([system_message] + messages) + + # Store the interaction + mem0.add( + [{"role": "user", "content": messages[-1].content}, + {"role": "assistant", "content": response.content}], + user_id=user_id + ) + return {"messages": [response]} + +graph = StateGraph(State) +graph.add_node("chatbot", chatbot) +graph.add_edge(START, "chatbot") +app = graph.compile() + +# Usage +result = app.invoke({ + "messages": [HumanMessage(content="I need help with my order")], + "mem0_user_id": "customer_123" +}) +``` + +--- + +## LlamaIndex + +Source: [docs.mem0.ai/integrations/llama-index](https://docs.mem0.ai/integrations/llama-index) + +Install: `pip install llama-index-core llama-index-memory-mem0` + +LlamaIndex has native Mem0 support via `Mem0Memory`. Works with ReAct and FunctionCalling agents. + +```python +from llama_index.memory.mem0 import Mem0Memory + +context = {"user_id": "alice", "agent_id": "llama_agent_1"} +memory = Mem0Memory.from_client( + context=context, + search_msg_limit=4, # messages from chat history used for retrieval (default: 5) +) + +# Use with LlamaIndex agent +from llama_index.core.agent import FunctionCallingAgent +from llama_index.llms.openai import OpenAI + +llm = OpenAI(model="gpt-5-mini") +agent = FunctionCallingAgent.from_tools( + tools=[], + llm=llm, + memory=memory, + verbose=True, +) + +response = agent.chat("I prefer vegetarian restaurants") +# Memory automatically stores and retrieves context +response = agent.chat("What kind of food do I like?") +# Agent retrieves the vegetarian preference from Mem0 +``` + +--- + +## AutoGen + +Source: [docs.mem0.ai/integrations/autogen](https://docs.mem0.ai/integrations/autogen) + +Install: `pip install autogen mem0ai` + +Multi-agent conversational systems with memory persistence. + +```python +from autogen import ConversableAgent +from mem0 import MemoryClient + +memory_client = MemoryClient() +USER_ID = "alice" + +agent = ConversableAgent( + "chatbot", + llm_config={"config_list": [{"model": "gpt-5-mini", "api_key": os.environ["OPENAI_API_KEY"]}]}, + code_execution_config=False, + human_input_mode="NEVER", +) + +def get_context_aware_response(question: str) -> str: + # Retrieve memories for context + relevant_memories = memory_client.search(question, filters={"user_id": USER_ID}) + context = "\n".join([m["memory"] for m in relevant_memories.get("results", [])]) + + prompt = f"""Answer considering previous interactions: + Previous context: {context} + Question: {question}""" + + reply = agent.generate_reply(messages=[{"content": prompt, "role": "user"}]) + + # Store the new interaction + memory_client.add( + [{"role": "user", "content": question}, {"role": "assistant", "content": reply}], + user_id=USER_ID + ) + return reply +``` + +--- + +## All Supported Frameworks + +Beyond the examples above, Mem0 integrates with: + +| Framework | Type | Install | +|-----------|------|---------| +| [Mastra](https://docs.mem0.ai/integrations/mastra) | TS agent framework | `npm install @mastra/mem0` | +| [ElevenLabs](https://docs.mem0.ai/integrations/elevenlabs) | Voice AI | `pip install elevenlabs mem0ai` | +| [LiveKit](https://docs.mem0.ai/integrations/livekit) | Real-time voice/video | `pip install livekit-agents mem0ai` | +| [Camel AI](https://docs.mem0.ai/integrations/camel-ai) | Multi-agent framework | `pip install camel-ai[all] mem0ai` | +| [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) | Cloud LLM provider | `pip install boto3 mem0ai` | +| [Dify](https://docs.mem0.ai/integrations/dify) | Low-code AI platform | Plugin-based | +| [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) | Google agent framework | `pip install google-adk mem0ai` | + +For the general Python pattern (no framework), see the "Common integration pattern" in [SKILL.md](../SKILL.md). diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/quickstart.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/quickstart.md new file mode 100644 index 000000000..132982a49 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/quickstart.md @@ -0,0 +1,119 @@ +# Mem0 Platform Quickstart + +Get running with Mem0 in 2 minutes. No infrastructure to deploy -- just an API key. + +## Prerequisites + +- Python 3.10+ or Node.js 18+ +- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill-quickstart)) + +## Python Setup + +```bash +pip install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +```python +from mem0 import MemoryClient + +client = MemoryClient(api_key="your-api-key") + +# Add a memory +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember your dietary preferences."} +] +client.add(messages, user_id="user123") + +# Search memories +results = client.search("What are my dietary restrictions?", filters={"user_id": "user123"}) +print(results) +``` + +### Async Client + +```python +from mem0 import AsyncMemoryClient + +client = AsyncMemoryClient(api_key="your-api-key") + +await client.add(messages, user_id="user123") +results = await client.search("query", filters={"user_id": "user123"}) +``` + +## TypeScript / JavaScript Setup + +```bash +npm install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +```javascript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: 'your-api-key' }); + +// Add a memory +const messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember your dietary preferences."} +]; +await client.add(messages, { userId: "user123" }); + +// Search memories +const results = await client.search("What are my dietary restrictions?", { + filters: { user_id: "user123" } +}); +console.log(results); +``` + +## cURL + +```bash +export MEM0_API_KEY="m0-your-api-key" + +# Add memory +curl -X POST https://api.mem0.ai/v3/memories/add/ \ + -H "Authorization: Token $MEM0_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role": "user", "content": "I am a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I will remember your dietary preferences."} + ], + "user_id": "user123" + }' + +# Search memories +curl -X POST https://api.mem0.ai/v3/memories/search/ \ + -H "Authorization: Token $MEM0_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "What are my dietary restrictions?", + "filters": {"user_id": "user123"} + }' +``` + +## Sample Response + +```json +{ + "results": [ + { + "id": "14e1b28a-2014-40ad-ac42-69c9ef42193d", + "memory": "Allergic to nuts", + "user_id": "user123", + "categories": ["health"], + "created_at": "2025-10-22T04:40:22.864647-07:00", + "score": 0.30 + } + ] +} +``` + +## Next Steps + +- [SDK Guide](sdk-guide.md) -- all methods for Python and TypeScript +- [API Reference](api-reference.md) -- REST endpoints and memory object structure +- [Integration Patterns](integration-patterns.md) -- LangChain, CrewAI, Vercel AI, etc. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/sdk-guide.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/sdk-guide.md new file mode 100644 index 000000000..512c0d19c --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/sdk-guide.md @@ -0,0 +1,353 @@ +# Mem0 SDK Guide + +Complete SDK reference for Python and TypeScript. All methods use `MemoryClient` (Platform API). + +> **For language-specific deep references (including OSS):** See [client/python.md](../client/python.md) and [client/node.md](../client/node.md). For Python vs TypeScript differences: [client/differences.md](../client/differences.md). + +## Initialization + +**Python:** +```python +from mem0 import MemoryClient +client = MemoryClient(api_key="m0-your-api-key") +``` + +**Python (Async):** +```python +from mem0 import AsyncMemoryClient +client = AsyncMemoryClient(api_key="m0-your-api-key") +``` + +**TypeScript:** +```typescript +import MemoryClient from 'mem0ai'; +const client = new MemoryClient({ apiKey: 'm0-your-api-key' }); +``` + +Constructor accepts `apiKey` (required) and `host` (optional, default: `https://api.mem0.ai`). + +--- + +## add() -- Store Memories + +**Python:** +```python +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember that."} +] +client.add(messages, user_id="alice") + +# With metadata +client.add(messages, user_id="alice", metadata={"source": "onboarding"}) +``` + +**TypeScript:** +```typescript +await client.add(messages, { userId: "alice" }); +await client.add(messages, { userId: "alice", metadata: { source: "onboarding" } }); +``` + +### Parameters + +| Name | Type | Description | +|------|------|-------------| +| `messages` | array | `[{"role": "user", "content": "..."}]` | +| `user_id` | string | User identifier (recommended) | +| `agent_id` | string | Agent identifier | +| `run_id` | string | Session identifier | +| `metadata` | object | Custom key-value pairs | +| `infer` | boolean | If `false`, store raw text without inference (default: `true`) | + +### Advanced Add Options + +```python +# Agent + session scoping +client.add(messages, user_id="alice", agent_id="nutrition-agent", run_id="session-456") + +# Raw text -- skip LLM inference +client.add( + [{"role": "user", "content": "User prefers dark mode."}], + user_id="alice", + infer=False, +) +``` + +--- + +## search() -- Find Memories + +**Python:** +```python +results = client.search("dietary preferences?", filters={"user_id": "alice"}) + +# With filters and reranking +results = client.search( + query="work experience", + filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "professional_details"}}]}, + top_k=5, + rerank=True, + threshold=0.5 +) +``` + +**TypeScript:** +```typescript +const results = await client.search("dietary preferences", { filters: { user_id: "alice" } }); +const results = await client.search("work experience", { + filters: { AND: [{ user_id: "alice" }, { categories: { contains: "professional_details" } }] }, + topK: 5, + rerank: true, +}); +``` + +### Parameters + +| Name | Type | Description | +|------|------|-------------| +| `query` | string | Natural language search query | +| `filters` | object | Filter object (AND/OR operators). Use `{"user_id": "..."}` to filter by user | +| `top_k` | number | Number of results (default: 10 for Platform) | +| `rerank` | boolean | Enable reranking for better relevance (default: `false`) | +| `threshold` | number | Minimum similarity score (default: 0.1) | + +### Common Filter Patterns + +**Python:** +```python +# Single user filter +filters={"user_id": "alice"} + +# OR across agents +filters={"OR": [{"user_id": "alice"}, {"agent_id": {"in": ["travel-agent", "sports-agent"]}}]} + +# Category filtering (partial match) +filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "finance"}}]} + +# Category filtering (exact match) +filters={"AND": [{"user_id": "alice"}, {"categories": {"in": ["personal_information"]}}]} + +# Wildcard (match any non-null run) +filters={"AND": [{"user_id": "alice"}, {"run_id": "*"}]} + +# Date range +filters={"AND": [ + {"user_id": "alice"}, + {"created_at": {"gte": "2024-01-01T00:00:00Z"}}, + {"created_at": {"lt": "2024-02-01T00:00:00Z"}} +]} + +# Exclude categories with NOT +filters={"AND": [{"user_id": "user_123"}, {"NOT": {"categories": {"in": ["spam", "test"]}}}]} + +# Multi-dimensional query +filters={"AND": [ + {"user_id": "user_123"}, + {"keywords": {"icontains": "invoice"}}, + {"categories": {"in": ["finance"]}}, + {"created_at": {"gte": "2024-01-01T00:00:00Z"}} +]} +``` + +**TypeScript:** +```typescript +// Single user filter +filters: { user_id: "alice" } + +// OR across agents +filters: { OR: [{ user_id: "alice" }, { agent_id: { in: ["travel-agent", "sports-agent"] } }] } + +// Category filtering (partial match) +filters: { AND: [{ user_id: "alice" }, { categories: { contains: "finance" } }] } + +// Category filtering (exact match) +filters: { AND: [{ user_id: "alice" }, { categories: { in: ["personal_information"] } }] } +``` + +--- + +## get() / getAll() -- Retrieve Memories + +**Python:** +```python +# Single memory by ID +memory = client.get(memory_id="ea925981-...") + +# All memories for a user +memories = client.get_all(filters={"user_id": "alice"}) + +# With date range +memories = client.get_all( + filters={"AND": [ + {"user_id": "alex"}, + {"created_at": {"gte": "2024-07-01", "lte": "2024-07-31"}} + ]} +) +``` + +**TypeScript:** +```typescript +const memory = await client.get("ea925981-..."); +const memories = await client.getAll({ filters: { user_id: "alice" } }); +``` + +**Note:** `get_all` requires at least one of `user_id`, `agent_id`, `app_id`, or `run_id` in filters. + +--- + +## update() -- Modify Memories + +**Python:** +```python +client.update(memory_id="ea925981-...", text="Updated: vegan since 2024") +client.update(memory_id="ea925981-...", text="Updated", metadata={"verified": True}) +``` + +**TypeScript:** +```typescript +await client.update("ea925981-...", { text: "Updated: vegan since 2024" }); +``` + +--- + +## delete() / deleteAll() -- Remove Memories + +**Python:** +```python +client.delete(memory_id="ea925981-...") +client.delete_all(user_id="alice") # Irreversible bulk delete +``` + +**TypeScript:** +```typescript +await client.delete("ea925981-..."); +await client.deleteAll({ userId: "alice" }); +``` + +--- + +## history() -- Track Changes + +**Python:** +```python +history = client.history(memory_id="ea925981-...") +# Returns: [{previous_value, new_value, action, timestamps}] +``` + +**TypeScript:** +```typescript +const history = await client.history("ea925981-..."); +``` + +--- + +## Batch Operations (TypeScript) + +```typescript +// Batch update +await client.batchUpdate([ + { memoryId: "uuid-1", text: "Updated text" }, + { memoryId: "uuid-2", text: "Another updated text" }, +]); + +// Batch delete +await client.batchDelete(["uuid-1", "uuid-2", "uuid-3"]); +``` + +--- + +## Additional Methods + +```python +# List all users/agents/sessions with memories +users = client.users() + +# Delete a user/agent entity +client.delete_users(user_id="alice") + +# Submit feedback on a memory +client.feedback(memory_id="...", feedback="POSITIVE", feedback_reason="Accurate extraction") + +# Export memories +export = client.create_memory_export(filters={"AND": [{"user_id": "alice"}]}) +data = client.get_memory_export(memory_export_id=export["id"]) +``` + +--- + +## Common Pitfalls + +1. **Entity cross-filtering fails silently** -- `AND` with `user_id` + `agent_id` returns empty. Use `OR`. +2. **SQL operators rejected** -- use `gte`, `lt`, etc. Not `>=`, `<`. +3. **Metadata filtering is limited** -- only top-level keys with `eq`, `contains`, `ne`. +4. **Wildcard `*` excludes null** -- only matches non-null values. +5. **Default threshold is 0.1** -- increase for stricter matching. +6. **Async processing** -- memories process asynchronously. Wait 2-3s after `add()` before searching. + +## Naming Conventions + +Python uses `snake_case` everywhere (`user_id`, `memory_id`, `get_all`). TypeScript uses `camelCase` for methods (`getAll`, `deleteAll`, `batchUpdate`) and top-level parameters (`userId`, `topK`, `pageSize`), but filter keys use `snake_case` (`user_id`, `agent_id`). + +--- + +## v2 to v3 Migration + +### Breaking Changes in v3 + +**1. Entity IDs in search() and getAll()** + +v3 requires entity IDs (`user_id`, `agent_id`, `run_id`) inside `filters` instead of as top-level parameters: + +```python +# v2 (deprecated) +client.search("query", user_id="alice") +client.get_all(user_id="alice") + +# v3 +client.search("query", filters={"user_id": "alice"}) +client.get_all(filters={"user_id": "alice"}) +``` + +```typescript +// v2 (deprecated) +await client.search("query", { user_id: "alice" }); +await client.getAll({ user_id: "alice" }); + +// v3 +await client.search("query", { filters: { user_id: "alice" } }); +await client.getAll({ filters: { user_id: "alice" } }); +``` + +**2. TypeScript Parameter Naming** + +v3 TypeScript uses camelCase for all parameters: + +| v2 | v3 | +|----|-----| +| `user_id` | `userId` | +| `agent_id` | `agentId` | +| `run_id` | `runId` | +| `top_k` | `topK` | +| `page_size` | `pageSize` | + +**3. Default Values Changed** + +| Parameter | v2 Default | v3 Default | +|-----------|------------|------------| +| `threshold` | 0.3 | 0.1 | +| `rerank` | (not specified) | `false` | + +**4. Removed Parameters** + +The following parameters are no longer supported: + +| Parameter | Status | +|-----------|--------| +| `enable_graph` | Removed from add/search/getAll | +| `keyword_search` | Removed from search | +| `filter_memories` | Removed | +| `immutable` | Removed from add | +| `expiration_date` | Removed from add | +| `includes` | Removed from add | +| `excludes` | Removed from add | +| `async_mode` | Removed from add | diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/use-cases.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/use-cases.md new file mode 100644 index 000000000..5f3ba655d --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/use-cases.md @@ -0,0 +1,720 @@ +# Mem0 Use Cases & Examples + +Real-world implementation patterns for Mem0 Platform. Each use case includes complete, runnable code in both Python and TypeScript. + +## Table of Contents + +- [Personalized AI Companion](#1-personalized-ai-companion) +- [Customer Support with Categories](#2-customer-support-with-categories) +- [Healthcare Coach](#3-healthcare-coach) +- [Content Creation Workflow](#4-content-creation-workflow) +- [Multi-Agent / Multi-Tenant](#5-multi-agent--multi-tenant) +- [Personalized Search](#6-personalized-search) +- [Email Intelligence](#7-email-intelligence) +- [Common Patterns Across Use Cases](#common-patterns-across-use-cases) + +--- + +## 1. Personalized AI Companion + +A fitness coach that remembers goals, preferences, and progress across sessions. Mem0 persists context across app restarts — no session state needed. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +def chat(user_input: str, user_id: str) -> str: + # 1. Retrieve relevant memories + memories = mem0.search(user_input, user_id=user_id) + context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])]) + + # 2. Generate response with memory context + system_prompt = f"""You are Ray, a personal fitness coach. +Use these known facts about the user to personalize your response: +{context if context else 'No prior context yet.'}""" + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_input}, + ] + ) + reply = response.choices[0].message.content + + # 3. Store interaction for future context + mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": reply}], + user_id=user_id + ) + return reply + +# Usage +chat("I want to run a marathon in under 4 hours", user_id="max") +# Next day, app restarted: +chat("What should I focus on today?", user_id="max") +# Ray remembers the sub-4 marathon goal +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function chat(userInput: string, userId: string): Promise { + // 1. Retrieve relevant memories + const memories = await mem0.search(userInput, { filters: { user_id: userId } }); + const context = memories.results + ?.map((m: any) => `- ${m.memory}`) + .join('\n') || 'No prior context yet.'; + + // 2. Generate response with memory context + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `You are Ray, a personal fitness coach.\nUser context:\n${context}` }, + { role: 'user', content: userInput }, + ], + }); + const reply = response.choices[0].message.content!; + + // 3. Store interaction + await mem0.add( + [{ role: 'user', content: userInput }, { role: 'assistant', content: reply }], + { userId: userId } + ); + return reply; +} +``` + +### Key Benefits + +- Context persists across app restarts — no session management needed +- Memories are automatically deduplicated and updated +- Works with any LLM provider (OpenAI, Anthropic, etc.) + +**Best for:** Fitness coaches, tutors, therapists — any assistant that needs to remember goals across sessions. + +--- + +## 2. Customer Support with Categories + +Auto-categorize support data so teams retrieve the right facts fast. Uses custom categories for structured retrieval. + +### Implementation (Python) + +```python +from mem0 import MemoryClient + +client = MemoryClient() + +# 1. Define categories at the project level (one-time setup) +custom_categories = [ + {"support_tickets": "Customer issues and resolutions"}, + {"account_info": "Account details and preferences"}, + {"billing": "Payment history and billing questions"}, + {"product_feedback": "Feature requests and feedback"}, +] +client.project.update(custom_categories=custom_categories) + +# 2. Store interactions — auto-classified into categories +def log_support_interaction(user_id: str, message: str, priority: str = "normal"): + client.add( + [{"role": "user", "content": message}], + user_id=user_id, + metadata={"priority": priority, "source": "support_chat"} + ) + +# 3. Retrieve by category +def get_billing_issues(user_id: str): + return client.get_all( + filters={ + "AND": [ + {"user_id": user_id}, + {"categories": {"in": ["billing"]}} + ] + } + ) + +def search_support_history(user_id: str, query: str): + return client.search( + query, + filters={ + "AND": [ + {"user_id": user_id}, + {"categories": {"contains": "support_tickets"}} + ] + }, + top_k=5 + ) + +# Usage +log_support_interaction("maria", "I was charged twice for last month's subscription", priority="high") +log_support_interaction("maria", "The dashboard is loading slowly on mobile") +billing = get_billing_issues("maria") # Returns only billing-related memories +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); + +// Setup categories (one-time) +await client.updateProject({ + custom_categories: [ + { support_tickets: 'Customer issues and resolutions' }, + { billing: 'Payment history and billing questions' }, + { product_feedback: 'Feature requests and feedback' }, + ], +}); + +async function logInteraction(userId: string, message: string, priority = 'normal') { + await client.add( + [{ role: 'user', content: message }], + { userId: userId, metadata: { priority, source: 'support_chat' } } + ); +} + +async function getBillingIssues(userId: string) { + return client.getAll({ + filters: { AND: [{ user_id: userId }, { categories: { in: ['billing'] } }] }, + }); +} +``` + +### Key Benefits + +- Automatic categorization — no manual tagging +- Filter by category for structured retrieval +- Metadata (`priority`, `source`) enables multi-dimensional queries + +**Best for:** Help desks, SaaS support, e-commerce — structured retrieval by category eliminates manual scanning. + +--- + +## 3. Healthcare Coach + +Guide patients with an assistant that remembers medical history. Uses high `threshold` for confident retrieval in safety-critical contexts. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +def save_patient_info(user_id: str, information: str): + mem0.add( + [{"role": "user", "content": information}], + user_id=user_id, + run_id="healthcare_session", + metadata={"type": "patient_information"} + ) + +def consult(user_id: str, question: str) -> str: + # High threshold for medical accuracy + memories = mem0.search(question, user_id=user_id, top_k=5, threshold=0.7) + context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])]) + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"You are a health coach. Patient context:\n{context}"}, + {"role": "user", "content": question}, + ] + ) + reply = response.choices[0].message.content + + # Store the interaction + mem0.add( + [{"role": "user", "content": question}, {"role": "assistant", "content": reply}], + user_id=user_id, + run_id="healthcare_session", + ) + return reply + +# Usage +save_patient_info("alex", "I'm allergic to penicillin and take metformin for type 2 diabetes") +consult("alex", "Can I take amoxicillin for my sore throat?") +# Remembers penicillin allergy — amoxicillin is a penicillin-type antibiotic +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function savePatientInfo(userId: string, info: string) { + await mem0.add( + [{ role: 'user', content: info }], + { userId: userId, runId: 'healthcare_session', metadata: { type: 'patient_information' } } + ); +} + +async function consult(userId: string, question: string): Promise { + const memories = await mem0.search(question, { + filters: { user_id: userId }, + topK: 5, + threshold: 0.7, + }); + const context = memories.results?.map((m: any) => `- ${m.memory}`).join('\n') || ''; + + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `You are a health coach. Patient context:\n${context}` }, + { role: 'user', content: question }, + ], + }); + const reply = response.choices[0].message.content!; + + await mem0.add( + [{ role: 'user', content: question }, { role: 'assistant', content: reply }], + { userId: userId, runId: 'healthcare_session' } + ); + return reply; +} +``` + +### Key Benefits + +- High threshold (0.7) ensures only confident matches for safety-critical retrieval +- Session scoping via `run_id` groups related health interactions +- Metadata tagging separates patient info from conversation history + +**Best for:** Telehealth, wellness apps, patient management — persistent health context across visits. + +--- + +## 4. Content Creation Workflow + +Store voice guidelines once and apply them across every draft. Uses `run_id` and `metadata` to scope writing preferences per session. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +def store_writing_preferences(user_id: str, preferences: str): + mem0.add( + [{"role": "user", "content": preferences}], + user_id=user_id, + run_id="editing_session", + metadata={"type": "preferences", "category": "writing_style"} + ) + +def draft_content(user_id: str, topic: str) -> str: + # Retrieve writing preferences + prefs = mem0.search( + "writing style preferences", + filters={"AND": [{"user_id": user_id}, {"run_id": "editing_session"}]} + ) + style_context = "\n".join([f"- {m['memory']}" for m in prefs.get("results", [])]) + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"Write content matching these style preferences:\n{style_context}"}, + {"role": "user", "content": f"Write a blog post about: {topic}"}, + ] + ) + return response.choices[0].message.content + +# Usage +store_writing_preferences("writer_01", "I prefer short sentences. Active voice. No jargon. Use analogies.") +draft_content("writer_01", "Why AI memory matters for chatbots") +# Drafts content matching the stored voice guidelines +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function storePreferences(userId: string, preferences: string) { + await mem0.add( + [{ role: 'user', content: preferences }], + { userId: userId, runId: 'editing_session', metadata: { type: 'preferences' } } + ); +} + +async function draftContent(userId: string, topic: string): Promise { + const prefs = await mem0.search('writing style preferences', { + filters: { AND: [{ user_id: userId }, { run_id: 'editing_session' }] }, + }); + const styleContext = prefs.results?.map((m: any) => `- ${m.memory}`).join('\n') || ''; + + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `Write content matching these preferences:\n${styleContext}` }, + { role: 'user', content: `Write a blog post about: ${topic}` }, + ], + }); + return response.choices[0].message.content!; +} +``` + +### Key Benefits + +- Voice consistency across all content without repeating guidelines +- Scoped sessions let you maintain different style profiles +- Preferences update automatically as you refine them + +**Best for:** Marketing teams, technical writers, agencies — consistent voice across all content. + +--- + +## 5. Multi-Agent / Multi-Tenant + +Keep memories separate using `user_id`, `agent_id`, `app_id`, and `run_id` scoping. Critical for multi-agent workflows and multi-tenant apps. + +### Implementation (Python) + +```python +from mem0 import MemoryClient + +client = MemoryClient() + +# Store memories scoped to user + agent + session +def store_scoped_memory(messages: list, user_id: str, agent_id: str, run_id: str, app_id: str): + client.add( + messages, + user_id=user_id, + agent_id=agent_id, + run_id=run_id, + app_id=app_id + ) + +# Query within a specific scope +def search_user_session(query: str, user_id: str, app_id: str, run_id: str): + """Search memories for a specific user within a specific session.""" + return client.search( + query, + filters={ + "AND": [ + {"user_id": user_id}, + {"app_id": app_id}, + {"run_id": run_id} + ] + } + ) + +def search_agent_knowledge(query: str, agent_id: str, app_id: str): + """Search all memories an agent has across all users.""" + return client.search( + query, + filters={ + "AND": [ + {"agent_id": agent_id}, + {"app_id": app_id} + ] + } + ) + +# Usage: Travel concierge app with multiple agents +store_scoped_memory( + [{"role": "user", "content": "I'm vegetarian and prefer window seats"}], + user_id="traveler_cam", + agent_id="travel_planner", + run_id="tokyo-2025", + app_id="concierge_app" +) + +# User-scoped query: "What does Cam prefer?" +user_mems = search_user_session("dietary restrictions?", "traveler_cam", "concierge_app", "tokyo-2025") + +# Agent-scoped query: "What do all travelers prefer?" (across users) +agent_mems = search_agent_knowledge("common dietary restrictions?", "travel_planner", "concierge_app") +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); + +async function storeScopedMemory( + messages: Array<{ role: string; content: string }>, + userId: string, agentId: string, runId: string, appId: string +) { + await client.add(messages, { + userId: userId, + agentId: agentId, + runId: runId, + appId: appId, + }); +} + +async function searchUserSession(query: string, userId: string, appId: string, runId: string) { + return client.search(query, { + filters: { AND: [{ user_id: userId }, { app_id: appId }, { run_id: runId }] }, + }); +} + +async function searchAgentKnowledge(query: string, agentId: string, appId: string) { + return client.search(query, { + filters: { AND: [{ agent_id: agentId }, { app_id: appId }] }, + }); +} +``` + +### Key Benefits + +- Full isolation between users, agents, sessions, and apps +- Query at any scope level — user, agent, session, or app-wide +- No memory leakage between tenants + +**Best for:** Multi-agent workflows, multi-tenant SaaS — proper isolation at every level. + +--- + +## 6. Personalized Search + +Blend real-time search results with personal context. Uses `custom_instructions` to infer preferences from queries. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +# One-time setup: configure Mem0 to infer from queries +mem0.project.update( + custom_instructions="""Infer user preferences and facts from their search queries. +Extract dietary preferences, location, interests, and purchase history.""" +) + +def personalized_search(user_id: str, query: str, search_results: list) -> str: + # Get user context from memory + memories = mem0.search(query, user_id=user_id, top_k=5) + user_context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])]) + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"Personalize search results using user context:\n{user_context}"}, + {"role": "user", "content": f"Query: {query}\n\nSearch results:\n{search_results}"}, + ] + ) + reply = response.choices[0].message.content + + # Store the query to learn preferences over time + mem0.add( + [{"role": "user", "content": query}], + user_id=user_id + ) + return reply + +# Usage +personalized_search("user_42", "best restaurants nearby", ["Restaurant A", "Restaurant B"]) +# Over time, Mem0 learns: "user prefers vegetarian, lives in Austin" +# Future searches are automatically personalized +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function personalizedSearch(userId: string, query: string, searchResults: string[]): Promise { + const memories = await mem0.search(query, { filters: { user_id: userId }, topK: 5 }); + const context = memories.results?.map((m: any) => `- ${m.memory}`).join('\n') || ''; + + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `Personalize results using user context:\n${context}` }, + { role: 'user', content: `Query: ${query}\nResults: ${searchResults.join(', ')}` }, + ], + }); + const reply = response.choices[0].message.content!; + + await mem0.add([{ role: 'user', content: query }], { userId: userId }); + return reply; +} +``` + +### Key Benefits + +- Learns preferences from queries automatically via `custom_instructions` +- Personalizes any search provider (Tavily, Google, Bing) +- Zero manual preference setup — improves over time + +**Best for:** Personalized search engines, recommendation systems — search results tailored to individual users. + +--- + +## 7. Email Intelligence + +Capture, categorize, and recall inbox threads using persistent memories with rich metadata. + +### Implementation (Python) + +```python +from mem0 import MemoryClient + +client = MemoryClient() + +def store_email(user_id: str, sender: str, subject: str, body: str, date: str): + client.add( + [{"role": "user", "content": f"Email from {sender}: {subject}\n\n{body}"}], + user_id=user_id, + metadata={"email_type": "incoming", "sender": sender, "subject": subject, "date": date} + ) + +def search_emails(user_id: str, query: str): + return client.search( + query, + filters={"AND": [{"user_id": user_id}, {"categories": {"contains": "email"}}]}, + top_k=10 + ) + +def get_emails_from_sender(user_id: str, sender: str): + return client.get_all( + filters={ + "AND": [ + {"user_id": user_id}, + {"metadata": {"contains": sender}} + ] + } + ) + +# Usage +store_email("alice", "bob@acme.com", "Q3 Budget Review", "Attached is the Q3 budget...", "2025-01-15") +store_email("alice", "carol@acme.com", "Sprint Planning", "Here are the priorities...", "2025-01-16") + +results = search_emails("alice", "budget discussions") +sender_emails = get_emails_from_sender("alice", "bob@acme.com") +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); + +async function storeEmail(userId: string, sender: string, subject: string, body: string, date: string) { + await client.add( + [{ role: 'user', content: `Email from ${sender}: ${subject}\n\n${body}` }], + { userId: userId, metadata: { email_type: 'incoming', sender, subject, date } } + ); +} + +async function searchEmails(userId: string, query: string) { + return client.search(query, { + filters: { AND: [{ user_id: userId }, { categories: { contains: 'email' } }] }, + topK: 10, + }); +} +``` + +### Key Benefits + +- Rich metadata enables multi-dimensional queries (sender, date, subject) +- Category filtering separates emails from other memory types +- Semantic search across all email content + +**Best for:** Inbox management, email automation — searchable email memories with metadata filtering. + +--- + +## Common Patterns Across Use Cases + +### Pattern 1: Retrieve → Generate → Store + +Every use case follows the same 3-step loop: + +```python +# 1. Retrieve relevant context +memories = mem0.search(user_input, user_id=user_id) +context = "\n".join([m["memory"] for m in memories.get("results", [])]) + +# 2. Generate with context +response = llm.generate(system_prompt=f"Context:\n{context}", user_input=user_input) + +# 3. Store the interaction +mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": response}], + user_id=user_id +) +``` + +### Pattern 2: Scope with Entity Identifiers + +Use `user_id`, `agent_id`, `app_id`, and `run_id` to isolate memories: + +```python +# User-level: personal preferences +client.add(messages, user_id="alice") + +# Session-level: conversation within one session +client.add(messages, user_id="alice", run_id="session_123") + +# Agent-level: agent-specific knowledge +client.add(messages, agent_id="support_bot", app_id="helpdesk") +``` + +### Pattern 3: Rich Metadata for Filtering + +Attach structured metadata for multi-dimensional queries: + +```python +# Store with metadata +client.add(messages, user_id="alice", metadata={"priority": "high", "source": "phone_call"}) + +# Filter by category + metadata +client.search("billing issues", filters={ + "AND": [{"user_id": "alice"}, {"categories": {"contains": "billing"}}] +}) +``` + +### Pattern 4: Custom Instructions for Domain-Specific Extraction + +Control what Mem0 extracts from conversations: + +```python +client.project.update( + custom_instructions="Extract medical conditions, medications, and allergies. Exclude billing info." +) +``` + +--- + +## More Examples + +For 30+ cookbooks with complete working code: [docs.mem0.ai/cookbooks](https://docs.mem0.ai/cookbooks) diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/scripts/mem0_doc_search.ts b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/scripts/mem0_doc_search.ts new file mode 100644 index 000000000..8c8c637ce --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/scripts/mem0_doc_search.ts @@ -0,0 +1,181 @@ +#!/usr/bin/env bun +export {}; +const DOCS_BASE = "https://docs.mem0.ai"; +const SEARCH_ENDPOINT = `${DOCS_BASE}/api/search`; +const LLMS_INDEX = `${DOCS_BASE}/llms.txt`; + +const SECTION_MAP: Record = { + platform: [ + "/platform/overview", + "/platform/quickstart", + "/platform/features", + "/platform/features/graph-memory", + "/platform/features/selective-memory", + "/platform/features/custom-categories", + "/platform/features/v2-memory-filters", + "/platform/features/async-client", + "/platform/features/webhooks", + "/platform/features/multimodal-support", + ], + api: [ + "/api-reference/memory/add-memories", + "/api-reference/memory/v2-search-memories", + "/api-reference/memory/v2-get-memories", + "/api-reference/memory/get-memory", + "/api-reference/memory/update-memory", + "/api-reference/memory/delete-memory", + ], + "open-source": [ + "/open-source/overview", + "/open-source/python-quickstart", + "/open-source/node-quickstart", + "/open-source/features", + "/open-source/features/graph-memory", + "/open-source/features/rest-api", + "/open-source/configure-components", + ], + openmemory: [ + "/openmemory/overview", + "/openmemory/quickstart", + ], + sdks: ["/sdks/python", "/sdks/js"], + integrations: ["/integrations"], +}; + +async function fetchUrl(url: string): Promise { + try { + const res = await fetch(url, { + headers: { "User-Agent": "Mem0DocSearchAgent/1.0" }, + signal: AbortSignal.timeout(15000), + }); + if (!res.ok) return `HTTP Error ${res.status}: ${res.statusText}`; + return await res.text(); + } catch (e: any) { + return `Error: ${e.message}`; + } +} + +async function searchDocs(query: string, section?: string) { + const params = new URLSearchParams({ query }); + try { + const result = await fetchUrl(`${SEARCH_ENDPOINT}?${params}`); + const data = JSON.parse(result); + if (data?.results) { + let results = data.results; + if (section && SECTION_MAP[section]) { + const paths = SECTION_MAP[section]; + results = results.filter((r: any) => + paths.some((p) => r.url?.startsWith(p)), + ); + } + return { source: "mintlify_search", results }; + } + } catch {} + + const indexContent = await fetchUrl(LLMS_INDEX); + const queryLower = query.toLowerCase(); + let matching = indexContent + .split("\n") + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith("#") && l.toLowerCase().includes(queryLower)); + + if (section && SECTION_MAP[section]) { + const paths = SECTION_MAP[section]; + matching = matching.filter((u) => paths.some((p) => u.includes(p))); + } + + return { + source: "llms_txt_index", + query, + matching_urls: matching.slice(0, 20), + suggestion: "Fetch specific URLs for detailed content", + }; +} + +async function fetchPage(pagePath: string) { + const url = pagePath.startsWith("/") ? `${DOCS_BASE}${pagePath}` : pagePath; + const content = await fetchUrl(url); + return { url, content: content.slice(0, 10000), truncated: content.length > 10000 }; +} + +async function getIndex() { + const content = await fetchUrl(LLMS_INDEX); + const urls = content + .split("\n") + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith("#")); + return { total_pages: urls.length, urls, sections: Object.keys(SECTION_MAP) }; +} + +function listSection(section: string) { + if (!SECTION_MAP[section]) { + return { error: `Unknown section: ${section}`, available: Object.keys(SECTION_MAP) }; + } + return { section, pages: SECTION_MAP[section].map((p) => `${DOCS_BASE}${p}`) }; +} + +function printResult(result: any) { + if (result.results) { + console.log(`Source: ${result.source ?? "unknown"}`); + for (const r of result.results) { + console.log(` - ${r.title ?? "N/A"}: ${r.url ?? "N/A"}`); + if (r.description) console.log(` ${r.description.slice(0, 200)}`); + } + } else if (result.matching_urls) { + console.log(`Source: ${result.source}`); + console.log(`Query: ${result.query}`); + for (const url of result.matching_urls) console.log(` - ${url}`); + if (result.suggestion) console.log(`\n${result.suggestion}`); + } else if (result.urls) { + console.log(`Total documentation pages: ${result.total_pages}`); + console.log(`Sections: ${result.sections.join(", ")}`); + for (const url of result.urls.slice(0, 30)) console.log(` - ${url}`); + if (result.total_pages > 30) console.log(` ... and ${result.total_pages - 30} more`); + } else if (result.pages) { + console.log(`Section: ${result.section}`); + for (const page of result.pages) console.log(` - ${page}`); + } else if (result.content) { + console.log(`URL: ${result.url}`); + if (result.truncated) console.log("[Content truncated to 10000 chars]"); + console.log(result.content); + } else if (result.error) { + console.log(`Error: ${result.error}`); + if (result.available) console.log(`Available sections: ${result.available.join(", ")}`); + } else { + console.log(JSON.stringify(result, null, 2)); + } +} + +const args = process.argv.slice(2); +const flags: Record = {}; +for (let i = 0; i < args.length; i++) { + if (args[i] === "--query" && args[i + 1]) flags.query = args[++i]; + else if (args[i] === "--page" && args[i + 1]) flags.page = args[++i]; + else if (args[i] === "--section" && args[i + 1]) flags.section = args[++i]; + else if (args[i] === "--index") flags.index = true; + else if (args[i] === "--json") flags.json = true; +} + +let result: any; +if (flags.index) { + result = await getIndex(); +} else if (flags.section && !flags.query) { + result = listSection(flags.section as string); +} else if (flags.page) { + result = await fetchPage(flags.page as string); +} else if (flags.query) { + result = await searchDocs(flags.query as string, flags.section as string | undefined); +} else { + console.log("Usage:"); + console.log(" bun scripts/mem0_doc_search.ts --query \"topic\""); + console.log(" bun scripts/mem0_doc_search.ts --page \"/platform/features/graph-memory\""); + console.log(" bun scripts/mem0_doc_search.ts --index"); + console.log(" bun scripts/mem0_doc_search.ts --section platform"); + process.exit(1); +} + +if (flags.json) { + console.log(JSON.stringify(result, null, 2)); +} else { + printResult(result); +} diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/memory-reviewer/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/memory-reviewer/SKILL.md new file mode 100644 index 000000000..fb2eb55b2 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/memory-reviewer/SKILL.md @@ -0,0 +1,63 @@ +--- +name: memory-reviewer +description: Reviews stored memory quality by detecting duplicates, contradictions, and stale entries with actionable recommendations. Use when search results seem conflicting, before running dream consolidation, or for periodic memory hygiene audits. +--- + +# Memory Reviewer + +Audits memory quality for the active project. Finds duplicates, contradictions, and low-confidence entries. + +## When to use + +- User asks "check my memories", "memory quality", "any duplicates?" +- User runs `/mem0:memory-reviewer` directly +- After a session with 5+ memory writes (suggest proactively) +- After `/mem0:health --deep` identifies issues + +## Steps + +1. **Fetch all memories** for active project via `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200`. Paginate if needed — cap at 200 memories. + +2. **Group by `metadata.type`**. Common types: `decision`, `convention`, `anti_pattern`, `task_learning`, `project_profile`, `user_preference`, `session_state`. + +3. **Scan each group for issues:** + + | Issue | Detection method | + |---|---| + | **Near-duplicates** | >60% noun overlap within same type. Compare memory text after stripping stop words. | + | **Contradictions** | Opposing facts about same topic (e.g., "use PostgreSQL" vs "use MySQL" for same component) | + | **Low-confidence** | `metadata.confidence < 0.3` | + | **Missing type** | No `metadata.type` set | + | **Stale** | `created_at` older than 180 days with no updates | + +4. **Output compact summary:** + +``` +memory-reviewer: project= total= + duplicates: found + contradictions: found + low_confidence: found + untagged: found + stale: found +``` + +5. **If issues found**, list them with memory IDs: + +``` +Issues: + [duplicate] "" ≈ "" [mem0:, mem0:] + [contradiction] "" vs "" [mem0:, mem0:] + [low_conf] "" (confidence: 0.1) [mem0:] +``` + +6. **Suggest action**: "Run `/mem0:dream` to consolidate duplicates and resolve contradictions." + +## Constraints + +- **Read-only** — never modify or delete memories (that's `/mem0:dream`'s job) +- **Max 200 memories** per scan +- Report findings, let user decide on action + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/onboard/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/onboard/SKILL.md new file mode 100644 index 000000000..9cb8c484b --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/onboard/SKILL.md @@ -0,0 +1,201 @@ +--- +name: onboard +description: Sets up mem0 for a new project including API key configuration, MCP authentication, project file import, and coding categories. Use on first run in a new project, when API key needs updating, or to re-run initial setup after configuration changes. +--- + +# Mem0 Onboarding Wizard + +Run this wizard to set up the mem0 plugin for the current project. Complete in ~60 seconds. + +**IMPORTANT: Execute steps strictly in order (0 → 1 → 2 → 3 → 4 → 5 → 6). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead. Complete one step fully before starting the next.** + +## Step 0: Skip dependency install + +The plugin lists `mem0ai` as a declared dependency — no install step is needed. Proceed directly to Step 1. + +## Step 1: Set up API key + +Check if the API key is available: + +```bash +[ -n "${MEM0_API_KEY:-}" ] && echo "SET" || echo "NOT_SET" +``` + +IMPORTANT: Never run `echo $MEM0_API_KEY` — that prints the secret in plaintext to the conversation log. + +### If API key IS set (output is "SET") + +Print: `- API key found.` and proceed to Step 2. + +### If API key is NOT set (output is "NOT_SET") + +Guide the user through API key setup. Show this message: + +``` +Step 1: Setting up API key. + +- API key not found. Let's set it up. + + 1. Get your API key from https://app.mem0.ai/dashboard/api-keys + + 2. Choose ONE method: + + Option A — Shell profile: + echo 'export MEM0_API_KEY="m0-your-key-here"' >> ~/.zshrc + source ~/.zshrc + + Option B — OpenCode environment config: + Add MEM0_API_KEY to your OpenCode environment settings + so it is available in all sessions. + + 3. Verify: + [ -n "${MEM0_API_KEY:-}" ] && echo "SET" || echo "NOT_SET" +``` + +After the user confirms, re-run the verify command. If NOT_SET, repeat. If SET, proceed to Step 2. + +## Step 2: MCP server connection + +First, check if MCP tools are already available using ToolSearch with query `"mem0 search_memories"`. The exact tool name varies by install method (may be `mcp__mem0__search_memories` or `mcp__plugin_mem0_mem0__search_memories`). + +**If MCP tools ARE found:** Print `- MCP already connected.` and proceed to Step 3. + +**If MCP tools are NOT found:** + +The MCP server authenticates using the `MEM0_API_KEY` set in Step 1. No OAuth or browser login is needed. + +1. Verify the API key is set (re-run the Step 1 check) +2. Check the plugin is installed and the MCP server for mem0 is listed in OpenCode's MCP configuration +3. If the server shows an error, ask the user to restart OpenCode and run `/mem0:onboard` again +4. If all checks pass but tools are still missing: "Restart OpenCode and run `/mem0:onboard` again." + +**STOP here** — do not proceed without MCP tools. + +## Step 3: Verify connectivity and show identity + +Call `search_memories` with `query="project setup"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` to verify connectivity. + +Print: +``` +- Connected + user: + project: + branch: +``` + +If the search fails, troubleshoot the API key and MCP connection. + +## Step 4: Import project files + +Check for common project context files in the working directory root. Read any that exist and store their key facts as memories using `add_memory`. + +### 4a: Detect project files + +```bash +for f in CLAUDE.md AGENTS.md .cursorrules .windsurfrules mem0.md .mem0.md; do + [ -f "$f" ] && echo "FOUND: $f ($(wc -c < "$f") bytes)" +done || true +``` + +If no files found, print `- No project files found. Skipping import.` and proceed to Step 5. + +### 4b: Read and import via MCP + +For each file found in 4a: + +1. Read the file contents with the Read tool. +2. Extract the key facts, conventions, and decisions documented in the file. Do not import the entire file verbatim — summarize meaningful chunks. +3. Call `add_memory` for each meaningful chunk with: + - `data`: the extracted fact or convention + - `user_id`: the active user id + - `app_id`: the active project id + - `metadata`: `{"source": "", "type": "project_context"}` + +If a file is very large (over 4000 bytes), split it into logical sections (one `add_memory` call per section). + +### 4c: Report to user + +After processing all files, print a user-friendly summary: + +``` +- Importing project files into mem0... done. + file(s) read, memories stored. + These are available as context for future sessions. +``` + +If `add_memory` calls fail, print: +``` +- Project file import failed. Check API key and MCP connection, then retry with: /mem0:onboard +``` + +## Step 5: Set up coding categories + +Ask: "Install coding categories optimized for development workflows? [Y/n]" + +If the user says no or skips, print `- Coding categories skipped.` and proceed to Step 6. + +If yes, store a project profile memory that records the project and its active coding categories. Call `add_memory` once with: + +- `data`: a plain-text description of the project profile and the list of active coding categories (see below) +- `user_id`: the active user id +- `app_id`: the active project id +- `metadata`: `{"type": "project_profile", "source": "onboard"}` + +The standard set of 17 coding categories to include in the `data` field: + +``` +architecture_decisions - High-level design choices and system structure +api_design - Interface contracts, REST/GraphQL/RPC conventions +data_models - Schemas, entity definitions, relationships +algorithms - Non-trivial logic, performance-sensitive routines +dependencies - Libraries, versions, upgrade notes +environment_setup - Dev environment, tooling, build system +testing_strategy - Test patterns, coverage targets, mocking approach +debugging_notes - Known issues, workarounds, gotchas +performance - Bottlenecks, profiling results, optimizations +security - Auth patterns, secret handling, threat notes +deployment - CI/CD pipelines, infra config, release process +code_conventions - Naming, formatting, style rules beyond the linter +error_handling - Error taxonomy, recovery patterns, logging approach +refactoring_history - Past rewrites, why changes were made +integrations - Third-party services, webhooks, external APIs +onboarding - New-contributor notes, repo orientation +project_meta - Goals, non-goals, stakeholder context +``` + +Example `data` value: + +``` +Project profile for . +Active coding categories: architecture_decisions, api_design, data_models, +algorithms, dependencies, environment_setup, testing_strategy, debugging_notes, +performance, security, deployment, code_conventions, error_handling, +refactoring_history, integrations, onboarding, project_meta. +``` + +After the `add_memory` call succeeds, print: +``` +- Coding categories installed (17 categories). +``` + +If `add_memory` fails, print the error and suggest re-running `/mem0:onboard`. + +## Step 6: Summary + +Print a summary: +``` +- Onboarding complete. + user_id: + project_id: (app_id) + files: found, memories stored + categories: + +Memory is now active for this project. Start working — mem0 will +automatically search relevant context and capture learnings. + +Run /mem0:tour to see what mem0 already knows about this project. +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/peek/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/peek/SKILL.md new file mode 100644 index 000000000..949e0f73f --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/peek/SKILL.md @@ -0,0 +1,56 @@ +--- +name: peek +description: Searches memories and displays compact one-liner results, or looks up a specific memory by ID. Use for quick memory lookups, checking if a decision was recorded, resolving [mem0:id] citations, or browsing memories without full category detail. +--- + +# Mem0 Peek + +Quick search with compact output. Lighter than `/mem0:tour`. + +## Execution + +### Step 1: Parse query + +The user provides a search query: `/mem0:peek auth middleware` + +If no query provided, ask: "What should I search for?" + +**Memory ID detection:** If the query matches any of these patterns, treat it as a +direct memory ID lookup instead of a search: +- Bare hex: `^[a-f0-9]{8}$` (short ID) or `^[a-f0-9]{8}-[a-f0-9-]+$` (full UUID) +- Citation ref: `[mem0:]` — extract the hex portion + +When an ID is detected: +1. Call `get_memory()` directly (if short ID, try as prefix of full UUID) +2. If found, skip to Step 3 and display the single result +3. If not found, fall through to search using the ID as query text + +### Step 2: Search + +Run 2 parallel `search_memories` calls: + +1. Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +2. Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5`, `rerank=true` + +### Step 3: Display + +Deduplicate by ID, then show compact results: + +``` +## mem0 peek: "" ( results) + +1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1] +2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a] +3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7] +``` + +Format: `. [] () [mem0:]` + +If no results: +``` +No memories matching "" for project . +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/pin/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/pin/SKILL.md new file mode 100644 index 000000000..463521ef7 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/pin/SKILL.md @@ -0,0 +1,71 @@ +--- +name: pin +description: Pins or unpins a memory to protect it from pruning during dream consolidation. Use when a memory is critical and must never be removed, such as architecture decisions, security constraints, or immutable team conventions. +--- + +# Mem0 Pin + +Pin a memory to mark it as high-priority and protect from pruning. + +## Execution + +### Step 1: Find the memory + +The user provides either a search query or memory ID. + +**If memory ID:** +- Call `get_memory` with the ID. + +**If search query:** +- Call `search_memories` with the query, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=5`. +- Show numbered list with content previews. +- Ask: "Which memory to pin? Enter a number." + +### Step 2: Read current content + +Call `get_memory` with the selected memory ID. Store: +- `original_text` — the memory's text content +- `original_metadata` — the existing `metadata` dict + +### Step 3: Pin it + +The MCP `update_memory` tool only accepts `memory_id`, `text`, and `source` — it +does not accept a `metadata` parameter. To pin, append a pin marker to the text: + +```python +pinned_text = "[PINNED] " + original_text if not original_text.startswith("[PINNED]") else original_text +update_memory(memory_id=, text=pinned_text) +``` + +**For new memories** (user wants to pin text that isn't stored yet): +1. Call `add_memory` with: + - `text="[PINNED] "` + - `user_id=` + - `app_id=` + - `metadata={"pinned": true, "type": "decision", "confidence": 1.0}` + - `infer=False` +2. The response contains `event_id`. Call `get_event_status(event_id=)` once to retrieve the memory ID, then confirm. + +### Step 4: Confirm + +``` +Pinned: "" +Memory ID: +``` + +Append `...` only if content exceeds 80 characters. + +### Unpin + +If the user says "unpin": +1. Call `get_memory` to read current content. +2. Remove the pin marker from the text: + ```python + unpinned_text = original_text.removeprefix("[PINNED] ") + update_memory(memory_id=, text=unpinned_text) + ``` +3. Print: `Unpinned: "..."` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/remember/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/remember/SKILL.md new file mode 100644 index 000000000..54157bd91 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/remember/SKILL.md @@ -0,0 +1,61 @@ +--- +name: remember +description: Stores a memory verbatim from user input with appropriate type classification and metadata. Use when the user says remember this, save this, store this, note that, or explicitly asks to record a decision, preference, convention, or learning. +--- + +# Mem0 Remember + +Store a fact or learning directly into mem0. + +## Execution + +### Step 1: Extract the content + +The user provides the content as an argument: `/mem0:remember ` + +If no text was provided, ask: "What should I remember?" + +### Step 2: Classify the memory + +Based on the content, pick the best `metadata.type`: + +| Content signal | Type | +|---|---| +| "we decided...", "always use...", "never..." | `decision` | +| "X doesn't work because...", "don't try..." | `anti_pattern` | +| "I prefer...", "use X instead of Y" | `user_preference` | +| "the convention is...", "we always..." | `convention` | +| "learned that...", "figured out..." | `task_learning` | +| setup, env, tooling, config | `environmental` | +| anything else | `task_learning` | + +### Step 3: Store + +Call `add_memory` with: +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={"type": "", "branch": "", "confidence": 1.0, "source": "remember_command"}` +- `infer=False` + +`infer=False` because the user stated the fact explicitly — no extraction needed. +`confidence=1.0` because the user explicitly asked to store this. + +### Step 4: Confirm + +The `add_memory` response returns `event_id` (not `memory_id`) because writes are async. +Call `get_event_status(event_id=)` once. + +- If status is `SUCCEEDED`: print the memory ID from the result. +- If status is `PENDING` or `processing`: print with the event ID as fallback. + +``` +Remembered as : "" +Memory ID: +``` + +Append `...` only if content was truncated (longer than 80 chars). + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/stats/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/stats/SKILL.md new file mode 100644 index 000000000..b58f04fa0 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/stats/SKILL.md @@ -0,0 +1,135 @@ +--- +name: stats +description: Displays memory usage statistics for the current session and project including counts by category, age distribution, and API latency. Use when checking how many memories exist, reviewing session activity, or auditing memory distribution across categories. +--- + +# Mem0 Stats + +Show session and lifetime memory statistics. + +## Execution + +### Step 1: Gather session context + +Read the session identity from env vars set by the plugin's shell.env hook: + +- `MEM0_USER_ID` (falls back to `$USER` if unset) +- `MEM0_APP_ID` — the active project identifier +- `MEM0_SESSION_ID` — current session identifier +- `MEM0_BRANCH` — current git branch + +If `MEM0_USER_ID` is unset, use `$USER`. If `MEM0_APP_ID` is unset, note "No project configured" and stop. + +### Step 2: Fetch total memory count + +Call `get_memories` MCP tool to get the total count for this project: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=1` + +Read the `count` field from the response — this is the total number of memories for the project regardless of page size. + +### Step 3: Fetch memories for category breakdown + +Call `get_memories` MCP tool to retrieve memories for grouping: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200` + +Group each memory by: +1. `categories[0]` (platform-assigned) — primary grouping +2. `metadata.type` (agent-assigned) — secondary if `categories` is empty or absent +3. `created_at` date — for age analysis + +**Category normalization:** Merge `auto_capture` and `uncategorized` into a single `uncategorized` row. Do NOT show `auto_capture` as its own row. + +Also run a `search_memories` MCP tool call with `query="project"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` to measure round-trip latency. Note the time before and after the MCP call — do NOT attempt raw HTTP calls to the API. + +### Step 4: Display + +Print a minimal plain-text dashboard. No markdown formatting — OpenCode TUI renders text verbatim. + +Example output shape: + +``` +mem0 stats + +Session () branch: main + +Project: my-project — 55 memories — API: 84ms + +Category Count +-------------------- ----- +decision 24 +convention 15 +anti_pattern 6 +task_learning 5 +user_preference 3 +session_state 2 + +Age — oldest: 2026-02-15 newest: 2026-05-23 + < 7 days: 5 | 7-30d: 12 | 30-90d: 10 | > 90d: 8 + +Identity — user: kartik project: my-project branch: main +``` + +**Display rules:** +- Use plain text with spaces to align columns — no markdown tables, no | pipes, no ** bold, no ## headers +- Category section: sort by count descending, omit categories with 0 memories +- Age: single line with pipe-separated buckets, computed from `created_at` +- Session line: show MEM0_SESSION_ID (first 12 chars) and MEM0_BRANCH if available; skip the line entirely if both are unset +- If only 1-2 total memories, skip the category table — just show the count +- Keep everything compact — no decorative borders or filler + +## Weekly digest mode + +When invoked with `--weekly` (e.g., `/mem0:stats --weekly`), append a weekly +activity digest after the standard stats dashboard. + +### W1: Fetch recent memories + +Call `search_memories` in parallel with time-scoped queries: +1. `query="decisions made this week"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"created_at": {"gte": "<7 days ago YYYY-MM-DD>"}}]}`, `top_k=20` +2. `query="bugs errors fixes"`, same time filter, `top_k=20` +3. `query="patterns conventions learnings"`, same time filter, `top_k=20` + +### W2: Analyze + +Merge by ID. Group into "New this week" by `categories[0]` or `metadata.type`. +Calculate: memories added last 7 days, most active categories, most active day. + +### W3: Display + +Append after the standard stats in plain text (no markdown): + +``` +This week (May 16 - May 23) + ++12 memories — most active: Wednesday (5) + +Category New +-------------- --- +decision 5 +task_learning 4 +bug_fix 3 + +Highlights +- <2-3 sentence summary of most important decisions/learnings this week> +``` + +### W4: Write digest file + +Write to `~/.mem0/weekly-digest.txt` (overwrite). Append one line to +`~/.mem0/digest-history.log`: +``` + | | + memories | top: +``` + +### W5: Empty state + +If no new memories in 7 days, output: +``` +No new memories in the past week. Total: memories in . +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/switch-project/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/switch-project/SKILL.md new file mode 100644 index 000000000..6ac75a377 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/switch-project/SKILL.md @@ -0,0 +1,50 @@ +--- +name: switch-project +description: Overrides the auto-detected project scope to read and write memories under a different project ID. Use when working across multiple projects, accessing memories from another repo, or when auto-detection resolves to the wrong project. +--- + +# Mem0 Switch Project + +Override the automatic project_id detection for the current directory. + +## Usage + +The user provides a project name as an argument: `/mem0:switch-project ` + +## Execution + +1. If no project name was given, ask: "What project_id should this directory use?" + +2. Write the mapping to `~/.mem0/project_map.json` using the Bash tool: + + ```bash + python3 -c " + import json, os + map_file = os.path.expanduser('~/.mem0/project_map.json') + mapping = {} + if os.path.isfile(map_file): + with open(map_file) as f: + mapping = json.load(f) + mapping[os.getcwd()] = '' + os.makedirs(os.path.dirname(map_file), exist_ok=True) + with open(map_file, 'w') as f: + json.dump(mapping, f, indent=2) + print(f'Mapped {os.getcwd()} -> ') + " + ``` + + (Replace `` with the user's chosen project name.) + +3. Verify by searching for existing memories: + - Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` + +4. Print: + ``` + Switched to project . + memories found for this project. + Note: This override persists across sessions for this directory. + ``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/tour/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/tour/SKILL.md new file mode 100644 index 000000000..04c061b04 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/tour/SKILL.md @@ -0,0 +1,156 @@ +--- +name: tour +description: Browses all stored memories grouped by category with full content display. Use when reviewing all project memories, exploring stored knowledge, onboarding to a project, or getting an overview of captured decisions, conventions, and learnings. +--- + +# Mem0 Project Tour + +Show the user what mem0 has stored for the current project. + +## Cross-project mode + +When invoked with `--all-projects` (e.g., `/mem0:tour --all-projects` or +`/mem0:tour --all-projects auth middleware`), search across ALL projects: + +1. Call `get_memories` with `filters={"AND": [{"user_id": ""}]}`, `page_size=200` — **no `app_id` filter**. +2. If a search query was also provided, run `search_memories` with `query=`, + `filters={"AND": [{"user_id": ""}]}`, `top_k=20` — again no `app_id`. +3. Group results by `app_id` first, then by category within each project. +4. Display: + ``` + ## ( memories) ← current + **Architecture Decisions** — + ... + + ## ( memories) + ... + + memories across projects + ``` +5. Mark the current project with `← (current)` in the heading. + +If `--all-projects` is NOT present, use the standard single-project flow below. + +## Peek mode (compact search) + +When `/mem0:tour` receives a search query argument (e.g., `/mem0:tour auth middleware`) +WITHOUT `--all-projects`, run in **peek mode** — compact one-liner results: + +1. Run 2 parallel `search_memories` calls: + - Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` + - Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5`, `rerank=true` +2. Deduplicate by ID, display compact results: + ``` + ## mem0 search: "" ( results) + + 1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1] + 2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a] + 3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7] + ``` + Format: `. [] () [mem0:]` +3. If no results: `No memories matching "" for project .` + +If no query argument and no `--all-projects` flag, use the full tour flow below. + +## Execution + +### Step 1: Fetch ALL memories for this project + +Call `get_memories` to fetch all memories for this project: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=100` + +### Step 2: Run supplementary semantic searches + +In parallel, run these `search_memories` calls to get relevance-ranked results for key topics: + +- `query="architecture decisions design choices"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +- `query="bugs errors failures anti-patterns"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +- `query="project setup tooling conventions preferences"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` + +**Do NOT filter by `metadata.type` in these calls.** The platform auto-assigns `categories` — filtering on `metadata.type` misses memories that were auto-categorized but don't have an explicit `metadata.type`. + +### Step 3: Merge and group + +Merge all results by memory ID (deduplicate). For each memory, determine its group using this priority: + +1. **Platform `categories` field** (array on each memory, auto-assigned by Mem0). Use the first category value. +2. **`metadata.type` field** (if present, set explicitly by hooks/agent). Use as fallback if no `categories`. +3. **"other"** bucket for memories with neither. + +Map category names to display names: + +| Platform category / metadata.type | Display name | +|---|---| +| `architecture decisions`, `architecture_decisions`, `decision` | Architecture Decisions | +| `anti patterns`, `anti_patterns`, `anti_pattern` | Anti-Patterns | +| `task learnings`, `task_learnings`, `task_learning` | Task Learnings | +| `coding conventions`, `coding_conventions`, `convention` | Coding Conventions | +| `user preferences`, `user_preferences`, `user_preference` | User Preferences | +| `project profile`, `project_profile` | Project Profile | +| `tooling setup`, `tooling_setup`, `environmental` | Tooling & Setup | +| `technology`, `professional_details` | Tooling & Setup | +| `session_state` | Session State | +| `compact_summary` | Compact Summaries | +| anything else | Other | + +### Step 4: Display results + +Sort groups by descending memory count. Display in compact tabular format: + +First show the category summary table: + +``` +mem0 tour + +Session (ses_abc123) branch: main +Project: my-project - 349 memories + +Category Count +----------------------------------------- +tooling_setup 119 +bug_fixes 78 +architecture_decisions 32 +task_learnings 14 +... +``` + +Then for each category (sorted by count descending), show memories as numbered one-liners. Truncate each memory to 100 chars max: + +``` +tooling_setup (119) + 1. User requires that no git commit or push be performed without explicit permission... + 2. OpenCode plugins are loaded from ~/.config/opencode/plugins/ for global installation... + 3. Assistant determined that the symlink method for loading the Mem0 plugin was failing... + ... and 116 more + +bug_fixes (78) + 1. Fixed getAll filter format from flat object to AND-wrapped array for mem0ai TS SDK v3... + 2. Root cause of user_id mismatch: plugin derived kartik.labhshetwar from git email... + ... and 76 more +``` + +Show top 5 memories per category by recency. If a group has more than 5, note `... and more`. + +Skip empty groups entirely. + +### Step 5: Print totals + +``` + memories across categories +project: branch: + +Identity - user: project: branch: +``` + +### Step 6: Empty state + +If zero memories found for this project, print: +``` +No memories stored yet for project . +Run /mem0-onboard to import project files, or start working - mem0 captures learnings automatically. +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim - markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode.json b/mem0-plugin/.opencode-plugin/opencode.json new file mode 100644 index 000000000..701e57c05 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://opencode.ai/config.json", + "plugin": ["@mem0/opencode-plugin"], + "mcp": { + "mem0": { + "type": "remote", + "url": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token {env:MEM0_API_KEY}" + }, + "oauth": false + } + } +} diff --git a/mem0-plugin/.opencode-plugin/package.json b/mem0-plugin/.opencode-plugin/package.json new file mode 100644 index 000000000..1f821e3ad --- /dev/null +++ b/mem0-plugin/.opencode-plugin/package.json @@ -0,0 +1,71 @@ +{ + "name": "@mem0/opencode-plugin", + "version": "0.1.1", + "type": "module", + "description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "bin": { + "mem0-opencode": "./cli.ts" + }, + "publishConfig": { + "access": "public" + }, + "license": "Apache-2.0", + "keywords": [ + "opencode", + "opencode-plugin", + "mem0", + "memory", + "mcp", + "ai-memory", + "persistent-memory" + ], + "repository": { + "type": "git", + "url": "https://github.com/mem0ai/mem0", + "directory": "mem0-plugin/.opencode-plugin" + }, + "files": [ + "dist", + "cli.ts", + "opencode.json", + "LICENSE", + "opencode-skills" + ], + "scripts": { + "build": "bun build opencode-mem0.ts --outdir dist --target bun --format esm --entry-naming index.[ext] && tsc --emitDeclarationOnly --outDir dist --declaration", + "dev": "bun build opencode-mem0.ts --outdir dist --target bun --format esm --entry-naming index.[ext] --watch", + "type-check": "tsc --noEmit", + "prepack": "bun run build", + "postpack": "" + }, + "opencode": { + "type": "plugin", + "hooks": [ + "chat.message", + "tool.execute.before", + "tool.execute.after", + "experimental.chat.messages.transform", + "experimental.session.compacting", + "shell.env" + ] + }, + "dependencies": { + "@opencode-ai/plugin": "^1.0.162", + "mem0ai": "^3.0.5" + }, + "devDependencies": { + "bun-types": ">=1.3.14", + "typescript": "^5.7.3" + }, + "peerDependencies": { + "bun": ">=1.0.0" + } +} diff --git a/mem0-plugin/.opencode-plugin/tsconfig.json b/mem0-plugin/.opencode-plugin/tsconfig.json new file mode 100644 index 000000000..97d3c9347 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "declarationDir": "dist", + "outDir": "dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "lib": ["ESNext"], + "types": ["bun-types"], + "noEmit": false, + "emitDeclarationOnly": true + }, + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/mem0-plugin/CHANGELOG.md b/mem0-plugin/CHANGELOG.md index d6f437a90..69e976e22 100644 --- a/mem0-plugin/CHANGELOG.md +++ b/mem0-plugin/CHANGELOG.md @@ -2,6 +2,282 @@ All notable changes to the Mem0 plugin will be documented in this file. +## 0.1.0 — OpenCode & Antigravity + +### Added + +- **OpenCode plugin** (`@mem0/opencode-plugin` on npm): Pure TypeScript plugin using the `mem0ai` TS SDK — no Python, no shell scripts. Hooks into all 6 OpenCode events (`chat.message`, `tool.execute.before`, `tool.execute.after`, `experimental.chat.system.transform`, `experimental.session.compacting`, `shell.env`). Features: session start memory loading, per-prompt semantic search, error pattern detection with memory lookup, resume/remember intent detection, auto-capture every 3rd message, periodic save nudges, full metadata defaults injection (confidence, source, type, session_id, files, branch), identity injection for search/get/delete filters, type-filtered error pre-fetch (anti_pattern + bug_fix), pre-compaction memory capture, MEMORY.md write blocking, and secret redaction. +- **16 OpenCode-native skills** bundled in `opencode-skills/`: `context-loader`, `dream`, `export`, `forget`, `health`, `import`, `list-projects`, `mem0` (SDK reference), `memory-reviewer`, `onboard`, `peek`, `pin`, `remember`, `stats`, `switch-project`, `tour`. All skills are pure MCP-tool-based — no Python scripts, no shell scripts, no Claude Code dependencies. +- **Auto-install skills and commands (`installSkills()`):** On plugin load, copies all 16 skills to `.opencode/skills/` and creates command wrapper files in `.opencode/commands/` so they appear in the OpenCode `/` palette. No manual setup needed. +- **`extractUserText()` handler:** Robust text extraction from OpenCode response shapes — handles `parts[]` array, `content[]` array, `message.content`, and plain string responses. +- **Identity resolution:** `getUserId()` uses `os.userInfo().username` (matching Claude Code's `${USER}` convention) with `MEM0_USER_ID` env override. `getProjectId()` uses git remote with `MEM0_APP_ID` env override. +- **Context injection via `experimental.chat.system.transform`:** All memory context (session start memories, per-prompt search results, error-related memories, compaction context) injected as system context. +- **CLI installer (`cli.ts`):** `bunx @mem0/opencode-plugin install` auto-configures plugin and MCP server in `~/.config/opencode/opencode.json`. +- **Antigravity plugin** (`.antigravity/`): Restructured to follow the same shared-infrastructure pattern as Claude Code, Cursor, and Codex. Self-contained plugin directory with `plugin.json`, `mcp_config.json`, `hooks/hooks.json` (own file), `scripts/` (symlink → `../scripts/`), and `skills/` (symlink → `../skills/`). Installable via `agy plugin install .antigravity` or `npx degit mem0ai/mem0/mem0-plugin/.antigravity ~/.gemini/config/plugins/mem0`. Uses `contextFileName: "AGENTS.md"` per Antigravity convention. +- **Codex hooks parity:** Added missing `PreToolUse` Write/Edit/MultiEdit block and `PreCompact` hook to Codex hooks config, bringing it to full parity with Claude Code. + +### Changed + +- **Antigravity plugin directory:** Renamed `.antigravity-plugin/` → `.antigravity/` to match the naming convention of `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`. Plugin is now self-contained so `agy plugin install` and `npx degit` both work. +- **Antigravity hooks:** Hooks now live directly in `.antigravity/hooks/hooks.json` as a standalone file — no indirection. +- **Antigravity install command:** Updated from `npx degit mem0ai/mem0/mem0-plugin` to `npx degit mem0ai/mem0/mem0-plugin/.antigravity`. Added `agy plugin install` as alternative for local clones. + +### Removed + +- **Stale root-level files:** Deleted `mem0-plugin/hooks.json`, `mem0-plugin/mcp_config.json`, `mem0-plugin/plugin.json` — leftover artifacts from before plugin restructuring into per-editor subdirectories. Nothing referenced them; install commands point to `.antigravity/`. + +## 0.2.7 + +### Fixed + +- **First-install auth failure (closes #4876):** Removed `authorizationUrl` from `.mcp.json`. When both a static `Authorization` header and `authorizationUrl` were present, Claude Code preferred the OAuth flow, which failed on reconnect — leaving new users stuck with only `authenticate`/`complete_authentication` stub tools. Authentication now uses the `MEM0_API_KEY` header exclusively; no browser OAuth flow is triggered. +- **Onboarding skill removed OAuth step:** `/mem0:onboard` Step 2 no longer guides users through a browser-based OAuth login. The MCP server authenticates via the API key set in Step 1. +- **Removed `claude plugin configure mem0` references:** This CLI command does not exist. The `userConfig` mechanism works through the plugin enable UI prompt — Claude Code prompts for the API key when the plugin is first enabled and stores it securely in the system keychain. Updated session start banner, onboarding skill, identity script comments, and manual testing guide. + +## 0.2.6 + +### Fixed + +- **Memory count zero / stats and tour showing 0 memories:** The `run_id: "*"` wildcard filter — added in the initial v0.2.6 fix — returns 0 on both the v3 list endpoint (`/v3/memories/`) and search endpoint (`/v3/memories/search/`) when memories were written without a `run_id` (which is all memories since v0.2.6 stopped setting `run_id` on `add_memory`). Removed `run_id: "*"` from: `on_session_start.sh` count queries, `enforce_metadata_defaults.sh` hook injection (was injecting into every `search_memories` and `get_memories` call), `_search.py` search payload, `/mem0:stats` and `/mem0:tour` skill instructions. All read paths now use simple `user_id` + `app_id` filters without `run_id`, matching how v0.2.3 worked. +- **`add_memory` no longer sets `run_id`:** Session tracking moved from top-level `run_id` (which creates a separate API partition) to `metadata.session_id`. New memories land in the default partition and are visible to all queries. +- **`enforce_metadata_defaults.sh` no longer injects `run_id`:** The hook was appending `{"run_id": "*"}` to every `search_memories` and `get_memories` filter, which broke both endpoints. Removed entirely — identity injection (`user_id`/`app_id`) still works. +- **`_search.py` simplified:** Removed `run_id: "*"` from search payload. Uses plain `user_id` + `app_id` filters. +- **`auto_import.py` delete endpoint 404:** Stale chunk deletion used `DELETE /v3/memories/{id}/` which returns 404 (v3 is ADD-only). Changed to `DELETE /v1/memories/{id}/`. +- **Banner count accurate:** `on_session_start.sh` count query uses `user_id` + `app_id` filters without `run_id`. Shows total count only (removed noisy auto-import breakdown). +- **`quickstart.md` wrong add endpoint:** cURL example used `POST /v1/memories/` (v1 add is removed). Fixed to `POST /v3/memories/add/`. +- **Session stats always 0:** PostToolUse hooks never fire for plugin MCP tools (confirmed via debug logs — only SessionStart, UserPromptSubmit, PreToolUse, and Stop fire). Moved session stats tracking (`session_stats.py add/search`) into `enforce_metadata_defaults.sh` (PreToolUse), which does fire on every MCP tool call. +- **Periodic nudge never firing:** Message count file used session UUID in filename (`/tmp/mem0_msg_count_${SESSION_ID}`) which was cleared on session start. Changed to `$USER`-keyed filename, matching the session stats file convention. +- **`/mem0:stats` session query returning 0:** Stats skill attempted API queries with `run_id` and `metadata.session_id` filters that return empty results. Session stats now come exclusively from the local stats file (which is accurate now that PreToolUse tracks adds). +- **Auto-capture stops working mid-session:** After the initial rubric injection (first message), subsequent messages got zero context from the UserPromptSubmit hook. The banner instruction to "proactively store learnings" fades as conversation grows and Claude forgets. Two-pronged fix: (1) **Direct API auto-capture hook** (`auto_capture.py`): every 3rd message, `on_user_prompt.sh` spawns a background Python script that reads the last 3 exchanges from the transcript JSONL and sends them directly to `POST /v3/memories/add/` with `infer=True`. No reliance on Claude calling `add_memory`. (2) **Proportional prompt nudge** as fallback: starting from 3rd message, if Claude has stored fewer than 1 memory per 3 messages, a brief "store learnings via add_memory" directive is injected. +- **Desktop app: API key not found:** Claude Code Desktop does not inherit shell environment variables — only `PATH` is read from shell profiles. Users who set `export MEM0_API_KEY=m0-...` in `~/.zshrc` or `~/.bashrc` got "Setup Required" on Desktop while CLI worked fine. Added grep-based shell profile extraction as a 4th fallback in both `_identity.sh` (bash) and `_identity.py` (Python). Scans `~/.zshrc`, `~/.bashrc`, `~/.zprofile`, `~/.bash_profile`, `~/.profile` for `MEM0_API_KEY=` assignments. Skips variable references (`$OTHER_VAR`), commented-out lines, and strips quotes/inline comments. +- **Desktop app: zero memories added over multi-day usage:** Agent never proactively called `add_memory` — only `search_memories` and `get_all`. Root cause: session banner instruction was passive ("before finishing a session, store learnings") and easily ignored. No mechanism existed to re-prompt the agent mid-session. Fixed with a periodic nudge in `on_user_prompt.sh`: every 5th substantial message, the hook checks `session_stats` for add count; if fewer than 2 memories stored, injects a directive into Claude's context via `additionalContext` telling it to store learnings immediately. Counter resets on session start. +- **Setup Required banner missing Desktop instructions:** Updated no-API-key banner with Desktop-specific setup paths: `claude plugin configure mem0`, Desktop app environment editor (Settings > Environment), and CLI `export` as fallback. + +### Removed + +- **Stop hook (all 3 editors):** Removed from `hooks.json`, `cursor-hooks.json`, `codex-hooks.json`. Deleted `on_stop.sh`, `on_stop_cursor.sh`, `on_stop_codex.sh`, `stop_hook_check.py`. The Stop hook could not reliably feed context back to Claude (command-type hooks' `reason` field is user-facing only, not injected into Claude's context). Auto-capture handled by PreCompact hook instead. +- **SessionEnd hook:** Removed from `hooks.json`. Deleted `on_session_end.sh`. Redundant with PreCompact auto-capture. +- **5 redundant hook scripts:** `on_git_commit_capture.sh` (fired on every Bash command containing "git"), `on_post_commit.sh` (fired on every Bash command), `on_task_completed.sh`, `on_post_compact.sh`, `on_subagent_stop.sh`. These were already removed from Claude's `hooks.json` in v0.2.5 but script files remained on disk. Also removed `on_post_commit.sh` references from `cursor-hooks.json` and `codex-hooks.json`. +- **Dead settings:** Removed `output_style`, `skip_tools`, `capture_tools` from `load_settings.py` defaults. The `output-styles/` directory and `on_tool_failure.sh` script were already deleted. +- **`test_on_file_read.py`:** Removed test file for deleted `on_file_read.sh` hook. + +### Changed + +- **`/mem0:stats` lifetime query:** Single `get_memories` call with `user_id` + `app_id` filters (no `run_id`). +- **`/mem0:tour` full fetch:** Single `get_memories` call with `user_id` + `app_id` filters (no `run_id`). +- **API key resolution order (4 fallbacks):** `MEM0_API_KEY` env var > `CLAUDE_PLUGIN_OPTION_API_KEY` (plugin configure) > `CLAUDE_PLUGIN_OPTION_MEM0_API_KEY` (legacy userConfig) > shell profile extraction. Applies to both `_identity.sh` and `_identity.py`. +- **Session start banner:** Proactive memory instruction changed from passive "before finishing a session" to active "proactively store learnings incrementally as work progresses. Do NOT wait until the session ends." +- **Message counter on session start:** `on_session_start.sh` now resets `/tmp/mem0_msg_count_*` files to ensure nudge counter starts fresh each session. + +## 0.2.5 + +### Fixed + +- **PostToolUse field name: `tool_output` → `tool_response`:** All three PostToolUse scripts (`on_bash_output.sh`, `on_post_commit.sh`, `on_post_tool_use.sh`) were reading `.tool_output` from stdin JSON — a field that never existed in the Claude Code hooks spec. The correct field is `.tool_response` (confirmed via official docs at code.claude.com/docs/en/hooks). This was silently `null` on every invocation, meaning bash error detection and post-commit checks never actually fired. +- **Stop hook invalid `hookSpecificOutput`:** `on_stop.sh` returned `hookSpecificOutput` with `hookEventName: "Stop"` — but `Stop` is not a valid `hookEventName` discriminant. Claude Code rejected the JSON with "Hook JSON output validation failed". Replaced with spec-compliant `{ decision: "block", reason: "..." }`. +- **SessionStart banner invisible:** Switched from JSON `hookSpecificOutput.additionalContext` (discrete/hidden system reminder) back to raw text `cat <)` to actually remove the losing memory. +- **`/mem0:health` Check 3 `search_memories` top-level `user_id`:** Removed top-level `user_id` param; identity only in `filters.AND[]`. Changed `limit` to `top_k`. +- **`/mem0:health` Check 4 `add_memory` missing `infer=False`:** Health probe wasted LLM tokens on extraction. Added `infer=False`. Also fixed: was expecting `memory_id` in response but v3 returns `event_id`. Now uses `get_event_status` to get memory ID for cleanup. +- **`/mem0:tour` `get_memories` top-level identity:** Both standard and cross-project modes passed `user_id`/`app_id` as top-level params. Moved to `filters.AND[]`. +- **`/mem0:onboard` `search_memories` top-level `user_id`:** Removed extra top-level `user_id` param from connectivity check. +- **`/mem0:context-loader` incomplete filter table:** Filter examples showed only `metadata.type` without `user_id`/`app_id`. Now shows full `AND` filter structure. +- **7 skills used `limit` instead of `top_k` for `search_memories`:** MCP tool param is `top_k`, not `limit`. Fixed in: health, onboard, tour (3 places), switch-project, stats (weekly mode + latency probe). +- **`/mem0:stats` latency probe missing `filters`:** `search_memories` call had no identity filters. Added `user_id`/`app_id` in `filters.AND[]`. + +### Added + +- **`stop_hook_check.py`:** Pure-stdlib transcript analyzer for the Stop hook. Reads last 500 lines of transcript JSONL, parses tool calls, file modifications, and git commands. Returns `{"should_block": bool, "context": "..."}`. Trivial sessions (< 3 tool calls, no file edits) skip capture entirely. +- **Checklist for `/mem0:dream`:** 6-step progress tracker per Claude skill best practices for complex multi-step workflows. +- **Checklist for `/mem0:onboard`:** 7-step progress tracker for onboarding wizard. +- **Expanded hook matcher (all 3 configs):** `enforce_metadata_defaults.sh` now triggers on `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, and `delete_all_memories` (12 tool name variants covering both MCP naming conventions). + +### Changed + +- **Stop hook uses MCP-driven capture:** When meaningful work detected and no memories stored, returns `decision: "block"` asking Claude to call `add_memory` via MCP. One-shot flag prevents infinite loops. REST API capture runs in background as fallback. +- **SessionStart banner uses raw text stdout:** Replaced JSON `additionalContext` with `cat <]` citation refs, routes to `get_memory` direct lookup instead of semantic search. +- **Dead `PostToolUseFailure` hook block:** Removed entirely — this hook event does not exist in Claude Code. + +### Added + +- **Session ID capture (`on_session_start.sh`):** Extracts `session_id` from Claude Code stdin JSON, persists to `/tmp/mem0_session_id_$USER`. Falls back to timestamp-based ID. Enables `run_id`-based session scoping. +- **`run_id` injection (`enforce_metadata_defaults.sh`):** Reads session ID from temp file, injects as `run_id` into every `add_memory` call. Tags all memories with session identity for entity-scoped filtering. +- **`min_score`, `metadata_filters`, `rerank`, `threshold` params (`_search.py`):** `search_memories()` now accepts `min_score: float` to filter low-relevance results, `metadata_filters: dict` for field-level filtering, `rerank: bool` for managed reranker, and `threshold: float` (default 0.3, up from platform default 0.1) for server-side relevance gating. Existing callers unaffected (new params have defaults). +- **Rerank on tour/peek:** `/mem0:tour` and `/mem0:peek` search calls now pass `rerank=true` for better result ordering (+150–200ms latency, significantly improved precision). +- **`/mem0:stats` session query via `run_id`:** Queries memories by `run_id` filter for API-backed session counts. Cross-checks against local stats file. Shows truncated session ID in output. + +### Removed + +- **`/mem0:protocol` skill:** Routing table fully superseded by individual skill descriptions (auto-trigger). Operational guidelines (search patterns, metadata rules) covered by `enforce_metadata_defaults.sh` hook and individual skill bodies. + +### Changed + +- **All 17 skill descriptions:** Rewritten per Claude skill best practices — each now includes what the skill does AND when to trigger it, with specific keywords for auto-discovery. Average length 200–270 chars (under 1024 max). Third person, action verbs. +- **Onboarding auto-trigger (`on_session_start.sh`):** Replaced 3-state marker-file logic with memory-count detection. New project (0 memories) → prompts Claude to invoke `/mem0:onboard`. No marker files, no OAuth state. Simplified no-API-key path to single inactive banner. + +## 0.2.3 + +### Added + +- **Read hook (`on_file_read.sh`):** `PreToolUse(Read)` hook that searches mem0 for memories tagged with the file being opened and injects them as context. Skips non-code files, lockfiles, and `node_modules/`. Deduplicates repeated reads within a session. +- **Shared search module (`_search.py`):** Wraps `POST /v3/memories/search/` into a reusable `search_memories()` call with `format_results_for_context()` for consistent `[type] content [mem0:id]` display. Used by all pre-fetch hooks. +- **User settings (`load_settings.py`):** Loads config from `~/.mem0/settings.json` with 9 keys: `auto_save`, `auto_search`, `search_limit`, `retention_session_days`, `confidence_threshold`, `output_style`, `debug`, `skip_tools`, `capture_tools`. Supports `init` subcommand for first-run creation. +- **`/mem0:forget` skill:** Standalone skill for deleting memories by search query or UUID with confirmation. Supports undo-last-N via `session_stats.py peek`. +- **`/mem0:peek` skill:** Compact quick-search with one-liner output. Runs 2 parallel searches (broad + `decision`-filtered), deduplicates by ID. +- **`/mem0:memory-reviewer` skill:** Read-only memory quality audit. Scans for near-duplicates (>60% noun overlap), contradictions, low-confidence entries, untagged memories, and stale entries (>180 days). Refers to `/mem0:dream` for remediation. +- **`/mem0:context-loader` skill:** Pre-fetch agent that runs 2–4 parallel `search_memories` calls across query angles and type filters, deduplicates, outputs up to 10 memories. Silent on empty results. +- **Compact memory output style (`output-styles/compact-memory.md`):** `[] [mem0:]` format used by `peek`, `tour`, and inline search display. +- **`userConfig` in `.claude-plugin/plugin.json`:** `api_key` field (`sensitive: true`) enables key storage via `claude plugin configure mem0`. +- **OAuth support:** `authorizationUrl` added to `.mcp.json` for OAuth flow alongside token-based auth. +- **Tests:** `test_search.py`, `test_rubric_dedup.py`, `test_on_file_read.py`. + +### Changed + +- **Skills renamed:** All `mem0-*` prefixed skill directories renamed to shorter forms (e.g., `mem0-dream` → `dream`, `mem0-mcp` → `protocol`, `mem0-tour` → `tour`). 12 skills renamed total. +- **Pre-fetch on bash errors:** `on_bash_output.sh` now performs actual `search_memories` calls via `_search.py` instead of emitting search query templates as text. +- **Session-resume pre-fetch:** `on_user_prompt.sh` detects resume phrases ("where did we leave off", "continue from where") and pre-fetches `session_state` + `decision` memories. +- **Remember-intent routing:** `on_user_prompt.sh` detects save phrases ("remember this", "don't forget that") and routes to `/mem0:remember` skill instead of raw `add_memory`. +- **Rubric deduplication:** Search guidance rubric injected only on first user prompt per session via flag file. +- **Namespace-agnostic tool matching:** `on_post_tool_use.sh` and `on_tool_failure.sh` now use wildcard suffix matching (`*__add_memory`) instead of hardcoded `mcp__mem0__` prefix. +- **API key resolution:** `_identity.sh`/`_identity.py` now check `CLAUDE_PLUGIN_OPTION_API_KEY` (from `userConfig`) before legacy `CLAUDE_PLUGIN_OPTION_MEM0_API_KEY`. +- **Session start:** Three-state no-key handling (first run → auto-onboard, OAuth mode → proceed, neither → inactive banner). Banner now shows `auth=api_key|oauth`. +- **Hook timeouts:** `on_user_prompt.sh` and `on_bash_output.sh` raised from 5s to 12s. +- **Compact prompts:** `on_task_completed.sh` and `on_stop.sh` replaced multi-step checklists with single-line directives (0–2 durable facts max). +- **`/mem0:dream`:** Removed `--forget` (now standalone `forget` skill) and `--schedule` flags. +- **`/mem0:health`:** Removed `--fix` auto-fix mode; output condensed to `PASS/FAIL CheckName Detail` one-liners. +- **`/mem0:protocol` (was `mem0-mcp`):** Added 14-entry natural-language-to-skill routing table. _(Removed in 0.2.4 — superseded by skill descriptions.)_ +- **CLI config fallback removed:** `_identity.sh`/`_identity.py` no longer read `~/.mem0/config.json`; API key resolution is env-var-only. +- **`auto_import.py`:** Added content-hash deduplication to skip files with identical content within a single import run. +- **All skill descriptions:** Shortened to concise one-liners. + +### Fixed + +- **Namespace-agnostic tool name parsing in `on_tool_failure.sh`:** `${TOOL_NAME##*__}` correctly strips any MCP namespace prefix, not just `mcp__mem0__`. +- **`_identity.py`/`_identity.sh`:** `CLAUDE_PLUGIN_OPTION_API_KEY` env var was not checked, causing key-not-found when using `userConfig`. +- **Three previously failing tests** (`test_on_file_read.py`, `test_rubric_dedup.py`, `test_write_path.py`) fixed after hook changes. + +### Removed + +- **`/mem0:dream --forget` flag:** Extracted to standalone `forget` skill. +- **`/mem0:dream --schedule` flag:** Use Claude Code's built-in `/schedule` command instead. +- **`/mem0:health --fix` flag:** Auto-fix mode removed entirely; use `/mem0:dream` for remediation. +- **CLI dependency for key management:** All `~/.mem0/config.json` fallback code removed. + +## 0.2.2 + +### Fixed + +- **Duplicate memory writes on compaction:** `on_pre_compact.py` was running as both primary and backup capture path. Now reads `session_stats` and skips if agent already stored 2+ memories. `on_stop.sh` and `on_session_end.sh` were both calling `on_pre_compact.py`, creating double captures — removed redundant calls. +- **Verbose session-state blobs:** `on_pre_compact.py` `build_content()` was producing 5000+ char structured markdown. Now produces minimal 2-line summary (`Working on:`, `Files touched:`), relying on `infer=True` for extraction. +- **Pre-compaction prompt rewrite:** Instruction changed from "store a single large `session_state` blob with `infer=False`" to "store 0–3 durable facts per session (15–50 words each), one per `add_memory` call, tagged by category." +- **False-positive error detection:** `on_bash_output.sh` and `on_user_prompt.sh` error grep pattern was too broad, triggering on routine test/linter output. Now uses two-tier approach: high-signal patterns (`Traceback`, `panic:`, `FATAL:`) fire always; lower-signal (`Error:`, `Exception:`) require 2+ occurrences. +- **Background `on_pre_commit.py` on every git commit:** `on_git_commit_capture.sh` was storing `commit_context` memories regardless of usefulness. Background call removed; hook now only performs memory search. `on_pre_commit.py` deleted. +- **Post-commit prompt always firing:** `on_post_commit.sh` now gated behind `settings.commit_prompts: true` in `mem0.md` (defaults to off). +- **Project ID lost after folder rename:** `_project.py` only looked up `project_map.json` by CWD path. Added remote-hash fallback: hashes `git remote.origin.url` to a 16-char key, self-heals map on hit. +- **Subagent skip list hardcoded:** `on_subagent_stop.sh` now reads `settings.subagent_skip` from `mem0.md` instead of hardcoded `Explore|Plan`. +- **Auto-import missed project-root files:** `auto_import.py` only searched CWD. Added `_git_root()` helper to also search git root when invoked from subdirectory. +- **Concurrent `ensure_deps.sh` install race:** Multiple parallel sessions could corrupt the venv. Added lock directory with 60s spin-wait and `.install-failed` sentinel. +- **Telemetry `distinct_id` used MD5:** Changed to SHA-256 (truncated 32 chars). +- **Telemetry caller props could override system props:** Moved system fields after spread so they always win. +- **Plugin version hardcoded in `telemetry.py`:** Replaced with `_load_plugin_version()` reading from `plugin.json`. +- **Onboard marker race:** Marker now created in `on_session_start.sh` when prompt is first displayed, not after skill completes. + +### Changed + +- **`capture_compact_summary.py`:** `infer` changed from `False` to `True` so platform can extract structured facts. +- **Chunking utilities extracted to `_chunking.py`:** `split_by_headers`, `split_by_hr_or_headers`, `filter_and_truncate` moved from `import_competing_tools.py` to shared module. +- **`auto_import.py` now chunks Markdown files:** `.md` files split by `## ` headers before import instead of single blob. +- **`enforce_metadata_defaults.sh`:** New `PreToolUse` hook on `add_memory` for all three editors. Injects default metadata (`confidence: 0.7`, `source: "auto_capture"`, `type: "task_learning"`) when agent omits them. +- **`mem0.md` Settings section parsing:** `parse_mem0_config.py` now parses `Settings` section; added `--key ` CLI argument for programmatic lookup. +- **Project config display condensed:** Session start shows compact summary line instead of raw JSON dump. + +### Added + +- `scripts/_chunking.py` — shared content-chunking utilities. +- `scripts/enforce_metadata_defaults.sh` — metadata defaults injection hook. +- `parse_mem0_config.py --key` — CLI accessor for individual `mem0.md` config keys. +- **Native `MEMORY.md` detection:** `on_session_start.sh` detects Claude Code auto-memory and prompts to disable or run `/mem0:import`. +- **`/mem0:list-projects` skill:** Discovers all project `app_id` scopes by paginating `get_memories` without an `app_id` filter. +- **`/mem0:tour` cross-project mode** (`--all-projects`) and peek mode (query argument). +- **`/mem0:stats` weekly digest mode** (`--weekly`). +- **`/mem0:dream --forget` mode:** Search-confirm-delete flow with undo-last-write. +- **`/mem0:import --tools` flag:** Import from competing AI tool configs. +- `conftest.py` — `_clean_project_map` autouse fixture preventing cross-test pollution. + +### Removed + +- `scripts/on_pre_commit.py` — source of unwanted background writes on every commit. +- **`/mem0:digest` skill** — merged into `/mem0:stats --weekly`. +- **`/mem0:forget` skill** — merged into `/mem0:dream --forget`. +- **`/mem0:import-tools` skill** — merged into `/mem0:import --tools`. +- **`/mem0:peek` skill** — merged into `/mem0:tour `. +- `tests/test_pre_commit.py` — tests for deleted script. + +## 0.2.1 + +### Added + +- **11 new skills:** `/mem0:dream` (memory consolidation), `/mem0:export` (portable YAML-frontmatter export), `/mem0:import` (re-import exported memories), `/mem0:import-tools` (import from Cursor/Copilot/Cline/Continue configs), `/mem0:forget` (delete with confirmation), `/mem0:health` (diagnostic check — API key, MCP connectivity, read/write), `/mem0:peek` (compact quick-search), `/mem0:pin` (mark memory as high-priority), `/mem0:remember` (quick verbatim store with auto-classification), `/mem0:stats` (session + lifetime statistics), `/mem0:digest` (weekly memory summary). +- **8 new hook scripts:** `on_bash_output.sh` (scan bash output for errors, surface `anti_pattern`/`bug_fix` memories), `on_git_commit_capture.sh` (detect git commit/merge/rebase, search relevant memories), `on_post_commit.sh` (prompt to save commit learnings), `on_post_compact.sh` (recovery prompt to reload context after compaction), `on_session_end.sh` (last-chance transcript capture with dedup marker), `on_subagent_stop.sh` (remind to capture learnings from non-Explore/Plan subagents), `on_tool_failure.sh` (classify MCP failures as auth/rate-limit/network, suggest recovery), `on_pre_commit.py` (capture staged changes via REST API). +- **PostHog telemetry (`telemetry.py`):** Anonymous, fire-and-forget, 10% sampled. Uses stdlib `urllib` (no SDK dependency). Sends event type, platform, plugin version, anonymized identity. Never sends memory content or API keys. Opt-out via `MEM0_TELEMETRY=false`. +- **10 new memory categories in `setup_coding_categories.py`:** `dependency_decisions`, `performance_findings`, `security_constraints`, `testing_patterns`, `data_model`, `api_contracts`, `deployment_runbook`, `team_norms`, `domain_glossary`, `experiment_results`. +- **Dependency management (`ensure_deps.sh`):** Installs `mem0ai` into persistent venv at `${CLAUDE_PLUGIN_DATA}/venv`. Skips re-install if `requirements.txt` hash unchanged. Runs on `Setup(init|maintenance)` hook and every `SessionStart`. +- **Competing tool importer (`import_competing_tools.py`):** Parses and uploads configs from `.cursorrules`, `.github/copilot-instructions.md`, `memory-bank/`, `.continue/rules.md`. Splits by Markdown headers/horizontal rules. +- **Export file parser (`parse_export_file.py`):** Parses YAML-frontmatter Markdown format into JSON array. +- **Config parser (`parse_mem0_config.py`):** Reads `mem0.md` and extracts `Retention` section into category-to-days mapping. +- **Auto-onboarding:** `on_session_start.sh` detects first-time projects and triggers `/mem0:onboard` automatically. +- **`mem0.md` config loading:** Session start parses `mem0.md` and injects project config into context. +- **Inactive API key banner:** Shows `Mem0 Inactive` with `api_key=NOT_SET` instead of silently exiting. +- **`requirements.txt`:** Declares `mem0ai` as plugin's Python dependency. +- **Tests:** `test_coding_categories.py`, `test_import_competing_tools.py`, `test_parse_export_file.py`, `test_parse_mem0_config.py`, `test_pre_commit.py`, `test_session_stats.py`, `test_telemetry.py`, `test_write_path.py`. + +### Changed + +- **API scoping: `metadata.project_id` → `app_id`:** All scripts and skills now pass `project_id` as top-level `app_id` parameter instead of inside `metadata`. Affects `auto_import.py`, `capture_compact_summary.py`, `on_pre_compact.py`, all stop hooks, all skill instructions. +- **API endpoint: v1 → v3:** `auto_import.py`, `capture_compact_summary.py`, `on_pre_compact.py` now call `/v3/memories/add/`. +- **API key resolution:** `_identity.py` and `_identity.sh` now check `CLAUDE_PLUGIN_OPTION_MEM0_API_KEY` (Claude Code `userConfig` env var) as fallback after `MEM0_API_KEY`. +- **`session_stats.py`:** Added per-category counters, rolling list of up to 50 recent memory IDs, `peek` subcommand for non-destructive stat reading. +- **`setup_coding_categories.py`:** Now imports `mem0ai` from managed venv via `sys.path` injection. +- **`on_stop_cursor.sh`:** Added `loop_count` guard to prevent re-entry loops. +- **`on_stop.sh`:** Removed `set -e` so session-end reminder always emits even if `session_stats.py` fails. Added dedup marker (`~/.mem0/.captured_`). +- **`on_user_prompt.sh`:** Detects source file paths in prompt and adds `metadata.files contains` search filter. Handles no-API-key case gracefully. +- **`/mem0:tour`:** Replaced 7 parallel type-filtered `search_memories` calls with single `get_memories(app_id=...)` + 3 broad searches (type filtering misses auto-categorized memories). +- **`/mem0:onboard`:** Added SDK install step, changed MCP check to use ToolSearch, writes onboard marker to prevent re-triggering. +- **`/mem0:mcp`:** Added 16-row category-to-query routing table, inline citations requirement, `branch` in metadata requirement. + +### Fixed + +- **`auto_import.py`, `capture_compact_summary.py`, `on_pre_compact.py`:** Were reading `MEM0_API_KEY` directly; now use `resolve_api_key()` so `CLAUDE_PLUGIN_OPTION_MEM0_API_KEY` is honored. +- **`on_pre_compact.py`:** `resolve_project_id()` and `resolve_branch()` now receive `cwd` from hook input instead of `os.getcwd()`, fixing incorrect identification when hook fires in different directory. +- **`on_stop_codex.sh`, `on_stop_cursor.sh`:** Missing `_identity.sh` source call; API key resolution via `userConfig` fallback was broken. +- **`on_post_tool_use.sh`:** Input field corrected from `tool_result` to `tool_output` to match hook JSON schema. + ## 0.2.0 ### Added diff --git a/mem0-plugin/README.md b/mem0-plugin/README.md index 9e28aa507..04fc68323 100644 --- a/mem0-plugin/README.md +++ b/mem0-plugin/README.md @@ -1,6 +1,6 @@ -# Mem0 Plugin for Claude Code, Claude Cowork, Cursor & Codex +# Mem0 Plugin for Claude Code, Claude Cowork, Cursor, Codex, OpenCode & Antigravity -Add persistent memory to your AI workflows. Store, retrieve, and manage memories across sessions using the Mem0 Platform. Works with **Claude Code** (CLI), **Claude Cowork** (desktop app), **Cursor**, and **Codex**. +Add persistent memory to your AI workflows. Store, retrieve, and manage memories across sessions using the Mem0 Platform. Works with **Claude Code** (CLI), **Claude Cowork** (desktop app), **Cursor**, **Codex**, **OpenCode**, and **Antigravity**. ## Quick path for agents @@ -21,7 +21,9 @@ Humans setting up Mem0 by hand should continue with Step 1 below. 1. Sign up at [app.mem0.ai](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-readme) if you haven't already 2. Go to [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-readme) 3. Click **Create API Key** and copy the key (starts with `m0-`) -4. Add it to your shell profile: +4. Set the key using **one** of these methods: + + **CLI** — add to your shell profile: ```bash # For zsh (default on macOS) @@ -33,6 +35,12 @@ Humans setting up Mem0 by hand should continue with Step 1 below. source ~/.bashrc ``` + **Desktop app** — use the local environment editor: + + Click the environment dropdown next to the prompt box → hover over **Local** → click the **gear icon** → add `MEM0_API_KEY` with your key. Values are stored encrypted on your machine. + + > **Note:** The Desktop app does not inherit custom environment variables from shell profiles — it only reads `PATH`. You must use the local environment editor for Desktop. + 5. Confirm it's set: ```bash @@ -147,35 +155,114 @@ Add the following to your `.cursor/mcp.json`: Install from the [Cursor Marketplace](https://cursor.com/marketplace) for the complete experience including lifecycle hooks and the Mem0 SDK skill. +### OpenCode + +```bash +bunx @mem0/opencode-plugin@latest install +``` + +Or via OpenCode's built-in CLI: `opencode plugin @mem0/opencode-plugin` + +Then add the MCP server to your `opencode.json` (project or global at `~/.config/opencode/opencode.json`): + +```json +{ + "mcp": { + "mem0": { + "type": "remote", + "url": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token {env:MEM0_API_KEY}" + }, + "oauth": false + } + } +} +``` + +Restart OpenCode. The plugin installs hooks and skills automatically. Drop the `plugin` install if you only want MCP. + +See [OpenCode integration docs](https://docs.mem0.ai/integrations/opencode) for full details. + +### Antigravity (Google) + +**Option A — degit** (recommended): + +```bash +# Install the plugin (MCP server, hooks, scripts) +npx degit mem0ai/mem0/mem0-plugin ~/.gemini/config/plugins/mem0 +``` + +This installs the MCP server, lifecycle hooks, and shared scripts. + +See [Antigravity integration docs](https://docs.mem0.ai/integrations/antigravity) for full details. + +## Post-Installation: Run `/mem0:onboard` + +After installing, start a new session and run: + +``` +/mem0:onboard +``` + +This runs the setup wizard which: +1. Verifies your API key and MCP connection +2. Detects and imports project files (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`) +3. Installs coding-optimized memory categories +4. Shows your identity (user ID, project scope, branch) + +The onboarding is idempotent — safe to re-run anytime. On first session in a new project (0 memories), Claude is prompted to run it automatically. + ## Verify it works -After installing, confirm the MCP server is connected: +After onboarding, confirm everything is connected: -1. Start a new session (or restart your current one) -2. Ask: *"List my mem0 entities"* or *"Search my memories for hello"* -3. If the `mem0` tools appear and respond, you're all set +1. Run `/mem0:health` to check connectivity +2. Run `/mem0:stats` to see memory counts +3. Try `/mem0:remember "we use TypeScript"` then `/mem0:tour` to see it stored + +## Available Skills + +The plugin includes 17 skills accessible via `/mem0:` commands: + +| Command | Description | +|---------|-------------| +| `/mem0:remember` | Store a memory verbatim — decisions, preferences, conventions | +| `/mem0:tour` | Browse all memories grouped by category | +| `/mem0:peek` | Quick search with compact one-liner results | +| `/mem0:stats` | Session and project memory statistics | +| `/mem0:dream` | Consolidate memories — merge duplicates, resolve contradictions | +| `/mem0:pin` | Protect critical memories from pruning | +| `/mem0:forget` | Delete memories by search or ID | +| `/mem0:health` | Diagnose connectivity, API key, and read/write | +| `/mem0:export` | Export memories to portable Markdown | +| `/mem0:import` | Import memories from export file or MEMORY.md | +| `/mem0:list-projects` | List all projects with stored memories | +| `/mem0:switch-project` | Override auto-detected project scope | +| `/mem0:memory-reviewer` | Audit memory quality — duplicates, contradictions, stale | +| `/mem0:context-loader` | Pre-load relevant memories for current task | ## What's included -| Component | Claude Code / Cowork | Cursor (Marketplace) | Cursor (Deeplink/Manual) | Codex (Sideload) | Codex (Direct MCP) | -|-----------|:--------------------:|:--------------------:|:------------------------:|:----------------:|:------------------:| -| MCP Server | Yes | Yes | Yes | Yes | Yes | -| Lifecycle Hooks | Yes | Yes | No | Opt-in | No | -| Mem0 SDK Skill | Yes | Yes | No | Yes | No | -| Memory Protocol Skill | No | No | No | Yes | No | +| Component | Claude Code / Cowork | Cursor (Marketplace) | Cursor (Deeplink/Manual) | Codex (Sideload) | Codex (Direct MCP) | OpenCode (Full) | OpenCode (MCP) | Antigravity | +|-----------|:--------------------:|:--------------------:|:------------------------:|:----------------:|:------------------:|:---------------:|:--------------:|:-----------:| +| MCP Server | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| Lifecycle Hooks | Yes | Yes | No | Opt-in | No | Yes | No | Yes | +| Mem0 SDK Skill | Yes | Yes | No | Yes | No | Yes | No | Yes | - **MCP Server** — Connects to the Mem0 remote MCP server (`mcp.mem0.ai`), providing tools to add, search, update, and delete memories. No local dependencies required. -- **Lifecycle Hooks** — Automatic memory capture at key points. Claude Code and Cursor wire hooks up natively when the plugin is installed (session start, context compaction, task completion, session end). Codex hooks are opt-in via a one-time installer (`scripts/install_codex_hooks.py`) that writes entries into `~/.codex/hooks.json` for `SessionStart`, `UserPromptSubmit`, and `Stop`. +- **Lifecycle Hooks** — Automatic memory capture at key points. Claude Code, Cursor, OpenCode, and Antigravity wire hooks natively when the full plugin is installed. Codex hooks are opt-in via a one-time installer (`scripts/install_codex_hooks.py`). - **Mem0 SDK Skill** — Guides the AI on how to integrate the Mem0 SDK (Python & TypeScript) into your applications. -- **Memory Protocol Skill** — Codex-specific skill that instructs the agent to retrieve relevant memories at task start, store learnings on completion, and capture session state before context loss. Complements the lifecycle hooks on Codex. ## Updating the plugin -When the plugin updates (new version pulled from the marketplace, or a fresh local install), the MCP server connection in your existing Claude Code / Cursor / Codex session is left holding a stale handle and stops responding. **Restart your client to reconnect:** +When the plugin updates (new version pulled from the marketplace, or a fresh local install), the MCP server connection in your existing session is left holding a stale handle and stops responding. **Restart your client to reconnect:** - **Claude Code:** run `/restart` in the prompt, or close and reopen the CLI. - **Cursor:** quit and relaunch. - **Codex:** restart the editor session. +- **OpenCode:** restart the session. +- **Antigravity:** restart the session. Your `MEM0_API_KEY` doesn't need to be re-entered — the auth header is re-read from your environment on the new session. The plugin's MCP config uses `${MEM0_API_KEY}` interpolation at session start, not at install time, so as long as the env var is set persistently (in your shell profile or `~/.claude/settings.json` `env` block), reconnection is automatic on restart. diff --git a/mem0-plugin/hooks.json b/mem0-plugin/hooks.json new file mode 100644 index 000000000..ddf38b900 --- /dev/null +++ b/mem0-plugin/hooks.json @@ -0,0 +1,81 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "name": "mem0-ensure-deps", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/ensure_deps.sh 2>/dev/null || true", + "timeout": 60 + }, + { + "name": "mem0-session-start", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_session_start.sh 2>/dev/null || true" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "name": "mem0-user-prompt", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_user_prompt.sh 2>/dev/null || true", + "timeout": 8 + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "name": "mem0-block-memory-write", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/block_memory_write.sh" + } + ] + }, + { + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", + "hooks": [ + { + "name": "mem0-enforce-metadata", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/enforce_metadata_defaults.sh", + "timeout": 3 + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", + "hooks": [ + { + "name": "mem0-post-tool", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_post_tool_use.sh", + "timeout": 3 + } + ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "name": "mem0-bash-output", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_bash_output.sh", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/mem0-plugin/hooks/codex-hooks.json b/mem0-plugin/hooks/codex-hooks.json index 6676d32f3..4756f28a2 100644 --- a/mem0-plugin/hooks/codex-hooks.json +++ b/mem0-plugin/hooks/codex-hooks.json @@ -1,12 +1,34 @@ { "hooks": { - "SessionStart": [ + "PreToolUse": [ { - "matcher": "startup|resume", + "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", - "command": "${CODEX_PLUGIN_ROOT}/scripts/on_session_start.sh", + "command": "${PLUGIN_ROOT}/scripts/block_memory_write.sh", + "timeout": 3 + } + ] + }, + { + "matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory|mcp__mem0__search_memories|mcp__plugin_mem0_mem0__search_memories|mcp__mem0__get_memories|mcp__plugin_mem0_mem0__get_memories|mcp__mem0__delete_all_memories|mcp__plugin_mem0_mem0__delete_all_memories", + "hooks": [ + { + "type": "command", + "command": "${PLUGIN_ROOT}/scripts/enforce_metadata_defaults.sh", + "timeout": 3 + } + ] + } + ], + "SessionStart": [ + { + "matcher": "startup|resume|compact", + "hooks": [ + { + "type": "command", + "command": "${PLUGIN_ROOT}/scripts/on_session_start.sh", "statusMessage": "Loading mem0 context..." } ] @@ -17,32 +39,42 @@ "hooks": [ { "type": "command", - "command": "${CODEX_PLUGIN_ROOT}/scripts/on_user_prompt.sh", + "command": "${PLUGIN_ROOT}/scripts/on_user_prompt.sh", "statusMessage": "Checking memory relevance...", - "timeout": 5 + "timeout": 12 } ] } ], "PostToolUse": [ { - "matcher": "mcp__mem0__", + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", "hooks": [ { "type": "command", - "command": "${CODEX_PLUGIN_ROOT}/scripts/on_post_tool_use.sh", + "command": "${PLUGIN_ROOT}/scripts/on_post_tool_use.sh", "timeout": 3 } ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "${PLUGIN_ROOT}/scripts/on_bash_output.sh", + "timeout": 12 + } + ] } ], - "Stop": [ + "PreCompact": [ { "hooks": [ { "type": "command", - "command": "${CODEX_PLUGIN_ROOT}/scripts/on_stop_codex.sh", - "timeout": 10 + "command": "${PLUGIN_ROOT}/scripts/on_pre_compact.sh", + "statusMessage": "Preparing pre-compaction summary..." } ] } diff --git a/mem0-plugin/hooks/cursor-hooks.json b/mem0-plugin/hooks/cursor-hooks.json index 0ec8f68ad..e5737ac37 100644 --- a/mem0-plugin/hooks/cursor-hooks.json +++ b/mem0-plugin/hooks/cursor-hooks.json @@ -1,4 +1,5 @@ { + "version": 1, "hooks": { "sessionStart": [ { @@ -9,14 +10,24 @@ "preToolUse": [ { "command": "${CURSOR_PLUGIN_ROOT}/scripts/block_memory_write_cursor.sh", - "matcher": "Write|Edit" + "matcher": "Write|Edit|MultiEdit" + }, + { + "command": "${CURSOR_PLUGIN_ROOT}/scripts/enforce_metadata_defaults.sh", + "matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory|mcp__mem0__search_memories|mcp__plugin_mem0_mem0__search_memories|mcp__mem0__get_memories|mcp__plugin_mem0_mem0__get_memories|mcp__mem0__delete_all_memories|mcp__plugin_mem0_mem0__delete_all_memories", + "timeout": 3 } ], "postToolUse": [ { "command": "${CURSOR_PLUGIN_ROOT}/scripts/on_post_tool_use_cursor.sh", - "matcher": "mcp__mem0__", + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", "timeout": 3 + }, + { + "command": "${CURSOR_PLUGIN_ROOT}/scripts/on_bash_output.sh", + "matcher": "Bash", + "timeout": 12 } ], "preCompact": [ @@ -24,16 +35,10 @@ "command": "${CURSOR_PLUGIN_ROOT}/scripts/on_pre_compact_cursor.sh" } ], - "stop": [ - { - "command": "${CURSOR_PLUGIN_ROOT}/scripts/on_stop_cursor.sh", - "timeout": 10 - } - ], "beforeSubmitPrompt": [ { "command": "${CURSOR_PLUGIN_ROOT}/scripts/on_user_prompt_cursor.sh", - "timeout": 5 + "timeout": 12 } ] } diff --git a/mem0-plugin/hooks/hooks.json b/mem0-plugin/hooks/hooks.json index 5f8fad83c..b4f5648aa 100644 --- a/mem0-plugin/hooks/hooks.json +++ b/mem0-plugin/hooks/hooks.json @@ -1,6 +1,29 @@ { "hooks": { + "Setup": [ + { + "matcher": "init|maintenance", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ensure_deps.sh", + "statusMessage": "Installing mem0 SDK...", + "timeout": 120 + } + ] + } + ], "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/requirements.txt\" \"${CLAUDE_PLUGIN_DATA:-$HOME/.mem0/plugin-data}/requirements.txt\" >/dev/null 2>&1 || \"${CLAUDE_PLUGIN_ROOT}/scripts/ensure_deps.sh\"", + "statusMessage": "Installing mem0 SDK...", + "timeout": 60 + } + ] + }, { "matcher": "startup|resume|compact", "hooks": [ @@ -14,18 +37,28 @@ ], "PreToolUse": [ { - "matcher": "Write|Edit", + "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/block_memory_write.sh" } ] + }, + { + "matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory|mcp__mem0__search_memories|mcp__plugin_mem0_mem0__search_memories|mcp__mem0__get_memories|mcp__plugin_mem0_mem0__get_memories|mcp__mem0__delete_all_memories|mcp__plugin_mem0_mem0__delete_all_memories", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/enforce_metadata_defaults.sh", + "timeout": 3 + } + ] } ], "PostToolUse": [ { - "matcher": "mcp__mem0__", + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", "hooks": [ { "type": "command", @@ -33,6 +66,16 @@ "timeout": 3 } ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_bash_output.sh", + "timeout": 5 + } + ] } ], "PreCompact": [ @@ -46,17 +89,6 @@ ] } ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_stop.sh", - "timeout": 10 - } - ] - } - ], "UserPromptSubmit": [ { "hooks": [ @@ -64,18 +96,7 @@ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_user_prompt.sh", "statusMessage": "Checking memory relevance...", - "timeout": 5 - } - ] - } - ], - "TaskCompleted": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_task_completed.sh", - "timeout": 10 + "timeout": 8 } ] } diff --git a/mem0-plugin/mcp_config.json b/mem0-plugin/mcp_config.json new file mode 100644 index 000000000..86e2428d5 --- /dev/null +++ b/mem0-plugin/mcp_config.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "mem0": { + "serverUrl": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token ${MEM0_API_KEY}" + } + } + } +} diff --git a/mem0-plugin/plugin.json b/mem0-plugin/plugin.json new file mode 100644 index 000000000..2f9086c5a --- /dev/null +++ b/mem0-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "id": "mem0", + "name": "mem0", + "version": "0.1.0", + "description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.", + "author": { "name": "Mem0", "email": "support@mem0.ai" }, + "publisher": "mem0ai", + "homepage": "https://mem0.ai", + "repository": "https://github.com/mem0ai/mem0", + "license": "Apache-2.0", + "keywords": ["memory", "persistence", "personalization", "mcp", "semantic-search"], + "contextFileName": "AGENTS.md" +} diff --git a/mem0-plugin/requirements.txt b/mem0-plugin/requirements.txt new file mode 100644 index 000000000..633d7eaad --- /dev/null +++ b/mem0-plugin/requirements.txt @@ -0,0 +1 @@ +mem0ai diff --git a/mem0-plugin/scripts/_chunking.py b/mem0-plugin/scripts/_chunking.py new file mode 100644 index 000000000..359199d9d --- /dev/null +++ b/mem0-plugin/scripts/_chunking.py @@ -0,0 +1,75 @@ +"""Shared content-chunking utilities for mem0-plugin import scripts.""" + +from __future__ import annotations + +MIN_CHUNK_CHARS = 50 +MAX_CHUNK_CHARS = 10_000 + + +def split_by_headers(content: str, header_prefix: str = "## ") -> list[str]: + """Split content by Markdown header lines (e.g. '## '). + + The header line is included at the start of each chunk. + Returns a list of non-empty chunk strings. + """ + chunks: list[str] = [] + current_lines: list[str] = [] + + for line in content.splitlines(keepends=True): + if line.startswith(header_prefix) and current_lines: + chunk = "".join(current_lines).strip() + if chunk: + chunks.append(chunk) + current_lines = [line] + else: + current_lines.append(line) + + if current_lines: + chunk = "".join(current_lines).strip() + if chunk: + chunks.append(chunk) + + return chunks + + +def split_by_hr_or_headers(content: str) -> list[str]: + """Split content by '---' horizontal rules or '## ' headers. + + Used for .continue/rules.md which may use either convention. + """ + import re + + # Split on lines that are exactly "---" or start with "## " + chunks: list[str] = [] + current_lines: list[str] = [] + + for line in content.splitlines(keepends=True): + is_hr = re.match(r"^---\s*$", line) + is_h2 = line.startswith("## ") + + if (is_hr or is_h2) and current_lines: + chunk = "".join(current_lines).strip() + if chunk: + chunks.append(chunk) + current_lines = [] if is_hr else [line] + else: + current_lines.append(line) + + if current_lines: + chunk = "".join(current_lines).strip() + if chunk: + chunks.append(chunk) + + return chunks + + +def filter_and_truncate(chunks: list[str]) -> list[str]: + """Filter out chunks shorter than MIN_CHUNK_CHARS, truncate long chunks.""" + result: list[str] = [] + for chunk in chunks: + if len(chunk) < MIN_CHUNK_CHARS: + continue + if len(chunk) > MAX_CHUNK_CHARS: + chunk = chunk[:MAX_CHUNK_CHARS] + result.append(chunk) + return result diff --git a/mem0-plugin/scripts/_identity.py b/mem0-plugin/scripts/_identity.py index e2e88fe42..d60ccb4a3 100644 --- a/mem0-plugin/scripts/_identity.py +++ b/mem0-plugin/scripts/_identity.py @@ -1,13 +1,71 @@ -"""Resolve mem0 user_id. +"""Resolve mem0 identity: API key, user_id, and settings. -Resolution priority: +API key resolution (first non-empty wins): + 1. MEM0_API_KEY env var (explicit / shell profile) + 2. CLAUDE_PLUGIN_OPTION_API_KEY (injected by Claude Code userConfig) + 3. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (legacy userConfig) + 4. Extract from shell profile files (~/.zshrc, ~/.bashrc, etc.) + Desktop app doesn't inherit shell env — this covers users who + set MEM0_API_KEY in their profile but use the Desktop app. + +User ID resolution: 1. MEM0_USER_ID env var (explicit override) 2. $USER, else "default" + +Settings resolution: + ~/.mem0/settings.json (user-editable, falls back to defaults) """ from __future__ import annotations import os +import re +from pathlib import Path + + +def _extract_key_from_shell_profiles() -> str: + """Extract MEM0_API_KEY from shell profile files. + + The Desktop app only reads PATH from shell profiles — env vars like + MEM0_API_KEY are not inherited. This handles the common + ``export MEM0_API_KEY=...`` pattern without sourcing the full profile. + """ + profiles = [".zshrc", ".bashrc", ".zprofile", ".bash_profile", ".profile"] + pattern = re.compile(r'^\s*(?:export\s+)?MEM0_API_KEY=(.+)$') + + for name in profiles: + path = Path.home() / name + if not path.is_file(): + continue + try: + for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + m = pattern.match(line) + if not m: + continue + value = m.group(1).strip() + value = re.sub(r'#.*$', '', value).strip() + value = value.strip("\"'") + if value and not value.startswith("$"): + return value + except OSError: + continue + return "" + + +def resolve_api_key() -> str: + key = os.environ.get("MEM0_API_KEY", "").strip() + if key: + return key + key = os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY", "").strip() + if key: + return key + key = os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", "").strip() + if key: + return key + key = _extract_key_from_shell_profiles() + if key: + return key + return "" def resolve_user_id() -> str: @@ -17,13 +75,29 @@ def resolve_user_id() -> str: return os.environ.get("USER") or "default" +def resolve_config() -> dict: + """Resolve settings from ~/.mem0/settings.json (primary) with env var overrides.""" + try: + from load_settings import load_settings + return load_settings() + except ImportError: + return { + "auto_save": True, + "auto_search": True, + "search_limit": 10, + "retention_session_days": 90, + "confidence_threshold": 0.3, + "debug": False, + } + + try: from _project import resolve_branch, resolve_project_id, save_project_mapping except ImportError: - def resolve_project_id() -> str: - return os.path.basename(os.getcwd()) + def resolve_project_id(cwd: str | None = None) -> str: + return os.path.basename(cwd or os.getcwd()) - def resolve_branch() -> str: + def resolve_branch(cwd: str | None = None) -> str: return "unknown" def save_project_mapping(cwd: str, project_id: str) -> None: diff --git a/mem0-plugin/scripts/_identity.sh b/mem0-plugin/scripts/_identity.sh index e032d2bec..8a7d3762a 100644 --- a/mem0-plugin/scripts/_identity.sh +++ b/mem0-plugin/scripts/_identity.sh @@ -1,8 +1,49 @@ -# Source this file. Sets MEM0_RESOLVED_USER_ID. +# Source this file. Sets MEM0_API_KEY, MEM0_RESOLVED_USER_ID, and settings. # -# Resolution priority: -# 1. MEM0_USER_ID env var (explicit override) -# 2. $USER, else "default" +# API key resolution (first non-empty wins): +# 1. MEM0_API_KEY env var (explicit / shell profile) +# 2. CLAUDE_PLUGIN_OPTION_API_KEY (injected by Claude Code userConfig) +# 3. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (legacy userConfig) +# 4. Extract from shell profile files (~/.zshrc, ~/.bashrc, etc.) +# Desktop app doesn't inherit shell env — this fallback covers users +# who set MEM0_API_KEY in their profile but use the Desktop app. +# +# Settings: ~/.mem0/settings.json (user-editable, falls back to defaults) + +_SCRIPT_DIR="$( cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd )" + +# Resolve API key: env var > userConfig > shell profile extraction +if [ -z "${MEM0_API_KEY:-}" ] && [ -n "${CLAUDE_PLUGIN_OPTION_API_KEY:-}" ]; then + MEM0_API_KEY="$CLAUDE_PLUGIN_OPTION_API_KEY" + export MEM0_API_KEY +fi +if [ -z "${MEM0_API_KEY:-}" ] && [ -n "${CLAUDE_PLUGIN_OPTION_MEM0_API_KEY:-}" ]; then + MEM0_API_KEY="$CLAUDE_PLUGIN_OPTION_MEM0_API_KEY" + export MEM0_API_KEY +fi +# Fallback: extract MEM0_API_KEY from shell profile files. +# The Desktop app only reads PATH from shell profiles — env vars like +# MEM0_API_KEY are not inherited. This grep-based extraction handles +# the common `export MEM0_API_KEY=...` pattern without sourcing the +# full profile (which could have side effects). +if [ -z "${MEM0_API_KEY:-}" ]; then + for _profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.zprofile" "$HOME/.bash_profile" "$HOME/.profile"; do + if [ -f "$_profile" ]; then + _extracted=$(grep -E '^\s*(export\s+)?MEM0_API_KEY=' "$_profile" 2>/dev/null \ + | tail -1 \ + | sed 's/^[^=]*=//' \ + | sed "s/^[\"']//;s/[\"']$//" \ + | sed 's/#.*//' \ + | tr -d '[:space:]') + # Only use literal values — skip variable references like ${OTHER_VAR} + if [ -n "$_extracted" ] && [ "${_extracted#\$}" = "$_extracted" ]; then + MEM0_API_KEY="$_extracted" + export MEM0_API_KEY + break + fi + fi + done +fi _mem0_resolve_identity() { if [ -n "${MEM0_USER_ID:-}" ]; then @@ -15,5 +56,30 @@ _mem0_resolve_identity() { MEM0_RESOLVED_USER_ID="$(_mem0_resolve_identity)" export MEM0_RESOLVED_USER_ID +_MEM0_IDENTITY_ANNOTATION="" +if [ -n "${MEM0_USER_ID:-}" ] && [ "$MEM0_USER_ID" != "${USER:-default}" ]; then + _MEM0_IDENTITY_ANNOTATION=" (override; default: ${USER:-default})" +fi +export _MEM0_IDENTITY_ANNOTATION + +# Load settings from ~/.mem0/settings.json +if command -v python3 >/dev/null 2>&1; then + _SETTINGS_JSON=$(PYTHONPATH="$_SCRIPT_DIR" python3 -c "from load_settings import load_settings; import json; print(json.dumps(load_settings()))" 2>/dev/null || echo "{}") + MEM0_AUTO_SAVE=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('auto_save',True)).lower())" 2>/dev/null || echo "true") + MEM0_AUTO_SEARCH=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('auto_search',True)).lower())" 2>/dev/null || echo "true") + MEM0_SEARCH_LIMIT=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('search_limit',10))" 2>/dev/null || echo "10") + MEM0_RETENTION_SESSION_DAYS=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('retention_session_days',90))" 2>/dev/null || echo "90") + MEM0_CONFIDENCE_THRESHOLD=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('confidence_threshold',0.3))" 2>/dev/null || echo "0.3") + MEM0_DEBUG=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('debug',False)).lower())" 2>/dev/null || echo "false") +else + MEM0_AUTO_SAVE="true" + MEM0_AUTO_SEARCH="true" + MEM0_SEARCH_LIMIT="10" + MEM0_RETENTION_SESSION_DAYS="90" + MEM0_CONFIDENCE_THRESHOLD="0.3" + MEM0_DEBUG="false" +fi +export MEM0_AUTO_SAVE MEM0_AUTO_SEARCH MEM0_SEARCH_LIMIT MEM0_RETENTION_SESSION_DAYS MEM0_CONFIDENCE_THRESHOLD MEM0_DEBUG + # Also resolve project context -. "$( cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd )/_project.sh" +. "$_SCRIPT_DIR/_project.sh" diff --git a/mem0-plugin/scripts/_project.py b/mem0-plugin/scripts/_project.py index 692922588..eb64272b5 100644 --- a/mem0-plugin/scripts/_project.py +++ b/mem0-plugin/scripts/_project.py @@ -3,6 +3,7 @@ Resolution priority (project_id): 1. MEM0_PROJECT_ID env var (explicit override) 2. ~/.mem0/project_map.json lookup by cwd + 2b. ~/.mem0/project_map.json lookup by remote hash (self-healing fallback) 3. Git remote slug: strip protocol/prefix, strip .git, replace / and : with - e.g. git@github.com:mem0ai/mem0.git -> mem0ai-mem0 4. Fallback: basename of cwd @@ -10,6 +11,7 @@ Resolution priority (project_id): from __future__ import annotations +import hashlib import json import os import re @@ -34,6 +36,19 @@ def resolve_project_id(cwd: str | None = None) -> str: mapped = project_map.get(cwd, "").strip() if mapped: return mapped + # 2b. Remote hash fallback (self-healing when folder is moved/renamed) + remote_key = _remote_hash_key(cwd) + if remote_key: + mapped = project_map.get(remote_key, "").strip() + if mapped: + # Self-heal: write the new CWD key so future lookups are fast + project_map[cwd] = mapped + try: + with open(map_path, "w") as f: + json.dump(project_map, f, indent=2) + except OSError: + pass + return mapped except (OSError, json.JSONDecodeError, AttributeError): pass @@ -76,7 +91,7 @@ def resolve_branch(cwd: str | None = None) -> str: def save_project_mapping(cwd: str, project_id: str) -> None: - """Write cwd -> project_id into ~/.mem0/project_map.json.""" + """Write cwd -> project_id (and remote hash key -> project_id) into ~/.mem0/project_map.json.""" mem0_dir = os.path.expanduser("~/.mem0") os.makedirs(mem0_dir, exist_ok=True) map_path = os.path.join(mem0_dir, "project_map.json") @@ -88,10 +103,40 @@ def save_project_mapping(cwd: str, project_id: str) -> None: except (OSError, json.JSONDecodeError): project_map = {} project_map[cwd] = project_id + # Also write the remote hash key so the mapping survives folder moves/renames + remote_key = _remote_hash_key(cwd) + if remote_key: + project_map[remote_key] = project_id with open(map_path, "w") as f: json.dump(project_map, f, indent=2) +def _remote_hash_key(cwd: str | None = None) -> str: + """Return a stable key derived from the git remote URL. + + Runs ``git config --get remote.origin.url`` in *cwd* and returns a string + of the form ``remote:``. Returns an empty string when + the directory is not a git repo or has no remote configured. + """ + if cwd is None: + cwd = os.getcwd() + try: + result = subprocess.run( + ["git", "config", "--get", "remote.origin.url"], + capture_output=True, + text=True, + check=True, + cwd=cwd, + ) + url = result.stdout.strip() + if not url: + return "" + digest = hashlib.sha256(url.encode()).hexdigest()[:16] + return f"remote:{digest}" + except (subprocess.CalledProcessError, OSError): + return "" + + def _remote_url_to_slug(url: str) -> str: """Convert a git remote URL to a deterministic slug. diff --git a/mem0-plugin/scripts/_project.sh b/mem0-plugin/scripts/_project.sh index f08c58197..c4419ac3b 100755 --- a/mem0-plugin/scripts/_project.sh +++ b/mem0-plugin/scripts/_project.sh @@ -53,6 +53,12 @@ _mem0_resolve_project_id() { _mem0_slug="${_mem0_slug//:/-}" if [ -n "$_mem0_slug" ]; then printf '%s' "$_mem0_slug" + _MEM0_PERSIST_CWD="$PWD" _MEM0_PERSIST_SLUG="$_mem0_slug" python3 -c " +import os, sys +sys.path.insert(0, '$(dirname "${BASH_SOURCE[0]:-$0}")') +from _project import save_project_mapping +save_project_mapping(os.environ['_MEM0_PERSIST_CWD'], os.environ['_MEM0_PERSIST_SLUG']) +" 2>/dev/null || true return fi fi diff --git a/mem0-plugin/scripts/_search.py b/mem0-plugin/scripts/_search.py new file mode 100644 index 000000000..81a38785a --- /dev/null +++ b/mem0-plugin/scripts/_search.py @@ -0,0 +1,79 @@ +"""Shared mem0 search API helper. + +Wraps POST /v3/memories/search/ into a single function call. +All pre-fetch hooks use this instead of duplicating urllib boilerplate. +""" + +from __future__ import annotations + +import json +import urllib.request + +SEARCH_URL = "https://api.mem0.ai/v3/memories/search/" +SEARCH_TIMEOUT = 5 + + +def _do_search(api_key: str, payload: dict) -> list[dict]: + body = json.dumps(payload).encode() + req = urllib.request.Request( + SEARCH_URL, + data=body, + headers={"Authorization": f"Token {api_key}", "Content-Type": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(req, timeout=SEARCH_TIMEOUT) as r: + data = json.loads(r.read()) + return data if isinstance(data, list) else data.get("results", []) + + +def search_memories( + api_key: str, + user_id: str, + project_id: str, + query: str, + metadata_type: str | None = None, + metadata_filters: dict | None = None, + top_k: int = 3, + min_score: float = 0.0, + rerank: bool = False, + threshold: float = 0.3, +) -> list[dict]: + if not api_key: + return [] + + base_clauses: list[dict] = [{"user_id": user_id}, {"app_id": project_id}] + if metadata_type: + base_clauses.append({"metadata": {"type": metadata_type}}) + if metadata_filters: + for key, value in metadata_filters.items(): + base_clauses.append({"metadata": {key: value}}) + + base_payload: dict = {"query": query, "top_k": top_k, "threshold": threshold} + if rerank: + base_payload["rerank"] = True + + try: + payload = {**base_payload, "filters": {"AND": list(base_clauses)}} + results = _do_search(api_key, payload)[:top_k] + + if min_score > 0: + results = [m for m in results if m.get("score", 0) >= min_score] + return results + except Exception: + return [] + + +def format_results_for_context( + memories: list[dict], + heading: str = "Relevant memories", +) -> str: + if not memories: + return "" + lines = [f"### {heading}", ""] + for m in memories: + mid = m.get("id", "?")[:8] + text = m.get("memory", "")[:200] + cat = (m.get("metadata") or {}).get("type", "unknown") + lines.append(f"- [{cat}] {text} [mem0:{mid}]") + lines.append("") + return "\n".join(lines) diff --git a/mem0-plugin/scripts/auto_capture.py b/mem0-plugin/scripts/auto_capture.py new file mode 100755 index 000000000..356c02803 --- /dev/null +++ b/mem0-plugin/scripts/auto_capture.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +"""Auto-capture recent conversation exchanges into mem0. + +Runs in the background from UserPromptSubmit hook (every 3rd message). +Reads the last few exchanges from the transcript, sends them to the +mem0 API with infer=True so the platform extracts facts automatically. + +Input: env vars (MEM0_API_KEY, MEM0_RESOLVED_USER_ID, MEM0_PROJECT_ID, etc.) + argv[1] = transcript_path +Output: stderr logs only (exit 0 always — must not block) +""" + +from __future__ import annotations + +import json +import logging +import os +import sys +import urllib.error +import urllib.request + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _identity import resolve_api_key, resolve_user_id +from _project import resolve_branch, resolve_project_id + +log = logging.getLogger("mem0-auto-capture") +log.setLevel(logging.DEBUG) +_handler = logging.StreamHandler(sys.stderr) +_handler.setFormatter(logging.Formatter("[mem0-auto-capture] %(message)s")) +log.addHandler(_handler) + +if os.environ.get("MEM0_DEBUG"): + _log_dir = os.path.expanduser("~/.mem0") + try: + os.makedirs(_log_dir, exist_ok=True) + _fh = logging.FileHandler(os.path.join(_log_dir, "hooks.log")) + _fh.setFormatter(logging.Formatter("[mem0-auto-capture] %(asctime)s %(message)s")) + log.addHandler(_fh) + except OSError: + pass + +API_URL = "https://api.mem0.ai" +TAIL_LINES = 200 +MAX_CONTENT_CHARS = 8000 +MIN_CONTENT_CHARS = 100 + + +def tail_lines(filepath: str, n: int) -> list[str]: + try: + with open(filepath, "rb") as f: + f.seek(0, 2) + file_size = f.tell() + if file_size == 0: + return [] + chunk_size = min(file_size, n * 4096) + f.seek(max(0, file_size - chunk_size)) + data = f.read().decode("utf-8", errors="replace") + return data.splitlines()[-n:] + except OSError: + return [] + + +def extract_recent_exchanges(lines: list[str], max_exchanges: int = 3) -> list[dict]: + """Extract the last N user+assistant message pairs from the transcript JSONL.""" + messages = [] + for line in lines: + line = line.strip() + if not line: + continue + try: + entry = json.loads(line) + except json.JSONDecodeError: + continue + + if entry.get("isCompactSummary"): + continue + + msg = entry.get("message", {}) + role = msg.get("role", "") + if role not in ("user", "assistant"): + continue + + content = msg.get("content", "") + if isinstance(content, list): + parts = [] + for block in content: + if isinstance(block, str): + parts.append(block) + elif isinstance(block, dict) and block.get("type") == "text": + parts.append(block.get("text", "")) + content = "\n".join(parts).strip() + + if not content or len(content) < 20: + continue + + # Skip tool-call-only assistant messages + if role == "assistant" and content.startswith("{"): + continue + + messages.append({"role": role, "content": content[:2000]}) + + # Take last N exchanges (pairs of user+assistant) + if not messages: + return [] + + result = messages[-(max_exchanges * 2):] + return result + + +def store_exchange(api_key: str, messages: list[dict], user_id: str, + project_id: str, branch: str, session_id: str) -> bool: + metadata = { + "type": "auto_capture", + "source": "auto_capture", + "confidence": 0.7, + } + if branch: + metadata["branch"] = branch + if session_id: + metadata["session_id"] = session_id + + body = { + "messages": messages, + "user_id": user_id, + "app_id": project_id, + "metadata": metadata, + "infer": True, + } + + data = json.dumps(body).encode("utf-8") + req = urllib.request.Request( + f"{API_URL}/v3/memories/add/", + data=data, + headers={ + "Content-Type": "application/json", + "Authorization": f"Token {api_key}", + }, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=15) as resp: + if resp.status in (200, 201): + result = json.loads(resp.read()) + log.info("Auto-captured: event_id=%s status=%s", + result.get("event_id", "?"), result.get("status", "?")) + return True + log.warning("API returned status %d", resp.status) + return False + except urllib.error.URLError as e: + log.warning("API call failed: %s", e) + return False + + +def main(): + api_key = resolve_api_key() + if not api_key: + log.debug("MEM0_API_KEY not set, skipping") + return + + if len(sys.argv) < 2: + log.debug("No transcript_path argument") + return + + transcript_path = sys.argv[1] + if not transcript_path or not os.path.isfile(transcript_path): + log.debug("Transcript not found: %s", transcript_path) + return + + user_id = resolve_user_id() + project_id = resolve_project_id() + branch = resolve_branch() + session_id = "" + sid_file = f"/tmp/mem0_session_id_{os.environ.get('USER', 'default')}" + if os.path.isfile(sid_file): + try: + with open(sid_file) as f: + session_id = f.read().strip() + except OSError: + pass + + lines = tail_lines(transcript_path, TAIL_LINES) + if not lines: + log.debug("Transcript empty") + return + + messages = extract_recent_exchanges(lines, max_exchanges=4) + if not messages: + log.debug("No substantial exchanges found") + return + + total_chars = sum(len(m["content"]) for m in messages) + if total_chars < MIN_CONTENT_CHARS: + log.debug("Exchanges too short (%d chars), skipping", total_chars) + return + + log.info("Auto-capturing %d messages (%d chars)", len(messages), total_chars) + if store_exchange(api_key, messages, user_id, project_id, branch, session_id): + try: + import session_stats + session_stats.record_add("auto_capture") + except Exception: + pass + + +if __name__ == "__main__": + try: + main() + except Exception as e: + log.error("Unexpected error: %s", e) + sys.exit(0) diff --git a/mem0-plugin/scripts/auto_import.py b/mem0-plugin/scripts/auto_import.py index 4be62bd40..004772bbd 100644 --- a/mem0-plugin/scripts/auto_import.py +++ b/mem0-plugin/scripts/auto_import.py @@ -21,8 +21,9 @@ import urllib.error import urllib.request sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _identity import resolve_user_id -from _project import resolve_project_id +from _chunking import filter_and_truncate, split_by_headers +from _identity import resolve_api_key, resolve_user_id +from _project import resolve_branch, resolve_project_id, save_project_mapping log = logging.getLogger("mem0-auto-import") log.setLevel(logging.DEBUG) @@ -44,6 +45,49 @@ API_URL = "https://api.mem0.ai" MAX_FILE_SIZE = 100_000 # skip files over 100 KB TARGET_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".windsurfrules", "mem0.md"] HASH_STORE = os.path.expanduser("~/.mem0/file_hashes.json") +LOCK_FILE = os.path.expanduser("~/.mem0/auto_import.lock") + + +def _acquire_lock() -> bool: + """Try to acquire a file lock. Returns False if another instance is running.""" + try: + os.makedirs(os.path.dirname(LOCK_FILE), exist_ok=True) + fd = os.open(LOCK_FILE, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + os.write(fd, str(os.getpid()).encode()) + os.close(fd) + return True + except FileExistsError: + try: + mtime = os.path.getmtime(LOCK_FILE) + import time + if time.time() - mtime > 120: + os.unlink(LOCK_FILE) + return _acquire_lock() + except OSError: + pass + return False + + +def _release_lock() -> None: + try: + os.unlink(LOCK_FILE) + except OSError: + pass + + +def _git_root(cwd: str) -> str: + """Return the git repo root, or empty string if not in a repo.""" + import subprocess + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + cwd=cwd, capture_output=True, text=True, timeout=5, + ) + if result.returncode == 0: + return result.stdout.strip() + except (OSError, subprocess.TimeoutExpired): + pass + return "" def sha256_file(path: str) -> str: @@ -77,8 +121,104 @@ def save_hashes(hashes: dict[str, str]) -> None: log.warning("Could not save hash store: %s", e) -def post_memory(api_key: str, content: str, user_id: str, filename: str, project_id: str) -> bool: +def already_imported(api_key: str, user_id: str, project_id: str, filename: str) -> bool: + body = json.dumps({ + "query": filename, + "filters": { + "AND": [ + {"user_id": user_id}, + {"app_id": project_id}, + {"metadata": {"source": "auto-import"}}, + ] + }, + "top_k": 10, + "threshold": 0.0, + }).encode() + req = urllib.request.Request( + f"{API_URL}/v3/memories/search/", + data=body, + headers={"Content-Type": "application/json", "Authorization": f"Token {api_key}"}, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=5) as r: + data = json.loads(r.read()) + results = data if isinstance(data, list) else data.get("results", []) + for result in results: + meta = result.get("metadata", {}) if isinstance(result, dict) else {} + file_field = meta.get("file", "") + if file_field == filename or file_field.startswith(f"{filename}["): + return True + return False + except Exception: + return False + + +def _delete_stale_chunks(api_key: str, user_id: str, project_id: str, filename: str) -> int: + """Find and delete existing chunks for a file before re-import. Returns count deleted.""" + body = json.dumps({ + "query": filename, + "filters": { + "AND": [ + {"user_id": user_id}, + {"app_id": project_id}, + {"metadata": {"source": "auto-import"}}, + ] + }, + "top_k": 20, + "threshold": 0.0, + }).encode() + req = urllib.request.Request( + f"{API_URL}/v3/memories/search/", + data=body, + headers={"Content-Type": "application/json", "Authorization": f"Token {api_key}"}, + method="POST", + ) + ids_to_delete = [] + try: + with urllib.request.urlopen(req, timeout=10) as r: + data = json.loads(r.read()) + results = data if isinstance(data, list) else data.get("results", []) + for result in results: + if not isinstance(result, dict): + continue + meta = result.get("metadata", {}) + file_field = meta.get("file", "") + if file_field == filename or file_field.startswith(f"{filename}["): + mid = result.get("id") + if mid: + ids_to_delete.append(mid) + except Exception as e: + log.warning("Failed to search for stale chunks of %s: %s", filename, e) + return 0 + + deleted = 0 + for mid in ids_to_delete: + try: + del_req = urllib.request.Request( + f"{API_URL}/v1/memories/{mid}/", + headers={"Authorization": f"Token {api_key}"}, + method="DELETE", + ) + with urllib.request.urlopen(del_req, timeout=10): + deleted += 1 + except Exception as e: + log.warning("Failed to delete stale chunk %s: %s", mid, e) + + if deleted: + log.info("Deleted %d stale chunk(s) for %s before re-import", deleted, filename) + return deleted + + +def post_memory(api_key: str, content: str, user_id: str, filename: str, project_id: str, branch: str = "") -> bool: """POST a project profile memory to the Mem0 REST API.""" + metadata = { + "type": "project_profile", + "file": filename, + "source": "auto-import", + } + if branch: + metadata["branch"] = branch body = { "messages": [ { @@ -87,18 +227,14 @@ def post_memory(api_key: str, content: str, user_id: str, filename: str, project } ], "user_id": user_id, - "metadata": { - "type": "project_profile", - "file": filename, - "project_id": project_id, - "source": "auto-import", - }, + "app_id": project_id, + "metadata": metadata, "infer": False, } data = json.dumps(body).encode("utf-8") req = urllib.request.Request( - f"{API_URL}/v1/memories/", + f"{API_URL}/v3/memories/add/", data=data, headers={ "Content-Type": "application/json", @@ -120,7 +256,7 @@ def post_memory(api_key: str, content: str, user_id: str, filename: str, project def main() -> None: - api_key = os.environ.get("MEM0_API_KEY", "") + api_key = resolve_api_key() if not api_key: log.debug("MEM0_API_KEY not set, skipping auto-import") return @@ -128,19 +264,35 @@ def main() -> None: cwd = os.environ.get("MEM0_CWD", "").strip() or os.getcwd() user_id = resolve_user_id() project_id = resolve_project_id(cwd) + branch = resolve_branch(cwd) - log.debug("Auto-import started: cwd=%s project=%s user=%s", cwd, project_id, user_id) + save_project_mapping(cwd, project_id) + + git_root = _git_root(cwd) + search_dirs = [cwd] + if git_root and os.path.realpath(git_root) != os.path.realpath(cwd): + search_dirs.append(git_root) + + log.debug("Auto-import started: cwd=%s git_root=%s project=%s user=%s branch=%s", cwd, git_root or "(none)", project_id, user_id, branch) hashes = load_hashes() updated = False + seen_content_hashes: set[str] = set() for filename in TARGET_FILES: - filepath = os.path.join(cwd, filename) + filepath = "" + for search_dir in search_dirs: + candidate = os.path.join(search_dir, filename) + if os.path.isfile(candidate): + filepath = candidate + break - if not os.path.isfile(filepath): + if not filepath: log.debug("Not found, skipping: %s", filename) continue + filepath = os.path.realpath(filepath) + try: file_size = os.path.getsize(filepath) except OSError: @@ -157,9 +309,22 @@ def main() -> None: log.debug("Cannot hash %s: %s", filename, e) continue - hash_key = f"{project_id}:{filename}" + if current_hash in seen_content_hashes: + log.debug("Duplicate content, skipping: %s (same as earlier file)", filename) + continue + seen_content_hashes.add(current_hash) + + hash_key = f"{project_id}:{branch}:{filename}" if branch else f"{project_id}:{filename}" if hashes.get(hash_key) == current_hash: - log.debug("Unchanged, skipping: %s", filename) + if already_imported(api_key, user_id, project_id, filename): + log.debug("Unchanged and still in mem0, skipping: %s", filename) + continue + log.info("Hash matches but memories missing server-side, re-importing: %s", filename) + + elif already_imported(api_key, user_id, project_id, filename): + log.debug("Already in mem0, updating hash store: %s", filename) + hashes[hash_key] = current_hash + updated = True continue try: @@ -169,10 +334,26 @@ def main() -> None: log.debug("Cannot read %s: %s", filename, e) continue - if post_memory(api_key, content, user_id, filename, project_id): + _delete_stale_chunks(api_key, user_id, project_id, filename) + + is_markdown = filename.endswith(".md") + if is_markdown: + chunks = filter_and_truncate(split_by_headers(content)) + else: + chunks = filter_and_truncate([content]) + + if not chunks: + chunks = [content[:10000]] + + success = True + for i, chunk in enumerate(chunks): + chunk_name = f"{filename}[{i+1}/{len(chunks)}]" if len(chunks) > 1 else filename + if not post_memory(api_key, chunk, user_id, chunk_name, project_id, branch): + success = False + + if success: hashes[hash_key] = current_hash updated = True - # on API failure we don't update the hash — retry next session if updated: save_hashes(hashes) @@ -181,8 +362,13 @@ def main() -> None: if __name__ == "__main__": + if not _acquire_lock(): + log.debug("Another auto_import instance is running — skipping") + sys.exit(0) try: main() except Exception as e: log.error("Unexpected error: %s", e) + finally: + _release_lock() sys.exit(0) diff --git a/mem0-plugin/scripts/block_memory_write.sh b/mem0-plugin/scripts/block_memory_write.sh index bb38686d6..1b3802663 100755 --- a/mem0-plugin/scripts/block_memory_write.sh +++ b/mem0-plugin/scripts/block_memory_write.sh @@ -26,7 +26,7 @@ if [ -z "$FILE_PATH" ]; then fi case "$FILE_PATH" in - */MEMORY.md|*/.claude/memory/*) + */.claude/*/MEMORY.md|*/.claude/memory/*) echo "BLOCKED: Do not write to $FILE_PATH. Use the mem0 MCP \`add_memory\` tool instead to persist memories. This project uses mem0 for all memory storage." >&2 exit 2 ;; diff --git a/mem0-plugin/scripts/capture_compact_summary.py b/mem0-plugin/scripts/capture_compact_summary.py index 1f3960a61..d6e70b356 100644 --- a/mem0-plugin/scripts/capture_compact_summary.py +++ b/mem0-plugin/scripts/capture_compact_summary.py @@ -25,7 +25,7 @@ import urllib.request from datetime import date, timedelta sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _identity import resolve_user_id +from _identity import resolve_api_key, resolve_user_id from _project import resolve_branch, resolve_project_id log = logging.getLogger("mem0-compact-summary") @@ -97,23 +97,25 @@ def find_compact_summary(lines: list[str]) -> str: def store_summary(api_key: str, summary: str, user_id: str, session_id: str, project_id: str = "", branch: str = "") -> bool: expires = (date.today() + timedelta(days=COMPACT_SUMMARY_EXPIRY_DAYS)).isoformat() + metadata = { + "type": "compact_summary", + "source": "session-start-compact", + "session_id": session_id, + } + if branch: + metadata["branch"] = branch body = { "messages": [{"role": "user", "content": summary}], "user_id": user_id, - "metadata": { - "type": "compact_summary", - "source": "session-start-compact", - "session_id": session_id, - "project_id": project_id, - "branch": branch, - }, - "infer": False, + "app_id": project_id, + "metadata": metadata, + "infer": True, "expiration_date": expires, } data = json.dumps(body).encode("utf-8") req = urllib.request.Request( - f"{API_URL}/v1/memories/", + f"{API_URL}/v3/memories/add/", data=data, headers={ "Content-Type": "application/json", @@ -134,7 +136,7 @@ def store_summary(api_key: str, summary: str, user_id: str, session_id: str, pro def main(): - api_key = os.environ.get("MEM0_API_KEY", "") + api_key = resolve_api_key() if not api_key: log.debug("MEM0_API_KEY not set, skipping capture") return @@ -151,9 +153,10 @@ def main(): return session_id = hook_input.get("session_id", "") + cwd = hook_input.get("cwd") or None user_id = resolve_user_id() - project_id = resolve_project_id() - branch = resolve_branch() + project_id = resolve_project_id(cwd) + branch = resolve_branch(cwd) lines = tail_lines(transcript_path, MAX_TAIL_LINES) if not lines: @@ -165,8 +168,25 @@ def main(): log.debug("No isCompactSummary entry found") return + if len(summary.strip()) < 100: + log.debug("Compact summary too short (%d chars) — skipping", len(summary.strip())) + return + + marker_dir = os.path.expanduser("~/.mem0") + marker_file = os.path.join(marker_dir, f"compact_captured_{session_id}") + if session_id and os.path.isfile(marker_file): + log.info("Compact summary already captured for session %s — skipping", session_id) + return + log.info("Capturing compact summary (%d chars)", len(summary)) - store_summary(api_key, summary, user_id, session_id, project_id, branch) + if store_summary(api_key, summary, user_id, session_id, project_id, branch): + if session_id: + try: + os.makedirs(marker_dir, exist_ok=True) + with open(marker_file, "w") as f: + f.write("") + except OSError: + pass if __name__ == "__main__": diff --git a/mem0-plugin/scripts/enforce_metadata_defaults.sh b/mem0-plugin/scripts/enforce_metadata_defaults.sh new file mode 100755 index 000000000..cb5c009d5 --- /dev/null +++ b/mem0-plugin/scripts/enforce_metadata_defaults.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash +# PreToolUse hook for mem0 MCP tools. +# Injects identity (user_id, app_id) and metadata defaults when the agent +# omits them. Uses the hookSpecificOutput.updatedInput contract to modify +# tool call parameters before execution. +# +# Handles: +# add_memory — top-level user_id, app_id, metadata defaults +# search_memories — user_id/app_id into filters.AND[] +# get_memories — user_id/app_id into filters.AND[] +# delete_all_memories — top-level user_id, app_id +# +# Hook contract: +# exit 0 = allow. If stdout contains {"hookSpecificOutput": {"updatedInput": ...}}, +# the updatedInput replaces the tool's input parameters. +# exit 2 = block (stderr shown as rejection reason). + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true + +INPUT=$(cat) + +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null) + +# Determine which handler to use based on tool name +HANDLER="" +case "$TOOL_NAME" in + mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory) + HANDLER="add_memory" ;; + mcp__mem0__search_memories|mcp__plugin_mem0_mem0__search_memories) + HANDLER="search_memories" ;; + mcp__mem0__get_memories|mcp__plugin_mem0_mem0__get_memories) + HANDLER="get_memories" ;; + mcp__mem0__delete_all_memories|mcp__plugin_mem0_mem0__delete_all_memories) + HANDLER="delete_all" ;; + *) exit 0 ;; +esac + +TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // "{}"' 2>/dev/null) + +_PATCH_OUT="/tmp/mem0_enforce_$$" +trap 'rm -f "$_PATCH_OUT"' EXIT +_MEM0_TOOL_INPUT="$TOOL_INPUT" \ +_MEM0_USER_ID="${MEM0_RESOLVED_USER_ID:-}" \ +_MEM0_APP_ID="${MEM0_PROJECT_ID:-}" \ +_MEM0_HANDLER="$HANDLER" \ +python3 <<'PYEOF' > "$_PATCH_OUT" 2>/dev/null || true +import json, os, sys + +raw = os.environ.get("_MEM0_TOOL_INPUT", "{}") +try: + inp = json.loads(raw) +except Exception: + sys.exit(0) + +handler = os.environ.get("_MEM0_HANDLER", "") +resolved_uid = os.environ.get("_MEM0_USER_ID", "") +resolved_aid = os.environ.get("_MEM0_APP_ID", "") +changed = False + + +def inject_top_level_identity(inp, uid, aid): + """Inject user_id/app_id as top-level params (for add_memory, delete_all).""" + changed = False + if uid and not inp.get("user_id"): + inp["user_id"] = uid + changed = True + if aid and not inp.get("app_id"): + inp["app_id"] = aid + changed = True + return changed + + +def inject_filter_identity(inp, uid, aid): + """Inject user_id/app_id into filters.AND[] (for search/get_memories).""" + changed = False + if not uid and not aid: + return False + + filters = inp.get("filters") + + if filters is None: + # No filters at all — create from scratch + and_clauses = [] + if uid: + and_clauses.append({"user_id": uid}) + if aid: + and_clauses.append({"app_id": aid}) + inp["filters"] = {"AND": and_clauses} + return True + + if not isinstance(filters, dict): + return False + + # Check if filters already contain user_id/app_id + and_clauses = filters.get("AND") + if and_clauses is None: + # Filters exist but no AND — could be flat like {"user_id": "x"} + has_uid = "user_id" in filters + has_aid = "app_id" in filters + if has_uid and has_aid: + return False + # Convert flat filters to AND format and add missing identity + existing = [] + for k, v in list(filters.items()): + existing.append({k: v}) + if uid and not has_uid: + existing.append({"user_id": uid}) + changed = True + if aid and not has_aid: + existing.append({"app_id": aid}) + changed = True + if changed: + inp["filters"] = {"AND": existing} + return changed + + if not isinstance(and_clauses, list): + return False + + # AND array exists — check for existing user_id/app_id + has_uid = any("user_id" in c for c in and_clauses if isinstance(c, dict)) + has_aid = any("app_id" in c for c in and_clauses if isinstance(c, dict)) + + if uid and not has_uid: + and_clauses.append({"user_id": uid}) + changed = True + if aid and not has_aid: + and_clauses.append({"app_id": aid}) + changed = True + + return changed + + +if handler == "add_memory": + changed = inject_top_level_identity(inp, resolved_uid, resolved_aid) + + meta = inp.get("metadata") or {} + + if "confidence" not in meta: + meta["confidence"] = 0.7 + changed = True + if "files" not in meta: + meta["files"] = ["*"] + changed = True + if "source" not in meta: + meta["source"] = "auto_capture" + changed = True + if "type" not in meta: + meta["type"] = "task_learning" + changed = True + + if meta.get("confidence", 0) >= 1.0 and "infer" not in inp: + inp["infer"] = False + changed = True + + # Track session in metadata instead of run_id. + # run_id creates a separate entity partition in the v3 API, + # making memories invisible to search/get_memories calls + # that don't include a run_id filter. + if "session_id" not in meta: + sid = os.environ.get("MEM0_SESSION_ID", "") + if not sid: + session_file = "/tmp/mem0_session_id_" + os.environ.get("USER", "default") + if os.path.isfile(session_file): + try: + with open(session_file) as f: + sid = f.read().strip() + except OSError: + pass + if sid: + meta["session_id"] = sid + changed = True + + if changed: + inp["metadata"] = meta + +elif handler in ("search_memories", "get_memories"): + changed = inject_filter_identity(inp, resolved_uid, resolved_aid) + +elif handler == "delete_all": + changed = inject_top_level_identity(inp, resolved_uid, resolved_aid) + +if changed: + print(json.dumps(inp)) +PYEOF +PATCHED=$(cat "$_PATCH_OUT" 2>/dev/null) +rm -f "$_PATCH_OUT" + +if [ -n "$PATCHED" ] && echo "$PATCHED" | jq empty 2>/dev/null; then + jq -n --argjson updated "$PATCHED" '{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow", + "updatedInput": $updated + } + }' 2>/dev/null || true +fi + +# Track session stats here because PostToolUse hooks don't fire for plugin MCP tools. +case "$HANDLER" in + add_memory) + _CAT=$(echo "$TOOL_INPUT" | jq -r '.metadata.type // .metadata.category // ""' 2>/dev/null || echo "") + python3 "$SCRIPT_DIR/session_stats.py" add "$_CAT" 2>/dev/null & + ;; + search_memories|get_memories) + python3 "$SCRIPT_DIR/session_stats.py" search 2>/dev/null & + ;; +esac + +exit 0 diff --git a/mem0-plugin/scripts/ensure_deps.sh b/mem0-plugin/scripts/ensure_deps.sh new file mode 100755 index 000000000..8bb54a483 --- /dev/null +++ b/mem0-plugin/scripts/ensure_deps.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Install mem0ai SDK into a persistent venv inside CLAUDE_PLUGIN_DATA. +# Runs on SessionStart; skips if requirements.txt hasn't changed. +set -euo pipefail + +PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}" +DATA_DIR="${CLAUDE_PLUGIN_DATA:-${HOME}/.mem0/plugin-data}" +VENV_DIR="${DATA_DIR}/venv" +REQ_SRC="${PLUGIN_ROOT}/requirements.txt" +REQ_STAMP="${DATA_DIR}/requirements.txt" + +mkdir -p "${DATA_DIR}" + +LOCKDIR="${DATA_DIR}/.install-lock" + +needs_install=false + +if [ ! -f "${VENV_DIR}/bin/python3" ]; then + needs_install=true +elif ! diff -q "${REQ_SRC}" "${REQ_STAMP}" >/dev/null 2>&1; then + needs_install=true +fi + +if [ "${needs_install}" = "true" ]; then + if mkdir "${LOCKDIR}" 2>/dev/null; then + # We acquired the lock — proceed with installation + trap 'rmdir "${LOCKDIR}" 2>/dev/null || true' EXIT + python3 -m venv "${VENV_DIR}" 2>/dev/null || python -m venv "${VENV_DIR}" + "${VENV_DIR}/bin/pip" install --quiet --upgrade pip >/dev/null 2>&1 || true + if "${VENV_DIR}/bin/pip" install --quiet -r "${REQ_SRC}" 2>/dev/null; then + cp "${REQ_SRC}" "${REQ_STAMP}" + rm -f "${DATA_DIR}/.install-failed" + else + rm -f "${REQ_STAMP}" + touch "${DATA_DIR}/.install-failed" + echo "mem0 plugin: failed to install Python dependencies" >&2 + exit 0 + fi + else + # Another process holds the lock — wait up to 60s for it to finish + for i in $(seq 1 60); do + [ ! -d "${LOCKDIR}" ] && break + sleep 1 + done + # Check if the other process's install failed + if [ -f "${DATA_DIR}/.install-failed" ]; then + echo "mem0 plugin: dependency installation failed (by another session)" >&2 + fi + fi +fi diff --git a/mem0-plugin/scripts/import_competing_tools.py b/mem0-plugin/scripts/import_competing_tools.py new file mode 100644 index 000000000..968bbbcd2 --- /dev/null +++ b/mem0-plugin/scripts/import_competing_tools.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +"""Import memories from competing AI tool configuration files into mem0. + +Sub-commands (via sys.argv[1]): + cursorrules [--path .cursorrules] + copilot [--path .github/copilot-instructions.md] + cline [--path memory-bank/] + continue [--path .continue/rules.md] + +Each sub-command reads configuration files from competing tools, +splits them into chunks, and POSTs each chunk to the mem0 API as a +project_profile memory. + +Output: progress messages to stdout, errors to stderr +Exit: 0 always +""" + +from __future__ import annotations + +import hashlib +import json +import os +import sys +import urllib.error +import urllib.request + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _chunking import ( + filter_and_truncate, + split_by_headers, + split_by_hr_or_headers, +) +from _identity import resolve_api_key, resolve_user_id +from _project import resolve_branch, resolve_project_id + +API_URL = "https://api.mem0.ai" +HASH_STORE = os.path.expanduser("~/.mem0/import_hashes.json") + + +def _load_hashes() -> dict[str, str]: + if not os.path.isfile(HASH_STORE): + return {} + try: + with open(HASH_STORE) as f: + return json.load(f) + except (OSError, json.JSONDecodeError): + return {} + + +def _save_hashes(hashes: dict[str, str]) -> None: + os.makedirs(os.path.dirname(HASH_STORE), exist_ok=True) + try: + with open(HASH_STORE, "w") as f: + json.dump(hashes, f, indent=2) + except OSError: + pass + + +def _content_hash(content: str) -> str: + return hashlib.sha256(content.encode("utf-8")).hexdigest() + + +# --------------------------------------------------------------------------- +# API helpers +# --------------------------------------------------------------------------- + + +def post_memory(api_key: str, content: str, user_id: str, project_id: str, branch: str, source: str) -> bool: + """POST a single memory chunk to the mem0 API.""" + metadata: dict = { + "type": "project_profile", + "source": source, + } + if branch: + metadata["branch"] = branch + + body = { + "messages": [{"role": "user", "content": content}], + "user_id": user_id, + "app_id": project_id, + "metadata": metadata, + "infer": False, + } + data = json.dumps(body).encode("utf-8") + req = urllib.request.Request( + f"{API_URL}/v3/memories/add/", + data=data, + headers={ + "Content-Type": "application/json", + "Authorization": f"Token {api_key}", + }, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=20) as resp: + return resp.status in (200, 201) + except urllib.error.URLError as e: + print(f" [warn] API call failed: {e}", file=sys.stderr) + return False + + +def import_chunks(chunks: list[str], api_key: str, user_id: str, project_id: str, branch: str, source: str, hash_key: str = "") -> int: + """Import a list of content chunks; return number of successful imports. + + Skips import if content hash matches a previous run for the same hash_key.""" + if hash_key: + combined = "\n".join(chunks) + current_hash = _content_hash(combined) + hashes = _load_hashes() + if hashes.get(hash_key) == current_hash: + print(f"Already imported (unchanged) -- skipping: {hash_key}") + return 0 + else: + current_hash = "" + hashes = {} + + success = 0 + for chunk in chunks: + if post_memory(api_key, chunk, user_id, project_id, branch, source): + success += 1 + + if success > 0 and hash_key and current_hash: + hashes[hash_key] = current_hash + _save_hashes(hashes) + + return success + + +# --------------------------------------------------------------------------- +# Sub-command implementations +# --------------------------------------------------------------------------- + + +def _parse_path_arg(args: list[str], flag: str, default: str) -> str: + """Extract --path from args list, falling back to default.""" + for i, arg in enumerate(args): + if arg == flag and i + 1 < len(args): + return args[i + 1] + if arg.startswith(f"{flag}="): + return arg[len(flag) + 1:] + return default + + +def cmd_cursorrules(args: list[str]) -> None: + path = _parse_path_arg(args, "--path", ".cursorrules") + source = "cursor-import" + + api_key = resolve_api_key() + user_id = resolve_user_id() + project_id = resolve_project_id() + branch = resolve_branch() + + if not api_key: + print("Error: MEM0_API_KEY not set", file=sys.stderr) + return + + if not os.path.isfile(path): + print(f"File not found: {path}", file=sys.stderr) + return + + with open(path, encoding="utf-8", errors="replace") as f: + content = f.read() + + raw_chunks = split_by_headers(content, "## ") + # Fall back to treating the whole file as one chunk if no headers found + if not raw_chunks: + raw_chunks = [content.strip()] if content.strip() else [] + + chunks = filter_and_truncate(raw_chunks) + n = import_chunks(chunks, api_key, user_id, project_id, branch, source, hash_key=f"{project_id}:{source}:{path}") + print(f"Imported {n} memories from {source} ({path})") + + +def cmd_copilot(args: list[str]) -> None: + path = _parse_path_arg(args, "--path", ".github/copilot-instructions.md") + source = "copilot-import" + + api_key = resolve_api_key() + user_id = resolve_user_id() + project_id = resolve_project_id() + branch = resolve_branch() + + if not api_key: + print("Error: MEM0_API_KEY not set", file=sys.stderr) + return + + if not os.path.isfile(path): + print(f"File not found: {path}", file=sys.stderr) + return + + with open(path, encoding="utf-8", errors="replace") as f: + content = f.read() + + raw_chunks = split_by_headers(content, "## ") + if not raw_chunks: + raw_chunks = [content.strip()] if content.strip() else [] + + chunks = filter_and_truncate(raw_chunks) + n = import_chunks(chunks, api_key, user_id, project_id, branch, source, hash_key=f"{project_id}:{source}:{path}") + print(f"Imported {n} memories from {source} ({path})") + + +def cmd_cline(args: list[str]) -> None: + dir_path = _parse_path_arg(args, "--path", "memory-bank/") + source = "cline-import" + + api_key = resolve_api_key() + user_id = resolve_user_id() + project_id = resolve_project_id() + branch = resolve_branch() + + if not api_key: + print("Error: MEM0_API_KEY not set", file=sys.stderr) + return + + if not os.path.isdir(dir_path): + print(f"Directory not found: {dir_path}", file=sys.stderr) + return + + md_files = sorted( + f for f in os.listdir(dir_path) if f.endswith(".md") + ) + if not md_files: + print(f"No .md files found in {dir_path}", file=sys.stderr) + return + + total = 0 + for filename in md_files: + filepath = os.path.join(dir_path, filename) + with open(filepath, encoding="utf-8", errors="replace") as f: + content = f.read().strip() + if not content: + continue + chunks = filter_and_truncate([content]) + n = import_chunks(chunks, api_key, user_id, project_id, branch, source, hash_key=f"{project_id}:{source}:{filepath}") + total += n + + print(f"Imported {total} memories from {source} ({dir_path})") + + +def cmd_continue(args: list[str]) -> None: + path = _parse_path_arg(args, "--path", ".continue/rules.md") + source = "continue-import" + + api_key = resolve_api_key() + user_id = resolve_user_id() + project_id = resolve_project_id() + branch = resolve_branch() + + if not api_key: + print("Error: MEM0_API_KEY not set", file=sys.stderr) + return + + if not os.path.isfile(path): + print(f"File not found: {path}", file=sys.stderr) + return + + with open(path, encoding="utf-8", errors="replace") as f: + content = f.read() + + raw_chunks = split_by_hr_or_headers(content) + if not raw_chunks: + raw_chunks = [content.strip()] if content.strip() else [] + + chunks = filter_and_truncate(raw_chunks) + n = import_chunks(chunks, api_key, user_id, project_id, branch, source, hash_key=f"{project_id}:{source}:{path}") + print(f"Imported {n} memories from {source} ({path})") + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +COMMANDS = { + "cursorrules": cmd_cursorrules, + "copilot": cmd_copilot, + "cline": cmd_cline, + "continue": cmd_continue, +} + + +def main() -> None: + if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS: + available = ", ".join(COMMANDS.keys()) + print("Usage: import_competing_tools.py [--path ]", file=sys.stderr) + print(f"Subcommands: {available}", file=sys.stderr) + sys.exit(0) + + subcommand = sys.argv[1] + remaining_args = sys.argv[2:] + COMMANDS[subcommand](remaining_args) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Unexpected error: {e}", file=sys.stderr) + sys.exit(0) diff --git a/mem0-plugin/scripts/install_codex_hooks.py b/mem0-plugin/scripts/install_codex_hooks.py index f3164287f..ac77c8221 100755 --- a/mem0-plugin/scripts/install_codex_hooks.py +++ b/mem0-plugin/scripts/install_codex_hooks.py @@ -4,7 +4,7 @@ Codex discovers hooks only at ~/.codex/hooks.json or /.codex/hooks.json, and has no plugin-host mechanism for auto-wiring hooks from an installed plugin. This installer reads the template at hooks/codex-hooks.json, rewrites -the ${CODEX_PLUGIN_ROOT} placeholder to the absolute install path of this +the ${PLUGIN_ROOT} placeholder to the absolute install path of this plugin, then merges the entries into ~/.codex/hooks.json. Re-running is idempotent: existing Mem0 entries (identified by the plugin @@ -25,6 +25,7 @@ from __future__ import annotations import argparse import json +import platform import sys from pathlib import Path @@ -44,7 +45,7 @@ OWNER_MARKER = "mem0-plugin" def load_template() -> dict: raw = TEMPLATE_FILE.read_text() - raw = raw.replace("${CODEX_PLUGIN_ROOT}", str(PLUGIN_ROOT)) + raw = raw.replace("${PLUGIN_ROOT}", str(PLUGIN_ROOT)) return json.loads(raw) @@ -126,6 +127,18 @@ def main() -> int: print(f"Removed Mem0 hooks from {HOOKS_FILE}") return 0 + # Codex lifecycle hooks register .sh paths directly in ~/.codex/hooks.json. + # On native Windows .sh has no default handler, so Codex spawning a hook + # triggers "Open With" dialogs (one OpenWith.exe per event). See #5243. + if platform.system() == "Windows": + print( + "Codex lifecycle hooks register .sh scripts directly, which Windows\n" + "cannot execute without a bash interpreter on PATH. Re-run this\n" + "installer from WSL or Git Bash, or use Mem0 via MCP / Direct tools\n", + file=sys.stderr, + ) + return 2 + if not TEMPLATE_FILE.exists(): print(f"error: template not found at {TEMPLATE_FILE}", file=sys.stderr) return 1 @@ -137,7 +150,7 @@ def main() -> int: print(f"Installed Mem0 hooks into {HOOKS_FILE}") print(f"Plugin path: {PLUGIN_ROOT}") - print("Events: SessionStart, UserPromptSubmit, Stop") + print("Events: PreToolUse, SessionStart, UserPromptSubmit, PostToolUse") if not feature_flag_enabled(): print_feature_flag_hint() diff --git a/mem0-plugin/scripts/load_settings.py b/mem0-plugin/scripts/load_settings.py new file mode 100644 index 000000000..271cec245 --- /dev/null +++ b/mem0-plugin/scripts/load_settings.py @@ -0,0 +1,49 @@ +"""Load plugin settings from ~/.mem0/settings.json. + +Settings file is user-editable. Missing file or keys fall back to defaults. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +SETTINGS_PATH = Path.home() / ".mem0" / "settings.json" + +DEFAULTS = { + "auto_save": True, + "auto_search": True, + "search_limit": 10, + "retention_session_days": 90, + "confidence_threshold": 0.3, + "debug": False, +} + + +def load_settings() -> dict: + settings = dict(DEFAULTS) + if SETTINGS_PATH.exists(): + try: + with open(SETTINGS_PATH) as f: + user = json.load(f) + settings.update({k: v for k, v in user.items() if k in DEFAULTS}) + except (json.JSONDecodeError, OSError): + pass + return settings + + +def create_default_settings() -> None: + SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True) + if not SETTINGS_PATH.exists(): + with open(SETTINGS_PATH, "w") as f: + json.dump(DEFAULTS, f, indent=2) + f.write("\n") + + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1 and sys.argv[1] == "init": + create_default_settings() + print(f"Created {SETTINGS_PATH}") + else: + print(json.dumps(load_settings())) diff --git a/mem0-plugin/scripts/on_bash_output.sh b/mem0-plugin/scripts/on_bash_output.sh new file mode 100755 index 000000000..698729f73 --- /dev/null +++ b/mem0-plugin/scripts/on_bash_output.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Hook: PostToolUse (matcher: Bash) +# +# Scans bash command output for stack traces and error patterns. +# When found, injects a search rubric telling the agent to check mem0 +# for prior occurrences of the same error. +# +# This complements on_user_prompt.sh (which catches errors in the user's +# typed message). This hook catches errors in COMMAND OUTPUT — e.g., +# when `npm test` or `python script.py` fails with a traceback. +# +# Input: JSON on stdin with tool_name, tool_input, tool_response +# Output: Context injected into Claude's next response (exit 0) + +set -uo pipefail + +INPUT=$(cat) + +TOOL_RESULT=$(echo "$INPUT" | jq -r '.tool_response // ""' 2>/dev/null || echo "") + +# Skip short output (< 50 chars unlikely to contain a real stack trace) +if [ ${#TOOL_RESULT} -lt 50 ]; then + exit 0 +fi + +# Skip git operations — not useful for error detection +COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "") +case "$COMMAND" in + *"git commit"*|*"git merge"*|*"git rebase"*) + exit 0 + ;; +esac + +# Detect stack traces and error patterns in command output +HAS_ERROR="" +if echo "$TOOL_RESULT" | grep -qE '(Traceback \(most recent call last\)|panic: |FATAL:|error\[E[0-9]+\])'; then + HAS_ERROR="true" +elif [ "$(echo "$TOOL_RESULT" | grep -cE '(Error:|Exception:)')" -ge 2 ]; then + HAS_ERROR="true" +fi + +if [ -z "$HAS_ERROR" ]; then + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true + +# Extract error class/message (first matching line) +ERROR_LINE=$(echo "$TOOL_RESULT" | grep -iE '(Error:|Exception:|panic:|FAIL:|fatal:)' | head -1 | sed 's/^[[:space:]]*//' | cut -c1-120) + +# Extract file paths from stack trace frames +TRACE_FILES=$(echo "$TOOL_RESULT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh))(:[0-9]+)?' | head -5 | sort -u) + +# Build file list for display +FILE_DISPLAY="" +if [ -n "$TRACE_FILES" ]; then + FILE_DISPLAY=$(echo "$TRACE_FILES" | sed 's/^/ - /') +fi + +USER_ID="${MEM0_RESOLVED_USER_ID:-${USER:-default}}" + +# Extract query (first 80 chars of error line) +ERROR_QUERY=$(echo "$ERROR_LINE" | cut -c1-80) + +# Telemetry (fire regardless of API key) +python3 "$SCRIPT_DIR/telemetry.py" bash_error --error_detected 2>/dev/null & + +# No API key — skip output entirely +if [ -z "${MEM0_API_KEY:-}" ]; then + exit 0 +fi + +# Pre-fetch memories: anti_pattern and bug_fix searches +RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_QUERY="$ERROR_QUERY" MEM0_SEARCH_USER="$USER_ID" \ + MEM0_API_KEY="${MEM0_API_KEY}" MEM0_PROJECT_ID="${MEM0_PROJECT_ID:-unknown}" \ + python3 -c " +import os, sys +sys.path.insert(0, os.environ.get('PYTHONPATH', '.')) +from _search import search_memories, format_results_for_context + +api_key = os.environ.get('MEM0_API_KEY', '') +user_id = os.environ.get('MEM0_SEARCH_USER', 'default') +project_id = os.environ.get('MEM0_PROJECT_ID', 'unknown') +query = os.environ.get('MEM0_SEARCH_QUERY', '') + +r1 = search_memories(api_key, user_id, project_id, query, metadata_type='anti_pattern', top_k=3) +r2 = search_memories(api_key, user_id, project_id, query, metadata_type='bug_fix', top_k=3) + +seen = set() +combined = [] +for m in r1 + r2: + mid = m.get('id', '') + if mid not in seen: + seen.add(mid) + combined.append(m) + +print(format_results_for_context(combined, heading='Prior error memories'), end='') +" 2>/dev/null || echo "") + +# Build context string for JSON output +CTX="Error detected in command output\n\n" +CTX="${CTX}\`${COMMAND}\` produced an error:\n> ${ERROR_LINE}\n" + +if [ -n "$FILE_DISPLAY" ]; then + CTX="${CTX}\nFiles in stack trace:\n${FILE_DISPLAY}\n" +fi + +if [ -n "$RESULTS" ]; then + CTX="${CTX}\n${RESULTS}\n" +fi + +CTX="${CTX}\nResolved errors are stored as anti_pattern or bug_fix memories for future reference." + +jq -cn --arg ctx "$CTX" '{ + hookSpecificOutput: { + hookEventName: "PostToolUse", + additionalContext: $ctx + } +}' 2>/dev/null || true + +exit 0 diff --git a/mem0-plugin/scripts/on_post_tool_use.sh b/mem0-plugin/scripts/on_post_tool_use.sh index fc1d3ad17..55636cfa5 100755 --- a/mem0-plugin/scripts/on_post_tool_use.sh +++ b/mem0-plugin/scripts/on_post_tool_use.sh @@ -5,7 +5,7 @@ # mcp__mem0__add_memory → record an add # mcp__mem0__search_memories → record a search # -# Input: JSON on stdin with tool_name, tool_input, tool_result +# Input: JSON on stdin with tool_name, tool_input, tool_response # Output: none (exit 0, non-blocking) set -uo pipefail @@ -16,12 +16,20 @@ INPUT=$(cat) TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null || echo "") case "$TOOL_NAME" in - mcp__mem0__add_memory) + *__add_memory) CATEGORY=$(echo "$INPUT" | jq -r '.tool_input.metadata.type // .tool_input.metadata.category // ""' 2>/dev/null || echo "") python3 "$SCRIPT_DIR/session_stats.py" add "$CATEGORY" 2>/dev/null || true + python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=add_memory 2>/dev/null & ;; - mcp__mem0__search_memories|mcp__mem0__get_memories) + *__search_memories|*__get_memories) python3 "$SCRIPT_DIR/session_stats.py" search 2>/dev/null || true + python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=search_memories 2>/dev/null & + ;; + *__delete_memory) + python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=delete_memory 2>/dev/null & + ;; + *__update_memory) + python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=update_memory 2>/dev/null & ;; esac diff --git a/mem0-plugin/scripts/on_pre_compact.py b/mem0-plugin/scripts/on_pre_compact.py index 737cbb928..882e26481 100755 --- a/mem0-plugin/scripts/on_pre_compact.py +++ b/mem0-plugin/scripts/on_pre_compact.py @@ -23,7 +23,7 @@ import urllib.request from datetime import date, timedelta sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _identity import resolve_user_id +from _identity import resolve_api_key, resolve_user_id from _project import resolve_branch, resolve_project_id log = logging.getLogger("mem0-capture") @@ -137,33 +137,23 @@ def parse_transcript(lines: list[str]) -> dict: def build_content(state: dict, source: str) -> str: - """Build structured markdown from parsed state.""" - parts = [f"## Session State ({source})\n"] + """Build minimal context — only what's needed to resume work. - if state["user_messages"]: - parts.append("### What the user was working on") - for msg in state["user_messages"]: - truncated = msg[:5000] + "..." if len(msg) > 5000 else msg - parts.append(f"- {truncated}") - parts.append("") + This is a FALLBACK safety net, not the primary capture path. + The agent handles rich memory storage via on_pre_compact.sh prompts. + This script only fires when the agent didn't store enough on its own. + + Keep it short — mem0 infer=True will extract structured facts. + """ + parts = [] if state["files_modified"]: - parts.append("### Files modified this session") - for fp in state["files_modified"]: - parts.append(f"- `{fp}`") - parts.append("") + parts.append(f"Files touched: {', '.join(state['files_modified'][:15])}") if state["bash_commands"]: - parts.append("### Recent commands") - for cmd in state["bash_commands"]: - truncated = cmd[:1000] + "..." if len(cmd) > 1000 else cmd - parts.append(f"- `{truncated}`") - parts.append("") - - if state["last_assistant_text"]: - parts.append("### Last context") - parts.append(state["last_assistant_text"]) - parts.append("") + git_cmds = [c for c in state["bash_commands"] if "git " in c] + if git_cmds: + parts.append(f"Git operations: {len(git_cmds)}") return "\n".join(parts) @@ -171,24 +161,27 @@ def build_content(state: dict, source: str) -> str: def store_memory(api_key: str, content: str, user_id: str, source: str, session_id: str = "", project_id: str = "", branch: str = "") -> bool: """Store session state as a memory via the Mem0 REST API.""" expires = (date.today() + timedelta(days=SESSION_STATE_EXPIRY_DAYS)).isoformat() + metadata = { + "type": "session_state", + "source": source, + "session_id": session_id, + } + if branch: + metadata["branch"] = branch body = { "messages": [ {"role": "user", "content": content} ], "user_id": user_id, - "metadata": { - "type": "session_state", - "source": source, - "session_id": session_id, - "project_id": project_id, - "branch": branch, - }, + "app_id": project_id, + "metadata": metadata, "expiration_date": expires, + "infer": True, } data = json.dumps(body).encode("utf-8") req = urllib.request.Request( - f"{API_URL}/v1/memories/", + f"{API_URL}/v3/memories/add/", data=data, headers={ "Content-Type": "application/json", @@ -209,15 +202,51 @@ def store_memory(api_key: str, content: str, user_id: str, source: str, session_ return False +def format_status(state: dict, source: str, stored: bool, skipped_reason: str = "") -> str: + """Build a clean, readable status line for terminal display.""" + files_count = len(state.get("files_modified", [])) + git_cmds = [c for c in state.get("bash_commands", []) if "git " in c] + user_msgs = len(state.get("user_messages", [])) + + parts = [] + if files_count: + parts.append(f"{files_count} file{'s' if files_count != 1 else ''} touched") + if git_cmds: + parts.append(f"{len(git_cmds)} git op{'s' if len(git_cmds) != 1 else ''}") + if user_msgs: + parts.append(f"{user_msgs} exchange{'s' if user_msgs != 1 else ''}") + + activity = ", ".join(parts) if parts else "minimal activity" + + if source == "pre-compaction": + icon = "✨" # ✨ + label = "Pre-compaction snapshot" + else: + icon = "\U0001f4be" # 💾 + label = "Session-end snapshot" + + if skipped_reason: + return f"{icon} Mem0 {label} — {activity} — {skipped_reason}" + elif stored: + return f"{icon} Mem0 {label} — {activity} — saved to mem0" + else: + return f"{icon} Mem0 {label} — {activity} — nothing to capture" + + def main(): source = "pre-compaction" + show_status = False for arg in sys.argv[1:]: if arg.startswith("--source="): source = arg.split("=", 1)[1] + elif arg == "--status": + show_status = True - api_key = os.environ.get("MEM0_API_KEY", "") + api_key = resolve_api_key() if not api_key: log.debug("MEM0_API_KEY not set, skipping capture") + if show_status: + print("✨ Mem0 — no API key, skipping capture") return try: @@ -232,9 +261,10 @@ def main(): return session_id = hook_input.get("session_id", "") + cwd = hook_input.get("cwd") or None user_id = resolve_user_id() - project_id = resolve_project_id() - branch = resolve_branch() + project_id = resolve_project_id(cwd) + branch = resolve_branch(cwd) lines = tail_lines(transcript_path, MAX_TAIL_LINES) if not lines: @@ -242,20 +272,38 @@ def main(): return state = parse_transcript(lines) - if not state["user_messages"] and not state["files_modified"]: - log.debug("No meaningful session state to capture") + + # Skip if agent already stored memories this session — avoid duplicate writes. + stats_file = f"/tmp/mem0_session_stats_{os.environ.get('USER', 'default')}.json" + try: + with open(stats_file) as f: + stats = json.load(f) + if stats.get("adds", 0) >= 1: + log.info("Agent stored %d memories this session — skipping fallback", stats["adds"]) + if show_status: + print(format_status(state, source, False, f"agent already stored {stats['adds']} memor{'ies' if stats['adds'] != 1 else 'y'}")) + return + except (OSError, json.JSONDecodeError): + pass + + if not state["files_modified"]: + log.debug("No files modified — skipping fallback capture") + if show_status: + print(format_status(state, source, False)) return content = build_content(state, source) + if not content.strip(): + log.debug("No content to store") + if show_status: + print(format_status(state, source, False)) + return - log.info( - "Capturing session state: %d user msgs, %d files, %d commands", - len(state["user_messages"]), - len(state["files_modified"]), - len(state["bash_commands"]), - ) + log.info("Fallback capture: %d files modified", len(state["files_modified"])) + stored = store_memory(api_key, content, user_id, source, session_id, project_id, branch) - store_memory(api_key, content, user_id, source, session_id, project_id, branch) + if show_status: + print(format_status(state, source, stored)) if __name__ == "__main__": diff --git a/mem0-plugin/scripts/on_pre_compact.sh b/mem0-plugin/scripts/on_pre_compact.sh index 38a503c5d..fb27acb75 100755 --- a/mem0-plugin/scripts/on_pre_compact.sh +++ b/mem0-plugin/scripts/on_pre_compact.sh @@ -1,77 +1,23 @@ #!/usr/bin/env bash # Hook: PreCompact # -# Fires BEFORE context compaction. This is the last chance to capture -# the full context before it gets compressed. -# -# Output: Text instructions injected into Claude's context. -# Claude still has the full conversation and can write an accurate summary, -# which it stores via add_memory(infer=False) so the platform preserves -# the structure verbatim instead of running a second extraction pass. +# Fires BEFORE context compaction. Captures session state via REST API +# in the background. Runs silently — no output to user. -set -euo pipefail +set -uo pipefail if [ -n "${MEM0_DEBUG:-}" ]; then mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log" fi -cat <<'EOF' -## CRITICAL: Pre-Compaction Session Summary +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INPUT=$(cat) -Context compaction is about to happen. You are about to lose most of your conversation history. You MUST store a comprehensive session summary NOW using the mem0 `add_memory` tool. +python3 "$SCRIPT_DIR/telemetry.py" pre_compact 2>/dev/null & -### Step 1: Store session summary - -Call `add_memory` with `infer=False` and a thorough summary covering ALL of the following. - -`infer=False` is critical here: you've already done the extraction work yourself using full context. Without it, the platform runs a second LLM pass that loses your structure and pulls fragmented facts. With it, your summary is preserved verbatim. - -``` -## Session Summary (Pre-Compaction) - -### User's Goal -[What the user originally asked for and their intent] - -### What Was Accomplished -[Numbered list of tasks completed, features built, bugs fixed] - -### Key Decisions Made -[Architectural choices, design decisions, trade-offs discussed] - -### Files Created or Modified -[List of important file paths with what changed in each] - -### Current State -[What is in progress RIGHT NOW — the task you were in the middle of] -[Any pending items, blockers, or next steps] - -### Important Context -[User preferences observed, coding patterns, anything that would help -the post-compaction agent continue without asking redundant questions] -``` - -Tool call shape: -``` -add_memory( - messages=[{"role":"user","content":""}], - user_id="", - metadata={"type":"session_state","source":"pre-compaction","project_id":"","branch":""}, - infer=False, -) -``` - -### Step 2: Store any unstored learnings - -If there are learnings from this session that you haven't stored yet, store them as separate memories with `infer=False` (same reasoning -- you've already extracted the fact, don't re-extract): -- Failed approaches -> metadata `{"type": "anti_pattern"}` -- Successful strategies -> metadata `{"type": "task_learning"}` -- Architecture decisions -> metadata `{"type": "decision"}` - -### Step 3: Acknowledge - -After storing, briefly tell the user that session state has been saved and you're ready for compaction. - -Do this NOW. Do not skip any section. The quality of this summary directly determines whether you can continue the user's task after compaction. -EOF +# Capture in background, no stdout +_TMP="/tmp/mem0_precompact_input_$$.json" +printf '%s' "$INPUT" > "$_TMP" 2>/dev/null +(python3 "$SCRIPT_DIR/on_pre_compact.py" --source=pre-compaction < "$_TMP" 2>/dev/null; rm -f "$_TMP") & exit 0 diff --git a/mem0-plugin/scripts/on_session_start.sh b/mem0-plugin/scripts/on_session_start.sh index 927b0c868..64a1d3f28 100755 --- a/mem0-plugin/scripts/on_session_start.sh +++ b/mem0-plugin/scripts/on_session_start.sh @@ -1,130 +1,167 @@ #!/usr/bin/env bash -# Hook: SessionStart (matcher: startup|resume|compact) -# -# Bootstraps mem0 context at the start of every session. -# Output becomes part of Claude's context so it calls mem0 MCP tools. -# -# Input: JSON on stdin with session_id, source, transcript_path, model, cwd -# Output: Text injected into Claude's context (exit 0) - -# Intentionally omit -e so the script always outputs a bootstrap prompt -# even if jq is missing or stdin is malformed. set -uo pipefail if [ -n "${MEM0_DEBUG:-}" ]; then mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log" fi -# Skip the bootstrap entirely if no API key is configured -- the agent -# would otherwise be told to call mem0 MCP tools that will all fail. -if [ -z "${MEM0_API_KEY:-}" ]; then - exit 0 -fi - SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=_identity.sh -. "$SCRIPT_DIR/_identity.sh" - -# Initialize session stats tracker -python3 "$SCRIPT_DIR/session_stats.py" init 2>/dev/null || true +. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true INPUT=$(cat) SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"' 2>/dev/null || echo "startup") -# Fetch project-scoped memory count (best-effort, don't block on failure, 5s timeout) +if [ "$SOURCE" = "startup" ]; then + python3 "$SCRIPT_DIR/session_stats.py" init 2>/dev/null || true + rm -f /tmp/mem0_recent_reads_${USER}_* 2>/dev/null || true +fi +PYTHONPATH="$SCRIPT_DIR" python3 "$SCRIPT_DIR/load_settings.py" init 2>/dev/null || true +rm -f "/tmp/mem0_rubric_injected_${USER}" 2>/dev/null || true +rm -f /tmp/mem0_rubric_* 2>/dev/null || true +rm -f "/tmp/mem0_msg_count_${USER:-default}" 2>/dev/null || true +MEM0_SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "") +if [ -z "$MEM0_SESSION_ID" ]; then + MEM0_SESSION_ID="ses_$(date +%s)_$$" +fi +printf '%s' "$MEM0_SESSION_ID" > "/tmp/mem0_session_id_${USER}" +export MEM0_SESSION_ID + +# Persist identity to Claude's env so Bash tool calls, MCP config, and other hooks see them +if [ -n "${CLAUDE_ENV_FILE:-}" ]; then + echo "export MEM0_SESSION_ID=\"$MEM0_SESSION_ID\"" >> "$CLAUDE_ENV_FILE" + echo "export MEM0_RESOLVED_USER_ID=\"${MEM0_RESOLVED_USER_ID:-$USER}\"" >> "$CLAUDE_ENV_FILE" + echo "export MEM0_PROJECT_ID=\"${MEM0_PROJECT_ID:-unknown}\"" >> "$CLAUDE_ENV_FILE" + echo "export MEM0_BRANCH=\"${MEM0_BRANCH:-unknown}\"" >> "$CLAUDE_ENV_FILE" + if [ -n "${MEM0_API_KEY:-}" ]; then + echo "export MEM0_API_KEY=\"$MEM0_API_KEY\"" >> "$CLAUDE_ENV_FILE" + fi +fi + +if [ -z "${MEM0_API_KEY:-}" ]; then + _UID="${MEM0_RESOLVED_USER_ID:-${USER:-default}}" + _PID="${MEM0_PROJECT_ID:-unknown}" + _BR="${MEM0_BRANCH:-unknown}" + cat </dev/null 2>&1; then MEM0_COUNT=$(python3 -c " import json, os, urllib.request, urllib.error api_key = os.environ.get('MEM0_API_KEY', '') user_id = os.environ.get('MEM0_RESOLVED_USER_ID', 'default') -project_id = os.environ.get('MEM0_PROJECT_ID', '') -body = json.dumps({ - 'query': 'project context', - 'user_id': user_id, - 'filters': {'AND': [{'user_id': user_id}, {'metadata': {'project_id': project_id}}]}, - 'limit': 100, -}).encode() -req = urllib.request.Request( - 'https://api.mem0.ai/v2/memories/search/', - data=body, - headers={'Authorization': f'Token {api_key}', 'Content-Type': 'application/json'}, - method='POST', -) -try: +app_id = os.environ.get('MEM0_PROJECT_ID', '') + +def get_count(filters): + body = json.dumps({'filters': filters}).encode() + req = urllib.request.Request( + 'https://api.mem0.ai/v3/memories/?page=1&page_size=1', + headers={'Authorization': f'Token {api_key}', 'Content-Type': 'application/json'}, + data=body, method='POST', + ) with urllib.request.urlopen(req, timeout=5) as r: - results = json.loads(r.read()) - if isinstance(results, list): - n = len(results) - print(f'{n}+' if n >= 100 else str(n)) - else: - print('?') + data = json.loads(r.read()) + if isinstance(data, dict) and 'count' in data: + return data['count'] + if isinstance(data, list): + return len(data) + return 0 + +try: + base = [{'user_id': user_id}, {'app_id': app_id}] + total = get_count({'AND': base}) + print(total) except Exception: print('?') " 2>/dev/null || echo "?") fi -# Identity line is emitted before every bootstrap variant so the agent -# uses the same user_id the hooks resolved. Without this, the agent's -# search_memories/add_memory MCP calls may bind to a different bucket -# than what the hooks write to. -echo "## Mem0 Active" -echo "" -echo "\`user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT\`" -echo "" -echo "Always include \`user_id\` + \`metadata.project_id\` in every \`search_memories\` filter and \`add_memory\` call:" -echo "- user_id: \`$MEM0_RESOLVED_USER_ID\`" -echo "- project_id: \`$MEM0_PROJECT_ID\`" -echo "- branch: \`$MEM0_BRANCH\` (include in session-state / compact-summary metadata)" -echo "" +_UID="${MEM0_RESOLVED_USER_ID:-${USER:-default}}" +_ANN="${_MEM0_IDENTITY_ANNOTATION:-}" +_PID="${MEM0_PROJECT_ID:-unknown}" +_BR="${MEM0_BRANCH:-unknown}" + +cat </dev/null || echo ".") +if command -v python3 >/dev/null 2>&1; then + MEM0_PROJECT_CONFIG=$(python3 "$SCRIPT_DIR/parse_mem0_config.py" --full "$MEM0_CWD_RESOLVED" 2>/dev/null || echo "{}") + if [ -n "$MEM0_PROJECT_CONFIG" ] && [ "$MEM0_PROJECT_CONFIG" != "{}" ]; then + _CONFIG_KEYS=$(echo "$MEM0_PROJECT_CONFIG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d))" 2>/dev/null || echo "?") + echo "mem0.md loaded (${_CONFIG_KEYS} sections configured)." + fi +fi if [ "$SOURCE" = "startup" ]; then - cat <<'EOF' -## Mem0 Session Bootstrap + if [ "$MEM0_COUNT" = "0" ]; then + echo "New project with 0 memories. Invoke the mem0:onboard skill to import project files and install coding categories." + else + echo "Search mem0 for recent decisions and task learnings before responding. Run 2 parallel searches: one for decision type, one for task_learning type." + fi -You have access to persistent memory via the mem0 MCP tools. Before doing anything else: + _PROJ_KEY=$(printf '%s' "$MEM0_CWD_RESOLVED" | tr '/' '-') + _MEMORY_MD="$HOME/.claude/projects/${_PROJ_KEY}/memory/MEMORY.md" + if [ -f "$_MEMORY_MD" ] && [ -s "$_MEMORY_MD" ]; then + echo "Native MEMORY.md detected at ${_MEMORY_MD}. Add autoMemoryEnabled:false to settings.json or run /mem0:import." + fi -1. Call `search_memories` with a query related to the current project or user request to load relevant context. -2. Review the returned memories to understand what has been learned in prior sessions. -3. If appropriate, call `get_memories` to browse all stored memories for this user. - -IMPORTANT: Do NOT skip this step. Always bootstrap context first. -EOF - - # Auto-import declarative project files in background - MEM0_CWD="$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null || echo ".")" \ + MEM0_CWD="$MEM0_CWD_RESOLVED" \ python3 "$SCRIPT_DIR/auto_import.py" 2>/dev/null & elif [ "$SOURCE" = "resume" ]; then - cat <<'EOF' -## Mem0 Session Resumed - -This is a resumed session. Your prior context is already loaded. Before continuing: - -1. Call `search_memories` with a query related to the current task to refresh relevant memories. -2. If significant time has passed, search for recent project-wide updates. - -Continue where you left off. -EOF + echo "Session resumed. Search mem0 for session_state and decision memories to pick up where you left off. Run 2 parallel searches." elif [ "$SOURCE" = "compact" ]; then - # Capture the just-generated compact summary in the background. - # PreCompact fires too early to see this entry; SessionStart-compact - # is the first place isCompactSummary=true is in the transcript. - echo "$INPUT" | python3 "$SCRIPT_DIR/capture_compact_summary.py" 2>/dev/null & - - cat <<'EOF' -## Mem0 Post-Compaction Recovery - -Context was just compacted. The Claude Code-generated compact summary -is being captured to mem0 in the background as `metadata.type=compact_summary`. - -1. Call `search_memories` to reload context, layering up to three angles: - - `metadata.type=session_state` -- the rich pre-compaction summary you wrote - - `metadata.type=compact_summary` -- the platform-generated condensed summary just now - - `metadata.type=decision` / `anti_pattern` -- specific facts you stored during the session -2. Continue working from the recovered context. -EOF + echo "Context compacted. Search mem0 for session_state and decision memories to recover context. Run 2 parallel searches." + printf '%s' "$INPUT" | python3 "$SCRIPT_DIR/capture_compact_summary.py" 2>/dev/null & fi +python3 "$SCRIPT_DIR/telemetry.py" session_start --source="$SOURCE" --memory_count="${MEM0_COUNT:-0}" 2>/dev/null & + exit 0 diff --git a/mem0-plugin/scripts/on_stop.sh b/mem0-plugin/scripts/on_stop.sh deleted file mode 100755 index a555b8c33..000000000 --- a/mem0-plugin/scripts/on_stop.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -# Hook: Stop -# -# Fires when Claude finishes responding. -# Reminds Claude to store any unsaved learnings, then spawns a background -# process to capture transcript state via the Mem0 REST API directly. -# -# Input: JSON on stdin with stop_hook_active, transcript_path, cwd -# Output: Text that becomes Claude's context (exit 0), or nothing -# -# IMPORTANT: Check stop_hook_active to avoid infinite loops. - -set -euo pipefail - -if [ -n "${MEM0_DEBUG:-}" ]; then - mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log" -fi - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -INPUT=$(cat) -STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false") - -if [ "$STOP_HOOK_ACTIVE" = "true" ]; then - exit 0 -fi - -# Print session-end report -REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "") -if [ -n "$REPORT" ]; then - echo "" - echo "---" - echo "**mem0 $REPORT**" - echo "---" - echo "" -fi - -# Append to persistent session log (guarded — on_stop.sh uses set -euo pipefail) -if [ -n "$REPORT" ]; then - mkdir -p "$HOME/.mem0" 2>/dev/null || true - echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $REPORT" >> "$HOME/.mem0/session-log.md" 2>/dev/null || true -fi - -cat <<'EOF' -Before finishing, check if there are important learnings from this interaction that should be persisted using the mem0 `add_memory` tool: - -1. Were any significant decisions made? -> Store with metadata `{"type": "decision"}` -2. Were any new patterns or strategies discovered? -> Store with metadata `{"type": "task_learning"}` -3. Did any approach fail? -> Store with metadata `{"type": "anti_pattern"}` -4. Did you learn anything about the user's preferences? -> Store with metadata `{"type": "user_preference"}` -5. Were there environment/setup discoveries? -> Store with metadata `{"type": "environmental"}` - -Memories can be as detailed as needed — include full context, reasoning, code snippets, file paths, and examples. Longer, searchable memories are more valuable than vague one-liners. - -If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings. - -Always include `"project_id"` in the metadata of any memory you store. -EOF - -# Capture transcript state in the background via Mem0 REST API -echo "$INPUT" | python3 "$SCRIPT_DIR/on_pre_compact.py" --source=session-end 2>/dev/null & - -exit 0 diff --git a/mem0-plugin/scripts/on_stop_codex.sh b/mem0-plugin/scripts/on_stop_codex.sh deleted file mode 100755 index 645b471a6..000000000 --- a/mem0-plugin/scripts/on_stop_codex.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash -# Hook: Stop (Codex) -# -# Fires when Codex finishes a turn. Reminds the agent to persist any -# important learnings via the mem0 MCP tools before the turn closes. -# -# Input: JSON on stdin with session_id, turn_id, stop_hook_active, -# last_assistant_message, transcript_path, cwd, -# hook_event_name, model -# Output: JSON on stdout (Codex rejects plain text on Stop). -# - stop_hook_active=true -> {"continue": true} (let the turn end) -# - stop_hook_active=false -> {"decision":"block","reason":"..."} -# (continue the turn with the reminder as context) -# -# We must respect stop_hook_active or we'd loop forever: every "block" -# reopens the turn, which triggers Stop again when the agent settles. - -set -uo pipefail - -if [ -n "${MEM0_DEBUG:-}" ]; then - mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log" -fi - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -INPUT=$(cat) -STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false") - -if [ "$STOP_HOOK_ACTIVE" = "true" ]; then - printf '{"continue":true}\n' - exit 0 -fi - -# Session-end report (best-effort, must not break JSON output) -REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "") -REPORT_BLOCK="" -if [ -n "$REPORT" ]; then - REPORT_BLOCK="---\nmem0 $REPORT\n---\n\n" - mkdir -p "$HOME/.mem0" 2>/dev/null || true - echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $REPORT" >> "$HOME/.mem0/session-log.md" 2>/dev/null || true -fi - -REASON=$(cat < Store with metadata \`{"type": "decision"}\` -2. Were any new patterns or strategies discovered? -> Store with metadata \`{"type": "task_learning"}\` -3. Did any approach fail? -> Store with metadata \`{"type": "anti_pattern"}\` -4. Did you learn anything about the user's preferences? -> Store with metadata \`{"type": "user_preference"}\` -5. Were there environment/setup discoveries? -> Store with metadata \`{"type": "environmental"}\` - -Memories can be as detailed as needed — include full context, reasoning, code snippets, file paths, and examples. Longer, searchable memories are more valuable than vague one-liners. - -Always include \`"project_id"\` in the metadata of any memory you store. - -If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings. -EOF -) - -jq -cn --arg reason "$REASON" '{decision:"block", reason:$reason}' - -# Capture transcript state in the background via Mem0 REST API -echo "$INPUT" | python3 "$SCRIPT_DIR/on_pre_compact.py" --source=session-end 2>/dev/null & - -exit 0 diff --git a/mem0-plugin/scripts/on_stop_cursor.sh b/mem0-plugin/scripts/on_stop_cursor.sh deleted file mode 100755 index 90c41b2fe..000000000 --- a/mem0-plugin/scripts/on_stop_cursor.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -# Hook: Stop (Cursor) -# -# Fires when Cursor agent completes a turn. Wraps the same logic as -# on_stop.sh but outputs JSON (Cursor expects {"followup_message":"..."}). -# -# Input: JSON on stdin with status, loop_count, conversation_id, etc. -# Output: JSON on stdout: {"followup_message":""} - -set -uo pipefail - -if [ -n "${MEM0_DEBUG:-}" ]; then - mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log" -fi - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -INPUT=$(cat) - -# Session-end report (best-effort) -REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "") -REPORT_BLOCK="" -if [ -n "$REPORT" ]; then - REPORT_BLOCK="---\nmem0 $REPORT\n---\n\n" - mkdir -p "$HOME/.mem0" 2>/dev/null || true - echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $REPORT" >> "$HOME/.mem0/session-log.md" 2>/dev/null || true -fi - -MESSAGE=$(cat < Store with metadata \`{"type": "decision"}\` -2. Were any new patterns or strategies discovered? -> Store with metadata \`{"type": "task_learning"}\` -3. Did any approach fail? -> Store with metadata \`{"type": "anti_pattern"}\` -4. Did you learn anything about the user's preferences? -> Store with metadata \`{"type": "user_preference"}\` -5. Were there environment/setup discoveries? -> Store with metadata \`{"type": "environmental"}\` - -Always include \`"project_id"\` in the metadata of any memory you store. - -If nothing notable happened, it's fine to skip. Only store genuinely useful learnings. -EOF -) - -jq -cn --arg msg "$MESSAGE" '{followup_message:$msg}' - -# Capture transcript state in the background via Mem0 REST API -echo "$INPUT" | python3 "$SCRIPT_DIR/on_pre_compact.py" --source=session-end 2>/dev/null & - -exit 0 diff --git a/mem0-plugin/scripts/on_task_completed.sh b/mem0-plugin/scripts/on_task_completed.sh deleted file mode 100755 index 7d839cdb2..000000000 --- a/mem0-plugin/scripts/on_task_completed.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -# Hook: TaskCompleted -# -# Fires when a task is marked as completed. Reminds Claude to extract -# and store learnings via the mem0 MCP tools. -# -# Input: JSON on stdin with task_id, task_subject, task_description -# Output: Text that becomes feedback to the model (exit 0) - -set -euo pipefail - -if [ -n "${MEM0_DEBUG:-}" ]; then - mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log" -fi - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -. "$SCRIPT_DIR/_identity.sh" || true - -INPUT=$(cat) -TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject // "unknown task"' 2>/dev/null || echo "unknown task") - -cat < Store with metadata \`{"type": "task_learning"}\` -2. Were there failed approaches before finding the solution? -> Store with metadata \`{"type": "anti_pattern"}\` -3. Were there architectural decisions? -> Store with metadata \`{"type": "decision"}\` -4. Any new conventions or patterns established? -> Store with metadata \`{"type": "convention"}\` - -Memories can be as detailed as needed — include full context, reasoning, code snippets, and examples. -Only store genuinely useful learnings — skip if the task was trivial. -Include \`"project_id": "$MEM0_PROJECT_ID"\` in metadata for all memories. -EOF - -exit 0 diff --git a/mem0-plugin/scripts/on_user_prompt.sh b/mem0-plugin/scripts/on_user_prompt.sh index 2e7fc15e9..97f2bad9f 100755 --- a/mem0-plugin/scripts/on_user_prompt.sh +++ b/mem0-plugin/scripts/on_user_prompt.sh @@ -25,48 +25,177 @@ if [ ${#PROMPT} -lt 20 ]; then exit 0 fi -# No API key means the agent can't search anyway -if [ -z "${MEM0_API_KEY:-}" ]; then - exit 0 -fi - SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=_identity.sh -. "$SCRIPT_DIR/_identity.sh" +. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true + +# Rubric dedup: only inject full rubric once per session. +# Key on session ID to avoid cross-session interference. +SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "") +if [ -z "$SESSION_ID" ]; then + _SID_FILE="/tmp/mem0_session_id_${USER:-default}" + [ -f "$_SID_FILE" ] && SESSION_ID=$(cat "$_SID_FILE" 2>/dev/null) || true +fi +if [ -z "$SESSION_ID" ]; then + SESSION_ID="default_${USER:-unknown}" +fi +RUBRIC_DIR="${MEM0_RUBRIC_DIR:-/tmp}" +RUBRIC_FLAG="$RUBRIC_DIR/mem0_rubric_${SESSION_ID}" +RUBRIC_ALREADY_SHOWN="" +if [ -f "$RUBRIC_FLAG" ]; then + RUBRIC_ALREADY_SHOWN="true" +fi + +# Track message count for periodic memory-save nudges. +# Every 5th substantial message, remind the agent to store learnings. +MSG_COUNT_FILE="/tmp/mem0_msg_count_${USER:-default}" +MSG_COUNT=0 +if [ -f "$MSG_COUNT_FILE" ]; then + MSG_COUNT=$(cat "$MSG_COUNT_FILE" 2>/dev/null || echo "0") +fi +MSG_COUNT=$((MSG_COUNT + 1)) +printf '%s' "$MSG_COUNT" > "$MSG_COUNT_FILE" 2>/dev/null || true +NEEDS_SAVE_NUDGE="" +if [ $((MSG_COUNT % 5)) -eq 0 ] && [ "$MSG_COUNT" -gt 0 ]; then + NEEDS_SAVE_NUDGE="true" +fi + +# Detect stack traces and error patterns in the prompt (no API needed) +HAS_ERROR="" +if echo "$PROMPT" | grep -qE '(Traceback|panic:)'; then + HAS_ERROR="true" +elif echo "$PROMPT" | grep -qE '^\s*fatal: '; then + HAS_ERROR="true" +elif [ "$(echo "$PROMPT" | grep -cE '(Error:|Exception:|FAIL:)')" -ge 2 ]; then + HAS_ERROR="true" +fi + +# Detect file paths in the prompt (no API needed) +FILE_PATHS=$(echo "$PROMPT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh|yaml|yml|json|toml|md|sql|css|html))\b' 2>/dev/null | head -5 || echo "") + +# Detect session-resume patterns +HAS_RESUME="" +if echo "$PROMPT" | grep -qiE '(where (did )?(we|I) (leave|left) off|continue (from )?(where|last)|what were we (working|doing)|pick up where|resume (from |where)|what.s the (current|latest) (state|status)|catch me up|where are we)'; then + HAS_RESUME="true" +fi + +# Detect explicit memory-save intent +HAS_REMEMBER="" +if echo "$PROMPT" | grep -qiE '(remember (this|that)|save (this|that) (fact|info|memory|note)|store (this|that)|don.t forget (this|that)|keep (this|that) in (mind|memory))'; then + HAS_REMEMBER="true" +fi + +# Telemetry (background, fire-and-forget) +_TELEM_ARGS="" +[ -n "$HAS_ERROR" ] && _TELEM_ARGS="$_TELEM_ARGS --error_detected" +[ -n "$FILE_PATHS" ] && _TELEM_ARGS="$_TELEM_ARGS --file_paths_detected" +[ -n "$HAS_RESUME" ] && _TELEM_ARGS="$_TELEM_ARGS --resume_detected" +[ -n "$HAS_REMEMBER" ] && _TELEM_ARGS="$_TELEM_ARGS --remember_detected" +python3 "$SCRIPT_DIR/telemetry.py" user_prompt $_TELEM_ARGS 2>/dev/null & + +# No API key — emit detections only, skip search rubric +if [ -z "${MEM0_API_KEY:-}" ]; then + _PROMPT_CTX="" + if [ -n "$HAS_ERROR" ]; then + _PROMPT_CTX="Error detected in prompt. Set MEM0_API_KEY to search past debugging context." + fi + if [ -n "$FILE_PATHS" ]; then + _PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}File paths detected: ${FILE_PATHS}" + fi + if [ -n "$_PROMPT_CTX" ]; then + jq -cn --arg ctx "$_PROMPT_CTX" '{ + hookSpecificOutput: { + hookEventName: "UserPromptSubmit", + additionalContext: $ctx + } + }' + fi + exit 0 +fi USER_ID="$MEM0_RESOLVED_USER_ID" -cat </dev/null || echo "") + + if [ -n "$RESUME_RESULTS" ]; then + _PROMPT_CTX="${RESUME_RESULTS}" + fi +fi + +if [ -n "$HAS_REMEMBER" ]; then + _PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}Remember intent detected. The /mem0:remember skill auto-classifies, sets confidence=1.0, and stores verbatim." +fi + +if [ -z "$RUBRIC_ALREADY_SHOWN" ]; then + _PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}Mem0 searches apply when user references past work, decision questions, errors, or non-trivial tasks. Queries use noun-phrases, 2-4 parallel calls with different metadata.type filters, and include user_id + app_id." + touch "$RUBRIC_FLAG" 2>/dev/null || true +fi + +if [ -n "$HAS_ERROR" ]; then + _PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}Error detected in prompt. Prior occurrences are available in mem0 via anti_pattern and task_learning type filters." +fi + +if [ -n "$FILE_PATHS" ]; then + _PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}File paths detected: ${FILE_PATHS}" +fi + +# Auto-capture: directly call mem0 API in background every 3rd message. +# At MSG_COUNT=3 the 3rd response isn't in the transcript yet (hook fires +# before Claude responds), so we capture 4 exchanges instead of 3. The +# overlapping window ensures the next batch (MSG_COUNT=6) picks up the +# exchange that was incomplete in the previous batch. +TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""' 2>/dev/null || echo "") +if [ $((MSG_COUNT % 3)) -eq 0 ] && [ "$MSG_COUNT" -gt 0 ] && [ -n "$TRANSCRIPT_PATH" ]; then + python3 "$SCRIPT_DIR/auto_capture.py" "$TRANSCRIPT_PATH" 2>/dev/null & +fi + +# Prompt-based nudge as fallback when auto-capture hasn't run yet. +_ADDS=0 +_STATS_FILE="/tmp/mem0_session_stats_${USER:-default}.json" +if [ -f "$_STATS_FILE" ]; then + _ADDS=$(python3 -c "import json; print(json.load(open('$_STATS_FILE')).get('adds',0))" 2>/dev/null || echo "0") +fi + +if [ "$MSG_COUNT" -ge 3 ] && [ "$_ADDS" -lt "$((MSG_COUNT / 3))" ]; then + _PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}After responding, store any new decisions, learnings, or preferences from this exchange via add_memory. Keep it to 1 sentence per memory." +fi + +if [ -n "$_PROMPT_CTX" ]; then + jq -cn --arg ctx "$_PROMPT_CTX" '{ + hookSpecificOutput: { + hookEventName: "UserPromptSubmit", + additionalContext: $ctx + } + }' +fi exit 0 diff --git a/mem0-plugin/scripts/parse_export_file.py b/mem0-plugin/scripts/parse_export_file.py new file mode 100644 index 000000000..eb28ab919 --- /dev/null +++ b/mem0-plugin/scripts/parse_export_file.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Parse a mem0 export file and output JSON. + +Input: path to a mem0-export-*.md file (sys.argv[1]) +Output: JSON array of memory records to stdout +Exit: 0 always + +Each block in the file is delimited by lines containing exactly "---". +Blocks have a YAML-like frontmatter section (key: value lines) followed +by a blank line and the memory content text. + +Example block format: +--- +id: abc123 +created_at: 2024-01-01T00:00:00Z +type: task_learnings +confidence: 0.9 +branch: main +files: src/foo.py, src/bar.py +categories: coding_conventions, task_learnings +--- +The actual memory content text goes here. + +""" + +from __future__ import annotations + +import json +import re +import sys + + +def parse_blocks(content: str) -> list[dict]: + """Split content on '---' boundaries and parse each block. + + Returns a list of dicts with keys: + id, type, confidence, branch, files (list), categories (list), content (str) + + Blocks with empty content are skipped. + Missing optional fields default to "" (scalar) or [] (list fields). + """ + # Normalise line endings + content = content.replace("\r\n", "\n").replace("\r", "\n") + + # Split on lines that are exactly "---" + raw_blocks = re.split(r"(?m)^---\s*$", content) + + # After splitting on "---", the structure for each memory is: + # raw_blocks[0] = preamble (before first ---, typically empty) + # raw_blocks[1] = frontmatter for block 1 + # raw_blocks[2] = content for block 1 + # raw_blocks[3] = frontmatter for block 2 + # raw_blocks[4] = content for block 2 + # ... + # So frontmatter blocks are at odd indices (1, 3, 5, ...) and + # content blocks at even indices (2, 4, 6, ...). + + results: list[dict] = [] + + # Pair up frontmatter + content starting at index 1 + i = 1 + while i < len(raw_blocks): + frontmatter_raw = raw_blocks[i] + content_raw = raw_blocks[i + 1] if i + 1 < len(raw_blocks) else "" + + # Parse the frontmatter key-value pairs + fm = _parse_frontmatter(frontmatter_raw) + + # Strip leading/trailing whitespace from content + memory_content = content_raw.strip() + + # Skip blocks with empty content + if not memory_content: + i += 2 + continue + + record = { + "id": fm.get("id", ""), + "type": fm.get("type", ""), + "confidence": fm.get("confidence", ""), + "branch": fm.get("branch", ""), + "files": _parse_list_field(fm.get("files", "")), + "categories": _parse_list_field(fm.get("categories", "")), + "content": memory_content, + } + + # Include created_at if present + if "created_at" in fm: + record["created_at"] = fm["created_at"] + + results.append(record) + i += 2 + + return results + + +def _parse_frontmatter(text: str) -> dict[str, str]: + """Parse simple 'key: value' lines from frontmatter text. + + Only the first colon is used as the delimiter — values may contain colons. + Lines not matching 'key: value' are ignored. + """ + result: dict[str, str] = {} + for line in text.splitlines(): + line = line.strip() + if not line: + continue + match = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$", line) + if match: + key = match.group(1).strip() + value = match.group(2).strip() + result[key] = value + return result + + +def _parse_list_field(value: str) -> list[str]: + """Split a comma-separated value into a list, stripping whitespace. + + Returns [] for empty/whitespace-only input. + """ + if not value or not value.strip(): + return [] + return [item.strip() for item in value.split(",") if item.strip()] + + +def main() -> None: + if len(sys.argv) < 2: + print("Usage: parse_export_file.py ", file=sys.stderr) + print("[]") + sys.exit(0) + + filepath = sys.argv[1] + try: + if filepath == "-": + content = sys.stdin.read() + else: + with open(filepath, encoding="utf-8", errors="replace") as f: + content = f.read() + except OSError as e: + print(f"Error reading file: {e}", file=sys.stderr) + print("[]") + sys.exit(0) + + records = parse_blocks(content) + print(json.dumps(records, ensure_ascii=False, indent=2)) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/mem0-plugin/scripts/parse_mem0_config.py b/mem0-plugin/scripts/parse_mem0_config.py new file mode 100644 index 000000000..7dc132faa --- /dev/null +++ b/mem0-plugin/scripts/parse_mem0_config.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +"""Parse mem0.md project configuration file. + +Reads the optional ``mem0.md`` file in a project directory and extracts +retention policies from a ``## Retention`` section. + +Retention format (inside the section): + : d — keep for N days + : forever — never prune (returned as None) + +Usage (CLI): + python3 parse_mem0_config.py [] + +Prints a JSON object mapping category names to day counts (int) or null +(forever) on stdout. Prints ``{}`` when no mem0.md or no ## Retention +section is found. +""" + +from __future__ import annotations + +import json +import os +import re +import sys + + +def find_mem0_config(cwd: str) -> str | None: + """Look for ``mem0.md`` in *cwd*. + + Returns the absolute path to ``mem0.md`` if found, else ``None``. + """ + candidate = os.path.join(cwd, "mem0.md") + return candidate if os.path.isfile(candidate) else None + + +def parse_retention(content: str) -> dict[str, int | None]: + """Parse the ``## Retention`` section of *content*. + + Scans for a heading that matches ``## Retention`` (case-insensitive), + then reads lines until the next ``##``-level heading or end of string. + + Each non-blank, non-comment line inside the section is expected to be:: + + : d → days=N (int) + : forever → days=None + + Malformed lines are silently skipped. + + Args: + content: Full text of a mem0.md file. + + Returns: + Dict mapping category name (str) to day count (int) or ``None`` + (forever). Empty dict when no ``## Retention`` section is found. + """ + # Find the ## Retention section (allow any amount of trailing whitespace / + # extra words, but the heading must start with "## Retention"). + section_match = re.search( + r"^##\s+Retention[^\n]*\n(.*?)(?=^##\s|\Z)", + content, + flags=re.MULTILINE | re.DOTALL | re.IGNORECASE, + ) + if not section_match: + return {} + + section_text = section_match.group(1) + policies: dict[str, int | None] = {} + + for line in section_text.splitlines(): + # Strip comments and whitespace + line = re.sub(r"#.*$", "", line).strip() + if not line: + continue + + # Match ": " + line_match = re.match(r"^([^:]+):\s*(.+)$", line) + if not line_match: + continue + + category = line_match.group(1).strip() + value = line_match.group(2).strip().lower() + + if value == "forever": + policies[category] = None + else: + days_match = re.match(r"^(\d+)d$", value) + if days_match: + policies[category] = int(days_match.group(1)) + # else: malformed value — skip silently + + return policies + + +def parse_section_kv(content: str, heading: str) -> dict[str, str]: + """Parse a key-value section from mem0.md. + + Looks for ``## `` (case-insensitive) and reads ``key: value`` + lines until the next ``##``-level heading or end of string. + """ + pattern = rf"^##\s+{re.escape(heading)}[^\n]*\n(.*?)(?=^##\s|\Z)" + match = re.search(pattern, content, flags=re.MULTILINE | re.DOTALL | re.IGNORECASE) + if not match: + return {} + + result: dict[str, str] = {} + for line in match.group(1).splitlines(): + line = re.sub(r"#.*$", "", line).strip() + if not line: + continue + m = re.match(r"^([^:]+):\s*(.+)$", line) + if m: + result[m.group(1).strip()] = m.group(2).strip() + return result + + +def parse_section_list(content: str, heading: str) -> list[str]: + """Parse a list section from mem0.md. + + Looks for ``## `` and reads ``- item`` or bare lines. + """ + pattern = rf"^##\s+{re.escape(heading)}[^\n]*\n(.*?)(?=^##\s|\Z)" + match = re.search(pattern, content, flags=re.MULTILINE | re.DOTALL | re.IGNORECASE) + if not match: + return [] + + items: list[str] = [] + for line in match.group(1).splitlines(): + line = re.sub(r"#.*$", "", line).strip() + line = re.sub(r"^[-*]\s+", "", line).strip() + if line: + items.append(line) + return items + + +def parse_ignore_patterns(content: str) -> list[str]: + """Parse the ``## Ignore`` section of *content*. + + Each non-blank line is a glob pattern (e.g., ``node_modules``, ``*.lock``). + Lines starting with ``#`` are comments and skipped. + """ + pattern = r"^##\s+Ignore[^\n]*\n(.*?)(?=^##\s|\Z)" + match = re.search(pattern, content, flags=re.MULTILINE | re.DOTALL | re.IGNORECASE) + if not match: + return [] + + patterns: list[str] = [] + for line in match.group(1).splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + line = re.sub(r"^[-*]\s+", "", line).strip() + if line: + patterns.append(line) + return patterns + + +def load_full_config(cwd: str | None = None) -> dict: + """Load all config sections from mem0.md. + + Returns a dict with keys: retention, search, categories, identity, + ignore, project_id. + Each is populated only if the corresponding ``##`` section exists. + """ + if cwd is None: + cwd = os.getcwd() + + config_path = find_mem0_config(cwd) + if config_path is None: + return {} + + try: + with open(config_path, encoding="utf-8") as fh: + content = fh.read() + except OSError: + return {} + + config: dict = {} + + retention = parse_retention(content) + if retention: + config["retention"] = retention + + search = parse_section_kv(content, "Search") + if search: + config["search"] = search + + categories = parse_section_list(content, "Categories") + if categories: + config["categories"] = categories + config["default_categories"] = categories + + identity = parse_section_kv(content, "Identity") + if identity: + config["identity"] = identity + if "project_id" in identity: + config["project_id"] = identity["project_id"] + + ignore = parse_ignore_patterns(content) + if ignore: + config["ignore"] = ignore + + settings = parse_section_kv(content, "Settings") + if settings: + config["settings"] = settings + + return config + + +def load_retention_policies(cwd: str | None = None) -> dict[str, int | None]: + """Load retention policies from the mem0.md in *cwd*. + + Combines :func:`find_mem0_config` and :func:`parse_retention` into a + single convenience function. + """ + if cwd is None: + cwd = os.getcwd() + + config_path = find_mem0_config(cwd) + if config_path is None: + return {} + + try: + with open(config_path, encoding="utf-8") as fh: + content = fh.read() + except OSError: + return {} + + return parse_retention(content) + + +def main() -> int: + """CLI entry point. + + With ``--full``, prints the complete config. Without it, prints only + retention policies (backward-compatible). + + With ``--key ``, prints the scalar value at that path in the + full config (e.g. ``--key settings.commit_prompts``). Prints an empty + string when the key is absent. Exits 1 only on unexpected errors. + """ + full_mode = "--full" in sys.argv + + # Extract --key + key_path: str | None = None + raw_args = sys.argv[1:] + filtered_args: list[str] = [] + i = 0 + while i < len(raw_args): + if raw_args[i] == "--key" and i + 1 < len(raw_args): + key_path = raw_args[i + 1] + i += 2 + elif raw_args[i].startswith("--key="): + key_path = raw_args[i][len("--key="):] + i += 1 + elif raw_args[i].startswith("--"): + i += 1 # skip other flags like --full + else: + filtered_args.append(raw_args[i]) + i += 1 + + cwd = filtered_args[0] if filtered_args else os.getcwd() + + if key_path is not None: + config = load_full_config(cwd) + # Traverse dotted path + value: object = config + for part in key_path.split("."): + if not isinstance(value, dict): + value = None + break + value = value.get(part) + print(value if value is not None else "") + return 0 + + if full_mode: + config = load_full_config(cwd) + else: + config = load_retention_policies(cwd) + print(json.dumps(config)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/mem0-plugin/scripts/session_stats.py b/mem0-plugin/scripts/session_stats.py index b8fb1f08e..357fe4dd6 100644 --- a/mem0-plugin/scripts/session_stats.py +++ b/mem0-plugin/scripts/session_stats.py @@ -28,7 +28,13 @@ def _load() -> dict: return json.load(f) except (json.JSONDecodeError, OSError): pass - return {"adds": 0, "searches": 0, "categories": [], "started": datetime.now().isoformat()} + return { + "adds": 0, + "searches": 0, + "categories": [], + "category_counts": {}, + "started": datetime.now().isoformat(), + } def _save(stats: dict) -> None: @@ -36,15 +42,33 @@ def _save(stats: dict) -> None: json.dump(stats, f) +MAX_RECENT_IDS = 50 + + def init() -> None: - _save({"adds": 0, "searches": 0, "categories": [], "started": datetime.now().isoformat()}) + _save({ + "adds": 0, + "searches": 0, + "categories": [], + "category_counts": {}, + "recent_ids": [], + "started": datetime.now().isoformat(), + }) -def record_add(category: str = "") -> None: +def record_add(category: str = "", memory_id: str = "") -> None: stats = _load() stats["adds"] = stats.get("adds", 0) + 1 - if category and category not in stats.get("categories", []): - stats.setdefault("categories", []).append(category) + if category: + if category not in stats.get("categories", []): + stats.setdefault("categories", []).append(category) + counts = stats.setdefault("category_counts", {}) + counts[category] = counts.get(category, 0) + 1 + if memory_id: + recent = stats.setdefault("recent_ids", []) + recent.append({"id": memory_id, "category": category, "ts": datetime.now().isoformat()}) + if len(recent) > MAX_RECENT_IDS: + stats["recent_ids"] = recent[-MAX_RECENT_IDS:] _save(stats) @@ -54,23 +78,28 @@ def record_search() -> None: _save(stats) +def peek() -> str: + """Return current stats as JSON without clearing the file.""" + stats = _load() + return json.dumps(stats) + + def report() -> str: stats = _load() adds = stats.get("adds", 0) searches = stats.get("searches", 0) categories = stats.get("categories", []) - # Clean up temp file after reading - try: - os.unlink(STATS_FILE) - except OSError: - pass - if adds == 0 and searches == 0: return "" parts = [] - parts.append(f"Session: wrote {adds} memories, retrieved {searches}") + category_counts = stats.get("category_counts", {}) + if category_counts: + breakdown = ", ".join(f"{c} {n}" for c, n in sorted(category_counts.items(), key=lambda x: -x[1])) + parts.append(f"Session: wrote {adds} memories ({breakdown}), retrieved {searches}") + else: + parts.append(f"Session: wrote {adds} memories, retrieved {searches}") if categories: parts.append(f"Categories touched: {', '.join(categories)}") @@ -87,9 +116,12 @@ def main() -> int: init() elif cmd == "add": category = sys.argv[2] if len(sys.argv) > 2 else "" - record_add(category) + memory_id = sys.argv[3] if len(sys.argv) > 3 else "" + record_add(category, memory_id) elif cmd == "search": record_search() + elif cmd == "peek": + print(peek()) elif cmd == "report": result = report() if result: diff --git a/mem0-plugin/scripts/setup_coding_categories.py b/mem0-plugin/scripts/setup_coding_categories.py index 57d35066e..c3850adba 100644 --- a/mem0-plugin/scripts/setup_coding_categories.py +++ b/mem0-plugin/scripts/setup_coding_categories.py @@ -5,15 +5,14 @@ mem0 auto-tags every memory with one or more `categories`. By default the list is consumer-oriented (food, hobbies, music, ...), which is meaningless for code. This script replaces the project's category list with a coding-focused one. -The change is project-level (per the platform docs, per-request overrides are -not supported on the managed API). Run once per project; future memories will -be tagged using the new list automatically. +Uses the mem0ai SDK (client.project.update). The SDK is installed into a +persistent venv at ${CLAUDE_PLUGIN_DATA}/venv by the ensure_deps.sh hook. Usage: - python setup_coding_categories.py # dry-run: show current vs proposed, no changes + python setup_coding_categories.py # dry-run: show current vs proposed python setup_coding_categories.py --apply # actually call project.update() -Requires the mem0ai Python SDK and MEM0_API_KEY to be set. +Requires MEM0_API_KEY (or CLAUDE_PLUGIN_OPTION_MEM0_API_KEY). """ from __future__ import annotations @@ -23,6 +22,19 @@ import json import os import sys +_script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, _script_dir) +from _identity import resolve_api_key # noqa: E402 + +_plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", os.path.join(_script_dir, "..")) +_data_dir = os.environ.get("CLAUDE_PLUGIN_DATA", os.path.join(os.path.expanduser("~"), ".mem0", "plugin-data")) +_venv_site = os.path.join(_data_dir, "venv", "lib") +if os.path.isdir(_venv_site): + for d in sorted(os.listdir(_venv_site)): + sp = os.path.join(_venv_site, d, "site-packages") + if os.path.isdir(sp) and sp not in sys.path: + sys.path.insert(1, sp) + CODING_CATEGORIES = [ { "architecture_decisions": ( @@ -66,6 +78,66 @@ CODING_CATEGORIES = [ "and ways of working." ) }, + { + "dependency_decisions": ( + "Why specific libraries, frameworks, or package versions were chosen or replaced, " + "including the alternatives considered and the reasoning behind the selection." + ) + }, + { + "performance_findings": ( + "Profiling results, bottlenecks identified, optimisations applied, and measurable " + "improvements achieved -- useful for avoiding regressions and guiding future work." + ) + }, + { + "security_constraints": ( + "Security requirements, authentication and authorisation rules, data-handling " + "constraints, compliance obligations, and known threat mitigations in effect." + ) + }, + { + "testing_patterns": ( + "Test strategies, frameworks chosen, coverage targets, fixture patterns, mocking " + "approaches, and how the test suite is structured for this project." + ) + }, + { + "data_model": ( + "Schema definitions, database column semantics, domain object relationships, " + "field constraints, and how data flows between storage and application layers." + ) + }, + { + "api_contracts": ( + "API endpoint shapes, request and response schemas, authentication requirements, " + "versioning policy, and any breaking-change commitments or deprecation timelines." + ) + }, + { + "deployment_runbook": ( + "How to build, release, deploy, and roll back the project. CI/CD pipeline steps, " + "environment-specific configuration, and on-call runbook entries." + ) + }, + { + "team_norms": ( + "Team working agreements, PR review etiquette, branching strategy, on-call " + "rotation, and other social or process conventions the team has agreed on." + ) + }, + { + "domain_glossary": ( + "Domain-specific terms, abbreviations, and acronyms with their precise meanings " + "in this project -- prevents misunderstandings across code, docs, and discussion." + ) + }, + { + "experiment_results": ( + "Results from A/B tests, feature-flag experiments, spikes, or proof-of-concept " + "work -- what was tried, what was measured, and what conclusion was reached." + ) + }, ] @@ -78,6 +150,22 @@ def _print_categories(label: str, cats): print() +def _categories_match(current: list | None, proposed: list) -> bool: + """Compare categories by key sets, tolerating order differences and extra API fields.""" + if not current: + return False + current_keys = {k for d in current if isinstance(d, dict) for k in d} + proposed_keys = {k for d in proposed if isinstance(d, dict) for k in d} + if current_keys != proposed_keys: + return False + current_map = {k: v for d in current if isinstance(d, dict) for k, v in d.items()} + proposed_map = {k: v for d in proposed if isinstance(d, dict) for k, v in d.items()} + return all( + current_map.get(k, "").strip() == v.strip() + for k, v in proposed_map.items() + ) + + def main() -> int: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument( @@ -87,17 +175,19 @@ def main() -> int: ) args = ap.parse_args() - if not os.environ.get("MEM0_API_KEY"): - print("ERROR: MEM0_API_KEY is not set. Export it and try again.", file=sys.stderr) + api_key = resolve_api_key() + if not api_key: + print("ERROR: MEM0_API_KEY is not set. Export it or configure it via plugin userConfig.", file=sys.stderr) return 1 + os.environ["MEM0_API_KEY"] = api_key try: from mem0 import MemoryClient except ImportError: print( - "ERROR: the mem0ai Python SDK is not installed.\n" - "Install with: pip install mem0ai\n" - "Then re-run this script.", + "ERROR: mem0ai SDK not found. The plugin's ensure_deps.sh hook should\n" + "install it automatically on session start. Try restarting Claude Code,\n" + "or run manually: pip install mem0ai", file=sys.stderr, ) return 1 @@ -127,6 +217,10 @@ def main() -> int: print("Dry-run only -- no changes made. Re-run with --apply to write.") return 0 + if _categories_match(current_cats, CODING_CATEGORIES): + print("Categories already match -- skipping update.") + return 0 + print("Applying coding categories...") try: response = client.project.update(custom_categories=CODING_CATEGORIES) diff --git a/mem0-plugin/scripts/telemetry.py b/mem0-plugin/scripts/telemetry.py new file mode 100644 index 000000000..62948c587 --- /dev/null +++ b/mem0-plugin/scripts/telemetry.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Lightweight fire-and-forget telemetry for the mem0 plugin. + +Sends anonymous usage events to PostHog using the same project key and +endpoint as the mem0 Python SDK and CLI. No posthog library dependency — +uses stdlib urllib directly (same pattern as cli/python telemetry_sender.py). + +CLI usage (called from hooks as a background subprocess): + python3 telemetry.py [--memory_count=N] [--categories_count=N] + [--error_detected] [--file_paths_detected] + [--source=] [--tool=] + +Opt-out: set MEM0_TELEMETRY=false (or 0/no/off) to disable all telemetry. + +Never sends: user content, memory content, API keys, raw user/project IDs. +Only sends: event type, platform, plugin version, anonymized hashes, counts. +""" + +from __future__ import annotations + +import hashlib +import json +import os +import platform +import sys +import urllib.error +import urllib.request + + +def _load_plugin_version() -> str: + try: + plugin_json = os.path.join(os.path.dirname(__file__), "..", ".claude-plugin", "plugin.json") + with open(plugin_json) as f: + return json.load(f).get("version", "unknown") + except (OSError, json.JSONDecodeError, KeyError): + return "unknown" + +PLUGIN_VERSION = _load_plugin_version() + +POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX" +POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/" +REQUEST_TIMEOUT = 2 + +SAMPLE_RATE = 1.0 + + +def _sha256(value: str) -> str: + return hashlib.sha256(value.encode("utf-8")).hexdigest() + + +def _distinct_id() -> str: + """Stable anonymous ID: SHA-256 of API key if available, else SHA-256 of username.""" + api_key = os.environ.get("MEM0_API_KEY") or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY") or "" + if api_key: + return hashlib.sha256(api_key.encode()).hexdigest()[:32] + user_id = os.environ.get("MEM0_RESOLVED_USER_ID") or os.environ.get("USER") or "unknown" + return _sha256(user_id) + + +def detect_platform() -> str: + if os.environ.get("PLUGIN_ROOT"): + return "codex" + if os.environ.get("CLAUDECODE") or os.environ.get("CLAUDE_PLUGIN_ROOT"): + return "claude-code" + if os.environ.get("CURSOR_PLUGIN_ROOT"): + return "cursor" + if os.environ.get("WINDSURF_PLUGIN_ROOT"): + return "windsurf" + return "plugin" + + +def is_enabled() -> bool: + return os.environ.get("MEM0_TELEMETRY", "true").lower() not in ("false", "0", "no", "off") + + +def build_posthog_payload(event_name: str, properties: dict | None = None) -> dict: + project_id = os.environ.get("MEM0_PROJECT_ID") or "unknown" + return { + "api_key": POSTHOG_API_KEY, + "distinct_id": _distinct_id(), + "event": event_name, + "properties": { + **(properties or {}), + "source": "plugin", + "platform": detect_platform(), + "plugin_version": PLUGIN_VERSION, + "project_hash": _sha256(project_id), + "os": sys.platform, + "os_version": platform.version(), + "sample_rate": SAMPLE_RATE, + "$process_person_profile": False, + "$lib": "posthog-python", + }, + } + + +def send(payload: dict) -> None: + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + POSTHOG_HOST, + data=data, + headers={"Content-Type": "application/json"}, + ) + try: + with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT): + pass + except Exception: + pass + + +def emit(event_type: str, properties: dict | None = None) -> None: + if not is_enabled(): + return + send(build_posthog_payload(f"plugin.{event_type}", properties)) + + +def main() -> int: + if not is_enabled(): + return 0 + if len(sys.argv) < 2: + return 1 + + event_type = sys.argv[1] + properties: dict = {} + + for arg in sys.argv[2:]: + if arg.startswith("--memory_count="): + try: + properties["memory_count"] = int(arg.split("=", 1)[1]) + except ValueError: + pass + elif arg.startswith("--categories_count="): + try: + properties["categories_count"] = int(arg.split("=", 1)[1]) + except ValueError: + pass + elif arg == "--error_detected": + properties["error_detected"] = True + elif arg == "--file_paths_detected": + properties["file_paths_detected"] = True + elif arg.startswith("--source="): + properties["source_detail"] = arg.split("=", 1)[1] + elif arg.startswith("--tool="): + properties["tool"] = arg.split("=", 1)[1] + elif arg.startswith("--files_count="): + try: + properties["files_count"] = int(arg.split("=", 1)[1]) + except ValueError: + pass + + emit(event_type, properties) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/mem0-plugin/skills/context-loader/SKILL.md b/mem0-plugin/skills/context-loader/SKILL.md new file mode 100644 index 000000000..25d3a339d --- /dev/null +++ b/mem0-plugin/skills/context-loader/SKILL.md @@ -0,0 +1,48 @@ +--- +name: context-loader +description: Searches and injects relevant memories into context before starting work on a task. Use when beginning a new task, switching context, or when project history, past decisions, or coding conventions need to be loaded. +--- + +# Context Loader + +Pre-fetches relevant memories to prime context before working on a task. + +## When to use + +- Session start (invoke manually or auto-triggered by skill description matching) +- User starts work on a specific feature or file set +- Complex multi-step task begins +- User says "what do we know about X" or "context for X" + +## Steps + +1. **Extract topics** from current message/task. Identify: file paths, module names, feature areas, error patterns. + +2. **Run 2-4 parallel `search_memories` calls** with different angles: + + | Query angle | Filter | Purpose | + |---|---|---| + | Feature/module name | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}` | Architecture decisions | + | File paths mentioned | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "convention"}}]}` | Coding patterns | + | Error keywords (if any) | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "anti_pattern"}}]}` | Known pitfalls | + | Broad project context | `{"AND": [{"user_id": ""}, {"app_id": ""}]}` | Catch-all | + +3. **Deduplicate** results by memory ID across all search responses. + +4. **Output compact context block** (max 10 memories): + +``` +context-loader: loaded memories for "" + - [decision] [mem0:] + - [convention] [mem0:] + - [anti_pattern] [mem0:] +``` + +5. If **zero results**: output nothing. Don't announce empty context. + +## Constraints + +- **Read-only** — never modify or delete memories +- **Max 10 memories** returned (most relevant only) +- **Silent on empty** — only surfaces findings if relevant context exists +- Skip memories already visible in current session context diff --git a/mem0-plugin/skills/dream/SKILL.md b/mem0-plugin/skills/dream/SKILL.md new file mode 100644 index 000000000..c5319c565 --- /dev/null +++ b/mem0-plugin/skills/dream/SKILL.md @@ -0,0 +1,232 @@ +--- +name: dream +description: Consolidates stored memories by merging duplicates, resolving contradictions, and pruning stale entries. Use when memory count is high, search results feel noisy or repetitive, or periodic cleanup is needed to maintain memory quality. +--- + +# Mem0 Dream — Memory Consolidation + +This skill performs a memory consolidation pass: it fetches all project memories, +identifies near-duplicates, flags contradictions, and prunes stale entries based on +configured retention policies. All proposed changes are shown as a diff for user +approval before anything is modified. + +**IMPORTANT: Execute steps strictly in order (1 → 2 → 3 → 4 → 5 → 6). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead.** + +## Step 1: Load Retention Policies + +Determine the active retention policy by running the parser script. Use the +appropriate `PLUGIN_ROOT` variable for the current platform (`${CLAUDE_PLUGIN_ROOT}`, +`${CODEX_PLUGIN_ROOT}`, or `${CURSOR_PLUGIN_ROOT}`): + +```bash +python3 "/scripts/parse_mem0_config.py" "" +``` + +Parse the JSON output (a dict of `category → days | null`). If the script fails +or returns `{}`, fall back to these built-in defaults: + +| `metadata.type` | Default retention | +|---|---| +| `session_state` | 90 days | +| `compact_summary` | 90 days | +| all others | no pruning | + +Store the resolved policies for use in Step 3. + +--- + +## Step 2: Fetch ALL Project Memories + +Call `get_memories` to retrieve every memory for the active project: + +```python +get_memories( + filters={"AND": [{"user_id": ""}, {"app_id": ""}]}, + page_size=200, +) +``` + +If the response indicates more pages exist, paginate until all memories are fetched. +Collect the full list before proceeding. If zero memories are found, print: + +``` +No memories found for project . Nothing to consolidate. +``` + +…and stop. + +--- + +## Step 3: Analyze — Find Issues + +Work entirely in-memory; do not modify anything yet. + +Group memories by `metadata.type` (use `"unknown"` when the field is absent). +For each group, identify the following: + +### 3a. Near-duplicate pairs (merge candidates) + +Two memories are near-duplicates when they express the same fact or decision but +phrased differently (e.g., "Use PostgreSQL for auth" and "Auth DB is PostgreSQL"). + +Heuristics — two memories are near-duplicates if **all** of these hold: +- Similarity threshold: estimated cosine similarity > 0.9 (use noun/keyword overlap as proxy — if >60% of significant nouns overlap, treat as >0.9 similarity). +- Same `metadata.type`. +- Neither memory is pinned (`metadata.pinned != true`). + +For each qualifying pair, draft a merged version that is more complete and specific +than either original. + +### 3b. Contradictions + +Two memories contradict when they assert opposing facts about the same topic +(e.g., "Deploy to ECS" vs. "Deploy to Vercel"). + +Identify the likely winner: the more recent memory with higher confidence wins. +Store both IDs and their content for user review. + +### 3c. Prune candidates + +A memory is a prune candidate when **any** of the following is true: + +1. Its `metadata.type` has a retention policy and the memory is older than the + configured number of days (compare `created_at` to today). +2. Its confidence score is below 0.3 AND it contains no information unique to + this project (no file paths, identifiers, or domain-specific nouns). + +**Always skip memories where `metadata.pinned == true`**, regardless of age or +confidence. + +--- + +## Step 4: Print Diff Report + +Print a structured diff to the terminal before making any changes. Use exactly +this format: + +``` +## dream — consolidation report + +Merges (): + [mem0:] + [mem0:] → "" + +Conflicts (): + [mem0:] vs [mem0:] — "" [A/B/skip] + +Prune (): + [mem0:] — , d old + +Proposed: merges, prunes, conflicts. Apply? [Y/n] +``` + +If there are zero items in any category, omit that section entirely. + +If there are zero total proposals (no merges, no prunes, no conflicts), print: + +``` +Dream complete. No duplicate, contradictory, or stale memories found. +``` + +…and stop. + +--- + +## Step 5: Wait for User Input and Apply + +### 5a. Contradictions + +For each `CONFLICT` pair in the report, wait for the user to type `A`, `B`, or +`skip` (case-insensitive). If they enter nothing (empty), treat as `skip`. + +Record the winner for each pair before proceeding to the final apply confirmation. + +### 5b. Final confirmation + +After all conflict resolutions are collected, prompt: + +``` +Apply? [Y/n] +``` + +If the user types `n` or `no` (case-insensitive), print `Cancelled. No changes made.` +and stop. + +If the user confirms (`Y`, `yes`, or empty / Enter), apply all changes in this order: + +#### Merges + +For each approved merge pair: +1. `delete_memory()` +2. `delete_memory()` +3. `add_memory` with: + - `text=""` + - `user_id=` + - `app_id=` (top-level, not in metadata) + - `metadata={"type": "", "branch": "", "confidence": , "source": "mem0-dream"}` + - `infer=False` + +#### Contradictions (resolved) + +For each resolved conflict where the user chose A or B: +- Delete the loser (the non-chosen memory): `delete_memory(memory_id=)` + +Contradictions where the user chose `skip` are left untouched. + +#### Prunes + +For each prune candidate: +- `delete_memory()` + +--- + +## Step 6: Print Summary + +After all changes are applied, print: + +``` +Dream complete — merged: , pruned: , conflicts resolved: , skipped: +``` + +--- + +## Auto mode + +When invoked with `--auto` (e.g., `/mem0:dream --auto`), run non-interactively: + +- **Merges**: applied automatically (no contradiction, both are compatible). +- **Prunes**: applied automatically (age/confidence-based, no ambiguity). +- **Contradictions**: skipped — they require human judgment. + +### Concurrency guard + +Before doing any work, check for a lock file at `/tmp/mem0_dream_auto.lock`: +- If the lock file exists and is less than 10 minutes old, print `[mem0-dream --auto] Another run in progress — skipping.` and stop. +- Otherwise, create the lock file (write the current timestamp). Delete it when done (in all exit paths). + +### Execution + +In auto mode: +1. Load policies and fetch memories (Steps 1–3) as normal. +2. Apply merges and prunes silently without printing the diff or prompting. +3. Print a compact summary: + ``` + [mem0-dream --auto] project= merged= pruned= conflicts_skipped= + ``` +4. If contradictions were detected but skipped, check if a `mem0-dream-auto` reminder already exists before storing one: + - Search for existing reminders: `search_memories(query="mem0-dream contradictions manual review", filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"source": "mem0-dream-auto"}}]}, top_k=1)` + - If a result exists with similarity > 0.9, skip storing the reminder (one already exists). + - If no match, store the reminder: + ```python + add_memory( + text="mem0-dream detected contradiction(s) requiring manual review. Run /mem0:dream to resolve them interactively.", + user_id="", + app_id="", + metadata={"type": "task_learning", "source": "mem0-dream-auto", "branch": ""}, + infer=False, + ) + ``` + +## See also + +- `/mem0:forget` — targeted deletion of specific memories (search + confirm + delete) +- `/mem0:health --deep` — quick quality scan without applying changes diff --git a/mem0-plugin/skills/export/SKILL.md b/mem0-plugin/skills/export/SKILL.md new file mode 100644 index 000000000..67fb0ae57 --- /dev/null +++ b/mem0-plugin/skills/export/SKILL.md @@ -0,0 +1,76 @@ +--- +name: export +description: Exports all project memories to a portable Markdown file for backup or migration. Use when backing up memories, migrating to another project, sharing memory state with teammates, or archiving before cleanup. +--- + +# Mem0 Export + +Export all memories for the current project to a portable Markdown file. + +## Execution + +### Step 1: Resolve identity + +Determine the active identity: +- `user_id` from `MEM0_USER_ID` env var, else `$USER`, else `"default"` +- `project_id` (used as `app_id`) from `MEM0_PROJECT_ID` env var, or via the project resolver + +### Step 2: Fetch all memories + +Call `get_memories` with: +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` +- `page_size=200` + +If the response is paginated (i.e. the result contains a `next` cursor or the count equals `page_size`), continue fetching pages until all memories are retrieved. + +### Step 3: Format each memory as a YAML-frontmatter block + +For each memory record, produce a block in this exact format: + +``` +--- +id: +created_at: +type: +confidence: +branch: +files: +categories: +--- + + +``` + +Notes: +- The `---` delimiters must be on their own lines with no extra whitespace. +- `files` and `categories` are written as comma-separated values on a single line. +- Leave a blank line after the content before the next `---` (for readability). +- If a field is missing or null, write an empty string (not "null"). + +### Step 4: Write the export file + +Determine the output filename: + +``` +mem0-export--.md +``` + +Where `` is today's date in UTC. + +Write all formatted blocks to this file using the Write tool (or equivalent). The file is written to the current working directory. + +### Step 5: Print summary + +``` +Exported memories to +``` + +Where `` is the total number of memory blocks written. + +## Error Handling + +- If `get_memories` returns an error or zero memories, print: + ``` + No memories found for project . Nothing exported. + ``` +- If the write fails, report the error to the user. diff --git a/mem0-plugin/skills/forget/SKILL.md b/mem0-plugin/skills/forget/SKILL.md new file mode 100644 index 000000000..2b32d865f --- /dev/null +++ b/mem0-plugin/skills/forget/SKILL.md @@ -0,0 +1,71 @@ +--- +name: forget +description: Deletes memories by search query or memory ID with confirmation before removal. Use when removing outdated decisions, incorrect memories, sensitive data, or cleaning up after experiments. Also handles undo of recent additions. +--- + +# Mem0 Forget + +Delete specific memories from mem0. + +## Execution + +### Step 1: Parse input + +The user provides either: +- A search query: `/mem0:forget auth module decisions` +- A memory ID: `/mem0:forget ` + +If no argument, ask: "What should I forget? Provide a search query or memory ID." + +### Step 2: Find memories + +**If memory ID provided** (looks like a UUID or hex string): +- Call `get_memory` with the ID to verify it exists. +- Show: `Found: "" (created )` + +**If search query provided:** +- Call `search_memories` with: + - `query=` + - `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` + - `top_k=10` +- Show numbered list: + ``` + Found memories matching "": + 1. (type: , created: ) [ID: ] + 2. ... + ``` + +### Step 3: Confirm + +Ask: "Delete which memories? Enter numbers (e.g., 1,3,5), 'all', or 'cancel'." + +For a single memory ID, ask: "Delete this memory? [y/N]" + +**Never delete without confirmation.** This is destructive. + +### Step 4: Delete + +For each confirmed memory, call `delete_memory` with the memory ID. + +### Step 5: Report + +``` +Deleted memories. +``` + +If any deletions failed, report which ones and why. + +## Undo recent writes + +If the user says "undo last N memories" or "undo last write": + +1. Read session stats to get recently written memory IDs: + ```bash + SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts" + python3 "$SCRIPT_DIR/session_stats.py" peek + ``` +2. Parse the `recent_ids` array from the JSON output. Each entry has `id`, `category`, `ts`. +3. Show the last N entries (default 1) and ask for confirmation. +4. Delete confirmed entries via `delete_memory`. + +If `recent_ids` is empty, tell the user: "No recent memory IDs tracked this session. Try `/mem0:tour` to browse recent memories, or `/mem0:forget ` to find specific ones." diff --git a/mem0-plugin/skills/health/SKILL.md b/mem0-plugin/skills/health/SKILL.md new file mode 100644 index 000000000..9b5b51a8c --- /dev/null +++ b/mem0-plugin/skills/health/SKILL.md @@ -0,0 +1,160 @@ +--- +name: health +description: Diagnoses mem0 connectivity, API key validity, and memory read/write functionality. Use when memory operations fail, searches return empty, add_memory errors occur, MCP connection drops, or to verify the plugin is working correctly. +--- + +# Mem0 Health Check + +Run a diagnostic check on the mem0 plugin. Useful for troubleshooting. + +## Execution + +Run ALL checks, then display a single summary. Do not stop on the first failure. + +### Check 1: API key + +```bash +_KEY="${MEM0_API_KEY:-${CLAUDE_PLUGIN_OPTION_MEM0_API_KEY:-}}" +[ -n "$_KEY" ] && echo "${_KEY:0:6}..." || echo "NOT_SET" +``` + +- If `NOT_SET`: FAIL — "No API key configured" +- If set: PASS — the command already prints only the first 6 chars + +### Check 2: Identity resolution + +Resolve identity using the plugin's own resolver scripts to match what hooks use: + +```bash +SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts" +source "$SCRIPT_DIR/_identity.sh" 2>/dev/null +echo "user_id=${MEM0_RESOLVED_USER_ID:-}" +echo "project_id=${MEM0_PROJECT_ID:-}" +echo "branch=${MEM0_BRANCH:-}" +``` + +If `CLAUDE_PLUGIN_ROOT` is not available, fall back to: +- `user_id`: from `MEM0_USER_ID` or `$USER` +- `project_id`: from `MEM0_PROJECT_ID` or check `~/.mem0/project_map.json` for `$PWD` +- `branch`: from `git branch --show-current` + +PASS if all three are non-empty. WARN if any falls back to defaults. + +### Check 3: MCP server connectivity + +Call `search_memories` with: +- `query="health check"` +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` +- `top_k=1` + +- If returns successfully (even empty): PASS +- If errors: FAIL — show the error message + +### Check 4: Memory write capability + +Call `add_memory` with: +- `text="Health check probe — safe to delete."` +- `user_id=` +- `app_id=` +- `metadata={"type": "health_check", "probe": true}` +- `infer=False` + +The response returns `event_id` (v3 writes are async). Call `get_event_status(event_id=)` to check processing. + +- If status is `SUCCEEDED`: PASS — extract the memory ID from the event result, then call `delete_memory` with that ID to clean up. +- If status is `PENDING` after 5 seconds: PASS (write accepted, processing delayed) +- If errors: FAIL — show the error. + +### Check 5: Session stats tracker + +Check if the session stats file exists and is readable: + +```bash +STATS_FILE="/tmp/mem0_session_stats_${USER}.json" +if [ -f "$STATS_FILE" ] && python3 -c "import json; json.load(open('$STATS_FILE'))" 2>/dev/null; then + echo "OK" +else + echo "FAIL" +fi +``` + +This file is created by the SessionStart hook and updated by PostToolUse hooks throughout the session. If it doesn't exist, the session hooks may not have fired yet — try sending a message first, then recheck. + +### Display + +``` +## mem0 health + +PASS API Key m0-dVe... +PASS Identity user=kartik, project=mem0, branch=main +PASS MCP Connection 142ms +PASS Write/Read write + delete OK +PASS Session Tracker stats file active + +All checks passed. +``` + +If any check fails, add a `## Troubleshooting` section with specific fix steps for each failure. + +## Extended mode: Memory Quality Analysis + +When invoked with `--deep` (e.g., `/mem0:health --deep`), run the standard 5 checks above **plus** a memory quality scan. + +### Quality Check 1: Duplicates + +Call `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200`. Compare all pairs within the same `metadata.type` group for high textual overlap (shared nouns/keywords > 60%). Report: + +``` +Potential duplicates: pairs + [mem0:] ≈ [mem0:] — both about "" +``` + +### Quality Check 2: Stale memories + +Flag memories where: +- `metadata.type` is `session_state` or `compact_summary` AND older than 90 days +- `metadata.confidence` < 0.3 AND older than 30 days + +``` +Stale candidates: + [mem0:] — session_state, 142d old +``` + +### Quality Check 2b: Low-confidence memories + +Flag memories where `metadata.confidence` < 0.5 (regardless of age). Report separately from stale: + +``` +Low-confidence memories: + [mem0:] — confidence=0.3, "" +``` + +### Quality Check 3: Contradictions + +Within each `metadata.type` group, flag pairs that assert opposing facts about the same topic. Use semantic judgment — look for negation patterns, conflicting tool/framework choices, or reversed decisions. + +``` +Possible contradictions: + [mem0:] vs [mem0:] — conflicting on "" +``` + +### Quality Check 4: Orphan memories + +Memories with no `metadata.type` set, or with `metadata.type` not in the 17 known coding categories. These were likely written without proper tagging. + +``` +Untagged/orphan memories: +``` + +### Quality summary + +``` +## Memory Quality + +Duplicates: · Stale: · Contradictions: · Orphans: +``` + +If all counts are 0: `Memory quality: clean.` +If any non-zero: append `Run /mem0:dream to fix.` + +To fix issues found by `--deep`, run `/mem0:dream` for automated consolidation (merges, prunes, conflict resolution). diff --git a/mem0-plugin/skills/import/SKILL.md b/mem0-plugin/skills/import/SKILL.md new file mode 100644 index 000000000..7e6c657fd --- /dev/null +++ b/mem0-plugin/skills/import/SKILL.md @@ -0,0 +1,177 @@ +--- +name: import +description: Imports memories from an exported Markdown file or MEMORY.md into the current project. Use when migrating from another project, restoring from backup, importing Claude Code native MEMORY.md content, or setting up a new project with existing knowledge. +--- + +# Mem0 Import + +Import memories from a mem0 export file into the current project. + +## Execution + +### Step 1: Determine the export file to import + +If the user provided a filename as an argument to `/mem0:import `, use that file. + +Otherwise, list `.md` files in the current directory whose names contain `mem0-export`: + +```bash +ls -1 *.md 2>/dev/null | grep mem0-export || echo "No export files found" +``` + +If multiple files are found, ask the user which one to import. If none are found, print: +``` +No mem0-export files found in the current directory. +Run /mem0:export first, or provide the filename: /mem0:import +``` + +### Step 2: Parse the export file + +Determine the plugin root. Use the appropriate variable for the current platform: +- Claude Code: `${CLAUDE_PLUGIN_ROOT}` +- Codex: `${CODEX_PLUGIN_ROOT}` +- Cursor: `${CURSOR_PLUGIN_ROOT}` + +Run the parser script to extract memory records as JSON: + +```bash +python3 "/scripts/parse_export_file.py" "" +``` + +This outputs a JSON array where each element has: +- `id` — original memory ID (for reference only; a new ID will be assigned on import) +- `type` — metadata type +- `confidence` — metadata confidence value +- `branch` — metadata branch +- `files` — list of associated files +- `categories` — list of categories +- `content` — the memory text + +If the script fails or outputs `[]`, print: +``` +Failed to parse or file contains no valid memory blocks. +``` +and stop. + +### Step 3: Resolve identity + +Determine the active identity: +- `user_id` from `MEM0_USER_ID` env var, else `$USER`, else `"default"` +- `project_id` (used as `app_id`) from `MEM0_PROJECT_ID` env var, or via the project resolver + +### Step 4: Import each memory + +For each record in the parsed JSON array, call `add_memory` with: + +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={` + - `"type": ""` (if non-empty) + - `"confidence": ""` (if non-empty) + - `"branch": ""` (if non-empty) + - `"files": ` (the list, if non-empty) + - `"source": "import"` + - `}` +- `infer=False` + +Notes: +- Do NOT pass the original `id` — the platform assigns a new ID. +- Skip records where `content` is empty (the parser already filters these, but be defensive). +- Continue importing even if individual records fail; track the count of successes. + +### Step 5: Print results + +``` +Imported memories into project +``` + +Where `` is the number of successfully imported memories. + +If any failed: +``` +Imported / memories into project ( failed) +``` + +## Importing from competing AI tools (`--tools`) + +When invoked with `--tools` (e.g., `/mem0:import --tools`), detect and import +from competing AI tool configuration files: + +### Supported tools + +| Tool | File/directory | +|------|---------------| +| Cursor | `.cursorrules` | +| GitHub Copilot | `.github/copilot-instructions.md` | +| Cline | `memory-bank/` (directory of `.md` files) | +| Continue | `.continue/rules.md` | + +### T1: Detect + +```bash +test -f .cursorrules && echo "cursor: .cursorrules" +test -f .github/copilot-instructions.md && echo "copilot: .github/copilot-instructions.md" +test -d memory-bank/ && echo "cline: memory-bank/" +test -f .continue/rules.md && echo "continue: .continue/rules.md" +``` + +### T2: Ask user + +List found files, ask which to import (numbers, comma-separated, or "all"). +If none found: +``` +No competing tool configuration files found. +Checked: .cursorrules, .github/copilot-instructions.md, memory-bank/, .continue/rules.md +``` + +### T3: Run import + +For each selected tool: +```bash +python3 "/scripts/import_competing_tools.py" --path +``` + +Tools: `cursorrules`, `copilot`, `cline`, `continue`. + +### T4: Report + +``` +Imported memories into (cursor: , copilot: ) +``` + +Notes: `infer=False`, tagged `metadata.source=-import`, sections <50 chars +skipped, chunks >10k chars truncated, safe to re-run (deduplication handles it). + +--- + +## Importing Claude Code's native MEMORY.md + +When invoked with a path to Claude Code's native `MEMORY.md` file (typically +`~/.claude/projects//memory/MEMORY.md`), or when `on_session_start.sh` +detects native auto-memory and the user chooses to import: + +1. Read the file. It contains newline-separated memory entries (one fact per line, + sometimes with `- ` bullet prefix). +2. Split by non-empty lines. Each line becomes one memory. +3. Skip lines shorter than 20 characters or lines that are just headers (`#`). +4. For each line, call `add_memory` with: + - `text=""` + - `user_id=` + - `app_id=` + - `metadata={"type": "task_learning", "source": "memory-md-import", "confidence": 0.8}` + - `infer=False` +5. Report: `Imported memories from MEMORY.md into project ` +6. Suggest disabling native auto-memory: + ``` + To avoid duplicate memory systems, add to ~/.claude/settings.json: + "autoMemoryEnabled": false + ``` + +This handles the cold-start gap when a user has been using Claude Code's native +memory and switches to mem0. + +## Error Handling + +- If the parser script is not found at `/scripts/parse_export_file.py`, print an error and stop. +- If `add_memory` calls fail consistently (e.g. auth error), report the issue and stop early. diff --git a/mem0-plugin/skills/list-projects/SKILL.md b/mem0-plugin/skills/list-projects/SKILL.md new file mode 100644 index 000000000..8cdbfee71 --- /dev/null +++ b/mem0-plugin/skills/list-projects/SKILL.md @@ -0,0 +1,60 @@ +--- +name: list-projects +description: Lists all projects with stored memories for the current user, showing memory counts and last activity dates. Use when checking which projects have memories, comparing memory distribution across repos, or finding a specific project scope. +--- + +# Mem0 List Projects + +Show all known project scopes for the current user. + +## Execution + +### Step 1: Fetch memories to discover app_ids + +There is no dedicated "list projects" API endpoint. Discover projects by fetching +the user's memories across all scopes. + +**Important:** A filter with only `user_id` triggers implicit null scoping — it +excludes memories that have a non-null `app_id`. Run two queries and merge: + +1. **Null-scoped:** `get_memories` with `filters={"AND": [{"user_id": ""}]}`, `page_size=200` + — catches memories without `app_id` +2. **App-scoped:** `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": {"exists": true}}]}`, `page_size=200` + — catches memories with any `app_id` + +Run both calls in parallel. Merge results, deduplicate by memory `id`. + +If either response indicates more pages, paginate (up to 1000 total). + +### Step 2: Extract distinct projects + +For each memory, determine project by: +1. Top-level `app_id` field (preferred) +2. `metadata.project_id` (legacy memories) +3. `metadata.project` (oldest format) +4. `"(unscoped)"` if none found + +Group by resolved project name. For each project, count: +- Total memories +- Most recent `created_at` date +- Top 3 `metadata.type` values by frequency + +### Step 3: Display + +``` +## mem0 projects + + memories (last: ) ← current + memories (last: ) + + projects, total memories +``` + +Mark current project with `← current`. Sort by memory count descending. + +### Step 4: Empty state + +If zero memories found: +``` +No projects found. Run /mem0:onboard to get started. +``` diff --git a/mem0-plugin/skills/mem0-mcp/SKILL.md b/mem0-plugin/skills/mem0-mcp/SKILL.md deleted file mode 100644 index b3c5a6387..000000000 --- a/mem0-plugin/skills/mem0-mcp/SKILL.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -name: mem0-mcp -description: > - Mem0 memory protocol for agents using the mem0 MCP tools (Claude Code, Cursor, - Codex, and any other MCP-aware runtime). Decide deliberately when memory context - would help, run targeted searches with metadata filters when it would, and store - key learnings as work completes. Use the mem0 MCP tools (add_memory, - search_memories, get_memories, etc.) for all memory operations. ---- - -# Mem0 MCP Memory Protocol - -You have access to persistent memory via the mem0 MCP tools. Follow this protocol to maintain context across sessions. - -## On every new task - -Decide whether persistent memory context would improve your response, then act accordingly. Don't search by default — search deliberately. - -## Project scoping - -Every memory operation MUST be scoped to the current project: - -- **On `add_memory`:** Always include `metadata.project_id` (the active project_id from SessionStart). -- **On `search_memories`:** Always include `{"metadata": {"project_id": ""}}` in the AND filter. -- **Session-state memories:** Also include `metadata.branch` (the active branch from SessionStart). - -Full filter template: -```python -filters={"AND": [ - {"user_id": ""}, - {"metadata": {"project_id": ""}}, - {"metadata": {"type": "decision"}} -]} -``` - -### Decide: search or skip? - -**Search WHEN** the user: -- references past work, decisions, or things "we" built -- asks "how should we...", "best way to...", or any decision-style question -- hits an error, bug, or asks for debugging help -- requests work that touches their stack, tools, conventions, or preferences -- starts a non-trivial task in a known project - -**Skip WHEN:** -- the prompt is an acknowledgement or continuation ("ok", "thanks", "continue") -- the user is *stating* new info — that's a write trigger (`add_memory`), not a search -- it's a pure syntax / factual question answerable from general knowledge -- you already searched this scope earlier in the turn - -Empty results are normal. Proceed without context — they don't mean the system is broken. - -### How to search well - -When you do search, run **2–4 parallel** `search_memories` calls at different angles instead of one query echoing the user's prompt. - -**Query phrasing:** -- Use **nouns**, not sentences. `"auth module decisions"` beats `"what did we decide about auth"`. -- Strip conversational filler. *"remember when we picked Postgres?"* → search `"Postgres choice"`. -- Use entity names, not pronouns. Resolve "that thing" from recent context first. -- Don't search on meta-questions ("what was that?") — use recent context or `get_memories` ordered by `created_at`. - -**Metadata filters** match the same `type` values written under "After completing significant work" below. - -Two rules from the v2 filter spec: - -1. The root **must** be a logical operator (`AND` / `OR` / `NOT`) with an array. A bare `{"user_id": "..."}` won't work. -2. Metadata uses a **nested** object, not a dotted key. `{"metadata": {"type": "decision"}}`, never `{"metadata.type": "decision"}`. Only top-level metadata keys are filterable. - -Combine `user_id` with one metadata clause per call: - -| `metadata.type` clause | Use for | -|--------|---------| -| `{"metadata": {"type": "decision"}}` | design / architecture / "how should we" questions | -| `{"metadata": {"type": "anti_pattern"}}` | debugging, error handling, things that failed before | -| `{"metadata": {"type": "user_preference"}}` | tooling, stack, style — always include for code work | -| `{"metadata": {"type": "convention"}}` | established patterns in this project | - -Full filter (replace `` and `` with the active values from SessionStart): -```python -filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "decision"}}]} -``` - -### Worked example - -User asks: *"Refactor the auth module to use JWT."* - -Don't: -```python -search_memories(query="Refactor the auth module to use JWT") -# Hits whatever shares words. Misses prior decisions and preferences. -``` - -Do (parallel — substitute the active `user_id` and `project_id` for the placeholders): -```python -search_memories(query="auth module decisions", - filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "decision"}}]}) -search_memories(query="JWT", - filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}]}) -search_memories(query="auth refactor failures", - filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "anti_pattern"}}]}) -search_memories(query="auth", - filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "user_preference"}}]}) -``` - -## After completing significant work - -Extract key learnings and store them using the `add_memory` tool: - -- **Decisions made** -> Include metadata `{"type": "decision"}` -- **Strategies that worked** -> Include metadata `{"type": "task_learning"}` -- **Failed approaches** -> Include metadata `{"type": "anti_pattern"}` -- **User preferences observed** -> Include metadata `{"type": "user_preference"}` -- **Environment/setup discoveries** -> Include metadata `{"type": "environmental"}` -- **Conventions established** -> Include metadata `{"type": "convention"}` - -> `metadata.type` (which you set explicitly) and `categories` (which the platform auto-tags after the project's custom-category list — see `scripts/setup_coding_categories.py`) are complementary. Always set `metadata.type` for explicit filtering; the platform fills in `categories` on its own. Don't try to set `categories` on `add_memory` calls — per-request overrides aren't supported on the managed API. - -### Expiration: high-churn vs durable - -Some memory types are state snapshots that go stale fast; others are durable facts that should outlive the session that created them. Mark the difference with `expiration_date` on writes. - -| Type | Expiration | Why | -|---|---|---| -| `session_state`, `compact_summary` | `expiration_date` ≈ today + 90 days | Describe a single moment of project state. Useless after a quarter; clutter the recall surface. | -| `decision`, `anti_pattern`, `convention`, `user_preference`, `task_learning`, `environmental` | omit `expiration_date` | Durable facts. A decision made last year is still a decision; same for a convention or a user preference. | - -`add_memory` accepts `expiration_date` as a string (`"YYYY-MM-DD"`). The two server-side hooks (`on_pre_compact.py`, `capture_compact_summary.py`) already set this for the types they write. When you write directly via the MCP tool, follow the same rule. - -### Recency filter on recall - -When the user is asking about *current* state ("where were we", "what's the active task", "the latest decision on X"), filter recall to recent memories so stale snapshots don't surface: - -```python -# Last 90 days only -{"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "session_state"}}, {"created_at": {"gte": "<90 days ago, YYYY-MM-DD>"}}]} -``` - -Skip the recency filter when the user is asking about durable facts ("what conventions does this project use", "have we hit this bug before") — those are timeless and recency would hide them. - -Memories can be as detailed as needed -- include full context, reasoning, code snippets, file paths, and examples. Longer, searchable memories are more valuable than vague one-liners. - -### Use `infer=False` for already-structured content - -When you've done the extraction work yourself — pre-compaction summaries, decisions, anti-patterns, conventions you've explicitly identified — pass `infer=False` so the platform stores your text verbatim instead of running a second extraction pass over it. - -```python -add_memory( - messages=[{"role": "user", "content": ""}], - user_id="", - metadata={"type": "decision"}, - infer=False, -) -``` - -Stick to one mode per distinct piece of content — don't mix `infer=True` (default) and `infer=False` for the same fact, you'll get duplicates. Default (`infer=True`) is right for raw conversational signal you want extracted; `infer=False` is right for pre-extracted structure. - -## Before losing context - -If context is about to be compacted or the session is ending, store a comprehensive session summary: - -``` -## Session Summary - -### User's Goal -[What the user originally asked for] - -### What Was Accomplished -[Numbered list of tasks completed] - -### Key Decisions Made -[Architectural choices, trade-offs discussed] - -### Files Created or Modified -[Important file paths with what changed] - -### Current State -[What is in progress, pending items, next steps] -``` - -Include metadata: `{"type": "session_state"}` - -## Memory hygiene - -- Do NOT write to MEMORY.md or any file-based memory. Use mem0 MCP tools exclusively. -- Only store genuinely useful learnings. Skip trivial interactions. -- Use specific, searchable language in memory content. diff --git a/mem0-plugin/skills/mem0-onboard/SKILL.md b/mem0-plugin/skills/mem0-onboard/SKILL.md deleted file mode 100644 index 001abead6..000000000 --- a/mem0-plugin/skills/mem0-onboard/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: mem0-onboard -description: > - Post-install onboarding wizard for the mem0 plugin. - Detects CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules, mem0.md - and offers to import them. Installs coding categories. Shows active identity. - TRIGGER: user runs /mem0:onboard, or mentions "setup mem0", "configure mem0 plugin". ---- - -# Mem0 Onboarding Wizard - -Run this wizard to set up the mem0 plugin for the current project. Complete in ~30 seconds. - -## Step 1: Verify API key - -Check if `MEM0_API_KEY` is set in the current environment: - -```bash -echo "${MEM0_API_KEY:+SET}" || echo "NOT_SET" -``` - -- If **NOT set**: - 1. Ask the user: "No MEM0_API_KEY found. Do you have one, or need to create one?" - 2. If they need one, provide two options: - - **Browser**: Go to https://app.mem0.ai/dashboard/api-keys and copy the key - - **CLI**: Run `pip install mem0-cli && mem0 init --agent --json` to mint a key without email - 3. Once they have the key, tell them to run: `export MEM0_API_KEY="m0-..."` in their terminal, then restart this Claude Code session (the env var must be set before Claude Code starts). - 4. **STOP here.** Do not proceed until the key is confirmed set. -- If **SET**: Proceed to Step 2. - -## Step 2: Show identity - -Report the active identity to the user: -- Call `search_memories` with `query="project setup"`, `user_id=`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}]}`, `limit=1` to verify connectivity. -- Print: `Connected. user=, project=, branch=` -- If the search fails, troubleshoot the API key. - -## Step 3: Detect and import project files - -Check for these files in the project root: -1. `CLAUDE.md` -2. `AGENTS.md` -3. `.cursorrules` -4. `.windsurfrules` -5. `mem0.md` - -For each file found, ask the user: "Found `` ( bytes). Import into mem0? [Y/n]" - -If user says yes (or default): -- Read the file content -- Call `add_memory` with: - - `messages=[{"role": "user", "content": "## Project Profile: \n\nProject: \n\n"}]` - - `user_id=` - - `metadata={"type": "project_profile", "file": "", "project_id": "", "source": "onboard"}` - - `infer=False` - -## Step 4: Install coding categories - -Ask: "Install coding categories optimized for development workflows? [Y/n]" - -If yes, run the script directly (no external dependencies required — uses stdlib only). - -The script lives at `scripts/setup_coding_categories.py` relative to the plugin root. Use the appropriate plugin root variable for the current platform: -- Claude Code: `${CLAUDE_PLUGIN_ROOT}` -- Codex: `${CODEX_PLUGIN_ROOT}` -- Cursor: `${CURSOR_PLUGIN_ROOT}` - -```bash -python3 "/scripts/setup_coding_categories.py" --apply -``` - -If the script reports an error, show the error message and suggest checking the API key. - -## Step 5: Summary - -Print a summary: -``` -Onboarding complete. - user_id: - project_id: - branch: - imported: files - categories: - -Memory is now active for this project. Start working — mem0 will -automatically search relevant context and capture learnings. - -Run /mem0:tour to see what mem0 already knows about this project. -``` diff --git a/mem0-plugin/skills/mem0-tour/SKILL.md b/mem0-plugin/skills/mem0-tour/SKILL.md deleted file mode 100644 index 2efc216bc..000000000 --- a/mem0-plugin/skills/mem0-tour/SKILL.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: mem0-tour -description: > - Show what mem0 knows about the current project. Dumps top memories - grouped by category. Power-user-friendly proof of value. - TRIGGER: user runs /mem0:tour, or asks "what do you know about this project", - "show me my memories", "what has mem0 stored". ---- - -# Mem0 Project Tour - -Show the user what mem0 has stored for the current project. - -## Execution - -1. Run the following `search_memories` calls in parallel (all with the active `user_id` and `metadata.project_id`): - - - `query="architecture decisions"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "decision"}}]}`, `limit=5` - - `query="anti patterns failures"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "anti_pattern"}}]}`, `limit=5` - - `query="task learnings strategies"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "task_learning"}}]}`, `limit=5` - - `query="coding conventions"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "convention"}}]}`, `limit=5` - - `query="user preferences"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "user_preference"}}]}`, `limit=5` - - `query="project profile"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "project_profile"}}]}`, `limit=5` - - `query="tooling setup environment"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}, {"metadata": {"type": "environmental"}}]}`, `limit=5` - -2. Group results by category. For each category with results, print: - - ``` - ## ( memories) - - (score: ) - - ... - ``` - -3. For categories with zero results, print: `: (empty)` - -4. Print totals at the end: - ``` - --- - Total: memories across categories for project - ``` - -5. If ALL categories are empty, print: - ``` - No memories stored yet for project . - Run /mem0:onboard to import project files, or start working — mem0 captures learnings automatically. - ``` diff --git a/mem0-plugin/skills/mem0/SKILL.md b/mem0-plugin/skills/mem0/SKILL.md index e5ee18068..24cd827f2 100644 --- a/mem0-plugin/skills/mem0/SKILL.md +++ b/mem0-plugin/skills/mem0/SKILL.md @@ -1,17 +1,6 @@ --- name: mem0 -description: > - Mem0 Platform SDK for adding persistent memory to AI applications. - TRIGGER when: user mentions "mem0", "MemoryClient", "memory layer", - "remember user preferences", "persistent context", "personalization", - or needs to add long-term memory to chatbots, agents, or AI apps. - Covers Python SDK (mem0ai), TypeScript SDK (mem0ai), and framework integrations - (LangChain, CrewAI, OpenAI Agents SDK, Pipecat, LlamaIndex, AutoGen, LangGraph). - Also covers the open-source self-hosted Memory class. - This is the DEFAULT mem0 skill for ambiguous queries. - DO NOT TRIGGER when: user asks about CLI commands, terminal usage, or shell - scripts (use mem0-cli), or Vercel AI SDK / @mem0/vercel-ai-provider / createMem0 - (use mem0-vercel-ai-sdk). +description: Mem0 SDK reference covering Python and TypeScript APIs, memory client methods, configuration, and framework integrations. Use when writing code that calls mem0 APIs, configuring memory providers, or integrating mem0 into an application. license: Apache-2.0 metadata: author: mem0ai @@ -25,7 +14,6 @@ compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm i > **Skill Graph:** This skill is part of the Mem0 skill graph: > - **mem0** (this skill) -- Platform Client SDK + OSS (Python + TypeScript) -> - **[mem0-cli](https://github.com/mem0ai/mem0/tree/main/skills/mem0-cli)** -- Command-line interface > - **[mem0-vercel-ai-sdk](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk)** -- Vercel AI SDK provider Mem0 is a managed memory layer for AI applications. It stores, retrieves, and manages user memories via API — no infrastructure to deploy. For self-hosted usage, see the OSS section in the client references below. @@ -46,7 +34,7 @@ export MEM0_API_KEY="m0-your-api-key" Get an API key at: https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill -> **Don't have a `MEM0_API_KEY`?** Run `mem0 init --agent --json` (after `pip install mem0-cli` or `npm install -g @mem0/cli`) to mint an evaluation key without email or dashboard. The human can claim later with `mem0 init --email `. +> **Don't have a `MEM0_API_KEY`?** Sign up at https://app.mem0.ai and create one from the dashboard. Keys start with `m0-`. ## Step 2: Initialize the client @@ -134,20 +122,27 @@ def chat(user_input: str, user_id: str) -> str: ## Common edge cases -- **Search returns empty:** Memories process asynchronously. Wait 2-3s after `add()` before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax. -- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. Use `OR` instead, or query separately. -- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. Stick to one mode. -- **Wrong import:** Always use `from mem0 import MemoryClient` (or `AsyncMemoryClient` for async). Do not use `from mem0 import Memory`. -- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed for your use case. +- **Search returns empty:** v3 processes `add()` asynchronously — returns an event ID immediately. Wait 2-3s before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax. +- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. `{"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}` returns nothing. Use `OR` instead, or query each separately. +- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. `infer=True` extracts facts via LLM with dedup. `infer=False` stores raw — same text can be stored twice. +- **Implicit null scoping:** `filters={"user_id": "alice"}` only returns memories where `agent_id`, `app_id`, `run_id` are ALL null. Wrap in `{"OR": [...]}` to include memories with non-null scoping fields. +- **Platform vs OSS imports:** Platform: `from mem0 import MemoryClient`. OSS: `from mem0 import Memory`. Don't mix them — `MemoryClient` talks to `api.mem0.ai`, `Memory` runs locally. +- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed. -## v2 Compatibility +## v3 API (Current) -If you're using SDK v2.x, note these differences: -- **Entity IDs:** Pass `user_id` as top-level kwarg to `search()` instead of inside `filters` -- **Defaults:** `top_k=100`, no threshold, `rerank=True` -- **Graph memory:** Available via `enable_graph=True` +Mem0 v3 uses single-pass extraction, entity linking, and multi-signal retrieval. -See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details. +**Key v3 changes from v2:** +- **Endpoints:** `POST /v3/memories/add/`, `POST /v3/memories/search/`, `POST /v3/memories/` (paginated list) +- **Extraction:** Single ADD-only pass — no more UPDATE/DELETE operations during extraction. Memories accumulate rather than consolidate. +- **Entity linking:** Replaces graph memory. Auto-extracted during `add()`, no config needed. Remove `enable_graph` and `graph_store` from any old config. +- **Defaults:** `top_k=20`, `threshold=0.1`, `rerank=False` +- **Removed params:** `org_id`, `project_id`, `enable_graph` — all removed from SDK +- **TypeScript:** Exclusively camelCase (`userId`, `agentId`, `appId`, `topK`) +- **Add response:** Async — returns event ID immediately, poll via `GET /v1/event/{event_id}/` + +See the [migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details. ## Live documentation search @@ -189,5 +184,4 @@ Load these on demand for deeper detail: | Skill | When to use | Link | |-------|-------------|------| -| mem0-cli | Terminal commands, scripting, CI/CD, agent tool loops | [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-cli) | | mem0-vercel-ai-sdk | Vercel AI SDK provider with automatic memory | [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk) | diff --git a/mem0-plugin/skills/mem0/references/api-reference.md b/mem0-plugin/skills/mem0/references/api-reference.md index 62ec4bea7..4ab8548ca 100644 --- a/mem0-plugin/skills/mem0/references/api-reference.md +++ b/mem0-plugin/skills/mem0/references/api-reference.md @@ -14,8 +14,8 @@ All endpoints require: `Authorization: Token ` | Get Single Memory | `GET` | `/v1/memories/{memory_id}/` | | Update Memory | `PUT` | `/v1/memories/{memory_id}/` | | Delete Memory | `DELETE` | `/v1/memories/{memory_id}/` | - -Note: v1/v2 endpoints still work (backward compatible). +| Delete All Memories | `DELETE` | `/v1/memories/?user_id=X&app_id=Y` | +| Get Event Status | `GET` | `/v1/event/{event_id}/` | ## Memory Object Structure diff --git a/mem0-plugin/skills/mem0/references/features.md b/mem0-plugin/skills/mem0/references/features.md index b5d5e73ea..66875ee75 100644 --- a/mem0-plugin/skills/mem0/references/features.md +++ b/mem0-plugin/skills/mem0/references/features.md @@ -69,7 +69,7 @@ If you were using `enable_graph=True` in v2: - Remove `graph_store` from OSS configuration - Entity relationships are now consumed through retrieval ranking, not exposed as a separate `relations` array -See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details. +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details. --- diff --git a/mem0-plugin/skills/mem0/references/quickstart.md b/mem0-plugin/skills/mem0/references/quickstart.md index 0a47a2fd4..132982a49 100644 --- a/mem0-plugin/skills/mem0/references/quickstart.md +++ b/mem0-plugin/skills/mem0/references/quickstart.md @@ -74,7 +74,7 @@ console.log(results); export MEM0_API_KEY="m0-your-api-key" # Add memory -curl -X POST https://api.mem0.ai/v1/memories/ \ +curl -X POST https://api.mem0.ai/v3/memories/add/ \ -H "Authorization: Token $MEM0_API_KEY" \ -H "Content-Type: application/json" \ -d '{ diff --git a/mem0-plugin/skills/memory-reviewer/SKILL.md b/mem0-plugin/skills/memory-reviewer/SKILL.md new file mode 100644 index 000000000..4cc7a5970 --- /dev/null +++ b/mem0-plugin/skills/memory-reviewer/SKILL.md @@ -0,0 +1,59 @@ +--- +name: memory-reviewer +description: Reviews stored memory quality by detecting duplicates, contradictions, and stale entries with actionable recommendations. Use when search results seem conflicting, before running dream consolidation, or for periodic memory hygiene audits. +--- + +# Memory Reviewer + +Audits memory quality for the active project. Finds duplicates, contradictions, and low-confidence entries. + +## When to use + +- User asks "check my memories", "memory quality", "any duplicates?" +- User runs `/mem0:memory-reviewer` directly +- After a session with 5+ memory writes (suggest proactively) +- After `/mem0:health --deep` identifies issues + +## Steps + +1. **Fetch all memories** for active project via `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200`. Paginate if needed — cap at 200 memories. + +2. **Group by `metadata.type`**. Common types: `decision`, `convention`, `anti_pattern`, `task_learning`, `project_profile`, `user_preference`, `session_state`. + +3. **Scan each group for issues:** + + | Issue | Detection method | + |---|---| + | **Near-duplicates** | >60% noun overlap within same type. Compare memory text after stripping stop words. | + | **Contradictions** | Opposing facts about same topic (e.g., "use PostgreSQL" vs "use MySQL" for same component) | + | **Low-confidence** | `metadata.confidence < 0.3` | + | **Missing type** | No `metadata.type` set | + | **Stale** | `created_at` older than 180 days with no updates | + +4. **Output compact summary:** + +``` +memory-reviewer: project= total= + duplicates: found + contradictions: found + low_confidence: found + untagged: found + stale: found +``` + +5. **If issues found**, list them with memory IDs: + +``` +Issues: + [duplicate] "" ≈ "" [mem0:, mem0:] + [contradiction] "" vs "" [mem0:, mem0:] + [low_conf] "" (confidence: 0.1) [mem0:] +``` + +6. **Suggest action**: "Run `/mem0:dream` to consolidate duplicates and resolve contradictions." + +## Constraints + +- **Read-only** — never modify or delete memories (that's `/mem0:dream`'s job) +- **Max 200 memories** per scan +- Report findings, let user decide on action diff --git a/mem0-plugin/skills/onboard/SKILL.md b/mem0-plugin/skills/onboard/SKILL.md new file mode 100644 index 000000000..f8ecfb9d9 --- /dev/null +++ b/mem0-plugin/skills/onboard/SKILL.md @@ -0,0 +1,186 @@ +--- +name: onboard +description: Sets up mem0 for a new project including API key configuration, MCP authentication, project file import, and coding categories. Use on first run in a new project, when API key needs updating, or to re-run initial setup after configuration changes. +--- + +# Mem0 Onboarding Wizard + +Run this wizard to set up the mem0 plugin for the current project. Complete in ~60 seconds. + +**IMPORTANT: Execute steps strictly in order (0 → 1 → 2 → 3 → 4 → 5 → 6). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead. Complete one step fully before starting the next.** + +## Step 0: Ensure mem0ai SDK is installed + +The plugin installs the `mem0ai` Python SDK automatically on session start via a venv in `${CLAUDE_PLUGIN_DATA}/venv`. If Step 5 (categories) fails with an import error, run: + +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/ensure_deps.sh" +``` + +This is silent and idempotent — safe to run anytime. + +## Step 1: Set up API key + +Check if the API key is available from any source: + +```bash +[ -n "${MEM0_API_KEY:-${CLAUDE_PLUGIN_OPTION_API_KEY:-}}" ] && echo "SET" || echo "NOT_SET" +``` + +IMPORTANT: Never run `echo $MEM0_API_KEY` — that prints the secret in plaintext to the conversation log. + +### If API key IS set (output is "SET") + +Print: `- API key found.` and proceed to Step 2. + +### If API key is NOT set (output is "NOT_SET") + +Guide the user through API key setup. Show this message: + +``` +Step 1: Setting up API key. + +- API key not found. Let's set it up. + + 1. Get your API key from https://app.mem0.ai/dashboard/api-keys + + 2. Choose ONE method: + + Option A — CLI (shell profile): + echo 'export MEM0_API_KEY="m0-your-key-here"' >> ~/.zshrc + source ~/.zshrc + + Option B — Desktop app (local environment editor): + Click the environment dropdown next to the prompt box, + hover over "Local", click the gear icon, and add: + MEM0_API_KEY = m0-your-key-here + (Stored encrypted on your machine, applies to all local sessions) + + Note: The Desktop app does NOT inherit custom env vars from + shell profiles — it only reads PATH. Use Option B for Desktop. + + 3. Verify: + [ -n "${MEM0_API_KEY:-${CLAUDE_PLUGIN_OPTION_API_KEY:-}}" ] && echo "SET" || echo "NOT_SET" +``` + +After the user confirms, re-run the verify command. If NOT_SET, repeat. If SET, proceed to Step 2. + +## Step 2: MCP server connection + +First, check if MCP tools are already available using ToolSearch with query `"mem0 search_memories"`. The exact tool name varies by install method (may be `mcp__mem0__search_memories` or `mcp__plugin_mem0_mem0__search_memories`). + +**If MCP tools ARE found:** Print `- MCP already connected.` and proceed to Step 3. + +**If MCP tools are NOT found:** + +The MCP server authenticates using the `MEM0_API_KEY` set in Step 1. No OAuth or browser login is needed. + +1. Verify the API key is set (re-run the Step 1 check) +2. Check the plugin is installed: run `/plugins` and confirm `mem0` appears +3. Check the MCP server is listed: run `/mcp` and look for `mcp.mem0.ai` +4. If the server shows an error, ask the user to restart Claude Code and run `/mem0:onboard` again +5. If all checks pass but tools are still missing: "Restart Claude Code and run `/mem0:onboard` again." + +**STOP here** — do not proceed without MCP tools. + +## Step 3: Verify connectivity and show identity + +Call `search_memories` with `query="project setup"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` to verify connectivity. + +Print: +``` +- Connected + user: + project: + branch: +``` + +If the search fails, troubleshoot the API key and MCP connection. + +## Step 4: Import project files + +Project files (CLAUDE.md, AGENTS.md, etc.) are automatically imported into mem0 when a session starts. This step verifies import status and triggers a re-import if needed. + +### 4a: Detect project files + +```bash +for f in CLAUDE.md AGENTS.md .cursorrules .windsurfrules mem0.md; do + [ -f "$f" ] && echo "FOUND: $f ($(wc -c < "$f") bytes)" +done || true +``` + +If no files found, print `- No project files found. Skipping import.` and proceed to Step 5. + +### 4b: Check and import + +Run auto_import in foreground to check status and import if needed: + +```bash +MEM0_DEBUG=1 MEM0_CWD="$PWD" python3 "${CLAUDE_PLUGIN_ROOT}/scripts/auto_import.py" +``` + +### 4c: Report to user + +Parse the auto_import output and print a user-friendly summary: + +- If output contains `Imported` lines: + ``` + - Importing project files into mem0... done. + file(s) imported ( chunks). These are stored verbatim for future context. + ``` +- If output contains only `skipping` lines: + ``` + - Project files already in mem0 (imported during session start). Verified server-side. + ``` +- If output contains `re-importing`: + ``` + - Project files were missing from mem0. Re-imported successfully. + ``` +- If output contains errors or no files were processed: + ``` + - Project file import failed. Check API key and retry with: /mem0:onboard + ``` + +## Step 5: Install coding categories + +The setup script is idempotent — it compares existing categories against the proposed set and skips the API call if they already match (tolerates order differences and extra API fields). Safe to re-run. + +Ask: "Install coding categories optimized for development workflows? [Y/n]" + +If yes, run the setup script using the plugin's venv python: + +```bash +VENV_PY="${CLAUDE_PLUGIN_DATA}/venv/bin/python3" +if [ -x "${VENV_PY}" ]; then + "${VENV_PY}" "${CLAUDE_PLUGIN_ROOT}/scripts/setup_coding_categories.py" --apply +else + python3 "${CLAUDE_PLUGIN_ROOT}/scripts/setup_coding_categories.py" --apply +fi +``` + +Parse the output: +- `"Categories already match -- skipping update."` → Print: `- Coding categories already installed. Skipped.` +- `"Done."` → Print: `- Coding categories installed ( categories).` +- Error → Print the error and suggest re-running `/mem0:onboard`. + +If the script fails with "mem0ai SDK not found", run the dependency installer first: +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/ensure_deps.sh" +``` +Then retry the categories script. + +## Step 6: Summary + +Print a summary: +``` +- Onboarding complete. + user_id: + project_id: (app_id) + files: found, imported + categories: + +Memory is now active for this project. Start working — mem0 will +automatically search relevant context and capture learnings. + +Run /mem0:tour to see what mem0 already knows about this project. +``` diff --git a/mem0-plugin/skills/peek/SKILL.md b/mem0-plugin/skills/peek/SKILL.md new file mode 100644 index 000000000..ccc6b2a0c --- /dev/null +++ b/mem0-plugin/skills/peek/SKILL.md @@ -0,0 +1,52 @@ +--- +name: peek +description: Searches memories and displays compact one-liner results, or looks up a specific memory by ID. Use for quick memory lookups, checking if a decision was recorded, resolving [mem0:id] citations, or browsing memories without full category detail. +--- + +# Mem0 Peek + +Quick search with compact output. Lighter than `/mem0:tour`. + +## Execution + +### Step 1: Parse query + +The user provides a search query: `/mem0:peek auth middleware` + +If no query provided, ask: "What should I search for?" + +**Memory ID detection:** If the query matches any of these patterns, treat it as a +direct memory ID lookup instead of a search: +- Bare hex: `^[a-f0-9]{8}$` (short ID) or `^[a-f0-9]{8}-[a-f0-9-]+$` (full UUID) +- Citation ref: `[mem0:]` — extract the hex portion + +When an ID is detected: +1. Call `get_memory()` directly (if short ID, try as prefix of full UUID) +2. If found, skip to Step 3 and display the single result +3. If not found, fall through to search using the ID as query text + +### Step 2: Search + +Run 2 parallel `search_memories` calls: + +1. Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +2. Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5`, `rerank=true` + +### Step 3: Display + +Deduplicate by ID, then show compact results: + +``` +## mem0 peek: "" ( results) + +1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1] +2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a] +3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7] +``` + +Format: `. [] () [mem0:]` + +If no results: +``` +No memories matching "" for project . +``` diff --git a/mem0-plugin/skills/pin/SKILL.md b/mem0-plugin/skills/pin/SKILL.md new file mode 100644 index 000000000..5ee20be7d --- /dev/null +++ b/mem0-plugin/skills/pin/SKILL.md @@ -0,0 +1,67 @@ +--- +name: pin +description: Pins or unpins a memory to protect it from pruning during dream consolidation. Use when a memory is critical and must never be removed, such as architecture decisions, security constraints, or immutable team conventions. +--- + +# Mem0 Pin + +Pin a memory to mark it as high-priority and protect from pruning. + +## Execution + +### Step 1: Find the memory + +The user provides either a search query or memory ID. + +**If memory ID:** +- Call `get_memory` with the ID. + +**If search query:** +- Call `search_memories` with the query, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=5`. +- Show numbered list with content previews. +- Ask: "Which memory to pin? Enter a number." + +### Step 2: Read current content + +Call `get_memory` with the selected memory ID. Store: +- `original_text` — the memory's text content +- `original_metadata` — the existing `metadata` dict + +### Step 3: Pin it + +The MCP `update_memory` tool only accepts `memory_id`, `text`, and `source` — it +does not accept a `metadata` parameter. To pin, append a pin marker to the text: + +```python +pinned_text = "[PINNED] " + original_text if not original_text.startswith("[PINNED]") else original_text +update_memory(memory_id=, text=pinned_text) +``` + +**For new memories** (user wants to pin text that isn't stored yet): +1. Call `add_memory` with: + - `text="[PINNED] "` + - `user_id=` + - `app_id=` + - `metadata={"pinned": true, "type": "decision", "confidence": 1.0}` + - `infer=False` +2. The response contains `event_id`. Call `get_event_status(event_id=)` once to retrieve the memory ID, then confirm. + +### Step 4: Confirm + +``` +Pinned: "" +Memory ID: +``` + +Append `...` only if content exceeds 80 characters. + +### Unpin + +If the user says "unpin": +1. Call `get_memory` to read current content. +2. Remove the pin marker from the text: + ```python + unpinned_text = original_text.removeprefix("[PINNED] ") + update_memory(memory_id=, text=unpinned_text) + ``` +3. Print: `Unpinned: "..."` diff --git a/mem0-plugin/skills/remember/SKILL.md b/mem0-plugin/skills/remember/SKILL.md new file mode 100644 index 000000000..599c2f7a7 --- /dev/null +++ b/mem0-plugin/skills/remember/SKILL.md @@ -0,0 +1,57 @@ +--- +name: remember +description: Stores a memory verbatim from user input with appropriate type classification and metadata. Use when the user says remember this, save this, store this, note that, or explicitly asks to record a decision, preference, convention, or learning. +--- + +# Mem0 Remember + +Store a fact or learning directly into mem0. + +## Execution + +### Step 1: Extract the content + +The user provides the content as an argument: `/mem0:remember ` + +If no text was provided, ask: "What should I remember?" + +### Step 2: Classify the memory + +Based on the content, pick the best `metadata.type`: + +| Content signal | Type | +|---|---| +| "we decided...", "always use...", "never..." | `decision` | +| "X doesn't work because...", "don't try..." | `anti_pattern` | +| "I prefer...", "use X instead of Y" | `user_preference` | +| "the convention is...", "we always..." | `convention` | +| "learned that...", "figured out..." | `task_learning` | +| setup, env, tooling, config | `environmental` | +| anything else | `task_learning` | + +### Step 3: Store + +Call `add_memory` with: +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={"type": "", "branch": "", "confidence": 1.0, "source": "remember_command"}` +- `infer=False` + +`infer=False` because the user stated the fact explicitly — no extraction needed. +`confidence=1.0` because the user explicitly asked to store this. + +### Step 4: Confirm + +The `add_memory` response returns `event_id` (not `memory_id`) because writes are async. +Call `get_event_status(event_id=)` once. + +- If status is `SUCCEEDED`: print the memory ID from the result. +- If status is `PENDING` or `processing`: print with the event ID as fallback. + +``` +Remembered as : "" +Memory ID: +``` + +Append `...` only if content was truncated (longer than 80 chars). diff --git a/mem0-plugin/skills/stats/SKILL.md b/mem0-plugin/skills/stats/SKILL.md new file mode 100644 index 000000000..5652cb58e --- /dev/null +++ b/mem0-plugin/skills/stats/SKILL.md @@ -0,0 +1,130 @@ +--- +name: stats +description: Displays memory usage statistics for the current session and project including counts by category, age distribution, and API latency. Use when checking how many memories exist, reviewing session activity, or auditing memory distribution across categories. +--- + +# Mem0 Stats + +Show session and lifetime memory statistics. + +## Execution + +### Step 1: Gather session stats + +Run the session stats reporter: + +```bash +SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts" +python3 "$SCRIPT_DIR/session_stats.py" peek 2>/dev/null || echo "{}" +``` + +The `peek` command returns JSON without clearing the stats file (unlike `report`). + +If the script returns empty or errors, note "No session data available" and continue. + +### Step 2: Fetch lifetime and session stats from API + +**Lifetime stats:** +Call `get_memories` to fetch all memories for this project: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=100` + +Group by: +1. `categories[0]` (platform-assigned) — primary grouping +2. `metadata.type` (agent-assigned) — secondary if no categories +3. `created_at` date — for age analysis + +**Category normalization:** Merge `auto_capture` and `uncategorized` into a single `uncategorized` row. These are memories where the platform didn't assign a meaningful content category. Do NOT show `auto_capture` as its own row in the table. + +**Session stats (local only):** +Session stats come from the local stats file read in Step 1. Do NOT query the API with +`run_id` or `metadata.session_id` filters — these return unreliable results because +memories are stored without `run_id` and metadata filters on `session_id` are inconsistent. + +The local stats file tracks adds and searches for the current session accurately. + +Also run a `search_memories` MCP tool call with `query="project"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` to measure round-trip latency. Note the time before and after the MCP call — do NOT attempt raw HTTP calls to the API. + +### Step 3: Display + +Print a minimal dashboard. No ASCII bar charts — use a clean table layout: + +``` +## mem0 stats + +**Session** () — 3 written, 5 searches, categories: decision, convention + +**Project: my-project** — 55 memories, API: 84ms + +| Category | Count | +|----------------------|-------| +| decision | 24 | +| convention | 15 | +| anti_pattern | 6 | +| task_learning | 5 | +| user_preference | 3 | +| session_state | 2 | + +**Age** — oldest: 2026-02-15, newest: 2026-05-23 + < 7 days: 5 · 7–30d: 12 · 30–90d: 10 · > 90d: 8 + +**Identity** — user: kartik · project: my-project · branch: main +``` + +**Display rules:** +- Category table: sort by count descending, omit categories with 0 memories +- Age: single line with dot-separated buckets, computed from `created_at` +- Session line: skip if no session data available +- If only 1-2 total memories, skip the category table — just show the count +- Keep everything compact — no decorative borders or filler + +## Weekly digest mode + +When invoked with `--weekly` (e.g., `/mem0:stats --weekly`), append a weekly +activity digest after the standard stats dashboard: + +### W1: Fetch recent memories + +Call `search_memories` in parallel with time-scoped queries: +1. `query="decisions made this week"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"created_at": {"gte": "<7 days ago YYYY-MM-DD>"}}]}`, `top_k=20` +2. `query="bugs errors fixes"`, same time filter, `top_k=20` +3. `query="patterns conventions learnings"`, same time filter, `top_k=20` + +### W2: Analyze + +Merge by ID. Group into "New this week" by `categories[0]` or `metadata.type`. +Calculate: memories added last 7 days, most active categories, most active day. + +### W3: Display + +Append after the standard stats: + +``` +### This week (May 16 – May 23) + ++12 memories — most active: Wednesday (5) + +| Category | New | +|---------------|-----| +| decision | 5 | +| task_learning | 4 | +| bug_fix | 3 | + +**Highlights** +- <2-3 sentence summary of most important decisions/learnings this week> +``` + +### W4: Write digest file + +Write to `~/.mem0/weekly-digest.md` (overwrite). Append one-line to +`~/.mem0/digest-history.log`: +``` + | | + memories | top: +``` + +### W5: Empty state + +If no new memories in 7 days: +``` +No new memories in the past week. Total: memories in . +``` diff --git a/mem0-plugin/skills/mem0-switch-project/SKILL.md b/mem0-plugin/skills/switch-project/SKILL.md similarity index 75% rename from mem0-plugin/skills/mem0-switch-project/SKILL.md rename to mem0-plugin/skills/switch-project/SKILL.md index 8ed2415f2..706c20269 100644 --- a/mem0-plugin/skills/mem0-switch-project/SKILL.md +++ b/mem0-plugin/skills/switch-project/SKILL.md @@ -1,10 +1,6 @@ --- -name: mem0-switch-project -description: > - Manually override project_id for the current directory. - Useful for monorepos, nested git dirs, or non-git directories. - TRIGGER: user runs /mem0:switch-project , or asks "switch mem0 project", - "change project scope", "override project_id". +name: switch-project +description: Overrides the auto-detected project scope to read and write memories under a different project ID. Use when working across multiple projects, accessing memories from another repo, or when auto-detection resolves to the wrong project. --- # Mem0 Switch Project @@ -40,7 +36,7 @@ The user provides a project name as an argument: `/mem0:switch-project ` with the user's chosen project name.) 3. Verify by searching for existing memories: - - Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": ""}, {"metadata": {"project_id": ""}}]}`, `limit=1` + - Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` 4. Print: ``` diff --git a/mem0-plugin/skills/tour/SKILL.md b/mem0-plugin/skills/tour/SKILL.md new file mode 100644 index 000000000..7e3bcb4aa --- /dev/null +++ b/mem0-plugin/skills/tour/SKILL.md @@ -0,0 +1,123 @@ +--- +name: tour +description: Browses all stored memories grouped by category with full content display. Use when reviewing all project memories, exploring stored knowledge, onboarding to a project, or getting an overview of captured decisions, conventions, and learnings. +--- + +# Mem0 Project Tour + +Show the user what mem0 has stored for the current project. + +## Cross-project mode + +When invoked with `--all-projects` (e.g., `/mem0:tour --all-projects` or +`/mem0:tour --all-projects auth middleware`), search across ALL projects: + +1. Call `get_memories` with `filters={"AND": [{"user_id": ""}]}`, `page_size=200` — **no `app_id` filter**. +2. If a search query was also provided, run `search_memories` with `query=`, + `filters={"AND": [{"user_id": ""}]}`, `top_k=20` — again no `app_id`. +3. Group results by `app_id` first, then by category within each project. +4. Display: + ``` + ## ( memories) ← current + **Architecture Decisions** — + ... + + ## ( memories) + ... + + memories across projects + ``` +5. Mark the current project with `← (current)` in the heading. + +If `--all-projects` is NOT present, use the standard single-project flow below. + +## Peek mode (compact search) + +When `/mem0:tour` receives a search query argument (e.g., `/mem0:tour auth middleware`) +WITHOUT `--all-projects`, run in **peek mode** — compact one-liner results: + +1. Run 2 parallel `search_memories` calls: + - Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` + - Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5`, `rerank=true` +2. Deduplicate by ID, display compact results: + ``` + ## mem0 search: "" ( results) + + 1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1] + 2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a] + 3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7] + ``` + Format: `. [] () [mem0:]` +3. If no results: `No memories matching "" for project .` + +If no query argument and no `--all-projects` flag, use the full tour flow below. + +## Execution + +### Step 1: Fetch ALL memories for this project + +Call `get_memories` to fetch all memories for this project: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=100` + +### Step 2: Run supplementary semantic searches + +In parallel, run these `search_memories` calls to get relevance-ranked results for key topics: + +- `query="architecture decisions design choices"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +- `query="bugs errors failures anti-patterns"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +- `query="project setup tooling conventions preferences"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` + +**Do NOT filter by `metadata.type` in these calls.** The platform auto-assigns `categories` — filtering on `metadata.type` misses memories that were auto-categorized but don't have an explicit `metadata.type`. + +### Step 3: Merge and group + +Merge all results by memory ID (deduplicate). For each memory, determine its group using this priority: + +1. **Platform `categories` field** (array on each memory, auto-assigned by Mem0). Use the first category value. +2. **`metadata.type` field** (if present, set explicitly by hooks/agent). Use as fallback if no `categories`. +3. **"other"** bucket for memories with neither. + +Map category names to display names: + +| Platform category / metadata.type | Display name | +|---|---| +| `architecture decisions`, `architecture_decisions`, `decision` | Architecture Decisions | +| `anti patterns`, `anti_patterns`, `anti_pattern` | Anti-Patterns | +| `task learnings`, `task_learnings`, `task_learning` | Task Learnings | +| `coding conventions`, `coding_conventions`, `convention` | Coding Conventions | +| `user preferences`, `user_preferences`, `user_preference` | User Preferences | +| `project profile`, `project_profile` | Project Profile | +| `tooling setup`, `tooling_setup`, `environmental` | Tooling & Setup | +| `technology`, `professional_details` | Tooling & Setup | +| `session_state` | Session State | +| `compact_summary` | Compact Summaries | +| anything else | Other | + +### Step 4: Display results + +Sort groups by descending memory count. For each group that has results, print: + +``` +## ( memories) +- (score: ) +- ... +``` + +Show the **full memory text** for each entry — do NOT truncate. If a group has more than 10 entries, show top 10 by recency (or similarity score if from a search call) and note `... and more`. + +For groups with zero results, skip them entirely — don't print empty groups. + +### Step 5: Print totals + +``` + memories across categories — project: , branch: +``` + +### Step 6: Empty state + +If zero memories found for this project, print: +``` +No memories stored yet for project . +Run /mem0:onboard to import project files, or start working — mem0 captures learnings automatically. +``` diff --git a/mem0-plugin/tests/conftest.py b/mem0-plugin/tests/conftest.py index d760e5882..ab3eb809c 100644 --- a/mem0-plugin/tests/conftest.py +++ b/mem0-plugin/tests/conftest.py @@ -22,6 +22,18 @@ def _scripts_on_path(): sys.path.remove(abs_scripts) +@pytest.fixture(autouse=True) +def _clean_project_map(monkeypatch): + """Remove project_map.json and clear MEM0_PROJECT_ID before each test.""" + monkeypatch.delenv("MEM0_PROJECT_ID", raising=False) + map_path = os.path.expanduser("~/.mem0/project_map.json") + if os.path.isfile(map_path): + os.remove(map_path) + yield + if os.path.isfile(map_path): + os.remove(map_path) + + @pytest.fixture() def tmp_git_repo(tmp_path): """Create a temp dir with a git repo and HTTPS remote.""" diff --git a/mem0-plugin/tests/test_auto_capture.py b/mem0-plugin/tests/test_auto_capture.py new file mode 100644 index 000000000..3866b9503 --- /dev/null +++ b/mem0-plugin/tests/test_auto_capture.py @@ -0,0 +1,142 @@ +"""Tests for auto_capture.py transcript parsing and exchange extraction.""" + +from __future__ import annotations + +import json +import os +import sys + +import pytest + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") + + +@pytest.fixture(autouse=True) +def _scripts_path(): + abs_scripts = os.path.abspath(SCRIPTS_DIR) + if abs_scripts not in sys.path: + sys.path.insert(0, abs_scripts) + yield + if abs_scripts in sys.path: + sys.path.remove(abs_scripts) + + +def _make_transcript(tmp_path, entries): + path = tmp_path / "transcript.jsonl" + lines = [] + for entry in entries: + lines.append(json.dumps(entry)) + path.write_text("\n".join(lines) + "\n") + return str(path) + + +def _msg(role, content): + return {"message": {"role": role, "content": content}} + + +class TestTailLines: + def test_reads_last_n_lines(self, tmp_path): + from auto_capture import tail_lines + + p = tmp_path / "test.txt" + p.write_text("\n".join(f"line{i}" for i in range(100)) + "\n") + result = tail_lines(str(p), 5) + assert len(result) >= 5 + assert result[-1] == "line99" + + def test_empty_file(self, tmp_path): + from auto_capture import tail_lines + + p = tmp_path / "empty.txt" + p.write_text("") + assert tail_lines(str(p), 10) == [] + + def test_nonexistent_file(self): + from auto_capture import tail_lines + + assert tail_lines("/nonexistent/path", 10) == [] + + +class TestExtractRecentExchanges: + def test_extracts_user_assistant_pairs(self): + from auto_capture import extract_recent_exchanges + + lines = [ + json.dumps(_msg("user", "What is Python used for?" * 3)), + json.dumps(_msg("assistant", "Python is used for many things." * 3)), + json.dumps(_msg("user", "Tell me about web frameworks." * 3)), + json.dumps(_msg("assistant", "Django and Flask are popular." * 3)), + ] + result = extract_recent_exchanges(lines, max_exchanges=2) + assert len(result) == 4 + assert result[0]["role"] == "user" + assert result[1]["role"] == "assistant" + + def test_skips_short_messages(self): + from auto_capture import extract_recent_exchanges + + lines = [ + json.dumps(_msg("user", "ok")), + json.dumps(_msg("assistant", "Sure, here is a detailed explanation." * 3)), + ] + result = extract_recent_exchanges(lines, max_exchanges=2) + assert len(result) == 1 + assert result[0]["role"] == "assistant" + + def test_skips_compact_summaries(self): + from auto_capture import extract_recent_exchanges + + lines = [ + json.dumps({"isCompactSummary": True, "message": {"role": "assistant", "content": "summary " * 20}}), + json.dumps(_msg("user", "This is a real user message here." * 2)), + ] + result = extract_recent_exchanges(lines, max_exchanges=2) + assert len(result) == 1 + assert result[0]["role"] == "user" + + def test_limits_to_max_exchanges(self): + from auto_capture import extract_recent_exchanges + + lines = [] + for i in range(10): + lines.append(json.dumps(_msg("user", f"Question number {i} with enough text to pass." * 2))) + lines.append(json.dumps(_msg("assistant", f"Answer number {i} with enough text to pass." * 2))) + result = extract_recent_exchanges(lines, max_exchanges=2) + assert len(result) == 4 + + def test_handles_list_content(self): + from auto_capture import extract_recent_exchanges + + lines = [ + json.dumps({"message": {"role": "user", "content": [ + {"type": "text", "text": "This is block content that is long enough." * 2}, + ]}}), + ] + result = extract_recent_exchanges(lines, max_exchanges=2) + assert len(result) == 1 + assert "block content" in result[0]["content"] + + def test_empty_lines(self): + from auto_capture import extract_recent_exchanges + + assert extract_recent_exchanges([], max_exchanges=2) == [] + + def test_truncates_long_content(self): + from auto_capture import extract_recent_exchanges + + long_text = "x" * 5000 + lines = [json.dumps(_msg("user", long_text))] + result = extract_recent_exchanges(lines, max_exchanges=1) + assert len(result) == 1 + assert len(result[0]["content"]) == 2000 + + def test_skips_tool_call_assistant_messages(self): + from auto_capture import extract_recent_exchanges + + lines = [ + json.dumps(_msg("assistant", '{"tool_calls": [{"name": "read"}]}')), + json.dumps(_msg("user", "Thanks for reading that file for me!" * 2)), + ] + result = extract_recent_exchanges(lines, max_exchanges=2) + assert len(result) == 1 + assert result[0]["role"] == "user" diff --git a/mem0-plugin/tests/test_coding_categories.py b/mem0-plugin/tests/test_coding_categories.py new file mode 100644 index 000000000..c4d57b62f --- /dev/null +++ b/mem0-plugin/tests/test_coding_categories.py @@ -0,0 +1,88 @@ +"""Tests for setup_coding_categories.py -- CODING_CATEGORIES list completeness.""" + +from __future__ import annotations + +import importlib +import os +import sys + +import pytest + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") + +EXPECTED_KEYS = [ + "architecture_decisions", + "anti_patterns", + "task_learnings", + "tooling_setup", + "bug_fixes", + "coding_conventions", + "user_preferences", + "dependency_decisions", + "performance_findings", + "security_constraints", + "testing_patterns", + "data_model", + "api_contracts", + "deployment_runbook", + "team_norms", + "domain_glossary", + "experiment_results", +] + + +@pytest.fixture() +def coding_categories(): + """Import CODING_CATEGORIES from setup_coding_categories, ensuring scripts/ is on path.""" + abs_scripts = os.path.abspath(SCRIPTS_DIR) + inserted = False + if abs_scripts not in sys.path: + sys.path.insert(0, abs_scripts) + inserted = True + # Force re-import in case another test already loaded a stale version + mod_name = "setup_coding_categories" + if mod_name in sys.modules: + del sys.modules[mod_name] + mod = importlib.import_module(mod_name) + yield mod.CODING_CATEGORIES + if inserted and abs_scripts in sys.path: + sys.path.remove(abs_scripts) + + +def test_total_count(coding_categories): + """CODING_CATEGORIES must contain exactly 17 entries.""" + assert len(coding_categories) == 17, ( + f"Expected 17 categories, found {len(coding_categories)}: " + f"{[list(c.keys())[0] for c in coding_categories]}" + ) + + +def test_all_expected_keys_present(coding_categories): + """Every expected category key must appear exactly once.""" + actual_keys = [list(cat.keys())[0] for cat in coding_categories] + for key in EXPECTED_KEYS: + assert key in actual_keys, f"Missing expected category key: '{key}'" + + +def test_no_duplicate_keys(coding_categories): + """No category key may appear more than once.""" + actual_keys = [list(cat.keys())[0] for cat in coding_categories] + seen = set() + duplicates = [] + for key in actual_keys: + if key in seen: + duplicates.append(key) + seen.add(key) + assert not duplicates, f"Duplicate category keys found: {duplicates}" + + +def test_each_description_is_non_empty_string(coding_categories): + """Every category must have a non-empty string description.""" + for cat in coding_categories: + assert len(cat) == 1, f"Category dict should have exactly one key, got: {cat}" + key = list(cat.keys())[0] + description = cat[key] + assert isinstance(description, str), ( + f"Category '{key}' description is not a string: {type(description)}" + ) + assert description.strip(), f"Category '{key}' has an empty description" diff --git a/mem0-plugin/tests/test_import_competing_tools.py b/mem0-plugin/tests/test_import_competing_tools.py new file mode 100644 index 000000000..48a76d700 --- /dev/null +++ b/mem0-plugin/tests/test_import_competing_tools.py @@ -0,0 +1,355 @@ +"""Tests for import_competing_tools.py — competing tool file importers.""" + +from __future__ import annotations + +import json +import os +import sys +from unittest import mock + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") + + +# --------------------------------------------------------------------------- +# split_sections tests (unit tests on the splitter functions) +# --------------------------------------------------------------------------- + + +def test_split_by_headers_cursorrules(): + """split_by_headers correctly splits .cursorrules content on ## headers.""" + from _chunking import split_by_headers + + content = """\ +# My Cursor Rules + +Some preamble text that belongs to the first section. + +## TypeScript Conventions + +Always use strict mode. Prefer const over let. +Never use var. + +## React Patterns + +Use functional components with hooks. +Avoid class components. + +## Testing + +Write tests for all utility functions. +""" + chunks = split_by_headers(content, "## ") + assert len(chunks) == 4 # preamble + 3 sections + + # First chunk is the preamble (before any ## header) + assert "preamble text" in chunks[0] + + # Remaining chunks start with their header + assert chunks[1].startswith("## TypeScript Conventions") + assert "strict mode" in chunks[1] + + assert chunks[2].startswith("## React Patterns") + assert "functional components" in chunks[2] + + assert chunks[3].startswith("## Testing") + assert "utility functions" in chunks[3] + + +def test_split_by_headers_copilot(): + """split_by_headers correctly splits copilot-instructions.md on ## headers.""" + from _chunking import split_by_headers + + content = """\ +## Code Style + +Use 2-space indentation. Always add trailing commas. + +## Architecture + +Follow clean architecture principles. Keep business logic in domain layer. +""" + chunks = split_by_headers(content, "## ") + assert len(chunks) == 2 + assert chunks[0].startswith("## Code Style") + assert "2-space indentation" in chunks[0] + assert chunks[1].startswith("## Architecture") + assert "clean architecture" in chunks[1] + + +def test_split_by_headers_no_headers(): + """split_by_headers returns entire content as one chunk if no headers found.""" + from _chunking import split_by_headers + + content = "This file has no headers at all. Just plain text." + chunks = split_by_headers(content, "## ") + assert len(chunks) == 1 + assert "Just plain text" in chunks[0] + + +def test_split_cline_multiple_md_files(tmp_path): + """cmd_cline processes multiple .md files from memory-bank/ directory.""" + from _chunking import filter_and_truncate + + # Create a temporary memory-bank directory with .md files + mb_dir = tmp_path / "memory-bank" + mb_dir.mkdir() + + (mb_dir / "architecture.md").write_text( + "# Architecture Decisions\n\nUse microservices architecture with event sourcing." + ) + (mb_dir / "conventions.md").write_text( + "# Code Conventions\n\nAll functions must have type hints. Use black formatter." + ) + (mb_dir / "empty.md").write_text("") # empty file should be skipped + + # Read and verify we can split the files + md_files = sorted(f for f in os.listdir(str(mb_dir)) if f.endswith(".md")) + assert "architecture.md" in md_files + assert "conventions.md" in md_files + assert "empty.md" in md_files + + non_empty = [] + for filename in md_files: + filepath = os.path.join(str(mb_dir), filename) + with open(filepath) as f: + content = f.read().strip() + if content: + chunks = filter_and_truncate([content]) + non_empty.extend(chunks) + + assert len(non_empty) == 2 + assert any("microservices" in c for c in non_empty) + assert any("type hints" in c for c in non_empty) + + +def test_split_by_hr_or_headers_continue(): + """split_by_hr_or_headers correctly splits .continue/rules.md.""" + from _chunking import split_by_hr_or_headers + + content = """\ +## First Section + +Content of first section. + +--- + +## Second Section + +Content of second section. + +--- + +Third section without a header (just after HR). +""" + chunks = split_by_hr_or_headers(content) + # Should split into meaningful chunks + assert len(chunks) >= 2 + assert any("First Section" in c for c in chunks) + assert any("Second Section" in c for c in chunks) + + +def test_filter_and_truncate_skips_short(): + """filter_and_truncate skips chunks shorter than MIN_CHUNK_CHARS (50).""" + from _chunking import filter_and_truncate + + chunks = [ + "Short", # < 50 chars, should be filtered + "A" * 49, # exactly 49 chars, should be filtered + "A" * 50, # exactly 50 chars, should be kept + "A long enough chunk that definitely passes the minimum length filter.", + ] + result = filter_and_truncate(chunks) + assert len(result) == 2 + assert all(len(c) >= 50 for c in result) + + +def test_filter_and_truncate_truncates_long(): + """filter_and_truncate truncates chunks over MAX_CHUNK_CHARS (10000).""" + from _chunking import MAX_CHUNK_CHARS, filter_and_truncate + + long_chunk = "X" * (MAX_CHUNK_CHARS + 500) + result = filter_and_truncate([long_chunk]) + assert len(result) == 1 + assert len(result[0]) == MAX_CHUNK_CHARS + + +# --------------------------------------------------------------------------- +# Mock API tests +# --------------------------------------------------------------------------- + + +def _make_mock_response(status: int = 201, body: dict | None = None) -> mock.MagicMock: + """Create a mock HTTP response object.""" + if body is None: + body = {"id": "new-mem-id", "memory": "test"} + resp = mock.MagicMock() + resp.status = status + resp.read.return_value = json.dumps(body).encode() + resp.__enter__ = lambda s: s + resp.__exit__ = mock.MagicMock(return_value=False) + return resp + + +def test_cursorrules_import_api_call(tmp_path): + """cmd_cursorrules calls the API with correct app_id (top-level), infer=False, and correct source.""" + from import_competing_tools import cmd_cursorrules + + # Create a .cursorrules file with enough content + cursorrules = tmp_path / ".cursorrules" + cursorrules.write_text( + "## TypeScript Rules\n\nAlways use strict TypeScript. Never use 'any' type. " + "Prefer interfaces over type aliases for object shapes." + ) + + captured_requests: list[dict] = [] + + def mock_urlopen(req, timeout=None): + body = json.loads(req.data.decode()) + captured_requests.append(body) + return _make_mock_response(201) + + with mock.patch("import_competing_tools.resolve_api_key", return_value="m0-testkey"), \ + mock.patch("import_competing_tools.resolve_user_id", return_value="testuser"), \ + mock.patch("import_competing_tools.resolve_project_id", return_value="my-project"), \ + mock.patch("import_competing_tools.resolve_branch", return_value="main"), \ + mock.patch("urllib.request.urlopen", side_effect=mock_urlopen): + + original_cwd = os.getcwd() + os.chdir(str(tmp_path)) + try: + cmd_cursorrules(["--path", str(cursorrules)]) + finally: + os.chdir(original_cwd) + + assert len(captured_requests) >= 1 + + req_body = captured_requests[0] + + # app_id must be top-level (not inside metadata) + assert req_body["app_id"] == "my-project", f"Expected app_id at top level, got: {req_body}" + + # infer must be False (not "false", but the boolean False) + assert req_body["infer"] is False, f"Expected infer=False, got: {req_body['infer']}" + + # source must be cursor-import + assert req_body["metadata"]["source"] == "cursor-import", ( + f"Expected source=cursor-import, got: {req_body['metadata'].get('source')}" + ) + + # user_id must be set + assert req_body["user_id"] == "testuser" + + # messages must be a list with role/content + assert isinstance(req_body["messages"], list) + assert req_body["messages"][0]["role"] == "user" + assert len(req_body["messages"][0]["content"]) > 0 + + +def test_copilot_import_api_call(tmp_path): + """cmd_copilot calls the API with source=copilot-import.""" + from import_competing_tools import cmd_copilot + + copilot_dir = tmp_path / ".github" + copilot_dir.mkdir() + copilot_file = copilot_dir / "copilot-instructions.md" + copilot_file.write_text( + "## Code Style\n\nUse 2-space indentation. Always add trailing commas in multi-line structures. " + "Prefer const over let. Never use var in JavaScript code." + ) + + captured: list[dict] = [] + + def mock_urlopen(req, timeout=None): + captured.append(json.loads(req.data.decode())) + return _make_mock_response(201) + + with mock.patch("import_competing_tools.resolve_api_key", return_value="m0-key"), \ + mock.patch("import_competing_tools.resolve_user_id", return_value="user1"), \ + mock.patch("import_competing_tools.resolve_project_id", return_value="proj1"), \ + mock.patch("import_competing_tools.resolve_branch", return_value="main"), \ + mock.patch("urllib.request.urlopen", side_effect=mock_urlopen): + + cmd_copilot(["--path", str(copilot_file)]) + + assert len(captured) >= 1 + assert captured[0]["metadata"]["source"] == "copilot-import" + assert captured[0]["app_id"] == "proj1" + assert captured[0]["infer"] is False + + +def test_cline_import_multiple_files(tmp_path): + """cmd_cline imports one memory per non-empty .md file.""" + from import_competing_tools import cmd_cline + + mb = tmp_path / "memory-bank" + mb.mkdir() + (mb / "arch.md").write_text( + "Architecture: microservices with event-sourcing. Each service owns its database. " + "Communication via message bus only. No direct service-to-service HTTP calls." + ) + (mb / "style.md").write_text( + "Code style: PEP 8 for Python. Black formatter. isort for imports. " + "Line length 120. Type hints required on all public functions and methods." + ) + (mb / "empty.md").write_text("") + + captured: list[dict] = [] + + def mock_urlopen(req, timeout=None): + captured.append(json.loads(req.data.decode())) + return _make_mock_response(201) + + with mock.patch("import_competing_tools.resolve_api_key", return_value="m0-key"), \ + mock.patch("import_competing_tools.resolve_user_id", return_value="user1"), \ + mock.patch("import_competing_tools.resolve_project_id", return_value="proj1"), \ + mock.patch("import_competing_tools.resolve_branch", return_value="main"), \ + mock.patch("urllib.request.urlopen", side_effect=mock_urlopen): + + cmd_cline(["--path", str(mb)]) + + # Should have exactly 2 imports (empty.md skipped) + assert len(captured) == 2 + sources = {r["metadata"]["source"] for r in captured} + assert sources == {"cline-import"} + for r in captured: + assert r["infer"] is False + assert r["app_id"] == "proj1" + + +def test_no_api_key_does_not_call_api(tmp_path): + """When no API key is set, no HTTP call is made.""" + from import_competing_tools import cmd_cursorrules + + cursorrules = tmp_path / ".cursorrules" + cursorrules.write_text("## Rules\n\n" + "x" * 100) + + with mock.patch("import_competing_tools.resolve_api_key", return_value=""), \ + mock.patch("urllib.request.urlopen") as mock_url: + + cmd_cursorrules(["--path", str(cursorrules)]) + + mock_url.assert_not_called() + + +def test_missing_file_does_not_call_api(tmp_path): + """When the source file doesn't exist, no HTTP call is made.""" + from import_competing_tools import cmd_cursorrules + + with mock.patch("import_competing_tools.resolve_api_key", return_value="m0-key"), \ + mock.patch("urllib.request.urlopen") as mock_url: + + cmd_cursorrules(["--path", str(tmp_path / "nonexistent.cursorrules")]) + + mock_url.assert_not_called() + + +def test_main_unknown_subcommand_exits_zero(): + """Calling main() with an unknown subcommand exits 0.""" + import subprocess + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "import_competing_tools.py"), "unknown"], + capture_output=True, + text=True, + env={**os.environ, "MEM0_API_KEY": ""}, + ) + assert result.returncode == 0 diff --git a/mem0-plugin/tests/test_parse_export_file.py b/mem0-plugin/tests/test_parse_export_file.py new file mode 100644 index 000000000..66dccbd1d --- /dev/null +++ b/mem0-plugin/tests/test_parse_export_file.py @@ -0,0 +1,298 @@ +"""Tests for parse_export_file.py — mem0 export file parser.""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") + + +# --------------------------------------------------------------------------- +# Direct function tests +# --------------------------------------------------------------------------- + + +def test_parse_blocks_single_valid_block(): + """parse_blocks returns one record for a single valid block.""" + from parse_export_file import parse_blocks + + content = """\ +--- +id: abc123 +created_at: 2024-01-15T10:00:00Z +type: task_learnings +confidence: 0.85 +branch: main +files: src/foo.py, src/bar.py +categories: coding_conventions, task_learnings +--- +Always use context managers when opening files. +""" + records = parse_blocks(content) + assert len(records) == 1 + r = records[0] + assert r["id"] == "abc123" + assert r["type"] == "task_learnings" + assert r["confidence"] == "0.85" + assert r["branch"] == "main" + assert r["files"] == ["src/foo.py", "src/bar.py"] + assert r["categories"] == ["coding_conventions", "task_learnings"] + assert "Always use context managers" in r["content"] + + +def test_parse_blocks_multiple_blocks(): + """parse_blocks returns the correct number of records for multiple blocks.""" + from parse_export_file import parse_blocks + + content = """\ +--- +id: mem001 +type: architecture_decisions +confidence: 0.9 +branch: main +files: +categories: architecture_decisions +--- +Use hexagonal architecture for the core domain. + +--- +id: mem002 +type: anti_patterns +confidence: 0.75 +branch: feat/refactor +files: src/legacy.py +categories: anti_patterns +--- +Avoid direct database calls from view layer. + +--- +id: mem003 +type: coding_conventions +confidence: 0.8 +branch: main +files: src/utils.py, src/helpers.py +categories: +--- +Use snake_case for all Python identifiers. +""" + records = parse_blocks(content) + assert len(records) == 3 + + assert records[0]["id"] == "mem001" + assert records[0]["categories"] == ["architecture_decisions"] + assert "hexagonal architecture" in records[0]["content"] + + assert records[1]["id"] == "mem002" + assert records[1]["files"] == ["src/legacy.py"] + assert "direct database calls" in records[1]["content"] + + assert records[2]["id"] == "mem003" + assert records[2]["files"] == ["src/utils.py", "src/helpers.py"] + assert records[2]["categories"] == [] + assert "snake_case" in records[2]["content"] + + +def test_parse_blocks_missing_optional_fields(): + """parse_blocks uses defaults when optional fields are absent.""" + from parse_export_file import parse_blocks + + # confidence, branch, files, categories all absent + content = """\ +--- +id: xyz789 +type: task_learnings +--- +Run tests before committing. +""" + records = parse_blocks(content) + assert len(records) == 1 + r = records[0] + assert r["id"] == "xyz789" + assert r["confidence"] == "" # default empty string + assert r["branch"] == "" # default empty string + assert r["files"] == [] # default empty list + assert r["categories"] == [] # default empty list + assert "Run tests" in r["content"] + + +def test_parse_blocks_filters_empty_content(): + """parse_blocks skips blocks whose content is empty or whitespace-only.""" + from parse_export_file import parse_blocks + + content = """\ +--- +id: empty1 +type: task_learnings +--- + +--- +id: real1 +type: task_learnings +--- +This block has real content. + +--- +id: empty2 +type: coding_conventions +--- + +""" + records = parse_blocks(content) + # Only the block with actual content should be returned + assert len(records) == 1 + assert records[0]["id"] == "real1" + assert "real content" in records[0]["content"] + + +def test_parse_blocks_round_trip(): + """Content formatted by export matches what parse_blocks expects.""" + from parse_export_file import parse_blocks + + # Simulate the exact format produced by the export skill + memory_id = "test-id-001" + created_at = "2024-06-01T12:00:00Z" + mem_type = "architecture_decisions" + confidence = "0.92" + branch = "feat/new-feature" + files = ["src/main.py", "tests/test_main.py"] + categories = ["architecture_decisions", "coding_conventions"] + memory_content = "Use dependency injection for all service classes." + + # Format exactly as the export skill would + block = ( + "---\n" + f"id: {memory_id}\n" + f"created_at: {created_at}\n" + f"type: {mem_type}\n" + f"confidence: {confidence}\n" + f"branch: {branch}\n" + f"files: {', '.join(files)}\n" + f"categories: {', '.join(categories)}\n" + "---\n" + f"{memory_content}\n" + "\n" + ) + + records = parse_blocks(block) + assert len(records) == 1 + r = records[0] + assert r["id"] == memory_id + assert r["type"] == mem_type + assert r["confidence"] == confidence + assert r["branch"] == branch + assert r["files"] == files + assert r["categories"] == categories + assert r["content"] == memory_content + + +def test_parse_blocks_multiline_content(): + """parse_blocks correctly captures multi-line memory content.""" + from parse_export_file import parse_blocks + + content = """\ +--- +id: multi001 +type: task_learnings +--- +Line one of the memory. +Line two of the memory. + +Line four after blank line. +""" + records = parse_blocks(content) + assert len(records) == 1 + assert "Line one" in records[0]["content"] + assert "Line two" in records[0]["content"] + assert "Line four" in records[0]["content"] + + +def test_parse_blocks_empty_input(): + """parse_blocks returns empty list for empty input.""" + from parse_export_file import parse_blocks + + assert parse_blocks("") == [] + assert parse_blocks(" \n ") == [] + + +def test_parse_blocks_no_blocks(): + """parse_blocks returns empty list for content without any --- delimiters.""" + from parse_export_file import parse_blocks + + assert parse_blocks("Just some text without any delimiters.") == [] + + +def test_parse_blocks_value_with_colon(): + """parse_blocks handles values that themselves contain colons.""" + from parse_export_file import parse_blocks + + content = """\ +--- +id: colon-test +type: task_learnings +created_at: 2024-01-01T10:00:00Z +--- +Timestamp values contain colons and should parse correctly. +""" + records = parse_blocks(content) + assert len(records) == 1 + assert records[0]["id"] == "colon-test" + # created_at field should be captured (it's in the record if present) + assert "2024-01-01T10:00:00Z" in records[0].get("created_at", "") + + +# --------------------------------------------------------------------------- +# CLI / subprocess tests +# --------------------------------------------------------------------------- + + +def test_main_cli_outputs_json(tmp_path): + """Running parse_export_file.py as a script outputs valid JSON.""" + export_file = tmp_path / "mem0-export-test.md" + export_file.write_text("""\ +--- +id: cli-test-001 +type: task_learnings +confidence: 0.8 +branch: main +files: +categories: task_learnings +--- +Prefer composition over inheritance. +""") + + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "parse_export_file.py"), str(export_file)], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + records = json.loads(result.stdout) + assert isinstance(records, list) + assert len(records) == 1 + assert records[0]["id"] == "cli-test-001" + + +def test_main_cli_no_args_exits_zero(): + """Running parse_export_file.py with no arguments exits 0 and prints [].""" + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "parse_export_file.py")], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "[]" + + +def test_main_cli_missing_file_exits_zero(tmp_path): + """Running parse_export_file.py with a missing file exits 0 and prints [].""" + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "parse_export_file.py"), + str(tmp_path / "nonexistent.md")], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "[]" diff --git a/mem0-plugin/tests/test_parse_mem0_config.py b/mem0-plugin/tests/test_parse_mem0_config.py new file mode 100644 index 000000000..4ba261f6d --- /dev/null +++ b/mem0-plugin/tests/test_parse_mem0_config.py @@ -0,0 +1,439 @@ +"""Tests for parse_mem0_config.py — mem0.md retention policy parser.""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") + + +# --------------------------------------------------------------------------- +# parse_retention — unit tests +# --------------------------------------------------------------------------- + + +def test_parse_retention_valid_section(): + """parse_retention extracts day-count policies correctly.""" + from parse_mem0_config import parse_retention + + content = """\ +# Project Config + +## Retention + +session_state: 90d +compact_summary: 60d +decision: 180d +""" + result = parse_retention(content) + assert result == { + "session_state": 90, + "compact_summary": 60, + "decision": 180, + } + + +def test_parse_retention_forever_returns_none(): + """parse_retention maps 'forever' to None.""" + from parse_mem0_config import parse_retention + + content = """\ +## Retention + +user_preference: forever +anti_pattern: forever +session_state: 30d +""" + result = parse_retention(content) + assert result["user_preference"] is None + assert result["anti_pattern"] is None + assert result["session_state"] == 30 + + +def test_parse_retention_no_section_returns_empty(): + """parse_retention returns {} when there is no ## Retention heading.""" + from parse_mem0_config import parse_retention + + content = """\ +# Project Config + +## Some Other Section + +key: value +""" + result = parse_retention(content) + assert result == {} + + +def test_parse_retention_stops_at_next_heading(): + """parse_retention stops reading at the next ## heading.""" + from parse_mem0_config import parse_retention + + content = """\ +## Retention + +session_state: 7d + +## Other Section + +other_key: 999d +""" + result = parse_retention(content) + assert "session_state" in result + assert "other_key" not in result + + +def test_parse_retention_malformed_lines_skipped(): + """Malformed lines (no colon, bad day format) are silently ignored.""" + from parse_mem0_config import parse_retention + + content = """\ +## Retention + +session_state: 90d +bad_line_no_colon +another: badvalue +decision: 30d +""" + result = parse_retention(content) + assert result == {"session_state": 90, "decision": 30} + + +def test_parse_retention_comments_ignored(): + """Inline # comments are stripped before parsing.""" + from parse_mem0_config import parse_retention + + content = """\ +## Retention + +session_state: 90d # rolling 90-day window +user_preference: forever # never prune preferences +""" + result = parse_retention(content) + assert result["session_state"] == 90 + assert result["user_preference"] is None + + +def test_parse_retention_case_insensitive_heading(): + """## retention (lowercase) is matched the same as ## Retention.""" + from parse_mem0_config import parse_retention + + content = """\ +## retention + +session_state: 14d +""" + result = parse_retention(content) + assert result == {"session_state": 14} + + +def test_parse_retention_empty_section_returns_empty(): + """A ## Retention section with no valid lines returns {}.""" + from parse_mem0_config import parse_retention + + content = """\ +## Retention + +# only comments here + +## Next Section +""" + result = parse_retention(content) + assert result == {} + + +# --------------------------------------------------------------------------- +# load_retention_policies — integration tests with tmp files +# --------------------------------------------------------------------------- + + +def test_load_retention_policies_with_tmp_file(tmp_path): + """load_retention_policies reads a real mem0.md from disk.""" + from parse_mem0_config import load_retention_policies + + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text( + """\ +# My Project + +## Retention + +session_state: 90d +compact_summary: 60d +decision: forever +""", + encoding="utf-8", + ) + + result = load_retention_policies(str(tmp_path)) + assert result == { + "session_state": 90, + "compact_summary": 60, + "decision": None, + } + + +def test_load_retention_policies_no_mem0_md_returns_empty(tmp_path): + """load_retention_policies returns {} when no mem0.md exists.""" + from parse_mem0_config import load_retention_policies + + result = load_retention_policies(str(tmp_path)) + assert result == {} + + +def test_load_retention_policies_no_retention_section_returns_empty(tmp_path): + """load_retention_policies returns {} when mem0.md has no ## Retention.""" + from parse_mem0_config import load_retention_policies + + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text( + """\ +# My Project + +Some general project notes here. +No retention section. +""", + encoding="utf-8", + ) + + result = load_retention_policies(str(tmp_path)) + assert result == {} + + +def test_load_retention_policies_defaults_to_cwd(tmp_path, monkeypatch): + """load_retention_policies uses os.getcwd() when cwd is None.""" + from parse_mem0_config import load_retention_policies + + monkeypatch.chdir(tmp_path) + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text("## Retention\nsession_state: 45d\n", encoding="utf-8") + + result = load_retention_policies() # no cwd arg + assert result == {"session_state": 45} + + +# --------------------------------------------------------------------------- +# CLI / main() — subprocess test +# --------------------------------------------------------------------------- + + +def test_cli_main_prints_json(tmp_path): + """CLI: python parse_mem0_config.py prints valid JSON.""" + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text( + "## Retention\nsession_state: 90d\nuser_preference: forever\n", + encoding="utf-8", + ) + + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "parse_mem0_config.py"), str(tmp_path)], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert data["session_state"] == 90 + assert data["user_preference"] is None + + +def test_cli_main_no_file_prints_empty_json(tmp_path): + """CLI: prints '{}' when no mem0.md exists.""" + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "parse_mem0_config.py"), str(tmp_path)], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert json.loads(result.stdout) == {} + + +# --------------------------------------------------------------------------- +# parse_section_kv — unit tests +# --------------------------------------------------------------------------- + + +def test_parse_section_kv_basic(): + """parse_section_kv extracts key-value pairs from a named section.""" + from parse_mem0_config import parse_section_kv + + content = """\ +## Search + +default_limit: 10 +boost_recency: true +""" + result = parse_section_kv(content, "Search") + assert result == {"default_limit": "10", "boost_recency": "true"} + + +def test_parse_section_kv_missing_section(): + """parse_section_kv returns {} when section doesn't exist.""" + from parse_mem0_config import parse_section_kv + + result = parse_section_kv("## Other\nfoo: bar\n", "Search") + assert result == {} + + +def test_parse_section_kv_stops_at_next_heading(): + """parse_section_kv stops at the next ## heading.""" + from parse_mem0_config import parse_section_kv + + content = """\ +## Identity + +user_id: kartik +project_id: mem0 + +## Other + +ignored: yes +""" + result = parse_section_kv(content, "Identity") + assert result == {"user_id": "kartik", "project_id": "mem0"} + assert "ignored" not in result + + +# --------------------------------------------------------------------------- +# parse_section_list — unit tests +# --------------------------------------------------------------------------- + + +def test_parse_section_list_basic(): + """parse_section_list extracts list items from a named section.""" + from parse_mem0_config import parse_section_list + + content = """\ +## Categories + +- architecture_decisions +- bug_fixes +- coding_conventions +""" + result = parse_section_list(content, "Categories") + assert result == ["architecture_decisions", "bug_fixes", "coding_conventions"] + + +def test_parse_section_list_bare_lines(): + """parse_section_list works with bare lines (no bullet prefix).""" + from parse_mem0_config import parse_section_list + + content = """\ +## Categories + +architecture_decisions +bug_fixes +""" + result = parse_section_list(content, "Categories") + assert result == ["architecture_decisions", "bug_fixes"] + + +def test_parse_section_list_missing_section(): + """parse_section_list returns [] when section doesn't exist.""" + from parse_mem0_config import parse_section_list + + result = parse_section_list("## Other\n- foo\n", "Categories") + assert result == [] + + +# --------------------------------------------------------------------------- +# load_full_config — integration tests +# --------------------------------------------------------------------------- + + +def test_load_full_config_all_sections(tmp_path): + """load_full_config extracts all sections from mem0.md.""" + from parse_mem0_config import load_full_config + + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text( + """\ +# My Project + +## Retention + +session_state: 90d +decision: forever + +## Search + +default_limit: 20 +boost_recency: true + +## Categories + +- architecture_decisions +- bug_fixes +- security_constraints + +## Identity + +user_id: kartik +project_id: my-project +""", + encoding="utf-8", + ) + + config = load_full_config(str(tmp_path)) + assert config["retention"] == {"session_state": 90, "decision": None} + assert config["search"] == {"default_limit": "20", "boost_recency": "true"} + assert config["categories"] == ["architecture_decisions", "bug_fixes", "security_constraints"] + assert config["identity"] == {"user_id": "kartik", "project_id": "my-project"} + + +def test_load_full_config_partial_sections(tmp_path): + """load_full_config only includes sections that exist.""" + from parse_mem0_config import load_full_config + + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text("## Retention\nsession_state: 30d\n", encoding="utf-8") + + config = load_full_config(str(tmp_path)) + assert "retention" in config + assert "search" not in config + assert "categories" not in config + assert "identity" not in config + + +def test_load_full_config_no_file(tmp_path): + """load_full_config returns {} when no mem0.md exists.""" + from parse_mem0_config import load_full_config + + config = load_full_config(str(tmp_path)) + assert config == {} + + +def test_cli_full_flag(tmp_path): + """CLI: --full prints all sections as JSON.""" + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text( + "## Retention\nsession_state: 90d\n\n## Search\nlimit: 10\n", + encoding="utf-8", + ) + + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "parse_mem0_config.py"), "--full", str(tmp_path)], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert "retention" in data + assert "search" in data + + +def test_load_full_config_settings_section(tmp_path): + """Settings section is parsed by load_full_config.""" + mem0_md = tmp_path / "mem0.md" + mem0_md.write_text("""\ +## Settings +commit_prompts: true +subagent_skip: Explore, Plan, code-reviewer +""") + sys.path.insert(0, SCRIPTS_DIR) + from parse_mem0_config import load_full_config + config = load_full_config(str(tmp_path)) + assert config.get("settings", {}).get("commit_prompts") == "true" + assert config.get("settings", {}).get("subagent_skip") == "Explore, Plan, code-reviewer" diff --git a/mem0-plugin/tests/test_project.py b/mem0-plugin/tests/test_project.py index 058ceee9f..39867b41d 100644 --- a/mem0-plugin/tests/test_project.py +++ b/mem0-plugin/tests/test_project.py @@ -105,3 +105,67 @@ def test_resolve_project_id_priority_order(tmp_git_repo, monkeypatch): # Env var overrides everything monkeypatch.setenv("MEM0_PROJECT_ID", "from-env") assert resolve_project_id(str(tmp_git_repo)) == "from-env" + + +def test_remote_hash_key_format(tmp_git_repo): + """_remote_hash_key() returns 'remote:<16-char-hex>' for a repo with a remote.""" + import re + + from _project import _remote_hash_key + + key = _remote_hash_key(str(tmp_git_repo)) + assert re.fullmatch(r"remote:[0-9a-f]{16}", key), ( + f"Expected 'remote:<16-char-hex>', got {key!r}" + ) + + +def test_remote_hash_key_no_git(tmp_no_git): + """_remote_hash_key() returns empty string when not in a git repo.""" + from _project import _remote_hash_key + + key = _remote_hash_key(str(tmp_no_git)) + assert key == "" + + +def test_save_project_mapping_writes_remote_key(tmp_git_repo): + """save_project_mapping() writes both the CWD key and the remote hash key.""" + import re + + from _project import save_project_mapping + + save_project_mapping(str(tmp_git_repo), "my-project") + map_path = os.path.expanduser("~/.mem0/project_map.json") + with open(map_path) as f: + data = json.load(f) + + assert data[str(tmp_git_repo)] == "my-project" + remote_keys = [k for k in data if re.fullmatch(r"remote:[0-9a-f]{16}", k)] + assert remote_keys, "Expected at least one remote: key in project_map.json" + assert data[remote_keys[0]] == "my-project" + + +def test_resolve_project_id_remote_hash_fallback(tmp_git_repo, tmp_path): + """Moving the project folder: remote hash key is used as fallback.""" + from _project import resolve_project_id, save_project_mapping + + # Save mapping for original location + save_project_mapping(str(tmp_git_repo), "stable-project") + + # Simulate folder move: resolve using a different CWD path that shares the same remote. + # We use a second tmp_git_repo with the same remote URL to mimic a renamed directory. + import subprocess as _sp + new_repo = tmp_path / "moved_repo" + new_repo.mkdir() + _sp.run(["git", "init"], cwd=new_repo, capture_output=True, check=True) + _sp.run( + ["git", "remote", "add", "origin", "https://github.com/mem0ai/mem0.git"], + cwd=new_repo, + capture_output=True, + check=True, + ) + + # The new CWD is NOT in project_map, but remote hash should match + pid = resolve_project_id(str(new_repo)) + assert pid == "stable-project", ( + f"Expected 'stable-project' via remote hash fallback, got {pid!r}" + ) diff --git a/mem0-plugin/tests/test_rubric_dedup.py b/mem0-plugin/tests/test_rubric_dedup.py new file mode 100644 index 000000000..e37816019 --- /dev/null +++ b/mem0-plugin/tests/test_rubric_dedup.py @@ -0,0 +1,60 @@ +"""Tests for rubric deduplication in on_user_prompt.sh.""" + +from __future__ import annotations + +import json +import os +import subprocess + +import pytest + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") + + +@pytest.fixture(autouse=True) +def _clean_rubric_flag(tmp_path, monkeypatch): + """Use a temp dir for the rubric flag file and clean msg counter.""" + monkeypatch.setenv("MEM0_RUBRIC_DIR", str(tmp_path)) + msg_count_file = "/tmp/mem0_msg_count_testuser" + yield + if os.path.exists(msg_count_file): + os.unlink(msg_count_file) + + +def _run_hook(prompt: str, env_overrides: dict | None = None, session_id: str = "test-sess-001") -> str: + """Run on_user_prompt.sh with a simulated prompt and return stdout.""" + env = { + **os.environ, + "USER": "testuser", + "MEM0_API_KEY": "test-key-123", + "MEM0_RESOLVED_USER_ID": "testuser", + "MEM0_PROJECT_ID": "test-project", + "MEM0_BRANCH": "main", + } + if env_overrides: + env.update(env_overrides) + + input_json = json.dumps({"prompt": prompt, "session_id": session_id}) + result = subprocess.run( + ["bash", os.path.join(SCRIPTS_DIR, "on_user_prompt.sh")], + input=input_json, + capture_output=True, + text=True, + env=env, + timeout=10, + ) + return result.stdout + + +def test_first_prompt_gets_full_rubric(): + """First substantial prompt of session gets full memory check rubric.""" + output = _run_hook("How should we refactor the auth module?") + assert "Mem0 searches apply" in output + assert "metadata.type" in output + + +def test_second_prompt_gets_no_rubric(): + """Second prompt of session emits nothing — rubric and tips only on first prompt.""" + _run_hook("How should we refactor the auth module?") + output = _run_hook("What about the database layer?") + assert output.strip() == "" diff --git a/mem0-plugin/tests/test_search.py b/mem0-plugin/tests/test_search.py new file mode 100644 index 000000000..fd9f91de7 --- /dev/null +++ b/mem0-plugin/tests/test_search.py @@ -0,0 +1,116 @@ +"""Tests for _search.py — shared mem0 search API helper.""" + +from __future__ import annotations + +import json +from unittest.mock import MagicMock, patch + + +def test_search_memories_returns_results(): + from _search import search_memories + + fake_results = [ + {"id": "abc123", "memory": "Use Postgres for auth", "metadata": {"type": "decision"}}, + {"id": "def456", "memory": "Never use floats for money", "metadata": {"type": "anti_pattern"}}, + ] + + def mock_urlopen(req, timeout=None): + resp = MagicMock() + resp.read.return_value = json.dumps({"results": fake_results}).encode() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + results = search_memories("test-key", "user1", "proj1", "auth decisions") + + assert len(results) == 2 + assert results[0]["id"] == "abc123" + + +def test_search_memories_with_metadata_type(): + from _search import search_memories + + captured_body = {} + + def mock_urlopen(req, timeout=None): + captured_body.update(json.loads(req.data.decode())) + resp = MagicMock() + resp.read.return_value = json.dumps({"results": []}).encode() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + search_memories("key", "user", "proj", "query", metadata_type="decision") + + filters = captured_body["filters"] + assert {"metadata": {"type": "decision"}} in filters["AND"] + + +def test_search_memories_handles_api_error(): + from _search import search_memories + + with patch("urllib.request.urlopen", side_effect=Exception("timeout")): + results = search_memories("key", "user", "proj", "query") + + assert results == [] + + +def test_search_memories_handles_list_response(): + from _search import search_memories + + fake_results = [{"id": "abc", "memory": "test"}] + + def mock_urlopen(req, timeout=None): + resp = MagicMock() + resp.read.return_value = json.dumps(fake_results).encode() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + results = search_memories("key", "user", "proj", "query") + + assert len(results) == 1 + + +def test_search_memories_respects_top_k(): + from _search import search_memories + + captured_body = {} + + def mock_urlopen(req, timeout=None): + captured_body.update(json.loads(req.data.decode())) + resp = MagicMock() + resp.read.return_value = json.dumps({"results": []}).encode() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + search_memories("key", "user", "proj", "query", top_k=5) + + assert captured_body["top_k"] == 5 + + +def test_search_memories_no_api_key_returns_empty(): + from _search import search_memories + + results = search_memories("", "user", "proj", "query") + assert results == [] + + +def test_format_results_for_context(): + from _search import format_results_for_context + + memories = [ + {"id": "abc12345-long-id", "memory": "Use Postgres for auth", "metadata": {"type": "decision"}}, + {"id": "def67890-long-id", "memory": "JWT tokens expire in 1h", "metadata": {"type": "convention"}}, + ] + + output = format_results_for_context(memories, heading="Relevant memories") + assert "Relevant memories" in output + assert "[decision]" in output + assert "Use Postgres for auth" in output + assert "abc12345" in output diff --git a/mem0-plugin/tests/test_session_stats.py b/mem0-plugin/tests/test_session_stats.py index 94ae55e14..19d15e20d 100644 --- a/mem0-plugin/tests/test_session_stats.py +++ b/mem0-plugin/tests/test_session_stats.py @@ -83,13 +83,13 @@ def test_report_empty_session(_isolate_stats_file): assert result == "" -def test_report_cleans_up_file(_isolate_stats_file): +def test_report_preserves_file(_isolate_stats_file): import session_stats session_stats.init() session_stats.record_add() session_stats.report() - assert not os.path.isfile(_isolate_stats_file) + assert os.path.isfile(_isolate_stats_file) def test_record_add_no_category(_isolate_stats_file): @@ -141,3 +141,108 @@ def test_cli_report_no_data(tmp_path): env=env, ) assert result.returncode == 0 + + +def test_peek_returns_json_without_clearing(_isolate_stats_file): + """peek returns JSON stats without deleting the stats file.""" + import session_stats + + session_stats.init() + session_stats.record_add("decisions") + session_stats.record_add("decisions") + session_stats.record_search() + + result = session_stats.peek() + data = json.loads(result) + assert data["adds"] == 2 + assert data["searches"] == 1 + + assert os.path.isfile(_isolate_stats_file) + + +def test_category_counts_tracked(_isolate_stats_file): + """category_counts tracks per-category add counts.""" + import session_stats + + session_stats.init() + session_stats.record_add("bug_fixes") + session_stats.record_add("bug_fixes") + session_stats.record_add("bug_fixes") + session_stats.record_add("decisions") + + with open(_isolate_stats_file) as f: + data = json.load(f) + assert data["category_counts"]["bug_fixes"] == 3 + assert data["category_counts"]["decisions"] == 1 + + +def test_category_counts_empty_category_not_tracked(_isolate_stats_file): + """Empty category string doesn't appear in category_counts.""" + import session_stats + + session_stats.init() + session_stats.record_add("") + session_stats.record_add() + + with open(_isolate_stats_file) as f: + data = json.load(f) + assert data["category_counts"] == {} + + +def test_recent_ids_tracked(_isolate_stats_file): + """record_add with memory_id stores ID in recent_ids.""" + import session_stats + + session_stats.init() + session_stats.record_add("decision", "abc-123") + session_stats.record_add("convention", "def-456") + + with open(_isolate_stats_file) as f: + data = json.load(f) + assert len(data["recent_ids"]) == 2 + assert data["recent_ids"][0]["id"] == "abc-123" + assert data["recent_ids"][1]["id"] == "def-456" + assert data["recent_ids"][0]["category"] == "decision" + + +def test_recent_ids_capped(_isolate_stats_file): + """recent_ids list is capped at MAX_RECENT_IDS.""" + import session_stats + + session_stats.init() + for i in range(60): + session_stats.record_add("test", f"id-{i}") + + with open(_isolate_stats_file) as f: + data = json.load(f) + assert len(data["recent_ids"]) == session_stats.MAX_RECENT_IDS + assert data["recent_ids"][0]["id"] == f"id-{60 - session_stats.MAX_RECENT_IDS}" + + +def test_recent_ids_empty_without_memory_id(_isolate_stats_file): + """record_add without memory_id doesn't add to recent_ids.""" + import session_stats + + session_stats.init() + session_stats.record_add("decision") + session_stats.record_add("convention", "") + + with open(_isolate_stats_file) as f: + data = json.load(f) + assert data["recent_ids"] == [] + + +def test_cli_peek(tmp_path): + """Test CLI invocation: session_stats.py peek outputs JSON.""" + env = {**os.environ, "USER": "test"} + subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "session_stats.py"), "init"], + capture_output=True, text=True, env=env, + ) + result = subprocess.run( + [sys.executable, os.path.join(SCRIPTS_DIR, "session_stats.py"), "peek"], + capture_output=True, text=True, env=env, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert "adds" in data diff --git a/mem0-plugin/tests/test_telemetry.py b/mem0-plugin/tests/test_telemetry.py new file mode 100644 index 000000000..c0a75a21a --- /dev/null +++ b/mem0-plugin/tests/test_telemetry.py @@ -0,0 +1,177 @@ +"""Tests for telemetry.py — fire-and-forget PostHog plugin telemetry.""" + +from __future__ import annotations + +import json +import os +import sys +import urllib.error + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts") +sys.path.insert(0, os.path.abspath(SCRIPTS_DIR)) + + +def test_import_succeeds(): + import telemetry + + assert hasattr(telemetry, "emit") + assert hasattr(telemetry, "main") + + +def test_opt_out_skips_send(monkeypatch): + import telemetry + + monkeypatch.setenv("MEM0_TELEMETRY", "false") + sent = [] + monkeypatch.setattr(telemetry, "send", lambda p: sent.append(p)) + telemetry.emit("session_start") + assert sent == [] + + +def test_opt_out_variants(monkeypatch): + import telemetry + + for val in ("0", "no", "off", "FALSE", "No"): + monkeypatch.setenv("MEM0_TELEMETRY", val) + assert not telemetry.is_enabled() + + +def test_enabled_by_default(monkeypatch): + import telemetry + + monkeypatch.delenv("MEM0_TELEMETRY", raising=False) + assert telemetry.is_enabled() + + +def test_posthog_payload_structure(monkeypatch): + import telemetry + + monkeypatch.setenv("MEM0_RESOLVED_USER_ID", "testuser") + monkeypatch.setenv("MEM0_PROJECT_ID", "test-project") + monkeypatch.delenv("MEM0_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False) + + payload = telemetry.build_posthog_payload("plugin.session_start", {"memory_count": 5}) + + assert payload["api_key"] == telemetry.POSTHOG_API_KEY + assert payload["event"] == "plugin.session_start" + assert "distinct_id" in payload + assert payload["properties"]["source"] == "plugin" + assert isinstance(payload["properties"]["plugin_version"], str) + assert payload["properties"]["plugin_version"] != "" + assert payload["properties"]["memory_count"] == 5 + assert payload["properties"]["$process_person_profile"] is False + + raw = json.dumps(payload) + assert "testuser" not in raw + assert "test-project" not in raw + + +def test_system_props_override_caller_props(monkeypatch): + """H8: system properties must win over caller-supplied properties.""" + import telemetry + + monkeypatch.setenv("MEM0_RESOLVED_USER_ID", "testuser") + monkeypatch.setenv("MEM0_PROJECT_ID", "test-project") + monkeypatch.delenv("MEM0_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False) + + # Caller tries to override system-controlled properties + caller_props = { + "source": "CALLER_OVERRIDE", + "platform": "CALLER_OVERRIDE", + "plugin_version": "CALLER_OVERRIDE", + "memory_count": 42, + } + payload = telemetry.build_posthog_payload("plugin.test", caller_props) + props = payload["properties"] + + # System props must win + assert props["source"] == "plugin" + assert props["platform"] == telemetry.detect_platform() + assert props["plugin_version"] == telemetry.PLUGIN_VERSION + # Caller-only props still present + assert props["memory_count"] == 42 + + +def test_distinct_id_from_api_key(monkeypatch): + import hashlib + + import telemetry + + monkeypatch.setenv("MEM0_API_KEY", "m0-testkey123") + expected = hashlib.sha256(b"m0-testkey123").hexdigest()[:32] + assert telemetry._distinct_id() == expected + + +def test_distinct_id_fallback_no_key(monkeypatch): + import telemetry + + monkeypatch.delenv("MEM0_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False) + monkeypatch.setenv("MEM0_RESOLVED_USER_ID", "kartik") + assert telemetry._distinct_id() == telemetry._sha256("kartik") + + +def test_hash_deterministic(): + import telemetry + + h1 = telemetry._sha256("same-value") + h2 = telemetry._sha256("same-value") + assert h1 == h2 + assert h1 != telemetry._sha256("different-value") + + +def test_platform_claude_code(monkeypatch): + import telemetry + + monkeypatch.setenv("CLAUDECODE", "1") + monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False) + monkeypatch.delenv("CODEX_PLUGIN_ROOT", raising=False) + assert telemetry.detect_platform() == "claude-code" + + +def test_platform_cursor(monkeypatch): + import telemetry + + monkeypatch.delenv("CLAUDECODE", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False) + monkeypatch.setenv("CURSOR_PLUGIN_ROOT", "/path") + monkeypatch.delenv("CODEX_PLUGIN_ROOT", raising=False) + assert telemetry.detect_platform() == "cursor" + + +def test_platform_codex(monkeypatch): + import telemetry + + monkeypatch.delenv("CLAUDECODE", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False) + monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False) + monkeypatch.setenv("PLUGIN_ROOT", "/path") + assert telemetry.detect_platform() == "codex" + + +def test_send_fails_silently(monkeypatch): + import telemetry + + def raise_error(req, timeout): + raise urllib.error.URLError("connection refused") + + monkeypatch.setattr(telemetry.urllib.request, "urlopen", raise_error) + telemetry.send({"event": "test"}) + + +def test_cli_exits_zero_when_disabled(monkeypatch): + import telemetry + + monkeypatch.setenv("MEM0_TELEMETRY", "false") + monkeypatch.setattr(sys, "argv", ["telemetry.py", "session_start"]) + assert telemetry.main() == 0 + + +def test_cli_no_args_exits_nonzero(monkeypatch): + import telemetry + + monkeypatch.delenv("MEM0_TELEMETRY", raising=False) + monkeypatch.setattr(sys, "argv", ["telemetry.py"]) + assert telemetry.main() == 1 diff --git a/mem0-plugin/tests/test_write_path.py b/mem0-plugin/tests/test_write_path.py new file mode 100644 index 000000000..1be57df83 --- /dev/null +++ b/mem0-plugin/tests/test_write_path.py @@ -0,0 +1,207 @@ +"""Tests for write-path app_id migration and API key resolution. + +Verifies that all scripts writing to the Mem0 API: +1. Pass app_id as a top-level parameter (not in metadata) +2. Do NOT include project_id in metadata +3. Include branch in metadata when available +4. Use resolve_api_key() for key resolution with userConfig fallback +""" + +from __future__ import annotations + +import json +from unittest.mock import MagicMock, patch + + +def test_auto_import_post_memory_uses_app_id(): + """auto_import.post_memory sends app_id top-level, not metadata.project_id.""" + from auto_import import post_memory + + captured = {} + + def mock_urlopen(req, timeout=None): + body = json.loads(req.data.decode("utf-8")) + captured.update(body) + resp = MagicMock() + resp.status = 200 + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + result = post_memory( + api_key="test-key", + content="test content", + user_id="testuser", + filename="CLAUDE.md", + project_id="my-project", + branch="main", + ) + + assert result is True + assert captured["app_id"] == "my-project" + assert captured["user_id"] == "testuser" + assert "project_id" not in captured.get("metadata", {}) + assert captured["metadata"]["type"] == "project_profile" + assert captured["metadata"]["branch"] == "main" + assert captured["infer"] is False + + +def test_auto_import_post_memory_omits_empty_branch(): + """auto_import.post_memory skips branch in metadata when empty.""" + from auto_import import post_memory + + captured = {} + + def mock_urlopen(req, timeout=None): + body = json.loads(req.data.decode("utf-8")) + captured.update(body) + resp = MagicMock() + resp.status = 200 + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + post_memory("key", "content", "user", "FILE.md", "proj", branch="") + + assert "branch" not in captured.get("metadata", {}) + + +def test_on_pre_compact_store_memory_uses_app_id(): + """on_pre_compact.store_memory sends app_id top-level.""" + from on_pre_compact import store_memory + + captured = {} + + def mock_urlopen(req, timeout=None): + body = json.loads(req.data.decode("utf-8")) + captured.update(body) + resp = MagicMock() + resp.status = 200 + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + result = store_memory( + api_key="test-key", + content="session state content", + user_id="testuser", + source="pre-compaction", + session_id="sess-123", + project_id="my-project", + branch="feat/auth", + ) + + assert result is True + assert captured["app_id"] == "my-project" + assert captured["user_id"] == "testuser" + assert "project_id" not in captured.get("metadata", {}) + assert captured["metadata"]["type"] == "session_state" + assert captured["metadata"]["source"] == "pre-compaction" + assert captured["metadata"]["branch"] == "feat/auth" + assert "expiration_date" in captured + + +def test_capture_compact_summary_store_uses_app_id(): + """capture_compact_summary.store_summary sends app_id top-level.""" + from capture_compact_summary import store_summary + + captured = {} + + def mock_urlopen(req, timeout=None): + body = json.loads(req.data.decode("utf-8")) + captured.update(body) + resp = MagicMock() + resp.status = 200 + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + result = store_summary( + api_key="test-key", + summary="compact summary text", + user_id="testuser", + session_id="sess-456", + project_id="my-project", + branch="main", + ) + + assert result is True + assert captured["app_id"] == "my-project" + assert captured["user_id"] == "testuser" + assert "project_id" not in captured.get("metadata", {}) + assert captured["metadata"]["type"] == "compact_summary" + assert captured["metadata"]["branch"] == "main" + assert captured["infer"] is True + assert "expiration_date" in captured + + +def test_no_metadata_project_id_anywhere(): + """Ensure none of the write functions put project_id in metadata.""" + from auto_import import post_memory + from capture_compact_summary import store_summary + from on_pre_compact import store_memory + + bodies = [] + + def mock_urlopen(req, timeout=None): + body = json.loads(req.data.decode("utf-8")) + bodies.append(body) + resp = MagicMock() + resp.status = 200 + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) + return resp + + with patch("urllib.request.urlopen", side_effect=mock_urlopen): + post_memory("k", "c", "u", "f", "proj", "br") + store_memory("k", "c", "u", "src", "sid", "proj", "br") + store_summary("k", "s", "u", "sid", "proj", "br") + + for i, body in enumerate(bodies): + metadata = body.get("metadata", {}) + assert "project_id" not in metadata, f"Write function #{i} still has metadata.project_id" + assert body.get("app_id") == "proj", f"Write function #{i} missing app_id top-level" + + +def test_resolve_api_key_prefers_env_var(monkeypatch): + """resolve_api_key returns MEM0_API_KEY when both are set.""" + from _identity import resolve_api_key + + monkeypatch.setenv("MEM0_API_KEY", "direct-key") + monkeypatch.setenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", "fallback-key") + assert resolve_api_key() == "direct-key" + + +def test_resolve_api_key_falls_back_to_plugin_option(monkeypatch): + """resolve_api_key falls back to CLAUDE_PLUGIN_OPTION_MEM0_API_KEY.""" + from _identity import resolve_api_key + + monkeypatch.delenv("MEM0_API_KEY", raising=False) + monkeypatch.setenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", "fallback-key") + assert resolve_api_key() == "fallback-key" + + +def test_resolve_api_key_returns_empty_when_neither_set(monkeypatch): + """resolve_api_key returns empty string when no key is available.""" + from _identity import resolve_api_key + + monkeypatch.delenv("MEM0_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False) + monkeypatch.setattr("_identity._extract_key_from_shell_profiles", lambda: "") + assert resolve_api_key() == "" + + +def test_resolve_api_key_falls_back_to_shell_profile(monkeypatch): + """resolve_api_key extracts key from shell profile when env vars are empty.""" + from _identity import resolve_api_key + + monkeypatch.delenv("MEM0_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_API_KEY", raising=False) + monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False) + monkeypatch.setattr("_identity._extract_key_from_shell_profiles", lambda: "m0-from-profile") + assert resolve_api_key() == "m0-from-profile" diff --git a/mem0-ts/package.json b/mem0-ts/package.json index e4cd60851..cc24d72ca 100644 --- a/mem0-ts/package.json +++ b/mem0-ts/package.json @@ -1,6 +1,6 @@ { "name": "mem0ai", - "version": "3.0.3", + "version": "3.0.5", "description": "The Memory Layer For Your AI Apps", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/mem0-ts/src/client/mem0.ts b/mem0-ts/src/client/mem0.ts index cac2fe36c..89fe45cef 100644 --- a/mem0-ts/src/client/mem0.ts +++ b/mem0-ts/src/client/mem0.ts @@ -9,6 +9,7 @@ import { SearchMemoryOptions, GetAllMemoryOptions, DeleteAllMemoryOptions, + DeleteMemoryOptions, MemoryUpdateBody, ProjectResponse, PromptUpdatePayload, @@ -377,11 +378,17 @@ export default class MemoryClient { return response; } - async delete(memoryId: string): Promise<{ message: string }> { + async delete( + memoryId: string, + options: DeleteMemoryOptions = {}, + ): Promise<{ message: string }> { if (this.telemetryId === "") await this.ping(); - this._captureEvent("delete", []); + this._captureEvent("delete", [Object.keys(options || {})]); + const snakeOptions = camelToSnakeKeys(this._prepareParams(options)); + // @ts-ignore + const query = new URLSearchParams(snakeOptions).toString(); return this._fetchWithErrorHandling( - `${this.host}/v1/memories/${memoryId}/`, + `${this.host}/v1/memories/${memoryId}/${query ? `?${query}` : ""}`, { method: "DELETE", headers: this.headers, diff --git a/mem0-ts/src/client/mem0.types.ts b/mem0-ts/src/client/mem0.types.ts index 0c5c1c93f..185b1dbea 100644 --- a/mem0-ts/src/client/mem0.types.ts +++ b/mem0-ts/src/client/mem0.types.ts @@ -39,6 +39,15 @@ export interface GetAllMemoryOptions { export interface DeleteAllMemoryOptions extends EntityOptions {} +export interface DeleteMemoryOptions { + /** + * When `true`, also delete the older memories this one superseded (the v3 + * linked chain), transitively — the delete-side counterpart of `latestOnly`. + * Off by default. Serialized as `delete_linked`. + */ + deleteLinked?: boolean; +} + // ─── Project Options ──────────────────────────────────────── export interface ProjectOptions { fields?: string[]; diff --git a/mem0-ts/src/client/tests/memoryClient.crud.test.ts b/mem0-ts/src/client/tests/memoryClient.crud.test.ts index 80bdcfa01..39e3f1733 100644 --- a/mem0-ts/src/client/tests/memoryClient.crud.test.ts +++ b/mem0-ts/src/client/tests/memoryClient.crud.test.ts @@ -200,9 +200,26 @@ describe("MemoryClient - delete()", () => { const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.delete("mem_123"); - expect( - findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"), - ).toBeDefined(); + const call = findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"); + expect(call).toBeDefined(); + // Default: no cascade query param, URL byte-identical to before. + expect(call![0]).not.toContain("delete_linked"); + }); + + test("serializes deleteLinked as delete_linked query param", async () => { + const extra = new Map(); + extra.set("/v1/memories/mem_123/", { + status: 200, + body: { message: "Memory deleted successfully", cascade_count: 1 }, + }); + const mock = setupMockFetch(extra); + + const client = new MemoryClient({ apiKey: TEST_API_KEY }); + await client.delete("mem_123", { deleteLinked: true }); + + const call = findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"); + expect(call).toBeDefined(); + expect(call![0]).toContain("delete_linked=true"); }); }); diff --git a/mem0-ts/src/oss/src/vector_stores/pgvector.ts b/mem0-ts/src/oss/src/vector_stores/pgvector.ts index 97393b9fe..aba0060f6 100644 --- a/mem0-ts/src/oss/src/vector_stores/pgvector.ts +++ b/mem0-ts/src/oss/src/vector_stores/pgvector.ts @@ -28,6 +28,133 @@ function escapeFilterKey(key: string): string { return key; } +interface FilterResult { + conditions: string[]; + values: any[]; + paramIndex: number; +} + +const OPERATOR_SQL_MAP: Record = + { + eq: { template: "payload->>'%KEY%' = $%IDX%", numeric: false }, + ne: { template: "payload->>'%KEY%' != $%IDX%", numeric: false }, + gt: { template: "(payload->>'%KEY%')::numeric > $%IDX%", numeric: true }, + gte: { template: "(payload->>'%KEY%')::numeric >= $%IDX%", numeric: true }, + lt: { template: "(payload->>'%KEY%')::numeric < $%IDX%", numeric: true }, + lte: { template: "(payload->>'%KEY%')::numeric <= $%IDX%", numeric: true }, + in: { template: "payload->>'%KEY%' = ANY($%IDX%::text[])", numeric: false }, + nin: { + template: "NOT (payload->>'%KEY%' = ANY($%IDX%::text[]))", + numeric: false, + }, + contains: { + template: "payload->>'%KEY%' LIKE $%IDX% ESCAPE '\\'", + numeric: false, + }, + icontains: { + template: "payload->>'%KEY%' ILIKE $%IDX% ESCAPE '\\'", + numeric: false, + }, + }; + +export function buildFilterConditions( + filters: Record | undefined, + startIndex: number, +): FilterResult { + const conditions: string[] = []; + const values: any[] = []; + let paramIndex = startIndex; + + if (!filters) { + return { conditions, values, paramIndex }; + } + + for (const [key, value] of Object.entries(filters)) { + if (key === "$or") { + const orGroups: string[] = []; + for (const orFilter of value as Record[]) { + const sub = buildFilterConditions(orFilter, paramIndex); + if (sub.conditions.length > 0) { + orGroups.push("(" + sub.conditions.join(" AND ") + ")"); + values.push(...sub.values); + paramIndex = sub.paramIndex; + } + } + if (orGroups.length > 0) { + conditions.push("(" + orGroups.join(" OR ") + ")"); + } + continue; + } + + if (key === "$not") { + const notGroups: string[] = []; + for (const notFilter of value as Record[]) { + const sub = buildFilterConditions(notFilter, paramIndex); + if (sub.conditions.length > 0) { + notGroups.push("(" + sub.conditions.join(" AND ") + ")"); + values.push(...sub.values); + paramIndex = sub.paramIndex; + } + } + if (notGroups.length > 0) { + conditions.push("NOT (" + notGroups.join(" OR ") + ")"); + } + continue; + } + + const safeKey = escapeFilterKey(key); + + if (value === "*") { + conditions.push(`payload ? $${paramIndex}`); + values.push(key); + paramIndex++; + continue; + } + + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + for (const [op, opValue] of Object.entries(value)) { + const mapping = OPERATOR_SQL_MAP[op]; + if (!mapping) { + throw new Error(`Unsupported filter operator: ${op}`); + } + const clause = mapping.template + .replace("%KEY%", safeKey) + .replace("%IDX%", String(paramIndex)); + conditions.push(clause); + + if (op === "in" || op === "nin") { + values.push((opValue as any[]).map(String)); + } else if (op === "contains" || op === "icontains") { + const escaped = String(opValue) + .replace(/\\/g, "\\\\") + .replace(/%/g, "\\%") + .replace(/_/g, "\\_"); + values.push(`%${escaped}%`); + } else if (mapping.numeric) { + values.push(Number(opValue)); + } else { + values.push(String(opValue)); + } + paramIndex++; + } + } else if (Array.isArray(value)) { + conditions.push(`payload->>'${safeKey}' = ANY($${paramIndex}::text[])`); + values.push(value.map(String)); + paramIndex++; + } else { + conditions.push(`payload->>'${safeKey}' = $${paramIndex}`); + if (typeof value === "boolean") { + values.push(JSON.stringify(value)); + } else { + values.push(String(value)); + } + paramIndex++; + } + } + + return { conditions, values, paramIndex }; +} + interface PGVectorConfig extends VectorStoreConfig { dbname?: string; user: string; @@ -203,23 +330,15 @@ export class PGVector implements VectorStore { filters?: SearchFilters, ): Promise { try { - const filterConditions: string[] = []; - const filterValues: any[] = [query, topK]; - let filterIndex = 3; - - if (filters) { - for (const [key, value] of Object.entries(filters)) { - const safeKey = escapeFilterKey(key); - filterConditions.push(`payload->>'${safeKey}' = $${filterIndex}`); - filterValues.push(value); - filterIndex++; - } - } + const { + conditions, + values, + paramIndex: _, + } = buildFilterConditions(filters, 3); + const filterValues: any[] = [query, topK, ...values]; const filterClause = - filterConditions.length > 0 - ? "AND " + filterConditions.join(" AND ") - : ""; + conditions.length > 0 ? "AND " + conditions.join(" AND ") : ""; const searchQuery = ` SELECT id, ts_rank_cd(to_tsvector('simple', payload->>'textLemmatized'), plainto_tsquery('simple', $1)) AS score, payload @@ -248,24 +367,16 @@ export class PGVector implements VectorStore { topK: number = 5, filters?: SearchFilters, ): Promise { - const filterConditions: string[] = []; const queryVector = `[${query.join(",")}]`; - const filterValues: any[] = [queryVector, topK]; - let filterIndex = 3; - - if (filters) { - for (const [key, value] of Object.entries(filters)) { - const safeKey = escapeFilterKey(key); - filterConditions.push(`payload->>'${safeKey}' = $${filterIndex}`); - filterValues.push(value); - filterIndex++; - } - } + const { + conditions, + values, + paramIndex: _, + } = buildFilterConditions(filters, 3); + const filterValues: any[] = [queryVector, topK, ...values]; const filterClause = - filterConditions.length > 0 - ? "WHERE " + filterConditions.join(" AND ") - : ""; + conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : ""; const searchQuery = ` SELECT id, vector <=> $1::vector AS distance, payload @@ -337,23 +448,14 @@ export class PGVector implements VectorStore { filters?: SearchFilters, topK: number = 100, ): Promise<[VectorStoreResult[], number]> { - const filterConditions: string[] = []; - const filterValues: any[] = []; - let paramIndex = 1; - - if (filters) { - for (const [key, value] of Object.entries(filters)) { - const safeKey = escapeFilterKey(key); - filterConditions.push(`payload->>'${safeKey}' = $${paramIndex}`); - filterValues.push(value); - paramIndex++; - } - } + const { + conditions, + values: filterValues, + paramIndex, + } = buildFilterConditions(filters, 1); const filterClause = - filterConditions.length > 0 - ? "WHERE " + filterConditions.join(" AND ") - : ""; + conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : ""; const listQuery = ` SELECT id, payload @@ -368,11 +470,11 @@ export class PGVector implements VectorStore { ${filterClause} `; - filterValues.push(topK); // Add limit as the last parameter + const listValues = [...filterValues, topK]; const [listResult, countResult] = await Promise.all([ - this.client.query(listQuery, filterValues), - this.client.query(countQuery, filterValues.slice(0, -1)), // Remove limit parameter for count query + this.client.query(listQuery, listValues), + this.client.query(countQuery, filterValues), ]); const results = listResult.rows.map((row) => ({ diff --git a/mem0-ts/src/oss/tests/pgvector.filters.test.ts b/mem0-ts/src/oss/tests/pgvector.filters.test.ts new file mode 100644 index 000000000..6b6523be0 --- /dev/null +++ b/mem0-ts/src/oss/tests/pgvector.filters.test.ts @@ -0,0 +1,244 @@ +/// + +jest.mock("pg", () => { + const Client = jest.fn().mockImplementation(() => ({ + connect: jest.fn().mockResolvedValue(undefined), + end: jest.fn().mockResolvedValue(undefined), + query: jest.fn().mockResolvedValue({ rows: [] }), + })); + const escapeIdentifier = (str: string) => `"${str.replace(/"/g, '""')}"`; + return { + __esModule: true, + default: { Client, escapeIdentifier }, + Client, + escapeIdentifier, + }; +}); + +import { buildFilterConditions } from "../src/vector_stores/pgvector"; + +describe("buildFilterConditions", () => { + test("returns empty for undefined filters", () => { + const result = buildFilterConditions(undefined, 1); + expect(result.conditions).toEqual([]); + expect(result.values).toEqual([]); + expect(result.paramIndex).toBe(1); + }); + + test("returns empty for empty filters", () => { + const result = buildFilterConditions({}, 1); + expect(result.conditions).toEqual([]); + expect(result.values).toEqual([]); + }); + + test("simple equality", () => { + const result = buildFilterConditions({ user_id: "alice" }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("payload->>'user_id' = $1"); + expect(result.values).toEqual(["alice"]); + expect(result.paramIndex).toBe(2); + }); + + test("multiple equalities", () => { + const result = buildFilterConditions( + { user_id: "alice", agent_id: "bot1" }, + 1, + ); + expect(result.conditions).toHaveLength(2); + expect(result.values).toEqual(["alice", "bot1"]); + expect(result.paramIndex).toBe(3); + }); + + test("eq operator", () => { + const result = buildFilterConditions({ status: { eq: "active" } }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("= $1"); + expect(result.values).toEqual(["active"]); + }); + + test("ne operator", () => { + const result = buildFilterConditions({ status: { ne: "deleted" } }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("!= $1"); + expect(result.values).toEqual(["deleted"]); + }); + + test("gt operator", () => { + const result = buildFilterConditions({ price: { gt: 100 } }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("::numeric > $1"); + expect(result.values).toEqual([100]); + }); + + test("gte operator", () => { + const result = buildFilterConditions({ price: { gte: 100 } }, 1); + expect(result.conditions[0]).toContain("::numeric >= $1"); + expect(result.values).toEqual([100]); + }); + + test("lt operator", () => { + const result = buildFilterConditions({ price: { lt: 50 } }, 1); + expect(result.conditions[0]).toContain("::numeric < $1"); + expect(result.values).toEqual([50]); + }); + + test("lte operator", () => { + const result = buildFilterConditions({ price: { lte: 50 } }, 1); + expect(result.conditions[0]).toContain("::numeric <= $1"); + expect(result.values).toEqual([50]); + }); + + test("range combination (gte + lte)", () => { + const result = buildFilterConditions({ score: { gte: 1, lte: 10 } }, 1); + expect(result.conditions).toHaveLength(2); + expect(result.conditions[0]).toContain("::numeric >= $1"); + expect(result.conditions[1]).toContain("::numeric <= $2"); + expect(result.values).toEqual([1, 10]); + }); + + test("in operator", () => { + const result = buildFilterConditions( + { status: { in: ["active", "pending"] } }, + 1, + ); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("= ANY($1::text[])"); + expect(result.values).toEqual([["active", "pending"]]); + }); + + test("nin operator", () => { + const result = buildFilterConditions( + { status: { nin: ["deleted", "archived"] } }, + 1, + ); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("NOT"); + expect(result.conditions[0]).toContain("= ANY($1::text[])"); + expect(result.values).toEqual([["deleted", "archived"]]); + }); + + test("contains operator", () => { + const result = buildFilterConditions({ name: { contains: "alice" } }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("LIKE $1 ESCAPE"); + expect(result.values).toEqual(["%alice%"]); + }); + + test("icontains operator", () => { + const result = buildFilterConditions({ name: { icontains: "Alice" } }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("ILIKE $1 ESCAPE"); + expect(result.values).toEqual(["%Alice%"]); + }); + + test("contains escapes LIKE wildcards", () => { + const result = buildFilterConditions({ name: { contains: "50%_off" } }, 1); + expect(result.values).toEqual(["%50\\%\\_off%"]); + }); + + test("icontains escapes LIKE wildcards", () => { + const result = buildFilterConditions({ promo: { icontains: "a%b_c" } }, 1); + expect(result.values).toEqual(["%a\\%b\\_c%"]); + }); + + test("wildcard value", () => { + const result = buildFilterConditions({ metadata_key: "*" }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("payload ? $1"); + expect(result.values).toEqual(["metadata_key"]); + }); + + test("list shorthand (array value)", () => { + const result = buildFilterConditions({ tags: ["a", "b", "c"] }, 1); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain("= ANY($1::text[])"); + expect(result.values).toEqual([["a", "b", "c"]]); + }); + + test("$or operator", () => { + const result = buildFilterConditions( + { + $or: [{ user_id: "alice" }, { user_id: "bob" }], + }, + 1, + ); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain(" OR "); + expect(result.conditions[0]).toMatch(/^\(/); + expect(result.values).toEqual(["alice", "bob"]); + }); + + test("$not operator", () => { + const result = buildFilterConditions( + { + $not: [{ status: "deleted" }], + }, + 1, + ); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toMatch(/^NOT/); + expect(result.values).toEqual(["deleted"]); + }); + + test("$or with operators", () => { + const result = buildFilterConditions( + { + $or: [{ price: { gt: 100 } }, { price: { lt: 10 } }], + }, + 1, + ); + expect(result.conditions).toHaveLength(1); + expect(result.conditions[0]).toContain(" OR "); + expect(result.values).toEqual([100, 10]); + }); + + test("mixed simple and operator filters", () => { + const result = buildFilterConditions( + { + user_id: "alice", + score: { gte: 5 }, + }, + 1, + ); + expect(result.conditions).toHaveLength(2); + expect(result.values[0]).toBe("alice"); + expect(result.values[1]).toBe(5); + }); + + test("unsupported operator throws", () => { + expect(() => buildFilterConditions({ x: { badop: 1 } }, 1)).toThrow( + "Unsupported filter operator", + ); + }); + + test("in with numeric values converts to strings", () => { + const result = buildFilterConditions({ priority: { in: [1, 2, 3] } }, 1); + expect(result.values).toEqual([["1", "2", "3"]]); + }); + + test("paramIndex increments correctly across multiple fields", () => { + const result = buildFilterConditions( + { a: "x", b: { gt: 5 }, c: { in: [1, 2] } }, + 3, + ); + expect(result.conditions[0]).toContain("$3"); + expect(result.conditions[1]).toContain("$4"); + expect(result.conditions[2]).toContain("$5"); + expect(result.paramIndex).toBe(6); + }); + + test("boolean true uses JSON casing", () => { + const result = buildFilterConditions({ is_active: true }, 1); + expect(result.values).toEqual(["true"]); + }); + + test("boolean false uses JSON casing", () => { + const result = buildFilterConditions({ is_active: false }, 1); + expect(result.values).toEqual(["false"]); + }); + + test("numeric scalar becomes string", () => { + const result = buildFilterConditions({ priority: 42 }, 1); + expect(result.values).toEqual(["42"]); + }); +}); diff --git a/mem0/client/main.py b/mem0/client/main.py index 69ff77bd8..d4633709f 100644 --- a/mem0/client/main.py +++ b/mem0/client/main.py @@ -360,11 +360,16 @@ class MemoryClient: return response.json() @api_error_handler - def delete(self, memory_id: str) -> Dict[str, Any]: + def delete(self, memory_id: str, delete_linked: bool = False) -> Dict[str, Any]: """Delete a specific memory by ID. Args: memory_id: The ID of the memory to delete. + delete_linked: When True, also delete the older memories this one + superseded (the v3 ``linked_memory_ids`` chain), transitively. + This is the delete-side counterpart of ``latest_only`` — it + stops a superseded memory from resurfacing after you delete the + current one. Defaults to False (only the given memory is deleted). Returns: A dictionary containing the API response. @@ -377,10 +382,12 @@ class MemoryClient: NetworkError: If network connectivity issues occur. MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). """ - params = self._prepare_params() + params = self._prepare_params({"delete_linked": delete_linked or None}) response = self.client.delete(f"/v1/memories/{memory_id}/", params=params) response.raise_for_status() - capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "sync"}) + capture_client_event( + "client.delete", self, {"memory_id": memory_id, "delete_linked": delete_linked, "sync_type": "sync"} + ) return response.json() @api_error_handler @@ -1268,11 +1275,16 @@ class AsyncMemoryClient: return response.json() @api_error_handler - async def delete(self, memory_id: str) -> Dict[str, Any]: + async def delete(self, memory_id: str, delete_linked: bool = False) -> Dict[str, Any]: """Delete a specific memory by ID. Args: memory_id: The ID of the memory to delete. + delete_linked: When True, also delete the older memories this one + superseded (the v3 ``linked_memory_ids`` chain), transitively. + This is the delete-side counterpart of ``latest_only`` — it + stops a superseded memory from resurfacing after you delete the + current one. Defaults to False (only the given memory is deleted). Returns: A dictionary containing the API response. @@ -1285,10 +1297,12 @@ class AsyncMemoryClient: NetworkError: If network connectivity issues occur. MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). """ - params = self._prepare_params() + params = self._prepare_params({"delete_linked": delete_linked or None}) response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params) response.raise_for_status() - capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "async"}) + capture_client_event( + "client.delete", self, {"memory_id": memory_id, "delete_linked": delete_linked, "sync_type": "async"} + ) return response.json() @api_error_handler diff --git a/mem0/vector_stores/pgvector.py b/mem0/vector_stores/pgvector.py index 8642766f6..86dcd5667 100644 --- a/mem0/vector_stores/pgvector.py +++ b/mem0/vector_stores/pgvector.py @@ -31,6 +31,87 @@ from mem0.vector_stores.base import VectorStoreBase logger = logging.getLogger(__name__) +OPERATOR_SQL_MAP = { + "eq": ("payload->>%s = %s", False), + "ne": ("payload->>%s != %s", False), + "gt": ("(payload->>%s)::numeric > %s", True), + "gte": ("(payload->>%s)::numeric >= %s", True), + "lt": ("(payload->>%s)::numeric < %s", True), + "lte": ("(payload->>%s)::numeric <= %s", True), + "in": ("payload->>%s = ANY(%s)", False), + "nin": ("NOT (payload->>%s = ANY(%s))", False), + "contains": ("payload->>%s LIKE %s", False), + "icontains": ("payload->>%s ILIKE %s", False), +} + + +def _build_filter_conditions(filters): + """Translate a processed filter dict into SQL WHERE fragments and parameter list.""" + conditions = [] + params = [] + + if not filters: + return conditions, params + + for key, value in filters.items(): + if key == "$or": + or_groups = [] + for or_filter in value: + sub_conds, sub_params = _build_filter_conditions(or_filter) + if sub_conds: + or_groups.append("(" + " AND ".join(sub_conds) + ")") + params.extend(sub_params) + if or_groups: + conditions.append("(" + " OR ".join(or_groups) + ")") + continue + + if key == "$not": + not_groups = [] + for not_filter in value: + sub_conds, sub_params = _build_filter_conditions(not_filter) + if sub_conds: + not_groups.append("(" + " AND ".join(sub_conds) + ")") + params.extend(sub_params) + if not_groups: + conditions.append("NOT (" + " OR ".join(not_groups) + ")") + continue + + if value == "*": + conditions.append("payload ? %s") + params.append(key) + continue + + if isinstance(value, dict): + for op, op_value in value.items(): + if op not in OPERATOR_SQL_MAP: + raise ValueError(f"Unsupported filter operator: {op}") + template, is_numeric = OPERATOR_SQL_MAP[op] + if op in ("in", "nin"): + str_list = [str(v) for v in op_value] + conditions.append(template) + params.extend([key, str_list]) + elif op in ("contains", "icontains"): + escaped = str(op_value).replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") + conditions.append(template + " ESCAPE '\\'") + params.extend([key, f"%{escaped}%"]) + else: + conditions.append(template) + if is_numeric: + params.extend([key, float(op_value)]) + else: + params.extend([key, str(op_value)]) + elif isinstance(value, list): + conditions.append("payload->>%s = ANY(%s)") + params.extend([key, [str(v) for v in value]]) + else: + conditions.append("payload->>%s = %s") + if isinstance(value, bool): + params.extend([key, json.dumps(value)]) + else: + params.extend([key, str(value)]) + + return conditions, params + class OutputData(BaseModel): id: Optional[str] @@ -237,14 +318,7 @@ class PGVector(VectorStoreBase): Returns: list: Search results. """ - filter_conditions = [] - filter_params = [] - - if filters: - for k, v in filters.items(): - filter_conditions.append("payload->>%s = %s") - filter_params.extend([k, str(v)]) - + filter_conditions, filter_params = _build_filter_conditions(filters) filter_clause = sql.SQL("WHERE " + " AND ".join(filter_conditions)) if filter_conditions else sql.SQL("") with self._get_cursor() as cur: @@ -274,14 +348,7 @@ class PGVector(VectorStoreBase): Returns: List[OutputData]: Search results ranked by text relevance. """ - filter_conditions = [] - filter_params = [] - - if filters: - for k, v in filters.items(): - filter_conditions.append("payload->>%s = %s") - filter_params.extend([k, str(v)]) - + filter_conditions, filter_params = _build_filter_conditions(filters) filter_clause = sql.SQL("AND " + " AND ".join(filter_conditions)) if filter_conditions else sql.SQL("") try: @@ -423,14 +490,7 @@ class PGVector(VectorStoreBase): Returns: List[OutputData]: List of vectors. """ - filter_conditions = [] - filter_params = [] - - if filters: - for k, v in filters.items(): - filter_conditions.append("payload->>%s = %s") - filter_params.extend([k, str(v)]) - + filter_conditions, filter_params = _build_filter_conditions(filters) filter_clause = sql.SQL("WHERE " + " AND ".join(filter_conditions)) if filter_conditions else sql.SQL("") with self._get_cursor() as cur: diff --git a/openclaw/package.json b/openclaw/package.json index 14b4b670b..f5ab4856d 100644 --- a/openclaw/package.json +++ b/openclaw/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@sinclair/typebox": "0.34.47", - "mem0ai": "3.0.2" + "mem0ai": "3.0.3" }, "openclaw": { "extensions": [ diff --git a/openclaw/pnpm-lock.yaml b/openclaw/pnpm-lock.yaml index 68bcdc63d..859adb360 100644 --- a/openclaw/pnpm-lock.yaml +++ b/openclaw/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: 0.34.47 version: 0.34.47 mem0ai: - specifier: 3.0.2 - version: 3.0.2(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0) + specifier: 3.0.3 + version: 3.0.3(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0) devDependencies: '@types/node': specifier: ^22.15.0 @@ -1508,8 +1508,8 @@ packages: md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} - mem0ai@3.0.2: - resolution: {integrity: sha512-smB9q27jrJu2D5WZje65+zMVptdS/WsqALxd2kjiZhEpThd/qkS8T3bumatcTu6Zb+LsT28FRpeWjXGTJjyQXQ==} + mem0ai@3.0.3: + resolution: {integrity: sha512-nKwGYNh8DipG9Sw0ik/huFLpKtVtwadSy+T6GjAxkkrHp6hO3tlbR75EWH/bCGDG/HO06nYDsDP5fPSnj3bp5g==} engines: {node: '>=18'} peerDependencies: '@anthropic-ai/sdk': ^0.40.1 @@ -2119,6 +2119,7 @@ packages: uuid@10.0.0: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@13.0.0: @@ -2127,10 +2128,12 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true vite@8.0.0: @@ -3690,7 +3693,7 @@ snapshots: crypt: 0.0.2 is-buffer: 1.1.6 - mem0ai@3.0.2(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0): + mem0ai@3.0.3(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0): dependencies: '@anthropic-ai/sdk': 0.40.1 '@azure/identity': 4.13.0 diff --git a/pyproject.toml b/pyproject.toml index a2517b5ad..13eb2fac6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mem0ai" -version = "2.0.2" +version = "2.0.4" description = "Long-term memory for AI Agents" authors = [ { name = "Mem0", email = "support@mem0.ai" } @@ -145,7 +145,7 @@ test = [ [tool.ruff] line-length = 120 -exclude = ["embedchain/", "openmemory/"] +exclude = ["openmemory/"] [tool.ruff.lint.isort] known-first-party = ["mem0", "mem0_cli"] diff --git a/server/main.py b/server/main.py index 07d300d00..098712bf1 100644 --- a/server/main.py +++ b/server/main.py @@ -4,16 +4,10 @@ import os import time from typing import Any, Dict, List, Optional -from dotenv import load_dotenv -from fastapi import Depends, FastAPI, HTTPException, Request -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse, RedirectResponse -from pydantic import BaseModel, Field -from slowapi import _rate_limit_exceeded_handler -from slowapi.errors import RateLimitExceeded -from sqlalchemy import func, select - +import telemetry from auth import ADMIN_API_KEY, AUTH_DISABLED, JWT_SECRET, verify_auth +from db import SessionLocal +from dotenv import load_dotenv from errors import ( UpstreamError, install_request_id_logging, @@ -22,16 +16,27 @@ from errors import ( upstream_error, upstream_error_handler, ) -from rate_limit import limiter -from db import SessionLocal +from fastapi import Depends, FastAPI, HTTPException, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse, RedirectResponse from models import RequestLog, User -import telemetry -from routers import auth as auth_router +from pydantic import BaseModel, Field +from rate_limit import limiter from routers import api_keys as api_keys_router +from routers import auth as auth_router from routers import entities as entities_router from routers import requests as requests_router from schemas import MessageResponse -from server_state import get_current_config, get_memory_instance, initialize_state, set_session_factory, update_config +from server_state import ( + get_current_config, + get_memory_instance, + initialize_state, + set_session_factory, + update_config, +) +from slowapi import _rate_limit_exceeded_handler +from slowapi.errors import RateLimitExceeded +from sqlalchemy import func, select load_dotenv() @@ -188,9 +193,9 @@ class MemoryUpdate(BaseModel): class SearchRequest(BaseModel): query: str = Field(..., description="Search query.") - user_id: Optional[str] = None - run_id: Optional[str] = None - agent_id: Optional[str] = None + user_id: Optional[str] = Field(None, description="Deprecated: pass inside `filters` instead.", deprecated=True) + run_id: Optional[str] = Field(None, description="Deprecated: pass inside `filters` instead.", deprecated=True) + agent_id: Optional[str] = Field(None, description="Deprecated: pass inside `filters` instead.", deprecated=True) filters: Optional[Dict[str, Any]] = None top_k: Optional[int] = Field(None, description="Maximum number of results to return.") threshold: Optional[float] = Field(None, description="Minimum similarity score for results.") @@ -416,8 +421,25 @@ def get_memory(memory_id: str, _auth=Depends(verify_auth)): def search_memories(search_req: SearchRequest, _auth=Depends(verify_auth)): """Search for memories based on a query.""" try: - params = {k: v for k, v in search_req.model_dump().items() if v is not None and k != "query"} - return get_memory_instance().search(query=search_req.query, **params) + filters = search_req.filters or {} + deprecated_keys = [] + for entity_key in ("user_id", "agent_id", "run_id"): + entity_val = getattr(search_req, entity_key, None) + if entity_val is not None: + filters[entity_key] = entity_val + deprecated_keys.append(entity_key) + if deprecated_keys: + logging.warning( + "Top-level %s in /search is deprecated. Use filters={%s} instead.", + ", ".join(deprecated_keys), + ", ".join(f'"{k}": "..."' for k in deprecated_keys), + ) + params = {} + if search_req.top_k is not None: + params["top_k"] = search_req.top_k + if search_req.threshold is not None: + params["threshold"] = search_req.threshold + return get_memory_instance().search(query=search_req.query, filters=filters, **params) except Exception: raise upstream_error() diff --git a/tests/test_client.py b/tests/test_client.py index fb685c02a..66d01a4c0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -147,3 +147,41 @@ class TestFilterOperatorPassthrough: call_args = mock_memory_client.client.post.call_args payload = call_args.kwargs.get("json", call_args.args[1] if len(call_args.args) > 1 else {}) assert payload["filters"] == complex_filter + + +class TestDeleteLinked: + """delete() should forward the opt-in delete_linked flag as a query param.""" + + def _setup_delete(self, client): + client.client.delete.return_value = MagicMock( + json=lambda: {"message": "Memory deleted successfully!"}, + raise_for_status=lambda: None, + ) + + def test_delete_default_omits_delete_linked(self, mock_memory_client): + """Default delete sends no delete_linked param — byte-identical to before.""" + self._setup_delete(mock_memory_client) + + mock_memory_client.delete("mem_123") + + call_args = mock_memory_client.client.delete.call_args + assert call_args.args[0] == "/v1/memories/mem_123/" + assert "delete_linked" not in call_args.kwargs.get("params", {}) + + def test_delete_linked_true_sets_param(self, mock_memory_client): + """delete_linked=True forwards delete_linked into the request params.""" + self._setup_delete(mock_memory_client) + + mock_memory_client.delete("mem_123", delete_linked=True) + + call_args = mock_memory_client.client.delete.call_args + assert call_args.kwargs.get("params", {}).get("delete_linked") is True + + def test_delete_linked_false_omits_param(self, mock_memory_client): + """delete_linked=False is stripped, so the default path is untouched.""" + self._setup_delete(mock_memory_client) + + mock_memory_client.delete("mem_123", delete_linked=False) + + call_args = mock_memory_client.client.delete.call_args + assert "delete_linked" not in call_args.kwargs.get("params", {}) diff --git a/tests/test_server_params.py b/tests/test_server_params.py index 54d6e1638..6233262fe 100644 --- a/tests/test_server_params.py +++ b/tests/test_server_params.py @@ -16,7 +16,6 @@ pytest.importorskip("fastapi", reason="fastapi not installed") from fastapi.testclient import TestClient - # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @@ -306,9 +305,9 @@ class TestExistingParamsUnchanged: }) assert resp.status_code == 200 _, kwargs = mock_memory.search.call_args - assert kwargs["user_id"] == "u1" - assert kwargs["agent_id"] == "a1" - assert kwargs["filters"] == {"category": "food"} + assert kwargs["filters"]["user_id"] == "u1" + assert kwargs["filters"]["agent_id"] == "a1" + assert kwargs["filters"]["category"] == "food" def test_add_metadata_still_forwarded(self, client, mock_memory): resp = client.post("/memories", json={ @@ -465,11 +464,14 @@ class TestCallSignatureMatch: "top_k": 10, "threshold": 0.5, }) assert resp.status_code == 200 - # The handler passes query= as a keyword arg, so it appears in kwargs too _, kwargs = mock_memory.search.call_args - valid_params = {"query", "user_id", "agent_id", "run_id", "top_k", "filters", "threshold", "rerank"} + valid_params = {"query", "top_k", "filters", "threshold", "rerank"} for key in kwargs: assert key in valid_params, f"Unexpected kwarg '{key}' forwarded to Memory.search()" + assert kwargs["filters"]["user_id"] == "u1" + assert kwargs["filters"]["agent_id"] == "a1" + assert kwargs["filters"]["run_id"] == "r1" + assert kwargs["filters"]["k"] == "v" def test_add_kwargs_are_valid(self, client, mock_memory): """All kwargs forwarded to Memory.add() must be in its signature.""" @@ -588,3 +590,69 @@ class TestGetMemories: # 3. Verify the core logic: the param was mapped to the filters dict! _, kwargs = mock_memory.get_all.call_args assert kwargs["filters"] == {"user_id": "test_routing_user"} + + +# =========================================================================== +# SearchRequest: entity IDs mapped into filters (fix for server 502) +# =========================================================================== + +class TestSearchEntityIdMapping: + """Verify that POST /search maps top-level user_id / agent_id / run_id + into the filters dict instead of forwarding them as kwargs, which would + cause Memory.search() to raise ValueError in v3.""" + + def test_user_id_mapped_to_filters(self, client, mock_memory): + resp = client.post("/search", json={"query": "food", "user_id": "u1"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert "user_id" not in kwargs + assert kwargs["filters"]["user_id"] == "u1" + + def test_agent_id_mapped_to_filters(self, client, mock_memory): + resp = client.post("/search", json={"query": "food", "agent_id": "a1"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert "agent_id" not in kwargs + assert kwargs["filters"]["agent_id"] == "a1" + + def test_run_id_mapped_to_filters(self, client, mock_memory): + resp = client.post("/search", json={"query": "food", "run_id": "r1"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert "run_id" not in kwargs + assert kwargs["filters"]["run_id"] == "r1" + + def test_all_entity_ids_mapped(self, client, mock_memory): + resp = client.post("/search", json={ + "query": "food", "user_id": "u1", "agent_id": "a1", "run_id": "r1", + }) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert kwargs["filters"] == {"user_id": "u1", "agent_id": "a1", "run_id": "r1"} + + def test_entity_ids_merged_with_explicit_filters(self, client, mock_memory): + resp = client.post("/search", json={ + "query": "food", + "user_id": "u1", + "filters": {"category": "food"}, + }) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert kwargs["filters"]["user_id"] == "u1" + assert kwargs["filters"]["category"] == "food" + + def test_no_entity_ids_no_filters(self, client, mock_memory): + resp = client.post("/search", json={"query": "food"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert kwargs["filters"] == {} + + def test_only_filters_no_entity_ids(self, client, mock_memory): + resp = client.post("/search", json={ + "query": "food", + "filters": {"user_id": "u1", "category": "food"}, + }) + assert resp.status_code == 200 + _, kwargs = mock_memory.search.call_args + assert kwargs["filters"]["user_id"] == "u1" + assert kwargs["filters"]["category"] == "food" diff --git a/tests/vector_stores/test_pgvector.py b/tests/vector_stores/test_pgvector.py index faa2029bc..c610ffc17 100644 --- a/tests/vector_stores/test_pgvector.py +++ b/tests/vector_stores/test_pgvector.py @@ -4,7 +4,7 @@ import unittest import uuid from unittest.mock import MagicMock, patch -from mem0.vector_stores.pgvector import PGVector +from mem0.vector_stores.pgvector import PGVector, _build_filter_conditions class TestPGVector(unittest.TestCase): @@ -2233,3 +2233,178 @@ class TestPGVector(unittest.TestCase): def tearDown(self): """Clean up after each test.""" pass + + +class TestBuildFilterConditions(unittest.TestCase): + """Tests for the _build_filter_conditions helper that translates filter dicts to SQL.""" + + def test_none_filters(self): + conditions, params = _build_filter_conditions(None) + self.assertEqual(conditions, []) + self.assertEqual(params, []) + + def test_empty_filters(self): + conditions, params = _build_filter_conditions({}) + self.assertEqual(conditions, []) + self.assertEqual(params, []) + + def test_simple_equality(self): + conditions, params = _build_filter_conditions({"user_id": "alice"}) + self.assertEqual(len(conditions), 1) + self.assertIn("payload->>%s = %s", conditions[0]) + self.assertEqual(params, ["user_id", "alice"]) + + def test_multiple_equalities(self): + conditions, params = _build_filter_conditions({"user_id": "alice", "agent_id": "bot1"}) + self.assertEqual(len(conditions), 2) + self.assertEqual(params, ["user_id", "alice", "agent_id", "bot1"]) + + def test_eq_operator(self): + conditions, params = _build_filter_conditions({"status": {"eq": "active"}}) + self.assertEqual(len(conditions), 1) + self.assertIn("payload->>%s = %s", conditions[0]) + self.assertEqual(params, ["status", "active"]) + + def test_ne_operator(self): + conditions, params = _build_filter_conditions({"status": {"ne": "deleted"}}) + self.assertEqual(len(conditions), 1) + self.assertIn("payload->>%s != %s", conditions[0]) + self.assertEqual(params, ["status", "deleted"]) + + def test_gt_operator(self): + conditions, params = _build_filter_conditions({"price": {"gt": 100}}) + self.assertEqual(len(conditions), 1) + self.assertIn("(payload->>%s)::numeric > %s", conditions[0]) + self.assertEqual(params, ["price", 100.0]) + + def test_gte_operator(self): + conditions, params = _build_filter_conditions({"price": {"gte": 100}}) + self.assertEqual(len(conditions), 1) + self.assertIn("(payload->>%s)::numeric >= %s", conditions[0]) + self.assertEqual(params, ["price", 100.0]) + + def test_lt_operator(self): + conditions, params = _build_filter_conditions({"price": {"lt": 50}}) + self.assertEqual(len(conditions), 1) + self.assertIn("(payload->>%s)::numeric < %s", conditions[0]) + self.assertEqual(params, ["price", 50.0]) + + def test_lte_operator(self): + conditions, params = _build_filter_conditions({"price": {"lte": 50}}) + self.assertEqual(len(conditions), 1) + self.assertIn("(payload->>%s)::numeric <= %s", conditions[0]) + self.assertEqual(params, ["price", 50.0]) + + def test_range_combination(self): + conditions, params = _build_filter_conditions({"score": {"gte": 1, "lte": 10}}) + self.assertEqual(len(conditions), 2) + self.assertIn("(payload->>%s)::numeric >= %s", conditions[0]) + self.assertIn("(payload->>%s)::numeric <= %s", conditions[1]) + self.assertEqual(params, ["score", 1.0, "score", 10.0]) + + def test_in_operator(self): + conditions, params = _build_filter_conditions({"status": {"in": ["active", "pending"]}}) + self.assertEqual(len(conditions), 1) + self.assertIn("payload->>%s = ANY(%s)", conditions[0]) + self.assertEqual(params, ["status", ["active", "pending"]]) + + def test_nin_operator(self): + conditions, params = _build_filter_conditions({"status": {"nin": ["deleted", "archived"]}}) + self.assertEqual(len(conditions), 1) + self.assertIn("NOT (payload->>%s = ANY(%s))", conditions[0]) + self.assertEqual(params, ["status", ["deleted", "archived"]]) + + def test_contains_operator(self): + conditions, params = _build_filter_conditions({"name": {"contains": "alice"}}) + self.assertEqual(len(conditions), 1) + self.assertIn("LIKE %s ESCAPE", conditions[0]) + self.assertEqual(params, ["name", "%alice%"]) + + def test_icontains_operator(self): + conditions, params = _build_filter_conditions({"name": {"icontains": "Alice"}}) + self.assertEqual(len(conditions), 1) + self.assertIn("ILIKE %s ESCAPE", conditions[0]) + self.assertEqual(params, ["name", "%Alice%"]) + + def test_contains_escapes_wildcards(self): + conditions, params = _build_filter_conditions({"name": {"contains": "50%_off"}}) + self.assertEqual(params, ["name", "%50\\%\\_off%"]) + + def test_icontains_escapes_wildcards(self): + conditions, params = _build_filter_conditions({"promo": {"icontains": "a%b_c"}}) + self.assertEqual(params, ["promo", "%a\\%b\\_c%"]) + + def test_wildcard(self): + conditions, params = _build_filter_conditions({"metadata_key": "*"}) + self.assertEqual(len(conditions), 1) + self.assertIn("payload ? %s", conditions[0]) + self.assertEqual(params, ["metadata_key"]) + + def test_list_shorthand(self): + conditions, params = _build_filter_conditions({"tags": ["a", "b", "c"]}) + self.assertEqual(len(conditions), 1) + self.assertIn("payload->>%s = ANY(%s)", conditions[0]) + self.assertEqual(params, ["tags", ["a", "b", "c"]]) + + def test_or_operator(self): + conditions, params = _build_filter_conditions({ + "$or": [ + {"user_id": "alice"}, + {"user_id": "bob"}, + ] + }) + self.assertEqual(len(conditions), 1) + self.assertIn(" OR ", conditions[0]) + self.assertTrue(conditions[0].startswith("(")) + self.assertEqual(params, ["user_id", "alice", "user_id", "bob"]) + + def test_not_operator(self): + conditions, params = _build_filter_conditions({ + "$not": [ + {"status": "deleted"}, + ] + }) + self.assertEqual(len(conditions), 1) + self.assertTrue(conditions[0].startswith("NOT")) + self.assertEqual(params, ["status", "deleted"]) + + def test_or_with_operators(self): + conditions, params = _build_filter_conditions({ + "$or": [ + {"price": {"gt": 100}}, + {"price": {"lt": 10}}, + ] + }) + self.assertEqual(len(conditions), 1) + self.assertIn(" OR ", conditions[0]) + self.assertEqual(params, ["price", 100.0, "price", 10.0]) + + def test_mixed_simple_and_operator_filters(self): + conditions, params = _build_filter_conditions({ + "user_id": "alice", + "score": {"gte": 5}, + }) + self.assertEqual(len(conditions), 2) + self.assertIn("payload->>%s = %s", conditions[0]) + self.assertIn("(payload->>%s)::numeric >= %s", conditions[1]) + + def test_unsupported_operator_raises(self): + with self.assertRaises(ValueError) as ctx: + _build_filter_conditions({"x": {"badop": 1}}) + self.assertIn("Unsupported filter operator", str(ctx.exception)) + + def test_in_with_numeric_values(self): + conditions, params = _build_filter_conditions({"priority": {"in": [1, 2, 3]}}) + self.assertEqual(params, ["priority", ["1", "2", "3"]]) + + def test_boolean_true_uses_json_casing(self): + conditions, params = _build_filter_conditions({"is_active": True}) + self.assertEqual(params, ["is_active", "true"]) + + def test_boolean_false_uses_json_casing(self): + conditions, params = _build_filter_conditions({"is_active": False}) + self.assertEqual(params, ["is_active", "false"]) + + def test_numeric_scalar_becomes_string(self): + conditions, params = _build_filter_conditions({"priority": 42}) + self.assertEqual(params, ["priority", "42"])