From 99558c71bd3494a05758d67d4189d5049f1f7e9a Mon Sep 17 00:00:00 2001 From: nikilok Date: Mon, 14 Jul 2025 22:13:37 +0100 Subject: [PATCH 1/8] setup an agentic workflow - add a new endpoint that recieves a company name - uses 3 agents the first agent being DuckDuckGo that searches the web for market news - a yahoo finances tool that searches Yahoo finance for information - an agent that manages the above 2 agents that summarizes the information - support http streaming to respond back with summary --- .env.example | 7 + app/main.py | 42 +- app/services/ai_investment.py | 155 +++++++ poetry.lock | 777 +++++++++++++++++++++++++++++++++- pyproject.toml | 8 +- 5 files changed, 985 insertions(+), 4 deletions(-) create mode 100644 .env.example create mode 100644 app/services/ai_investment.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3ad2575 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# xAI API Key for AI Investment Analysis (required) +# Get your API key from: https://console.x.ai/ +OPENAI_API_KEY=your_xai_api_key_here + +# xAI Model Configuration +XAI_MODEL=grok-3-latest + diff --git a/app/main.py b/app/main.py index 7299a37..aa0e8d3 100644 --- a/app/main.py +++ b/app/main.py @@ -2,9 +2,11 @@ from fastapi import FastAPI, Query from fastapi_mcp import FastApiMCP +from fastapi.responses import StreamingResponse from app.models import CompanySearchResult from app.services.search import search_companies +from app.services.ai_investment import investment_agent from app.utils import lessthan_x app = FastAPI() @@ -21,7 +23,45 @@ def search_company( company_name: str = Query(..., description="Company name to search for") ) -> List[CompanySearchResult]: + print("company_name", company_name) return search_companies(company_name) -mcp.setup_server() +@app.get("/investment-analysis") +async def get_investment_analysis( + company_name: str = Query(..., description="Company name to analyze for investment") +): + """ + Stream AI-powered investment analysis for a given company. + """ + # Validate input length (similar to @lessthan_x decorator) + if len(company_name) < 3: + return {"error": "company_name must be at least 3 characters long."} + + async def async_investment_stream(): + """Async wrapper for the investment analysis generator""" + try: + # Use the investment_agent function which returns a proper generator + from app.services.ai_investment import investment_agent + analysis_generator = investment_agent(company_name) + + # Yield from the generator + for chunk in analysis_generator: + if chunk: # Only yield non-empty chunks + yield str(chunk) + + except Exception as error: + yield f"Error during analysis: {str(error)}\n" + yield "Please try again or contact support.\n" + + # Return StreamingResponse + return StreamingResponse( + async_investment_stream(), + media_type="text/plain", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + } + ) + +mcp.setup_server() \ No newline at end of file diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py new file mode 100644 index 0000000..a6b19dd --- /dev/null +++ b/app/services/ai_investment.py @@ -0,0 +1,155 @@ +import os +from dotenv import load_dotenv +from agno.agent import Agent +from agno.models.xai import xAI +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.tools.yfinance import YFinanceTools + +# Load environment variables from .env file +load_dotenv() + +# Verify xAI API key is available +xai_api_key = os.getenv("OPENAI_API_KEY") +if not xai_api_key: + raise ValueError("OPENAI_API_KEY environment variable is required. Please set it in your .env file.") + +# Get model name from environment variables +llm_model = os.getenv("XAI_MODEL") +if not llm_model: + raise ValueError("XAI_MODEL environment variable is required. Please set it in your .env file.") +print(f"Using LLM model: {llm_model}") + +# Agent 1: Market Research Agent - Uses web search to gather market news and trends +market_research_agent = Agent( + name="Market Research Agent", + role="Gather latest market news, trends, and qualitative information from the web", + model=xAI(id=llm_model), + tools=[DuckDuckGoTools()], + instructions=[ + "Always cite sources and provide summaries of key articles or reports", + "Keep tool calls simple and avoid complex JSON structures", + "Be precise with search queries", + "If tool calls fail, provide analysis based on general knowledge" + ], + markdown=True, + show_tool_calls=False, +) + +# Agent 2: Financial Data Agent - Retrieves quantitative financial data using YFinance +financial_data_agent = Agent( + name="Financial Data Agent", + role="Fetch stock prices, analyst recommendations, company info, and financial metrics", + model=xAI(id=llm_model), + tools=[ + YFinanceTools( + stock_price=True, + analyst_recommendations=True, + company_info=True, + historical_prices=True, + ) + ], + instructions=[ + "Present financial data in tables for clarity and include historical trends where relevant", + "Use precise stock symbols and avoid ambiguous queries", + "Keep tool calls simple and properly formatted", + "If tool calls fail, provide analysis based on general knowledge" + ], + markdown=True, + show_tool_calls=False, +) + +# Agent 3: Investment Analysis Agent - Analyzes data from other agents to provide insights and recommendations +investment_analysis_agent = Agent( + name="Investment Analysis Agent", + role="Analyze market research and financial data to provide investment insights, risks, and recommendations", + model=xAI(id=llm_model), + instructions="Synthesize information from team members, evaluate risks, and suggest buy/sell/hold recommendations with reasoning", + markdown=True, +) + +# Create the main Investment Team Agent that coordinates the specialized agents +investment_team = Agent( + team=[market_research_agent, financial_data_agent, investment_analysis_agent], + model=xAI(id=llm_model), + instructions=[ + "Coordinate between agents to gather comprehensive data", + "Ensure responses are well-structured with sections for research, data, and analysis", + "Always include sources and use tables for data", + "Keep tool calls simple and avoid complex JSON structures" + ], + markdown=True, + show_tool_calls=False, +) + +def investment_agent(company_name: str): + """ + Investment analysis using multi-agent approach. + Returns a generator for streaming responses. + """ + def generate_streaming_analysis(): + """Generate streaming analysis using the investment team""" + try: + yield f"🔍 Starting comprehensive investment analysis for {company_name}...\n\n" + + # Use the run method to get the complete response + response = investment_team.run( + f"Provide a detailed investment analysis for {company_name} stock, including market outlook, financial performance, and recommendations." + ) + + # Extract and stream the content + if hasattr(response, 'content') and response.content: + content = response.content + # Stream in chunks for better user experience + chunk_size = 150 + for i in range(0, len(content), chunk_size): + chunk = content[i:i+chunk_size] + yield chunk + else: + yield f"Analysis completed for {company_name}" + + except Exception as e: + yield f"❌ Error during analysis: {str(e)}\n" + yield "🔄 Attempting simplified analysis...\n" + + try: + # Fallback to simple analysis + simplified_response = investment_analysis_agent.run( + f"Provide a basic investment analysis for {company_name} based on general knowledge." + ) + if hasattr(simplified_response, 'content') and simplified_response.content: + yield simplified_response.content + else: + yield f"Basic analysis completed for {company_name}" + except Exception as fallback_error: + yield f"Unable to complete analysis: {fallback_error}\n" + + return generate_streaming_analysis() + +# To test the code through the command line instead of FastAPI +if __name__ == "__main__": + import sys + + # Check if company name is provided as command line argument + if len(sys.argv) < 2: + print("Usage: python ai_investment.py ") + print("Example: python ai_investment.py Tesla") + print("Example: python ai_investment.py TSLA") + sys.exit(1) + + company_name = " ".join(sys.argv[1:]) # Join all arguments in case company name has spaces + print(f"Starting investment analysis for: {company_name}") + print("=" * 50) + print("Note: Using simplified approach to avoid JSON parsing issues") + print("=" * 50) + + try: + # Run the investment analysis + analysis_gen = investment_agent(company_name) + for chunk in analysis_gen: + print(chunk, end="") + print("\n" + "=" * 50) + print("Analysis completed successfully!") + except Exception as e: + print(f"Error: {e}") + print("Try using the stock symbol instead (e.g., TSLA instead of Tesla)") + sys.exit(1) diff --git a/poetry.lock b/poetry.lock index 59b87d0..e85b384 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,119 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "agno" +version = "1.7.2" +description = "Agno: a lightweight library for building Multi-Agent Systems" +optional = false +python-versions = "<4,>=3.7" +groups = ["main"] +files = [ + {file = "agno-1.7.2-py3-none-any.whl", hash = "sha256:37a1985e67762f0d8016318ba5d8b8697c855827d7c86b21a41cdff088294cc2"}, + {file = "agno-1.7.2.tar.gz", hash = "sha256:f4f97e5ea1f9c0781d9bb8396862c82671381386c1569cdb456d70f6c8d3a108"}, +] + +[package.dependencies] +docstring-parser = "*" +gitpython = "*" +httpx = "*" +pydantic = "*" +pydantic-settings = "*" +python-dotenv = "*" +python-multipart = "*" +pyyaml = "*" +rich = "*" +tomli = "*" +typer = "*" +typing-extensions = "*" + +[package.extras] +agentql = ["agentql"] +agui = ["ag-ui-protocol"] +anthropic = ["anthropic"] +apify = ["apify-client"] +arize = ["agno[opentelemetry]", "arize-phoenix", "opentelemetry-distro", "opentelemetry-exporter-otlp-proto-grpc"] +aws = ["agno-aws", "agno-docker"] +azure = ["aiohttp", "azure-ai-inference"] +brave = ["brave-search"] +browserbase = ["browserbase", "playwright"] +cartesia = ["cartesia"] +cassandra = ["cassio"] +cerebras = ["cerebras-cloud-sdk"] +chromadb = ["chromadb"] +clickhouse = ["clickhouse-connect"] +cohere = ["cohere"] +confluence = ["atlassian-python-api"] +cookbooks = ["email_validator", "inquirer"] +couchbase = ["couchbase"] +crawl4ai = ["crawl4ai"] +csv = ["aiofiles"] +ddg = ["duckduckgo-search"] +dev = ["arxiv", "fastapi", "mypy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "ruff", "timeout-decorator", "types-aiofiles", "types-pyyaml", "uvicorn"] +docker = ["agno-docker"] +docx = ["python-docx"] +duckdb = ["duckdb"] +elevenlabs = ["elevenlabs"] +embedders = ["agno[huggingface]"] +exa = ["exa_py"] +fal = ["fal_client"] +firecrawl = ["firecrawl-py"] +firestore = ["google-cloud-firestore"] +gcs = ["google-cloud-storage"] +github = ["PyGithub"] +gmail = ["google-api-python-client", "google-auth-httplib2", "google-auth-oauthlib"] +google = ["google-genai"] +google-bigquery = ["google-cloud-bigquery"] +googlemaps = ["google-maps-places", "googlemaps"] +groq = ["groq"] +huggingface = ["huggingface-hub"] +ibm = ["ibm-watsonx-ai"] +infinity = ["infinity_client"] +integration-tests = ["Pillow", "duckduckgo-search", "exa_py", "sqlalchemy", "yfinance"] +knowledge = ["agno[csv]", "agno[docx]", "agno[markdown]", "agno[pdf]", "agno[text]"] +lancedb = ["lancedb (==0.20.0)", "tantivy"] +langfuse = ["langfuse"] +litellm = ["litellm"] +lmstudio = ["lmstudio"] +markdown = ["aiofiles", "markdown", "unstructured"] +matplotlib = ["matplotlib"] +mcp = ["mcp"] +mem0 = ["mem0ai"] +meta = ["llama-api-client"] +milvusdb = ["pymilvus (>=2.5.10)"] +mistral = ["mistralai"] +models = ["agno[anthropic]", "agno[azure]", "agno[cerebras]", "agno[cohere]", "agno[google]", "agno[groq]", "agno[ibm]", "agno[infinity]", "agno[infinity]", "agno[litellm]", "agno[meta]", "agno[mistral]", "agno[ollama]", "agno[openai]"] +mongodb = ["pymongo[srv]"] +newspaper = ["lxml_html_clean", "newspaper4k"] +ollama = ["ollama"] +openai = ["openai"] +opencv = ["opencv-python"] +openlit = ["agno[opentelemetry]", "openlit"] +opentelemetry = ["opentelemetry-exporter-otlp", "opentelemetry-sdk"] +oxylabs = ["oxylabs"] +pdf = ["pypdf", "rapidocr_onnxruntime"] +performance = ["memory_profiler"] +pgvector = ["pgvector"] +pinecone = ["pinecone (==5.4.2)"] +postgres = ["psycopg", "psycopg-binary"] +qdrant = ["qdrant-client"] +redis = ["redis"] +singlestore = ["sqlalchemy"] +sql = ["sqlalchemy"] +sqlite = ["sqlalchemy"] +storage = ["agno[firestore]", "agno[gcs]", "agno[postgres]", "agno[redis]", "agno[sql]", "agno[sqlite]"] +tests = ["agno[agui]", "agno[cookbooks]", "agno[dev]", "agno[embedders]", "agno[knowledge]", "agno[models]", "agno[performance]", "agno[storage]", "agno[tools]", "agno[vectordbs]", "build", "twine"] +text = ["aiofiles"] +todoist = ["todoist-api-python"] +tools = ["agno[agentql]", "agno[apify]", "agno[brave]", "agno[browserbase]", "agno[cartesia]", "agno[confluence]", "agno[crawl4ai]", "agno[ddg]", "agno[duckdb]", "agno[elevenlabs]", "agno[exa]", "agno[fal]", "agno[firecrawl]", "agno[github]", "agno[gmail]", "agno[google-bigquery]", "agno[googlemaps]", "agno[matplotlib]", "agno[mcp]", "agno[mem0]", "agno[newspaper]", "agno[opencv]", "agno[oxylabs]", "agno[todoist]", "agno[valyu]", "agno[webex]", "agno[youtube]", "agno[zep]"] +valyu = ["valyu"] +vectordbs = ["agno[cassandra]", "agno[chromadb]", "agno[clickhouse]", "agno[couchbase]", "agno[lancedb]", "agno[milvusdb]", "agno[mongodb]", "agno[pgvector]", "agno[pinecone]", "agno[qdrant]", "agno[singlestore]", "agno[weaviate]"] +weave = ["weave"] +weaviate = ["weaviate-client"] +webex = ["webexpythonsdk"] +yfinance = ["yfinance"] +youtube = ["youtube_transcript_api"] +zep = ["zep-cloud"] + [[package]] name = "annotated-types" version = "0.7.0" @@ -54,6 +168,29 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "25.1.0" @@ -111,6 +248,86 @@ files = [ {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, ] +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "charset-normalizer" version = "3.4.2" @@ -241,6 +458,47 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "curl-cffi" +version = "0.12.0" +description = "libcurl ffi bindings for Python, with impersonation support." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "curl_cffi-0.12.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e663d6692aa923a60fd2f050dad4cccd317dc7dd3d9edceb5230f68017e811eb"}, + {file = "curl_cffi-0.12.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8f7f1745700fcd4f6d5681cdca27426cc3005c3e17ca9a66fe5753c5001a7314"}, + {file = "curl_cffi-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:368dd6e354933c62d3b35afbecdc5e7e7817ba748db0d23f7276f89b7eec49e8"}, + {file = "curl_cffi-0.12.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1df994f9ccf27b391259ba0157d415b1e60f01e59914f75280cb9c1eceb3a3c8"}, + {file = "curl_cffi-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18aadb313a1fe23098e867f9e6e42c57c5c68985521a1fe5fb8ca15bb990341b"}, + {file = "curl_cffi-0.12.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c0875e85eda5a5314bf1974ad1ecdcdd61811759b820b6617ec7be6daf85a1a3"}, + {file = "curl_cffi-0.12.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b9650b85964ed06c634cfff4ce926255b80195f73edf629a1272b1a19908811d"}, + {file = "curl_cffi-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:cdcbc492a68b7f3592a4dc4eb742281aa74d220f55affbd84645795a7fdb3ddc"}, + {file = "curl_cffi-0.12.0.tar.gz", hash = "sha256:01770d120e2ab82ad076687c7038abe4d614c7e13d7a5378520b027df529dbe7"}, +] + +[package.dependencies] +certifi = ">=2024.2.2" +cffi = ">=1.12.0" + +[package.extras] +build = ["cibuildwheel", "wheel"] +dev = ["charset_normalizer (>=3.3.2,<4.0)", "coverage (>=6.4.1,<7.0)", "cryptography (>=42.0.5,<43.0)", "httpx (==0.23.1)", "mypy (>=1.9.0,<2.0)", "pytest (>=8.1.1,<9.0)", "pytest-asyncio (>=0.23.6,<1.0)", "pytest-trio (>=0.8.0,<1.0)", "ruff (>=0.3.5,<1.0)", "trio (>=0.25.0,<1.0)", "trustme (>=1.1.0,<2.0)", "typing_extensions", "uvicorn (>=0.29.0,<1.0)", "websockets (>=12.0,<13.0)"] +extra = ["lxml_html_clean", "markdownify (>=1.1.0)", "readability-lxml (>=0.8.1)"] +test = ["charset_normalizer (>=3.3.2,<4.0)", "cryptography (>=42.0.5,<43.0)", "fastapi (==0.110.0)", "httpx (==0.23.1)", "proxy.py (>=2.4.3,<3.0)", "pytest (>=8.1.1,<9.0)", "pytest-asyncio (>=0.23.6,<1.0)", "pytest-trio (>=0.8.0,<1.0)", "python-multipart (>=0.0.9,<1.0)", "trio (>=0.25.0,<1.0)", "trustme (>=1.1.0,<2.0)", "typing_extensions", "uvicorn (>=0.29.0,<1.0)", "websockets (>=12.0,<13.0)"] + +[[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 = "dnspython" version = "2.7.0" @@ -262,6 +520,38 @@ idna = ["idna (>=3.7)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] +[[package]] +name = "docstring-parser" +version = "0.16" +description = "Parse Python docstrings in reST, Google and Numpydoc format" +optional = false +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 = "duckduckgo-search" +version = "8.1.1" +description = "Search for words, documents, images, news, maps and text translation using the DuckDuckGo.com search engine." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "duckduckgo_search-8.1.1-py3-none-any.whl", hash = "sha256:f48adbb06626ee05918f7e0cef3a45639e9939805c4fc179e68c48a12f1b5062"}, + {file = "duckduckgo_search-8.1.1.tar.gz", hash = "sha256:9da91c9eb26a17e016ea1da26235d40404b46b0565ea86d75a9f78cc9441f935"}, +] + +[package.dependencies] +click = ">=8.1.8" +lxml = ">=5.3.0" +primp = ">=0.15.0" + +[package.extras] +dev = ["mypy (>=1.14.1)", "pytest (>=8.3.4)", "pytest-dependency (>=0.6.0)", "ruff (>=0.9.2)"] + [[package]] name = "email-validator" version = "2.2.0" @@ -393,6 +683,55 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.14.0,<2.15.0" pyflakes = ">=3.4.0,<3.5.0" +[[package]] +name = "frozendict" +version = "2.4.6" +description = "A simple immutable dictionary" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "frozendict-2.4.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3a05c0a50cab96b4bb0ea25aa752efbfceed5ccb24c007612bc63e51299336f"}, + {file = "frozendict-2.4.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5b94d5b07c00986f9e37a38dd83c13f5fe3bf3f1ccc8e88edea8fe15d6cd88c"}, + {file = "frozendict-2.4.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c789fd70879ccb6289a603cdebdc4953e7e5dea047d30c1b180529b28257b5"}, + {file = "frozendict-2.4.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da6a10164c8a50b34b9ab508a9420df38f4edf286b9ca7b7df8a91767baecb34"}, + {file = "frozendict-2.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9a8a43036754a941601635ea9c788ebd7a7efbed2becba01b54a887b41b175b9"}, + {file = "frozendict-2.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9905dcf7aa659e6a11b8051114c9fa76dfde3a6e50e6dc129d5aece75b449a2"}, + {file = "frozendict-2.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:323f1b674a2cc18f86ab81698e22aba8145d7a755e0ac2cccf142ee2db58620d"}, + {file = "frozendict-2.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:eabd21d8e5db0c58b60d26b4bb9839cac13132e88277e1376970172a85ee04b3"}, + {file = "frozendict-2.4.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eddabeb769fab1e122d3a6872982c78179b5bcc909fdc769f3cf1964f55a6d20"}, + {file = "frozendict-2.4.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:377a65be0a700188fc21e669c07de60f4f6d35fae8071c292b7df04776a1c27b"}, + {file = "frozendict-2.4.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce1e9217b85eec6ba9560d520d5089c82dbb15f977906eb345d81459723dd7e3"}, + {file = "frozendict-2.4.6-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:7291abacf51798d5ffe632771a69c14fb423ab98d63c4ccd1aa382619afe2f89"}, + {file = "frozendict-2.4.6-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e72fb86e48811957d66ffb3e95580af7b1af1e6fbd760ad63d7bd79b2c9a07f8"}, + {file = "frozendict-2.4.6-cp36-cp36m-win_amd64.whl", hash = "sha256:622301b1c29c4f9bba633667d592a3a2b093cb408ba3ce578b8901ace3931ef3"}, + {file = "frozendict-2.4.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a4e3737cb99ed03200cd303bdcd5514c9f34b29ee48f405c1184141bd68611c9"}, + {file = "frozendict-2.4.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49ffaf09241bc1417daa19362a2241a4aa435f758fd4375c39ce9790443a39cd"}, + {file = "frozendict-2.4.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d69418479bfb834ba75b0e764f058af46ceee3d655deb6a0dd0c0c1a5e82f09"}, + {file = "frozendict-2.4.6-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c131f10c4d3906866454c4e89b87a7e0027d533cce8f4652aa5255112c4d6677"}, + {file = "frozendict-2.4.6-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:fc67cbb3c96af7a798fab53d52589752c1673027e516b702ab355510ddf6bdff"}, + {file = "frozendict-2.4.6-cp37-cp37m-win_amd64.whl", hash = "sha256:7730f8ebe791d147a1586cbf6a42629351d4597773317002181b66a2da0d509e"}, + {file = "frozendict-2.4.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:807862e14b0e9665042458fde692c4431d660c4219b9bb240817f5b918182222"}, + {file = "frozendict-2.4.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9647c74efe3d845faa666d4853cfeabbaee403b53270cabfc635b321f770e6b8"}, + {file = "frozendict-2.4.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:665fad3f0f815aa41294e561d98dbedba4b483b3968e7e8cab7d728d64b96e33"}, + {file = "frozendict-2.4.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f42e6b75254ea2afe428ad6d095b62f95a7ae6d4f8272f0bd44a25dddd20f67"}, + {file = "frozendict-2.4.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:02331541611f3897f260900a1815b63389654951126e6e65545e529b63c08361"}, + {file = "frozendict-2.4.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:18d50a2598350b89189da9150058191f55057581e40533e470db46c942373acf"}, + {file = "frozendict-2.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:1b4a3f8f6dd51bee74a50995c39b5a606b612847862203dd5483b9cd91b0d36a"}, + {file = "frozendict-2.4.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a76cee5c4be2a5d1ff063188232fffcce05dde6fd5edd6afe7b75b247526490e"}, + {file = "frozendict-2.4.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba5ef7328706db857a2bdb2c2a17b4cd37c32a19c017cff1bb7eeebc86b0f411"}, + {file = "frozendict-2.4.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669237c571856be575eca28a69e92a3d18f8490511eff184937283dc6093bd67"}, + {file = "frozendict-2.4.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aaa11e7c472150efe65adbcd6c17ac0f586896096ab3963775e1c5c58ac0098"}, + {file = "frozendict-2.4.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b8f2829048f29fe115da4a60409be2130e69402e29029339663fac39c90e6e2b"}, + {file = "frozendict-2.4.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:94321e646cc39bebc66954a31edd1847d3a2a3483cf52ff051cd0996e7db07db"}, + {file = "frozendict-2.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:74b6b26c15dddfefddeb89813e455b00ebf78d0a3662b89506b4d55c6445a9f4"}, + {file = "frozendict-2.4.6-cp39-cp39-win_arm64.whl", hash = "sha256:7088102345d1606450bd1801a61139bbaa2cb0d805b9b692f8d81918ea835da6"}, + {file = "frozendict-2.4.6-py311-none-any.whl", hash = "sha256:d065db6a44db2e2375c23eac816f1a022feb2fa98cbb50df44a9e83700accbea"}, + {file = "frozendict-2.4.6-py312-none-any.whl", hash = "sha256:49344abe90fb75f0f9fdefe6d4ef6d4894e640fadab71f11009d52ad97f370b9"}, + {file = "frozendict-2.4.6-py313-none-any.whl", hash = "sha256:7134a2bb95d4a16556bb5f2b9736dceb6ea848fa5b6f3f6c2d6dba93b44b4757"}, + {file = "frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e"}, +] + [[package]] name = "fuzzywuzzy" version = "0.18.0" @@ -408,6 +747,63 @@ files = [ [package.extras] speedup = ["python-levenshtein (>=0.12)"] +[[package]] +name = "gitdb" +version = "4.0.12" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, + {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.44" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, + {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] + +[[package]] +name = "groq" +version = "0.30.0" +description = "The official Python library for the groq API" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "groq-0.30.0-py3-none-any.whl", hash = "sha256:6d9609a7778ba56432f45c1bac21b005f02c6c0aca9c1c094e65536f162c1e83"}, + {file = "groq-0.30.0.tar.gz", hash = "sha256:919466e48fcbebef08fed3f71debb0f96b0ea8d2ec77842c384aa843019f6e2c"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +typing-extensions = ">=4.10,<5" + +[package.extras] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.6)"] + [[package]] name = "h11" version = "0.16.0" @@ -597,6 +993,93 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jiter" +version = "0.10.0" +description = "Fast iterable JSON parser." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, + {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, + {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, + {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, + {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, + {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, + {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, + {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, + {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, + {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, + {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, + {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, + {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, + {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, + {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, + {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, + {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, + {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, + {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, + {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, + {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, + {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, + {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, + {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, + {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, + {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, + {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, + {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, + {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, + {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, + {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, + {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, + {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, + {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, + {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, + {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, + {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, + {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, + {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, + {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, + {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, +] + [[package]] name = "jsonschema" version = "4.24.0" @@ -741,6 +1224,116 @@ files = [ [package.dependencies] rapidfuzz = ">=3.9.0,<4.0.0" +[[package]] +name = "lxml" +version = "6.0.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "lxml-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35bc626eec405f745199200ccb5c6b36f202675d204aa29bb52e27ba2b71dea8"}, + {file = "lxml-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:246b40f8a4aec341cbbf52617cad8ab7c888d944bfe12a6abd2b1f6cfb6f6082"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2793a627e95d119e9f1e19720730472f5543a6d84c50ea33313ce328d870f2dd"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:46b9ed911f36bfeb6338e0b482e7fe7c27d362c52fde29f221fddbc9ee2227e7"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b4790b558bee331a933e08883c423f65bbcd07e278f91b2272489e31ab1e2b4"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2030956cf4886b10be9a0285c6802e078ec2391e1dd7ff3eb509c2c95a69b76"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d23854ecf381ab1facc8f353dcd9adeddef3652268ee75297c1164c987c11dc"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:43fe5af2d590bf4691531b1d9a2495d7aab2090547eaacd224a3afec95706d76"}, + {file = "lxml-6.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74e748012f8c19b47f7d6321ac929a9a94ee92ef12bc4298c47e8b7219b26541"}, + {file = "lxml-6.0.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:43cfbb7db02b30ad3926e8fceaef260ba2fb7df787e38fa2df890c1ca7966c3b"}, + {file = "lxml-6.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34190a1ec4f1e84af256495436b2d196529c3f2094f0af80202947567fdbf2e7"}, + {file = "lxml-6.0.0-cp310-cp310-win32.whl", hash = "sha256:5967fe415b1920a3877a4195e9a2b779249630ee49ece22021c690320ff07452"}, + {file = "lxml-6.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f3389924581d9a770c6caa4df4e74b606180869043b9073e2cec324bad6e306e"}, + {file = "lxml-6.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:522fe7abb41309e9543b0d9b8b434f2b630c5fdaf6482bee642b34c8c70079c8"}, + {file = "lxml-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ee56288d0df919e4aac43b539dd0e34bb55d6a12a6562038e8d6f3ed07f9e36"}, + {file = "lxml-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8dd6dd0e9c1992613ccda2bcb74fc9d49159dbe0f0ca4753f37527749885c25"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d7ae472f74afcc47320238b5dbfd363aba111a525943c8a34a1b657c6be934c3"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5592401cdf3dc682194727c1ddaa8aa0f3ddc57ca64fd03226a430b955eab6f6"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58ffd35bd5425c3c3b9692d078bf7ab851441434531a7e517c4984d5634cd65b"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f720a14aa102a38907c6d5030e3d66b3b680c3e6f6bc95473931ea3c00c59967"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a5e8d207311a0170aca0eb6b160af91adc29ec121832e4ac151a57743a1e1e"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2dd1cc3ea7e60bfb31ff32cafe07e24839df573a5e7c2d33304082a5019bcd58"}, + {file = "lxml-6.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cfcf84f1defed7e5798ef4f88aa25fcc52d279be731ce904789aa7ccfb7e8d2"}, + {file = "lxml-6.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a52a4704811e2623b0324a18d41ad4b9fabf43ce5ff99b14e40a520e2190c851"}, + {file = "lxml-6.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c16304bba98f48a28ae10e32a8e75c349dd742c45156f297e16eeb1ba9287a1f"}, + {file = "lxml-6.0.0-cp311-cp311-win32.whl", hash = "sha256:f8d19565ae3eb956d84da3ef367aa7def14a2735d05bd275cd54c0301f0d0d6c"}, + {file = "lxml-6.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b2d71cdefda9424adff9a3607ba5bbfc60ee972d73c21c7e3c19e71037574816"}, + {file = "lxml-6.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:8a2e76efbf8772add72d002d67a4c3d0958638696f541734304c7f28217a9cab"}, + {file = "lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108"}, + {file = "lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0"}, + {file = "lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a"}, + {file = "lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3"}, + {file = "lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb"}, + {file = "lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da"}, + {file = "lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef"}, + {file = "lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181"}, + {file = "lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e"}, + {file = "lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03"}, + {file = "lxml-6.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4eb114a0754fd00075c12648d991ec7a4357f9cb873042cc9a77bf3a7e30c9db"}, + {file = "lxml-6.0.0-cp38-cp38-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:7da298e1659e45d151b4028ad5c7974917e108afb48731f4ed785d02b6818994"}, + {file = "lxml-6.0.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bf61bc4345c1895221357af8f3e89f8c103d93156ef326532d35c707e2fb19d"}, + {file = "lxml-6.0.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63b634facdfbad421d4b61c90735688465d4ab3a8853ac22c76ccac2baf98d97"}, + {file = "lxml-6.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e380e85b93f148ad28ac15f8117e2fd8e5437aa7732d65e260134f83ce67911b"}, + {file = "lxml-6.0.0-cp38-cp38-win32.whl", hash = "sha256:185efc2fed89cdd97552585c624d3c908f0464090f4b91f7d92f8ed2f3b18f54"}, + {file = "lxml-6.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:f97487996a39cb18278ca33f7be98198f278d0bc3c5d0fd4d7b3d63646ca3c8a"}, + {file = "lxml-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85b14a4689d5cff426c12eefe750738648706ea2753b20c2f973b2a000d3d261"}, + {file = "lxml-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f64ccf593916e93b8d36ed55401bb7fe9c7d5de3180ce2e10b08f82a8f397316"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:b372d10d17a701b0945f67be58fae4664fd056b85e0ff0fbc1e6c951cdbc0512"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a674c0948789e9136d69065cc28009c1b1874c6ea340253db58be7622ce6398f"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:edf6e4c8fe14dfe316939711e3ece3f9a20760aabf686051b537a7562f4da91a"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:048a930eb4572829604982e39a0c7289ab5dc8abc7fc9f5aabd6fbc08c154e93"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0b5fa5eda84057a4f1bbb4bb77a8c28ff20ae7ce211588d698ae453e13c6281"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:c352fc8f36f7e9727db17adbf93f82499457b3d7e5511368569b4c5bd155a922"}, + {file = "lxml-6.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8db5dc617cb937ae17ff3403c3a70a7de9df4852a046f93e71edaec678f721d0"}, + {file = "lxml-6.0.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:2181e4b1d07dde53986023482673c0f1fba5178ef800f9ab95ad791e8bdded6a"}, + {file = "lxml-6.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b3c98d5b24c6095e89e03d65d5c574705be3d49c0d8ca10c17a8a4b5201b72f5"}, + {file = "lxml-6.0.0-cp39-cp39-win32.whl", hash = "sha256:04d67ceee6db4bcb92987ccb16e53bef6b42ced872509f333c04fb58a3315256"}, + {file = "lxml-6.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:e0b1520ef900e9ef62e392dd3d7ae4f5fa224d1dd62897a792cf353eb20b6cae"}, + {file = "lxml-6.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:e35e8aaaf3981489f42884b59726693de32dabfc438ac10ef4eb3409961fd402"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:dbdd7679a6f4f08152818043dbb39491d1af3332128b3752c3ec5cebc0011a72"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40442e2a4456e9910875ac12951476d36c0870dcb38a68719f8c4686609897c4"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db0efd6bae1c4730b9c863fc4f5f3c0fa3e8f05cae2c44ae141cb9dfc7d091dc"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ab542c91f5a47aaa58abdd8ea84b498e8e49fe4b883d67800017757a3eb78e8"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:013090383863b72c62a702d07678b658fa2567aa58d373d963cca245b017e065"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c86df1c9af35d903d2b52d22ea3e66db8058d21dc0f59842ca5deb0595921141"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4337e4aec93b7c011f7ee2e357b0d30562edd1955620fdd4aeab6aacd90d43c5"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ae74f7c762270196d2dda56f8dd7309411f08a4084ff2dfcc0b095a218df2e06"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:059c4cbf3973a621b62ea3132934ae737da2c132a788e6cfb9b08d63a0ef73f9"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f090a9bc0ce8da51a5632092f98a7e7f84bca26f33d161a98b57f7fb0004ca"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9da022c14baeec36edfcc8daf0e281e2f55b950249a455776f0d1adeeada4734"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a55da151d0b0c6ab176b4e761670ac0e2667817a1e0dadd04a01d0561a219349"}, + {file = "lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -890,6 +1483,18 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "multitasking" +version = "0.0.11" +description = "Non-blocking Python methods using decorators" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "multitasking-0.0.11-py3-none-any.whl", hash = "sha256:1e5b37a5f8fc1e6cfaafd1a82b6b1cc6d2ed20037d3b89c25a84f499bd7b3dd4"}, + {file = "multitasking-0.0.11.tar.gz", hash = "sha256:4d6bc3cc65f9b2dca72fb5a787850a88dae8f620c2b36ae9b55248e51bcd6026"}, +] + [[package]] name = "mypy-extensions" version = "1.1.0" @@ -963,6 +1568,34 @@ files = [ {file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"}, ] +[[package]] +name = "openai" +version = "1.95.1" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "openai-1.95.1-py3-none-any.whl", hash = "sha256:8bbdfeceef231b1ddfabbc232b179d79f8b849aab5a7da131178f8d10e0f162f"}, + {file = "openai-1.95.1.tar.gz", hash = "sha256:f089b605282e2a2b6776090b4b46563ac1da77f56402a222597d591e2dcc1086"}, +] + +[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] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.8)"] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +realtime = ["websockets (>=13,<16)"] +voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] + [[package]] name = "packaging" version = "25.0" @@ -1070,13 +1703,24 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "peewee" +version = "3.18.2" +description = "a little orm" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0"}, +] + [[package]] name = "platformdirs" version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -1103,6 +1747,47 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "primp" +version = "0.15.0" +description = "HTTP client that can impersonate web browsers, mimicking their headers and `TLS/JA3/JA4/HTTP2` fingerprints" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f"}, + {file = "primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299"}, + {file = "primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161"}, + {file = "primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080"}, + {file = "primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83"}, + {file = "primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260"}, + {file = "primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8"}, + {file = "primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32"}, + {file = "primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a"}, +] + +[package.extras] +dev = ["certifi", "mypy (>=1.14.1)", "pytest (>=8.1.1)", "pytest-asyncio (>=0.25.3)", "ruff (>=0.9.2)", "typing-extensions ; python_full_version < \"3.12.0\""] + +[[package]] +name = "protobuf" +version = "6.31.1" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, + {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, + {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, + {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, + {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, + {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, + {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, +] + [[package]] name = "pycodestyle" version = "2.14.0" @@ -1115,6 +1800,18 @@ files = [ {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +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.11.7" @@ -2021,6 +2718,18 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "smmap" +version = "5.0.2" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, + {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -2033,6 +2742,18 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "soupsieve" +version = "2.7" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, +] + [[package]] name = "sse-starlette" version = "2.4.1" @@ -2114,6 +2835,28 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "typer" version = "0.16.0" @@ -2466,7 +3209,37 @@ files = [ {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] +[[package]] +name = "yfinance" +version = "0.2.65" +description = "Download market data from Yahoo! Finance API" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "yfinance-0.2.65-py2.py3-none-any.whl", hash = "sha256:7be13abb0d80a17230bf798e9c6a324fa2bef0846684a6d4f7fa2abd21938963"}, + {file = "yfinance-0.2.65.tar.gz", hash = "sha256:3d465e58c49be9d61f9862829de3e00bef6b623809f32f4efb5197b62fc60485"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.11.1" +curl_cffi = ">=0.7" +frozendict = ">=2.3.4" +multitasking = ">=0.0.7" +numpy = ">=1.16.5" +pandas = ">=1.3.0" +peewee = ">=3.16.2" +platformdirs = ">=2.0.0" +protobuf = ">=3.19.0" +pytz = ">=2022.5" +requests = ">=2.31" +websockets = ">=13.0" + +[package.extras] +nospam = ["requests_cache (>=1.0)", "requests_ratelimiter (>=0.3.1)"] +repair = ["scipy (>=1.6.3)"] + [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "3cc60db5932709cb8e5275d7bd0b1a27f73dfa796a64c3ac1b9ed7f78fa3ed9a" +content-hash = "c20c627211ab5bb73a2ed59a1f9c998ae8174002edfc8b0ec33852533717aeb4" diff --git a/pyproject.toml b/pyproject.toml index 025267d..59a2531 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,13 @@ dependencies = [ "pandas", "fuzzywuzzy", "python-Levenshtein", - "fastapi-mcp (>=0.3.4,<0.4.0)" + "fastapi-mcp (>=0.3.4,<0.4.0)", + "agno (>=1.7.2,<2.0.0)", + "groq (>=0.30.0,<0.31.0)", + "yfinance (>=0.2.65,<0.3.0)", + "python-dotenv (>=1.1.1,<2.0.0)", + "openai (>=1.95.1,<2.0.0)", + "duckduckgo-search (>=8.1.1,<9.0.0)" ] From d5d75490ed5f5b15c7ba8c46d60b79fa25986fdc Mon Sep 17 00:00:00 2001 From: nikilok Date: Mon, 14 Jul 2025 22:27:42 +0100 Subject: [PATCH 2/8] fix linting issues --- app/main.py | 20 ++++++------ app/services/ai_investment.py | 60 ++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/app/main.py b/app/main.py index aa0e8d3..b460790 100644 --- a/app/main.py +++ b/app/main.py @@ -1,12 +1,12 @@ from typing import List from fastapi import FastAPI, Query -from fastapi_mcp import FastApiMCP from fastapi.responses import StreamingResponse +from fastapi_mcp import FastApiMCP from app.models import CompanySearchResult -from app.services.search import search_companies from app.services.ai_investment import investment_agent +from app.services.search import search_companies from app.utils import lessthan_x app = FastAPI() @@ -42,26 +42,28 @@ async def async_investment_stream(): """Async wrapper for the investment analysis generator""" try: # Use the investment_agent function which returns a proper generator - from app.services.ai_investment import investment_agent + # from app.services.ai_investment import investment_agent + analysis_generator = investment_agent(company_name) - + # Yield from the generator for chunk in analysis_generator: if chunk: # Only yield non-empty chunks yield str(chunk) - + except Exception as error: yield f"Error during analysis: {str(error)}\n" yield "Please try again or contact support.\n" - + # Return StreamingResponse return StreamingResponse( - async_investment_stream(), + async_investment_stream(), media_type="text/plain", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", - } + }, ) -mcp.setup_server() \ No newline at end of file + +mcp.setup_server() diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index a6b19dd..aaaa4da 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -1,22 +1,27 @@ import os -from dotenv import load_dotenv + from agno.agent import Agent from agno.models.xai import xAI from agno.tools.duckduckgo import DuckDuckGoTools from agno.tools.yfinance import YFinanceTools +from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Verify xAI API key is available -xai_api_key = os.getenv("OPENAI_API_KEY") +xai_api_key = os.getenv("OPENAI_API_KEY") if not xai_api_key: - raise ValueError("OPENAI_API_KEY environment variable is required. Please set it in your .env file.") + raise ValueError( + "OPENAI_API_KEY environment variable is required. Please set it in your .env file." + ) # Get model name from environment variables llm_model = os.getenv("XAI_MODEL") if not llm_model: - raise ValueError("XAI_MODEL environment variable is required. Please set it in your .env file.") + raise ValueError( + "XAI_MODEL environment variable is required. Please set it in your .env file." + ) print(f"Using LLM model: {llm_model}") # Agent 1: Market Research Agent - Uses web search to gather market news and trends @@ -29,10 +34,10 @@ "Always cite sources and provide summaries of key articles or reports", "Keep tool calls simple and avoid complex JSON structures", "Be precise with search queries", - "If tool calls fail, provide analysis based on general knowledge" + "If tool calls fail, provide analysis based on general knowledge", ], markdown=True, - show_tool_calls=False, + show_tool_calls=False, ) # Agent 2: Financial Data Agent - Retrieves quantitative financial data using YFinance @@ -52,18 +57,21 @@ "Present financial data in tables for clarity and include historical trends where relevant", "Use precise stock symbols and avoid ambiguous queries", "Keep tool calls simple and properly formatted", - "If tool calls fail, provide analysis based on general knowledge" + "If tool calls fail, provide analysis based on general knowledge", ], markdown=True, - show_tool_calls=False, + show_tool_calls=False, ) -# Agent 3: Investment Analysis Agent - Analyzes data from other agents to provide insights and recommendations +# Agent 3: Investment Analysis Agent - Analyzes data from other +# agents to provide insights and recommendations investment_analysis_agent = Agent( name="Investment Analysis Agent", - role="Analyze market research and financial data to provide investment insights, risks, and recommendations", + role="Analyze market research and financial data to provide investment insights," + " risks, and recommendations", model=xAI(id=llm_model), - instructions="Synthesize information from team members, evaluate risks, and suggest buy/sell/hold recommendations with reasoning", + instructions="Synthesize information from team members, evaluate risks, and " + "suggest buy/sell/hold recommendations with reasoning", markdown=True, ) @@ -75,48 +83,53 @@ "Coordinate between agents to gather comprehensive data", "Ensure responses are well-structured with sections for research, data, and analysis", "Always include sources and use tables for data", - "Keep tool calls simple and avoid complex JSON structures" + "Keep tool calls simple and avoid complex JSON structures", ], markdown=True, show_tool_calls=False, ) + def investment_agent(company_name: str): """ Investment analysis using multi-agent approach. Returns a generator for streaming responses. """ + def generate_streaming_analysis(): """Generate streaming analysis using the investment team""" try: yield f"🔍 Starting comprehensive investment analysis for {company_name}...\n\n" - + # Use the run method to get the complete response response = investment_team.run( - f"Provide a detailed investment analysis for {company_name} stock, including market outlook, financial performance, and recommendations." + f"Provide a detailed investment analysis for {company_name} stock, including market outlook, financial performance, and recommendations." # noqa: E501 ) - + # Extract and stream the content - if hasattr(response, 'content') and response.content: + if hasattr(response, "content") and response.content: content = response.content # Stream in chunks for better user experience chunk_size = 150 for i in range(0, len(content), chunk_size): - chunk = content[i:i+chunk_size] + chunk = content[i: i + chunk_size] yield chunk else: yield f"Analysis completed for {company_name}" - + except Exception as e: yield f"❌ Error during analysis: {str(e)}\n" yield "🔄 Attempting simplified analysis...\n" - + try: # Fallback to simple analysis simplified_response = investment_analysis_agent.run( - f"Provide a basic investment analysis for {company_name} based on general knowledge." + f"Provide a basic investment analysis for {company_name} based on general knowledge." # noqa: E501 ) - if hasattr(simplified_response, 'content') and simplified_response.content: + if ( + hasattr(simplified_response, "content") + and simplified_response.content + ): yield simplified_response.content else: yield f"Basic analysis completed for {company_name}" @@ -125,6 +138,7 @@ def generate_streaming_analysis(): return generate_streaming_analysis() + # To test the code through the command line instead of FastAPI if __name__ == "__main__": import sys @@ -136,7 +150,9 @@ def generate_streaming_analysis(): print("Example: python ai_investment.py TSLA") sys.exit(1) - company_name = " ".join(sys.argv[1:]) # Join all arguments in case company name has spaces + company_name = " ".join( + sys.argv[1:] + ) # Join all arguments in case company name has spaces print(f"Starting investment analysis for: {company_name}") print("=" * 50) print("Note: Using simplified approach to avoid JSON parsing issues") From c242b0b4b951640c8ff37d0c3e3725a8acad3828 Mon Sep 17 00:00:00 2001 From: nikilok Date: Mon, 14 Jul 2025 22:32:38 +0100 Subject: [PATCH 3/8] fix a clash between formatter and linter --- app/services/ai_investment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index aaaa4da..1d0fbc1 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -112,7 +112,7 @@ def generate_streaming_analysis(): # Stream in chunks for better user experience chunk_size = 150 for i in range(0, len(content), chunk_size): - chunk = content[i: i + chunk_size] + chunk = content[i : i + chunk_size] # noqa : E203 yield chunk else: yield f"Analysis completed for {company_name}" From 6ff37221f14971588e489f2ff05f464a58211cfa Mon Sep 17 00:00:00 2001 From: nikilok Date: Tue, 15 Jul 2025 10:58:20 +0100 Subject: [PATCH 4/8] optimize the cost of running 4 agents, by changing model size for each task It did not make sense to use one model for 4 agents. We use the smaller models for the web search, the medium one for fetching financial info from yahoo, and the Grok 4 model for reasoning. The coordinator also uses a medium sized model as it's not doing the thinking. --- .env.example | 6 +++-- app/main.py | 9 +++---- app/services/ai_investment.py | 49 ++++++++++++++++++----------------- app/utils.py | 27 +++++++++++++++++++ 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/.env.example b/.env.example index 3ad2575..869ee59 100644 --- a/.env.example +++ b/.env.example @@ -3,5 +3,7 @@ OPENAI_API_KEY=your_xai_api_key_here # xAI Model Configuration -XAI_MODEL=grok-3-latest - +XAI_MODEL_S=grok-3-mini +XAI_MODEL_M=grok-3-fast +XAI_MODEL_L=grok-3-latest +XAI_MODEL_XL=grok-4-0709 diff --git a/app/main.py b/app/main.py index b460790..771d619 100644 --- a/app/main.py +++ b/app/main.py @@ -29,14 +29,11 @@ def search_company( @app.get("/investment-analysis") async def get_investment_analysis( - company_name: str = Query(..., description="Company name to analyze for investment") + ticker_symbol: str = Query(..., description="Stock ticker symbol to analyze for investment") ): """ - Stream AI-powered investment analysis for a given company. + Stream AI-powered investment analysis for a given stock ticker. """ - # Validate input length (similar to @lessthan_x decorator) - if len(company_name) < 3: - return {"error": "company_name must be at least 3 characters long."} async def async_investment_stream(): """Async wrapper for the investment analysis generator""" @@ -44,7 +41,7 @@ async def async_investment_stream(): # Use the investment_agent function which returns a proper generator # from app.services.ai_investment import investment_agent - analysis_generator = investment_agent(company_name) + analysis_generator = investment_agent(ticker_symbol) # Yield from the generator for chunk in analysis_generator: diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index 1d0fbc1..0383d89 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -5,30 +5,25 @@ from agno.tools.duckduckgo import DuckDuckGoTools from agno.tools.yfinance import YFinanceTools from dotenv import load_dotenv +from app.utils import validate_env_variables # Load environment variables from .env file load_dotenv() -# Verify xAI API key is available -xai_api_key = os.getenv("OPENAI_API_KEY") -if not xai_api_key: - raise ValueError( - "OPENAI_API_KEY environment variable is required. Please set it in your .env file." - ) - -# Get model name from environment variables -llm_model = os.getenv("XAI_MODEL") -if not llm_model: - raise ValueError( - "XAI_MODEL environment variable is required. Please set it in your .env file." - ) -print(f"Using LLM model: {llm_model}") +# Validate all required environment variables +_, xai_model_s, xai_model_m, xai_model_l, xai_model_xl = validate_env_variables([ + "OPENAI_API_KEY", + "XAI_MODEL_S", + "XAI_MODEL_M", + "XAI_MODEL_L", + "XAI_MODEL_XL" +]) # Agent 1: Market Research Agent - Uses web search to gather market news and trends market_research_agent = Agent( name="Market Research Agent", role="Gather latest market news, trends, and qualitative information from the web", - model=xAI(id=llm_model), + model=xAI(id=xai_model_s), tools=[DuckDuckGoTools()], instructions=[ "Always cite sources and provide summaries of key articles or reports", @@ -44,7 +39,7 @@ financial_data_agent = Agent( name="Financial Data Agent", role="Fetch stock prices, analyst recommendations, company info, and financial metrics", - model=xAI(id=llm_model), + model=xAI(id=xai_model_m), tools=[ YFinanceTools( stock_price=True, @@ -69,7 +64,7 @@ name="Investment Analysis Agent", role="Analyze market research and financial data to provide investment insights," " risks, and recommendations", - model=xAI(id=llm_model), + model=xAI(id=xai_model_xl), instructions="Synthesize information from team members, evaluate risks, and " "suggest buy/sell/hold recommendations with reasoning", markdown=True, @@ -78,7 +73,7 @@ # Create the main Investment Team Agent that coordinates the specialized agents investment_team = Agent( team=[market_research_agent, financial_data_agent, investment_analysis_agent], - model=xAI(id=llm_model), + model=xAI(id=xai_model_l), instructions=[ "Coordinate between agents to gather comprehensive data", "Ensure responses are well-structured with sections for research, data, and analysis", @@ -90,7 +85,7 @@ ) -def investment_agent(company_name: str): +def investment_agent(ticker_symbol: str): """ Investment analysis using multi-agent approach. Returns a generator for streaming responses. @@ -99,11 +94,11 @@ def investment_agent(company_name: str): def generate_streaming_analysis(): """Generate streaming analysis using the investment team""" try: - yield f"🔍 Starting comprehensive investment analysis for {company_name}...\n\n" + yield f"🔍 Starting comprehensive investment analysis for {ticker_symbol}...\n\n" # Use the run method to get the complete response response = investment_team.run( - f"Provide a detailed investment analysis for {company_name} stock, including market outlook, financial performance, and recommendations." # noqa: E501 + f"Provide a detailed investment analysis for {ticker_symbol} stock, including market outlook, financial performance, and recommendations." # noqa: E501 ) # Extract and stream the content @@ -115,7 +110,7 @@ def generate_streaming_analysis(): chunk = content[i : i + chunk_size] # noqa : E203 yield chunk else: - yield f"Analysis completed for {company_name}" + yield f"Analysis completed for {ticker_symbol}" except Exception as e: yield f"❌ Error during analysis: {str(e)}\n" @@ -124,7 +119,7 @@ def generate_streaming_analysis(): try: # Fallback to simple analysis simplified_response = investment_analysis_agent.run( - f"Provide a basic investment analysis for {company_name} based on general knowledge." # noqa: E501 + f"Provide a basic investment analysis for {ticker_symbol} based on general knowledge." # noqa: E501 ) if ( hasattr(simplified_response, "content") @@ -132,7 +127,7 @@ def generate_streaming_analysis(): ): yield simplified_response.content else: - yield f"Basic analysis completed for {company_name}" + yield f"Basic analysis completed for {ticker_symbol}" except Exception as fallback_error: yield f"Unable to complete analysis: {fallback_error}\n" @@ -142,6 +137,12 @@ def generate_streaming_analysis(): # To test the code through the command line instead of FastAPI if __name__ == "__main__": import sys + import os + + # Add the project root to Python path for imports + project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + sys.path.insert(0, project_root) + import sys # Check if company name is provided as command line argument if len(sys.argv) < 2: diff --git a/app/utils.py b/app/utils.py index 295e3f8..d085580 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,9 +1,36 @@ +import os import inspect from functools import wraps +from typing import List, Tuple from fastapi import HTTPException +def validate_env_variables(env_var_names: List[str]) -> Tuple[str, ...]: + """ + Validate that multiple environment variables are set and return their values. + + Args: + env_var_names (List[str]): List of environment variable names to check + + Returns: + Tuple[str, ...]: Tuple of environment variable values in the same order as input + + Raises: + ValueError: If any environment variable is not set + """ + values = [] + for var_name in env_var_names: + value = os.getenv(var_name) + if not value: + raise ValueError( + f"{var_name} environment variable is required. Please set it in your .env file." # noqa: E501 + ) + values.append(value) + + return tuple(values) + + def lessthan_x(x: int, arg_name=None, message="Input is too short."): """ Decorator factory to validate the minimum length of a string argument for FastAPI endpoints. From d13f8072f7fad2339f1bb25eab2ea6c220370b49 Mon Sep 17 00:00:00 2001 From: nikilok Date: Tue, 15 Jul 2025 11:13:03 +0100 Subject: [PATCH 5/8] format, sort and lint --- app/main.py | 4 +++- app/services/ai_investment.py | 18 ++++++++---------- app/utils.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/main.py b/app/main.py index 771d619..b582aef 100644 --- a/app/main.py +++ b/app/main.py @@ -29,7 +29,9 @@ def search_company( @app.get("/investment-analysis") async def get_investment_analysis( - ticker_symbol: str = Query(..., description="Stock ticker symbol to analyze for investment") + ticker_symbol: str = Query( + ..., description="Stock ticker symbol to analyze for investment" + ) ): """ Stream AI-powered investment analysis for a given stock ticker. diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index 0383d89..d3330ef 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -5,19 +5,16 @@ from agno.tools.duckduckgo import DuckDuckGoTools from agno.tools.yfinance import YFinanceTools from dotenv import load_dotenv + from app.utils import validate_env_variables # Load environment variables from .env file load_dotenv() # Validate all required environment variables -_, xai_model_s, xai_model_m, xai_model_l, xai_model_xl = validate_env_variables([ - "OPENAI_API_KEY", - "XAI_MODEL_S", - "XAI_MODEL_M", - "XAI_MODEL_L", - "XAI_MODEL_XL" -]) +_, xai_model_s, xai_model_m, xai_model_l, xai_model_xl = validate_env_variables( + ["OPENAI_API_KEY", "XAI_MODEL_S", "XAI_MODEL_M", "XAI_MODEL_L", "XAI_MODEL_XL"] +) # Agent 1: Market Research Agent - Uses web search to gather market news and trends market_research_agent = Agent( @@ -137,10 +134,11 @@ def generate_streaming_analysis(): # To test the code through the command line instead of FastAPI if __name__ == "__main__": import sys - import os - + # Add the project root to Python path for imports - project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + project_root = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + ) sys.path.insert(0, project_root) import sys diff --git a/app/utils.py b/app/utils.py index d085580..943ba6d 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,5 +1,5 @@ -import os import inspect +import os from functools import wraps from typing import List, Tuple From c37f4650016a45a50ab421603e6f7e2ce3b7356d Mon Sep 17 00:00:00 2001 From: nikilok Date: Tue, 15 Jul 2025 12:34:09 +0100 Subject: [PATCH 6/8] fixing streaming initial part --- app/main.py | 20 +++++++++----------- app/services/ai_investment.py | 17 +++++++++-------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/main.py b/app/main.py index b582aef..49fa4e3 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,5 @@ from typing import List - +import asyncio from fastapi import FastAPI, Query from fastapi.responses import StreamingResponse from fastapi_mcp import FastApiMCP @@ -28,6 +28,7 @@ def search_company( @app.get("/investment-analysis") + async def get_investment_analysis( ticker_symbol: str = Query( ..., description="Stock ticker symbol to analyze for investment" @@ -37,24 +38,21 @@ async def get_investment_analysis( Stream AI-powered investment analysis for a given stock ticker. """ - async def async_investment_stream(): - """Async wrapper for the investment analysis generator""" + def investment_stream(): try: - # Use the investment_agent function which returns a proper generator - # from app.services.ai_investment import investment_agent - analysis_generator = investment_agent(ticker_symbol) - - # Yield from the generator for chunk in analysis_generator: - if chunk: # Only yield non-empty chunks + if chunk: yield str(chunk) - except Exception as error: yield f"Error during analysis: {str(error)}\n" yield "Please try again or contact support.\n" - # Return StreamingResponse + async def async_investment_stream(): + for chunk in investment_stream(): + await asyncio.sleep(0) # Yield control to event loop + yield chunk + return StreamingResponse( async_investment_stream(), media_type="text/plain", diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index d3330ef..eb78ca8 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -29,7 +29,7 @@ "If tool calls fail, provide analysis based on general knowledge", ], markdown=True, - show_tool_calls=False, + show_tool_calls=True, ) # Agent 2: Financial Data Agent - Retrieves quantitative financial data using YFinance @@ -52,7 +52,7 @@ "If tool calls fail, provide analysis based on general knowledge", ], markdown=True, - show_tool_calls=False, + show_tool_calls=True, ) # Agent 3: Investment Analysis Agent - Analyzes data from other @@ -65,6 +65,7 @@ instructions="Synthesize information from team members, evaluate risks, and " "suggest buy/sell/hold recommendations with reasoning", markdown=True, + show_tool_calls=True ) # Create the main Investment Team Agent that coordinates the specialized agents @@ -144,22 +145,22 @@ def generate_streaming_analysis(): # Check if company name is provided as command line argument if len(sys.argv) < 2: - print("Usage: python ai_investment.py ") - print("Example: python ai_investment.py Tesla") - print("Example: python ai_investment.py TSLA") + print("Usage: python -m app.services.ai_investment ") + print("Example: python -m app.services.ai_investment NVDA") + print("Example: python -m app.services.ai_investment TSLA") sys.exit(1) - company_name = " ".join( + stock_ticker = " ".join( sys.argv[1:] ) # Join all arguments in case company name has spaces - print(f"Starting investment analysis for: {company_name}") + print(f"Starting investment analysis for: {stock_ticker}") print("=" * 50) print("Note: Using simplified approach to avoid JSON parsing issues") print("=" * 50) try: # Run the investment analysis - analysis_gen = investment_agent(company_name) + analysis_gen = investment_agent(stock_ticker) for chunk in analysis_gen: print(chunk, end="") print("\n" + "=" * 50) From cb5e9dc34120e3fa26ff2fa03e7636b074d4ad7f Mon Sep 17 00:00:00 2001 From: nikilok Date: Mon, 21 Jul 2025 11:25:38 +0100 Subject: [PATCH 7/8] fix lint issues --- app/main.py | 4 ++-- app/services/ai_investment.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 49fa4e3..0509c8e 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,6 @@ -from typing import List import asyncio +from typing import List + from fastapi import FastAPI, Query from fastapi.responses import StreamingResponse from fastapi_mcp import FastApiMCP @@ -28,7 +29,6 @@ def search_company( @app.get("/investment-analysis") - async def get_investment_analysis( ticker_symbol: str = Query( ..., description="Stock ticker symbol to analyze for investment" diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index eb78ca8..1f79d18 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -65,7 +65,7 @@ instructions="Synthesize information from team members, evaluate risks, and " "suggest buy/sell/hold recommendations with reasoning", markdown=True, - show_tool_calls=True + show_tool_calls=True, ) # Create the main Investment Team Agent that coordinates the specialized agents From d31a04da29bf1c525dc3aa779f8af82744ad838c Mon Sep 17 00:00:00 2001 From: nikilok Date: Mon, 21 Jul 2025 12:02:36 +0100 Subject: [PATCH 8/8] avoid reimporting --- app/services/ai_investment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/ai_investment.py b/app/services/ai_investment.py index 1f79d18..4d8d931 100644 --- a/app/services/ai_investment.py +++ b/app/services/ai_investment.py @@ -141,7 +141,6 @@ def generate_streaming_analysis(): os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) sys.path.insert(0, project_root) - import sys # Check if company name is provided as command line argument if len(sys.argv) < 2: