finta-aurora-mcp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ from langchain_core.tools import StructuredTool, ToolException
2
+ import requests
3
+ from pydantic.v1 import BaseModel, Field, ValidationError
4
+ from typing import Optional
5
+ import os
6
+ from langchain_core.runnables.config import RunnableConfig
7
+
8
+ API_BASE_URL = os.environ.get('API_BASE_URL', '')
9
+ # API_BASE_URL = "https://us-central1-equity-token-1.cloudfunctions.net/"
10
+
11
+ class getInvestorInfoInput(BaseModel):
12
+ investorId: Optional[str] = Field(default="", description="Investor's ID. Use this if you have the ID.")
13
+ investorName: Optional[str] = Field(default="", description="Investor's name (e.g., 'David Rose', 'John Smith'). Use this to search by name if you don't have the ID.")
14
+ investorEmail: Optional[str] = Field(default="", description="Investor's email address. Use this to search by email if you don't have the ID or name.")
15
+
16
+ def call_get_investor_info(config: RunnableConfig, investorId: str = "", investorName: str = "", investorEmail: str = ""):
17
+ """Get investor information by ID, name, or email. This is for READ-ONLY queries to retrieve investor data."""
18
+ org_info = config.get('configurable', {}).get("org_info", {})
19
+ handle = org_info.get("handle")
20
+
21
+ if not handle:
22
+ raise ToolException("Organization handle not found in config")
23
+
24
+ # If ID provided, use direct lookup
25
+ if investorId:
26
+ url = API_BASE_URL + 'fintaAI/tools/get-investor-info'
27
+ params = {
28
+ "investorId": investorId,
29
+ "organizationHandle": handle
30
+ }
31
+
32
+ try:
33
+ response = requests.get(url, params=params, timeout=10)
34
+ response.raise_for_status()
35
+ return response.json()
36
+ except requests.exceptions.RequestException as e:
37
+ raise ToolException(f"Failed to get investor info: {str(e)}")
38
+
39
+ # If name or email provided, search first
40
+ elif investorName or investorEmail:
41
+ search_url = API_BASE_URL + "fintaAI/tools/search-investor"
42
+ search_params = {
43
+ "organizationHandle": handle,
44
+ }
45
+
46
+ if investorEmail:
47
+ search_params["email"] = investorEmail
48
+ elif investorName:
49
+ search_params["name"] = investorName
50
+
51
+ try:
52
+ search_response = requests.get(search_url, params=search_params, timeout=10)
53
+ search_response.raise_for_status()
54
+ search_result = search_response.json()
55
+ except requests.exceptions.RequestException as e:
56
+ raise ToolException(f"Failed to search for investor: {str(e)}")
57
+
58
+ if not search_result.get("investors") or len(search_result["investors"]) == 0:
59
+ raise ToolException(f"No investor found with {'email: ' + investorEmail if investorEmail else 'name: ' + investorName}")
60
+
61
+ if len(search_result["investors"]) > 1:
62
+ # Multiple matches - return info about all of them
63
+ investor_list = []
64
+ for inv in search_result["investors"][:5]:
65
+ inv_data = inv.get('investor', {})
66
+ investor_list.append({
67
+ 'id': inv['id'],
68
+ 'name': f"{inv_data.get('firstName', '')} {inv_data.get('lastName', '')}".strip(),
69
+ 'email': inv_data.get('email', ''),
70
+ 'status': inv.get('status', ''),
71
+ 'organizationName': inv_data.get('organizationName', ''),
72
+ })
73
+
74
+ return {
75
+ 'investorInfo': {
76
+ 'multipleMatches': True,
77
+ 'count': len(search_result["investors"]),
78
+ 'investors': investor_list,
79
+ 'message': f"Found {len(search_result['investors'])} investors with the same name. Here are the details:"
80
+ }
81
+ }
82
+
83
+ # Found exactly one match - get full info using the ID
84
+ matched_id = search_result["investors"][0]["id"]
85
+ url = API_BASE_URL + 'fintaAI/tools/get-investor-info'
86
+ params = {
87
+ "investorId": matched_id,
88
+ "organizationHandle": handle
89
+ }
90
+
91
+ try:
92
+ response = requests.get(url, params=params, timeout=10)
93
+ response.raise_for_status()
94
+ return response.json()
95
+ except requests.exceptions.RequestException as e:
96
+ raise ToolException(f"Failed to get investor info: {str(e)}")
97
+
98
+ else:
99
+ raise ToolException("Either investorId, investorName, or investorEmail must be provided")
100
+
101
+ get_investor_info_tool= StructuredTool.from_function(
102
+ func=call_get_investor_info,
103
+ name="GetInvestorInfo",
104
+ description="Given an input of investor ID, name, or email, this function returns all relevant investor information. Can search by investorName or investorEmail if ID is not provided.",
105
+ args_schema=getInvestorInfoInput,
106
+ return_direct=True,
107
+ verbose=True
108
+ )
109
+
tools/imageTool.py ADDED
@@ -0,0 +1,77 @@
1
+ from openai import OpenAI
2
+ from langchain_core.tools import tool
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ if not os.getenv('GAE_APPLICATION'):
7
+ load_dotenv(dotenv_path='../../../../settings.ini')
8
+
9
+ OpenAI.api_key = os.environ.get('OPENAI_API_KEY', '')
10
+
11
+ client = OpenAI()
12
+
13
+ # Import additional model providers for image support
14
+ try:
15
+ from langchain_google_genai import ChatGoogleGenerativeAI
16
+ except ImportError:
17
+ ChatGoogleGenerativeAI = None
18
+
19
+ try:
20
+ from langchain_anthropic import ChatAnthropic
21
+ except ImportError:
22
+ ChatAnthropic = None
23
+
24
+ @tool
25
+ def image_tool(query, imageUrl, model_name="gpt-4o"):
26
+ """Takes in images stored in Firebase URLs and returns a summary of the image.
27
+
28
+ Args:
29
+ query: The text query about the image
30
+ imageUrl: URL of the image to analyze
31
+ model_name: Model to use for image analysis (default: gpt-4o)
32
+ Supported: gpt-4o, gpt-4o-mini, gemini-pro, gemini-1.5-pro,
33
+ claude-3-opus, claude-3-sonnet, claude-3-haiku,
34
+ claude-3.5-sonnet, claude-3.5-opus
35
+ """
36
+ # Use OpenAI for image analysis (best support for vision)
37
+ # Note: Gemini and Claude also support images, but OpenAI GPT-4o has the best vision capabilities
38
+ # For now, we'll use OpenAI, but you can extend this to support other providers
39
+
40
+ # Check if it's a Gemini model
41
+ if model_name.startswith('gemini'):
42
+ if ChatGoogleGenerativeAI is None:
43
+ raise ValueError("langchain-google-genai package is required for Gemini models")
44
+ # Gemini vision support would go here
45
+ # For now, fallback to OpenAI for image analysis
46
+ model_name = "gpt-4o"
47
+
48
+ # Check if it's a Claude model
49
+ if model_name.startswith('claude') or model_name == 'model-garden-anthropic':
50
+ if ChatAnthropic is None:
51
+ raise ValueError("langchain-anthropic package is required for Claude models")
52
+ # Claude vision support would go here
53
+ # For now, fallback to OpenAI for image analysis
54
+ model_name = "gpt-4o"
55
+
56
+ # Use OpenAI for image analysis (best vision support)
57
+ response = client.chat.completions.create(
58
+ model=model_name if model_name.startswith('gpt') else "gpt-4o",
59
+ messages=[
60
+ {
61
+ "role": "user",
62
+ "content": [
63
+ {"type": "text", "text": query},
64
+ *[
65
+ {
66
+ "type": "image_url",
67
+ "image_url": {
68
+ "url": imageUrl,
69
+ "detail": "high"
70
+ },
71
+ }
72
+ ]
73
+ ],
74
+ }
75
+ ], stream=False
76
+ )
77
+ return response.choices[0].message.content
@@ -0,0 +1,31 @@
1
+ from langchain_core.tools import tool
2
+ import requests
3
+ from PyPDF2 import PdfReader
4
+ import io
5
+
6
+ @tool
7
+ def pdf_scraper_tool(pdfUrl: str) -> str:
8
+ """"When given any url for a PDF, this tool downloads the PDF and returns the text"""
9
+ return parsePDF(pdfUrl)
10
+
11
+ def parsePDF(url):
12
+ pdf_stream = download_file(url)
13
+ if pdf_stream:
14
+ extracted_text = extract_text_from_pdf(pdf_stream)
15
+ return extracted_text
16
+ else:
17
+ return "Failed to download the file."
18
+
19
+ def extract_text_from_pdf(file_stream):
20
+ reader = PdfReader(file_stream)
21
+ text = ''
22
+ for page in reader.pages:
23
+ text += page.extract_text()
24
+ return text
25
+
26
+ def download_file(url):
27
+ response = requests.get(url)
28
+ if response.status_code == 200:
29
+ return io.BytesIO(response.content)
30
+ else:
31
+ return None
@@ -0,0 +1,35 @@
1
+ from openai import OpenAI
2
+ from langchain_openai import OpenAIEmbeddings
3
+ from langchain_core.tools import tool
4
+ from langchain_pinecone import PineconeVectorStore
5
+ from dotenv import load_dotenv
6
+ import os
7
+
8
+ client = OpenAI()
9
+ embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
10
+ embeddings.openai_api_key = os.environ.get('OPENAI_API_KEY', '')
11
+
12
+ OpenAI.api_key = os.environ.get('OPENAI_API_KEY', '')
13
+ PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY', '')
14
+ PINECONE_INDEX = os.environ.get('PINECONE_INDEX', '')
15
+
16
+ phase_mapping = {
17
+ "Prepare Deal Materials": "Phase 1: Prepare Deal Materials",
18
+ "Prospect Target Investors": "Phase 2: Prospect Target",
19
+ "Deal Outreach": "Phase 3: Deal Outreach"
20
+ }
21
+
22
+ # Pinecone tool - using langchain-pinecone package (LangChain v1)
23
+ index = PineconeVectorStore(
24
+ index_name=PINECONE_INDEX,
25
+ embedding=embeddings,
26
+ namespace="Finta Knowledge",
27
+ pinecone_api_key=PINECONE_API_KEY
28
+ )
29
+
30
+
31
+ @tool
32
+ def pinecone_finta_knowledge_tool(query):
33
+ "Answers any questions about the Finta website with how-to-guides for using Deal Rooms, Investor Tracker CRM, how to prospect leads, the Cap Table, and anything else regarding the Finta Website."
34
+ retriever = index.as_retriever(search_type="similarity")
35
+ return retriever.invoke(query)
@@ -0,0 +1,27 @@
1
+ from openai import OpenAI
2
+ from langchain_openai import OpenAIEmbeddings
3
+ from langchain_core.tools import tool
4
+ from langchain_pinecone import PineconeVectorStore
5
+ import os
6
+
7
+ client = OpenAI()
8
+ embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
9
+ embeddings.openai_api_key = os.environ.get('OPENAI_API_KEY', '')
10
+
11
+ OpenAI.api_key = os.environ.get('OPENAI_API_KEY', '')
12
+ PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY', '')
13
+ PINECONE_INDEX = os.environ.get('PINECONE_INDEX', '')
14
+
15
+ # Pinecone tool - using langchain-pinecone package (LangChain v1)
16
+ index = PineconeVectorStore(
17
+ index_name=PINECONE_INDEX,
18
+ embedding=embeddings,
19
+ namespace="Finta Resources",
20
+ pinecone_api_key=PINECONE_API_KEY
21
+ )
22
+
23
+ @tool
24
+ def pinecone_finta_resource_tool(query):
25
+ "Answers any questions about the Finta website with how-to-guides for using Deal Rooms, Investor Tracker CRM, how to prospect leads, the Cap Table, and anything else regarding the Finta Website."
26
+ retriever = index.as_retriever(search_type="similarity")
27
+ return retriever.invoke(query)
tools/serpAPITool.py ADDED
@@ -0,0 +1,16 @@
1
+ from langchain_core.tools import tool
2
+ from langchain_community.utilities import SerpAPIWrapper
3
+ import os
4
+
5
+ os.environ['SERPAPI_API_KEY'] = '4978c95bcf7b0f61006102929068f302898894019ccd11e0c318094223f40a0f'
6
+ #Serp API search
7
+ @tool
8
+ def serpAPITool(query: str) -> str:
9
+ """Searches Google for web responses"""
10
+ try:
11
+ search = SerpAPIWrapper()
12
+ return search.run(query)
13
+ except ValueError as e:
14
+ return f"No results found for query: {query}"
15
+ except Exception as e:
16
+ return f"Error searching Google: {str(e)}"
@@ -0,0 +1,30 @@
1
+ from langchain_core.tools import tool
2
+ import requests
3
+ import os
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ API_BASE_URL = os.environ.get('API_BASE_URL', '')
9
+
10
+ cloudFunctionOrigin = API_BASE_URL + 'fintaAI/tools/scrape-website';
11
+ #cloudFunctionOrigin ='https://us-central1-equity-token-stage.cloudfunctions.net/fintaAI/tools/scrape-website'
12
+ #cloudFunctionOrigin = "http://localhost:5001/equity-token-stage/us-central1/fintaAI/tools/scrape-website"
13
+
14
+ @tool
15
+ def scrape_website_from_link_tool(website: str) -> str:
16
+ """"When given any url, this tool searches the link and returns the text"""
17
+ url =cloudFunctionOrigin
18
+ data = {
19
+ "url": website,
20
+ }
21
+ try:
22
+ response = requests.post(url, json=data)
23
+ response.raise_for_status()
24
+ return response.json().get('content', 'No content found')
25
+ except requests.exceptions.RequestException as e:
26
+ logger.error(f"Exception occurred: {e}", exc_info=True)
27
+ return {"error": str(e)}
28
+ except ValueError as e:
29
+ logger.error(f"JSON decode error: {e}", exc_info=True)
30
+ return {"error": "Invalid JSON response"}