-
Notifications
You must be signed in to change notification settings - Fork 215
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
2,828 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# uAgent Holiday integrations Examples | ||
### Step 1: Prerequisites | ||
Before starting, you'll need the following: | ||
* Python (3.8+ is recommended) | ||
* Poetry (a packaging and dependency management tool for Python) | ||
|
||
### Step 2: Set up .env file | ||
To run the demo, you need API keys from: | ||
* RapidAPI | ||
* OpenAI | ||
* SerpAPI | ||
|
||
##### RapidAPI Key | ||
* Visit RapidAPI. | ||
* Sign up or log in. | ||
* Search for the Skyscanner API and subscribe. | ||
* Once subscribed, copy your X-RapidAPI-Key | ||
|
||
##### OpenAI API Key | ||
* Visit OpenAI. | ||
* Sign up or log in. | ||
* Navigate to the API section to obtain your API key. | ||
|
||
Note that if you’ve run out of OpenAI credits, you will not be able to get results for this example. | ||
|
||
##### SerpAPI Key | ||
|
||
* Visit SerpAPI. | ||
* Sign up or log in. | ||
* Your API key will be available on the dashboard. | ||
|
||
Once you have all three keys, create a .env file in the holiday-integrations/src directory. | ||
```bash | ||
export RAPIDAPI_API_KEY="{GET THE API KEY}" | ||
export OPENAI_API_KEY="{GET THE API KEY}" | ||
export SERPAPI_API_KEY="{GET THE API KEY}" | ||
``` | ||
To use the environment variables from .env and install the project: | ||
```bash | ||
cd src | ||
source .env | ||
poetry intall | ||
``` | ||
### Step 3: Run the main script | ||
To run the project and its agents: | ||
```bash | ||
poetry run python main.py | ||
``` | ||
You need to look for the following output in the logs: | ||
``` | ||
Adding top destinations agent to Bureau: {top_dest_address} | ||
``` | ||
Copy the {top_dest_address} value and paste it somewhere safe. You will need it in the next step. | ||
### Step 4: Set up the client script | ||
Now that we have set up the integrations, let’s run a client script that will showcase the ‘top destinations’. To do this, create a new Python file in the src folder called top_dest_client.py, and paste the following: | ||
```python | ||
from messages import TopDestinations, UAgentResponse | ||
from uagents import Agent, Context | ||
from uagents.setup import fund_agent_if_low | ||
import os | ||
| ||
TOP_DESTINATIONS_CLIENT_SEED = os.getenv("TOP_DESTINATIONS_CLIENT_SEED", "top_destinations_client really secret phrase :)") | ||
| ||
top_dest_client = Agent( | ||
name="top_destinations_client", | ||
port=8008, | ||
seed=TOP_DESTINATIONS_CLIENT_SEED, | ||
endpoint=["http://127.0.0.1:8008/submit"], | ||
) | ||
fund_agent_if_low(top_dest_client.wallet.address()) | ||
| ||
top_dest_request = TopDestinations(preferences="new york") | ||
| ||
@top_dest_client.on_interval(period=10.0) | ||
async def send_message(ctx: Context): | ||
await ctx.send("{top_dest_address}", top_dest_request) | ||
| ||
@top_dest_client.on_message(model=UAgentResponse) | ||
async def message_handler(ctx: Context, _: str, msg: UAgentResponse): | ||
ctx.logger.info(f"Received top destination options from: {msg.options}") | ||
| ||
if __name__ == "__main__": | ||
top_dest_client.run() | ||
``` | ||
Remember to replace the address in ctx.send with the value you received in the previous step. | ||
|
||
This code sends a request to get the top destinations (in this example, from New York). To do this, it sends a request to the ‘top destinations agent’ every 10 seconds and displays the options in the console. | ||
### Step 5: Run the client script | ||
Open a new terminal (let the previous one be as is), and navigate to the src folder to run the client. | ||
```bash | ||
cd src | ||
poetry run python top_dest_client.py | ||
``` | ||
Once you hit enter, a request will be sent to the top destinations agent every 10 seconds, and you will be able to see your results in the console! |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[tool.poetry] | ||
name = "holiday-integrations" | ||
version = "0.1.0" | ||
description = "A starter template for creating holiday integration examples" | ||
authors = ["Fetch.AI Limited"] | ||
|
||
[tool.poetry.dependencies] | ||
python = ">=3.10,<3.12" | ||
google-search-results = "^2.4.2" | ||
langchain = "^0.0.149" | ||
openai = "^0.27.4" | ||
uagents = "*" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" |
Empty file.
Empty file.
Empty file.
62 changes: 62 additions & 0 deletions
62
integrations/fetch-holiday/src/agents/activities/top_activities.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from langchain.chat_models import ChatOpenAI | ||
from langchain.agents import load_tools | ||
from langchain.agents import initialize_agent | ||
from langchain.output_parsers import CommaSeparatedListOutputParser | ||
from langchain.prompts import PromptTemplate | ||
from messages import UAgentResponse, UAgentResponseType, TopActivities, KeyValue | ||
from uagents import Agent, Context, Protocol | ||
from uagents.setup import fund_agent_if_low | ||
import os | ||
|
||
|
||
TOP_ACTIVITIES_SEED = os.getenv("TOP_ACTIVITIES_SEED", "top_activities really secret phrase :)") | ||
|
||
agent = Agent( | ||
name="top_activities", | ||
seed=TOP_ACTIVITIES_SEED | ||
) | ||
|
||
fund_agent_if_low(agent.wallet.address()) | ||
|
||
output_parser = CommaSeparatedListOutputParser() | ||
format_instructions = output_parser.get_format_instructions() | ||
prompt = PromptTemplate( | ||
template=""" | ||
You are an expert AI in suggesting travel, holiday activities based on the date and city specified in user input.\n | ||
The question that SerpAPI has to answer: What are the top 5 tourist activities in {city} on {date}?\n | ||
{preferred_activities_str}\n | ||
You should find tourist attractions and programs which are available exactly on the specified date.\n | ||
{format_instructions}""", | ||
input_variables=["city", "date", "preferred_activities_str"], | ||
partial_variables={"format_instructions": format_instructions} | ||
) | ||
|
||
llm = ChatOpenAI(temperature=0.1) | ||
tools = load_tools(["serpapi"], llm=llm) | ||
langchain_agent = initialize_agent(tools, llm, agent="chat-zero-shot-react-description", verbose=True) | ||
|
||
top_activities_protocol = Protocol("TopActivities") | ||
|
||
@top_activities_protocol.on_message(model=TopActivities, replies=UAgentResponse) | ||
async def get_top_activity(ctx: Context, sender: str, msg: TopActivities): | ||
ctx.logger.info(f"Received message from {sender}, session: {ctx.session}") | ||
|
||
preferred_activities_str = f"You should only offer programs and activities related to {msg.preferred_activities}" if msg.preferred_activities else "" | ||
_input = prompt.format(city=msg.city, date=msg.date, preferred_activities_str = preferred_activities_str) | ||
try: | ||
output = await langchain_agent.arun(_input) | ||
result = output_parser.parse(output) | ||
options = list(map(lambda x: KeyValue(key=x, value=x), result)) | ||
ctx.logger.info(f"Agent executed and got following result: {result}. Mapped to options: {options}") | ||
await ctx.send( | ||
sender, | ||
UAgentResponse( | ||
options=options, | ||
type=UAgentResponseType.FINAL_OPTIONS, | ||
) | ||
) | ||
except Exception as exc: | ||
ctx.logger.warn(exc) | ||
await ctx.send(sender, UAgentResponse(message=str(exc), type=UAgentResponseType.ERROR)) | ||
|
||
agent.include(top_activities_protocol) |
Empty file.
53 changes: 53 additions & 0 deletions
53
integrations/fetch-holiday/src/agents/destinations/top_destinations.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from uagents import Agent, Context, Protocol | ||
from messages import TopDestinations, UAgentResponse, UAgentResponseType, KeyValue | ||
from uagents.setup import fund_agent_if_low | ||
from utils.llm import get_llm | ||
import os | ||
|
||
|
||
TOP_DESTINATIONS_SEED = os.getenv("TOP_DESTINATIONS_SEED", "top_destinations really secret phrase :)") | ||
|
||
agent = Agent( | ||
name="top_destinations", | ||
seed=TOP_DESTINATIONS_SEED | ||
) | ||
|
||
fund_agent_if_low(agent.wallet.address()) | ||
|
||
llm = get_llm() | ||
top_destinations_protocol = Protocol("TopDestinations") | ||
|
||
@top_destinations_protocol.on_message(model=TopDestinations, replies=UAgentResponse) | ||
async def get_top_destinations(ctx: Context, sender: str, msg: TopDestinations): | ||
ctx.logger.info(f"Received message from {sender}, session: {ctx.session}") | ||
prompt = f"""You are an expert AI in suggesting travel, holiday destination based on some user input. | ||
User input might not be provided, in which case suggest popular destinations. | ||
If user input is present, then suggest destinations based on user input. | ||
The response should be a list of destinations, each destination should have information about why it is a good destination. | ||
After listing all the suggestions say END. Every destination should be separated by a new line. | ||
Example: | ||
User input: I want to go to a place with good weather and beaches. | ||
Response: | ||
1. Goa, India. Goa is a popular destination for tourists. It has good weather and beaches. | ||
2. Malé, Maldives. Maldives is a popular destination for tourists. It has good weather and beaches. | ||
END | ||
User preferences: {msg.preferences} | ||
""" | ||
try: | ||
response = await llm.complete("", prompt, "Response:", max_tokens=500, stop=["END"]) | ||
result = response.strip() | ||
result = result.split("\n") | ||
await ctx.send( | ||
sender, | ||
UAgentResponse( | ||
options=list(map(lambda x: KeyValue(key=x, value=x), result)), | ||
type=UAgentResponseType.FINAL_OPTIONS | ||
) | ||
) | ||
except Exception as exc: | ||
ctx.logger.warn(exc) | ||
await ctx.send(sender, UAgentResponse(message=str(exc), type=UAgentResponseType.ERROR)) | ||
|
||
agent.include(top_destinations_protocol) |
Empty file.
113 changes: 113 additions & 0 deletions
113
integrations/fetch-holiday/src/agents/flights/flights.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
from uagents import Agent, Context, Protocol | ||
from uagents.setup import fund_agent_if_low | ||
import requests | ||
from messages import Flights, UAgentResponse, UAgentResponseType, KeyValue | ||
import os | ||
import uuid | ||
|
||
|
||
FLIGHTS_SEED = os.getenv("FLIGHTS_SEED", "flights really secret phrase :)") | ||
|
||
agent = Agent( | ||
name="flights_adaptor", | ||
seed=FLIGHTS_SEED | ||
) | ||
|
||
fund_agent_if_low(agent.wallet.address()) | ||
|
||
RAPIDAPI_API_KEY = os.environ.get("RAPIDAPI_API_KEY", "") | ||
|
||
assert RAPIDAPI_API_KEY, "RAPIDAPI_API_KEY environment variable is missing from .env" | ||
|
||
SKY_SCANNER_URL = "https://skyscanner-api.p.rapidapi.com/v3e/flights/live/search/synced" | ||
headers = { | ||
"content-type": "application/json", | ||
"X-RapidAPI-Key": RAPIDAPI_API_KEY, | ||
"X-RapidAPI-Host": "skyscanner-api.p.rapidapi.com" | ||
} | ||
|
||
def skyscanner_format_data(data): | ||
r = data["content"]["results"] | ||
carriers = r["carriers"] | ||
itineraries = r["itineraries"] | ||
segments = r["segments"] | ||
sorted_itineraries = data["content"]["sortingOptions"]["cheapest"] | ||
results = [] | ||
for o in sorted_itineraries: | ||
_id = o["itineraryId"] | ||
it = itineraries[_id] | ||
for option in it["pricingOptions"]: | ||
price = option["price"]["amount"] | ||
if len(option["items"]) != 1: | ||
continue | ||
fares = option["items"][0]["fares"] | ||
if len(fares) != 1: | ||
continue | ||
segment_id = fares[0]["segmentId"] | ||
seg = segments[segment_id] | ||
carrier = carriers[seg["marketingCarrierId"]] | ||
duration = seg["durationInMinutes"] | ||
departure = seg["departureDateTime"] | ||
arrival = seg["arrivalDateTime"] | ||
results.append({ | ||
"price": price, | ||
"duration": duration, | ||
"departure": departure, | ||
"arrival": arrival, | ||
"carrier": carrier | ||
}) | ||
return results | ||
|
||
def skyscanner_top5(fares): | ||
return [fares[i] for i in range(len(fares)) if i < 5] | ||
|
||
flights_protocol = Protocol("Flights") | ||
|
||
@flights_protocol.on_message(model=Flights, replies={UAgentResponse}) | ||
async def flight_offers(ctx: Context, sender: str, msg: Flights): | ||
ctx.logger.info(f"Received message from {sender}, session: {ctx.session}") | ||
|
||
dept_year, dept_month, dept_day = msg.date.split("-") | ||
payload = { | ||
"query": { | ||
"market": "UK", | ||
"locale": "en-GB", | ||
"currency": "GBP", | ||
"queryLegs": [ | ||
{ | ||
"originPlaceId": {"iata": msg.from_}, | ||
"destinationPlaceId": {"iata": msg.to}, | ||
"date": { | ||
"year": int(dept_year), | ||
"month": int(dept_month), | ||
"day": int(dept_day) | ||
} | ||
} | ||
], | ||
"cabinClass": "CABIN_CLASS_ECONOMY", | ||
"adults": msg.persons | ||
} | ||
} | ||
print("CALLING SKYSCANNER, payload ", payload) | ||
try: | ||
response = requests.request("POST", SKY_SCANNER_URL, json=payload, headers=headers) | ||
if response.status_code != 200: | ||
print("SKYSCANNER STATUS CODE not 200: ", response.json()) | ||
await ctx.send(sender, UAgentResponse(message=response.text, type=UAgentResponseType.ERROR)) | ||
return | ||
formatted = skyscanner_format_data(response.json()) | ||
top5 = skyscanner_top5(formatted) | ||
print("SKYSCANNER TOP5 RESPONSE: ", top5) | ||
request_id = str(uuid.uuid4()) | ||
options = [] | ||
for idx, o in enumerate(top5): | ||
dep = f"{o['departure']['hour']:02}:{o['departure']['minute']:02}" | ||
arr = f"{o['arrival']['hour']:02}:{o['arrival']['minute']:02}" | ||
option = f"""{o["carrier"]["name"]} for £{o['price']}, {dep} - {arr}, flight time {o['duration']} min""" | ||
options.append(KeyValue(key=idx, value=option)) | ||
await ctx.send(sender, UAgentResponse(options=options, type=UAgentResponseType.SELECT_FROM_OPTIONS, request_id=request_id)) | ||
except Exception as exc: | ||
ctx.logger.error(exc) | ||
await ctx.send(sender, UAgentResponse(message=str(exc), type=UAgentResponseType.ERROR)) | ||
|
||
agent.include(flights_protocol) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from uagents import Bureau | ||
|
||
from agents.activities.top_activities import agent as top_activities_agent | ||
from agents.destinations.top_destinations import agent as top_destinations_agent | ||
from agents.flights.flights import agent as flights_agent | ||
|
||
|
||
if __name__ == "__main__": | ||
bureau = Bureau(endpoint="http://127.0.0.1:8000/submit", port=8000) | ||
print(f"Adding top activities agent to Bureau: {top_activities_agent.address}") | ||
bureau.add(top_activities_agent) | ||
print(f"Adding top destinations agent to Bureau: {top_destinations_agent.address}") | ||
bureau.add(top_destinations_agent) | ||
print(f"Adding flights agent to Bureau: {flights_agent.address}") | ||
bureau.add(flights_agent) | ||
bureau.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from .flight import Flights | ||
from .top_destinations import TopDestinations | ||
from .top_activities import TopActivities | ||
from .general import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from uagents import Model | ||
from pydantic import Field | ||
from typing import Optional | ||
|
||
class Flights(Model): | ||
from_: str = Field(alias="from", description="This field is the airport IATA code for of the airport from where the user wants to fly from. This should be airport IATA code. IATA airport code is a three-character alphanumeric geocode.") | ||
to: str = Field(description="This field is the airport IATA code of the destination airport! This should be airport IATA code. IATA airport code is a three-character alphanumeric geocode.") | ||
trip: Optional[str] = Field(description="This can be oneway or return") | ||
date: str = Field(description="Contains the date of flying out.") | ||
back_date: Optional[str] = Field(description="Optional field only for return flight. This is the date when the user wants to fly back") | ||
route: Optional[int] = Field(description="Selects the maximum number of stops, 0 means direct flight, 1 means with maximum 1 stop.") | ||
persons: int = Field(description="Describes how many persons are going to fly.") | ||
|
||
class Config: | ||
allow_population_by_field_name = True |
Oops, something went wrong.