Spaces:
Running
Running
| from dataclasses import dataclass, field | |
| from langchain_core.tools import tool | |
| import httpx | |
| from ask_candid.base.config.rest import GOLDEN_ORG, LOI_WRITER | |
| class LetterOfInterest: | |
| opening: str = field(default="") | |
| org_desc: str = field(default="") | |
| need: str = field(default="") | |
| project: str = field(default="") | |
| request: str = field(default="") | |
| conclusion: str = field(default="") | |
| def letter(self): | |
| return f"""{self.opening} | |
| {self.org_desc} | |
| {self.need} | |
| {self.project} | |
| {self.request} | |
| {self.conclusion} | |
| """ | |
| def estimate_budget( | |
| nonprofit_id: int, | |
| funder_id: int, | |
| project_description: str, | |
| # ctx: Context | |
| ) -> str: | |
| """This is an optional tool for estimating project budgets. Some users will already know what their budget is, or | |
| know how much money they are seeking from a grant, in which case this tool should not be used. | |
| This tool also provides guidance on setting a budget, and ultimately the user needs to decided based on the output | |
| from this tool | |
| Parameters | |
| ---------- | |
| nonprofit_id : int | |
| The unique identifier of the requesting organization. This will need to be found from a search using inputs | |
| elicited from the requeter | |
| funder_id : int | |
| The unique identifier of the funding organization which may be awarding a grant to the requester. | |
| This will need to be found from a search using inputs elicited from the requeter, or from recommendations | |
| project_description : str | |
| Natural language text describing the project/program that the user is requesting funding for | |
| Returns | |
| ------- | |
| str | |
| Budget guidance, including context on the funder's ability to provide the budget in question | |
| """ | |
| recip_data = httpx.get( | |
| url=GOLDEN_ORG.endpoint("v1/organization"), | |
| params={"id": nonprofit_id}, | |
| headers={**GOLDEN_ORG.header}, # type: ignore | |
| timeout=30 | |
| ).json().get("document_data", {}).get("preferred_data", {}).get("data", {}) | |
| funder_data = httpx.get( | |
| url=GOLDEN_ORG.endpoint("v1/organization"), | |
| params={"id": funder_id}, | |
| headers={**GOLDEN_ORG.header}, # type: ignore | |
| timeout=30 | |
| ).json().get("document_data", {}).get("preferred_data", {}).get("data", {}) | |
| return httpx.post( | |
| url=LOI_WRITER.endpoint("budget"), | |
| json={ | |
| "recipient_candid_entity_id": nonprofit_id, | |
| "program_description": project_description, | |
| "recipient_data": recip_data, | |
| "funder_data": funder_data | |
| }, | |
| headers={**LOI_WRITER.header}, # type: ignore | |
| timeout=30 | |
| ).json().get("response", "No budget could be estimated") | |
| def draft_loi( | |
| nonprofit_id: int, | |
| funder_id: int, | |
| project_description: str, | |
| budget: int, | |
| ) -> str: | |
| """Generate a letter of interest/intent from a chain-of-thought prompt chain using Candid's golden data and any | |
| inputs specified by the user, and/or recommended content. | |
| The output of this tool is the actual letter draft, please do not make changes to it other than adding headers | |
| and/or footers. | |
| Parameters | |
| ---------- | |
| nonprofit_id : int | |
| The unique identifier of the requesting organization. This will need to be found from a search using inputs | |
| elicited from the requeter | |
| funder_id : int | |
| The unique identifier of the funding organization which may be awarding a grant to the requester. | |
| This will need to be found from a search using inputs elicited from the requeter, or from recommendations | |
| project_description : str | |
| Natural language text describing the project/program that the user is requesting funding for | |
| budget : int | |
| The dollar amount (in USD) that the user is requesting for funding. This should be specified by the user, | |
| prompt for this if needed. | |
| Returns | |
| ------- | |
| str | |
| Formatted letter of interest | |
| """ | |
| client = httpx.Client(headers={**LOI_WRITER.header}, timeout=30, base_url=LOI_WRITER.url) # type: ignore | |
| def _make_request(ept: str, payload: dict): | |
| # return httpx.get( | |
| # url=LOI_WRITER.endpoint(ept), | |
| # params=payload, | |
| # headers={**LOI_WRITER.header}, # type: ignore | |
| # timeout=30 | |
| # ).json().get("response", "") | |
| return client.get(url=LOI_WRITER.endpoint(ept), params=payload).json().get("response", "") | |
| data = _make_request( | |
| ept="organization/autofill", | |
| payload={"recipient_candid_entity_id": nonprofit_id, "funder_candid_entity_id": funder_id} | |
| ) | |
| recip: dict = data.get("recipient_data", {}) | |
| funder: dict = data.get("funder_data", {}) | |
| pair_history: str = data.get("funding_history_text", "") | |
| sections = ( | |
| ("opening", "writer/opening"), | |
| ("organization description", "writer/org"), | |
| ("need statement", "writer/need"), | |
| ("project description", "writer/project"), | |
| ("funding request", "writer/fund"), | |
| ("conclusion", "writer/conclusion") | |
| ) | |
| output = LetterOfInterest() | |
| for _, (section, endpoint) in enumerate(sections, start=1): | |
| if section == "opening": | |
| output.opening = _make_request( | |
| ept=endpoint, | |
| payload={ | |
| "funder_name": [ | |
| n["name"] for n in funder.get("org_data", {}).get("names", []) | |
| if n["name_type"] == "main" | |
| ][0], | |
| "recipient_name": [ | |
| n["name"] for n in recip.get("org_data", {}).get("names", []) | |
| if n["name_type"] == "main" | |
| ][0], | |
| "project_purpose": project_description, | |
| "amount": budget, | |
| "prior_contact": None, | |
| "connection": None | |
| } | |
| ) | |
| elif section == "organization description": | |
| output.org_desc = _make_request( | |
| ept=endpoint, | |
| payload={ | |
| "opening": output.opening, | |
| "history": pair_history, | |
| "recipient_mission_statement": recip.get("mission_statement_text", ""), | |
| "capacity": recip.get("capacity_text", ""), | |
| "path": None, | |
| "accomplishment": recip.get("data_text", "") | |
| } | |
| ) | |
| elif section == "need statement": | |
| output.need = httpx.get( | |
| url=GOLDEN_ORG.endpoint(endpoint), | |
| params={ | |
| "recipient_desc": output.org_desc, | |
| "funder_mission_statement": funder.get("mission_statement_text", ""), | |
| "target": None, | |
| "data": None, | |
| }, | |
| headers={**GOLDEN_ORG.header}, # type: ignore | |
| timeout=30 | |
| ).json().get("response", "") | |
| elif section == "project description": | |
| output.project = _make_request( | |
| ept=endpoint, | |
| payload={ | |
| "need": output.need, | |
| "projects": project_description, | |
| "desired_objectives": None, | |
| "major_activities": None, | |
| "key_staff": None, | |
| "stand_out": None, | |
| "success": None | |
| } | |
| ) | |
| elif section == "funding request": | |
| output.request = _make_request( | |
| ept=endpoint, | |
| payload={ | |
| "project_desc": output.project, | |
| "amount": budget, | |
| "funding_history": pair_history, | |
| "other_funding": None, | |
| } | |
| ) | |
| elif section == "conclusion": | |
| output.conclusion = _make_request( | |
| ept=endpoint, | |
| payload={ | |
| "funding_request": output.request, | |
| "project_desc": output.project, | |
| "follow_up": recip.get("contact_text", ""), | |
| } | |
| ) | |
| client.close() | |
| return output.letter | |