Aasher commited on
Commit
1971b24
·
1 Parent(s): 71e0aa1

feat(db): Add Postgres db through Neon

Browse files

refactor: Update the prompt to use XML tags and update the instructions
fix(products): Update fetch_products tool to return a proper json

Files changed (6) hide show
  1. main.py +31 -85
  2. prompts.py +45 -0
  3. pyproject.toml +2 -0
  4. retriever.py +2 -2
  5. tools.py +33 -0
  6. uv.lock +27 -0
main.py CHANGED
@@ -1,101 +1,47 @@
1
  from agno.agent import Agent
2
  from agno.models.google import Gemini
3
- from agno.db.sqlite import SqliteDb
4
- from agno.tools.reasoning import ReasoningTools
5
- from agno.tools import tool
6
 
7
- from retriever import PineconeHybridRetriever, FilterModel, ProductItem
 
8
 
9
- from typing import Optional
10
  from dotenv import load_dotenv
 
11
  load_dotenv()
12
 
 
 
 
 
13
 
14
- @tool
15
- def fetch_products(
16
- query: str, limit: int = 5, filters: Optional[FilterModel] = None
17
- ) -> list[ProductItem]:
18
- """
19
- Fetch products from database based on query and filters
20
-
21
- Args:
22
- query (str): Search query
23
- limit (int, optional): Number of products to return. Defaults to 5.
24
- filters (FilterModel, optional): Filters to apply. Defaults to None.
25
-
26
- Returns:
27
- list[ProductItem]: List of products matching the query and filters
28
- """
29
- retriever = PineconeHybridRetriever("amazon-products-catalog")
30
- return retriever.search_products(
31
- query=query,
32
- filters=filters,
33
- limit=limit,
34
- use_hybrid_search=True,
35
- enable_reranking=True,
36
- )
37
-
38
- SYSTEM_PROMPT = """
39
- # ROLE
40
-
41
- You are a personalized shopping AI Agent. Your goal is to help user find the best products based on their needs.
42
-
43
- ## Instructions
44
-
45
- 1. **Analyze the User Need**: Chat with the user to help them identify the product, along with any specific constraints like price, rating, or category.
46
- 2. **Fetch Products**: Once they have given you the details about the product they want, use the `fetch_products` tool to get the products.
47
- * **Extract `filters`**: Identify any explicit constraints and populate the `filters` object.
48
- - **Price**: Look for terms like "under $50", "over 100 dollars", "between $20 and $60".
49
- - **Rating**: Look for terms like "highly rated", "4 stars or more", "best reviewed". A generic term like "best" can imply a `min_rating` of `4.0`.
50
- - **Rating Count**: Look for terms like "popular", "most reviewed". A generic term like "popular" can imply a `min_reviews` of `100`.
51
- - **Categories**: Identify the categories from the user query. **MUST** be from the allowed categories listed below. Do not invent new categories. For example, if the user says "I need black shirts for men", then the right category would be "men's clothing".
52
- 3. **Present Products**: Analyze the retrieved products, and present them to user in a nice human-readable format. Provide recommendations to the user.
53
-
54
- ## Allowed Categories
55
-
56
- You must use one of the following category values if a category filter is applicable:
57
- - appliances
58
- - car & motorbike
59
- - tv, audio & cameras
60
- - sports & fitness
61
- - grocery & gourmet foods
62
- - home & kitchen
63
- - pet supplies
64
- - stores
65
- - toys & baby products
66
- - kids' fashion
67
- - bags & luggage
68
- - accessories
69
- - women's shoes
70
- - beauty & health
71
- - men's shoes
72
- - women's clothing
73
- - industrial supplies
74
- - men's clothing
75
- - music
76
- - home, kitchen, pets
77
-
78
- NOTE: These categories are case-sensitive. So you must use the exact category values provided.
79
- """
80
-
81
- db = SqliteDb(db_file="tmp/data.db", memory_table="user_memories")
82
- tools = [ReasoningTools(add_instructions=True), fetch_products]
83
 
84
- agent = Agent(
85
- model=Gemini(id="gemini-2.0-flash"),
86
- markdown=True,
87
- db=db,
 
 
88
  tools=tools,
89
- instructions=SYSTEM_PROMPT,
90
  enable_agentic_memory=True,
 
 
 
 
91
  add_history_to_context=True,
92
  num_history_runs=10,
93
  )
94
 
95
- agent.cli_app(
96
- input="Hi. I am looking for something to wear in winter.",
97
- session_id="session-01",
98
- user_id="user-01",
99
- stream=True,
 
 
100
 
101
- )
 
 
1
  from agno.agent import Agent
2
  from agno.models.google import Gemini
3
+ from agno.db.postgres import PostgresDb
4
+ from agno.os import AgentOS
5
+ from agno.memory import MemoryManager
6
 
7
+ from prompts import SHOPPING_AGENT_PROMPT
8
+ from tools import fetch_products
9
 
 
10
  from dotenv import load_dotenv
11
+ import os
12
  load_dotenv()
13
 
14
+ postgres_db = PostgresDb(
15
+ db_url=os.getenv("NEON_KEY"),
16
+ memory_table="user_memories",
17
+ )
18
 
19
+ tools = [fetch_products]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ shopping_agent = Agent(
22
+ model=Gemini(id="gemini-2.5-flash"),
23
+ name="Gem - Shopping Agent",
24
+ id="shopping-agent",
25
+ add_datetime_to_context=True,
26
+ db=postgres_db,
27
  tools=tools,
28
+ instructions=SHOPPING_AGENT_PROMPT,
29
  enable_agentic_memory=True,
30
+ memory_manager=MemoryManager(
31
+ Gemini(id="gemini-2.5-flash-lite"),
32
+ db=db,
33
+ ),
34
  add_history_to_context=True,
35
  num_history_runs=10,
36
  )
37
 
38
+ agent_os = AgentOS(
39
+ os_id="shopping-agent-os",
40
+ description="A shopping Agent OS that helps users find the best products based on their needs.",
41
+ agents=[shopping_agent]
42
+ )
43
+
44
+ app = agent_os.get_app()
45
 
46
+ if __name__ == "__main__":
47
+ agent_os.serve(app="main:app", port=8000,reload=True)
prompts.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SHOPPING_AGENT_PROMPT = """
2
+ <role>
3
+ You are **Gem**, a personalized shopping AI Agent. Your goal is to help user find the best products based on their needs.
4
+ </role>
5
+
6
+ <task>
7
+
8
+ 1. **Analyze the User Need**: Chat with the user to help them identify the product, along with any specific constraints like price, rating, or category.
9
+ 2. **Fetch Products**: Once they have given you the details about the product they want, use the `fetch_products` tool to get the products. This tool will automatically display the products to the user in frontend. Follow these instructions when using the tool:
10
+ * **Extract `filters`**: Identify any explicit constraints and populate the `filters` object.
11
+ - **Price**: Look for terms like "under $50", "over 100 dollars", "between $20 and $60".
12
+ - **Rating**: Look for terms like "highly rated", "4 stars or more", "best reviewed". A generic term like "best" can imply a `min_rating` of `4.0`.
13
+ - **Rating Count**: Look for terms like "popular", "most reviewed". A generic term like "popular" can imply a `min_reviews` of `100`.
14
+ - **Categories**: Identify the categories from the user query. **MUST** be from the allowed categories listed below. Do not invent new categories. For example, if the user says "I need black shirts for men", then the right category would be "men's clothing".
15
+ 3. **Suggest Products**: Analyze the retrieved products. Provide recommendations to the user. DO NOT write each product details again, because the products are already displayed in frontend. Instead, you should highlight the best ones based on the user's needs and give them personalized suggestions.
16
+
17
+ </task>
18
+
19
+ <allowed_categories>
20
+ You must use one of the following category values if a category filter is applicable:
21
+ - appliances
22
+ - car & motorbike
23
+ - tv, audio & cameras
24
+ - sports & fitness
25
+ - grocery & gourmet foods
26
+ - home & kitchen
27
+ - pet supplies
28
+ - stores
29
+ - toys & baby products
30
+ - kids' fashion
31
+ - bags & luggage
32
+ - accessories
33
+ - women's shoes
34
+ - beauty & health
35
+ - men's shoes
36
+ - women's clothing
37
+ - industrial supplies
38
+ - men's clothing
39
+ - music
40
+ - home, kitchen, pets
41
+
42
+ NOTE: These categories are case-sensitive. So you must use the exact category values provided.
43
+ </allowed_categories>
44
+
45
+ """
pyproject.toml CHANGED
@@ -5,6 +5,7 @@ description = "Add your description here"
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
  dependencies = [
 
8
  "agno>=2.0.8",
9
  "cohere>=5.18.0",
10
  "ddgs>=9.6.0",
@@ -18,6 +19,7 @@ dependencies = [
18
  "pinecone>=7.3.0",
19
  "pinecone-client[grpc]>=6.0.0",
20
  "pinecone-text>=0.11.0",
 
21
  "python-dotenv>=1.1.1",
22
  "uvicorn>=0.37.0",
23
  ]
 
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
  dependencies = [
8
+ "ag-ui-protocol>=0.1.9",
9
  "agno>=2.0.8",
10
  "cohere>=5.18.0",
11
  "ddgs>=9.6.0",
 
19
  "pinecone>=7.3.0",
20
  "pinecone-client[grpc]>=6.0.0",
21
  "pinecone-text>=0.11.0",
22
+ "psycopg2>=2.9.10",
23
  "python-dotenv>=1.1.1",
24
  "uvicorn>=0.37.0",
25
  ]
retriever.py CHANGED
@@ -152,8 +152,8 @@ class PineconeHybridRetriever:
152
  try:
153
  product = ProductItem(
154
  name=metadata['name'],
155
- price=metadata['discount_price_usd'],
156
- original_price=metadata['actual_price_usd'],
157
  rating=metadata['ratings'],
158
  num_reviews=metadata['no_of_ratings'],
159
  category=metadata['main_category'],
 
152
  try:
153
  product = ProductItem(
154
  name=metadata['name'],
155
+ price=round(metadata['discount_price_usd'], 2),
156
+ original_price=round(metadata['actual_price_usd'], 2),
157
  rating=metadata['ratings'],
158
  num_reviews=metadata['no_of_ratings'],
159
  category=metadata['main_category'],
tools.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agno.tools import tool
2
+ from retriever import PineconeHybridRetriever, FilterModel, ProductItem
3
+
4
+ from typing import Optional
5
+ import json
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+ @tool
11
+ def fetch_products(
12
+ query: str, limit: int = 5, filters: Optional[FilterModel] = None
13
+ ) -> list[dict]:
14
+ """
15
+ Fetch products from database based on query and filters
16
+
17
+ Args:
18
+ query (str): Search query
19
+ limit (int, optional): Number of products to return. Defaults to 5.
20
+ filters (FilterModel, optional): Filters to apply. Defaults to None.
21
+
22
+ Returns:
23
+ list[dict]: List of products dictionary items matching the query and filters
24
+ """
25
+ retriever = PineconeHybridRetriever("amazon-products-catalog")
26
+ products = retriever.search_products(
27
+ query=query,
28
+ filters=filters,
29
+ limit=limit,
30
+ use_hybrid_search=True,
31
+ enable_reranking=True,
32
+ )
33
+ return json.dumps([product.model_dump() for product in products])
uv.lock CHANGED
@@ -7,6 +7,18 @@ resolution-markers = [
7
  "python_full_version >= '4'",
8
  ]
9
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  [[package]]
11
  name = "agno"
12
  version = "2.0.8"
@@ -1946,6 +1958,17 @@ wheels = [
1946
  { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" },
1947
  ]
1948
 
 
 
 
 
 
 
 
 
 
 
 
1949
  [[package]]
1950
  name = "ptyprocess"
1951
  version = "0.7.0"
@@ -2420,6 +2443,7 @@ name = "shopping-ai-agent"
2420
  version = "0.1.0"
2421
  source = { virtual = "." }
2422
  dependencies = [
 
2423
  { name = "agno" },
2424
  { name = "cohere" },
2425
  { name = "ddgs" },
@@ -2433,12 +2457,14 @@ dependencies = [
2433
  { name = "pinecone" },
2434
  { name = "pinecone-client", extra = ["grpc"] },
2435
  { name = "pinecone-text" },
 
2436
  { name = "python-dotenv" },
2437
  { name = "uvicorn" },
2438
  ]
2439
 
2440
  [package.metadata]
2441
  requires-dist = [
 
2442
  { name = "agno", specifier = ">=2.0.8" },
2443
  { name = "cohere", specifier = ">=5.18.0" },
2444
  { name = "ddgs", specifier = ">=9.6.0" },
@@ -2452,6 +2478,7 @@ requires-dist = [
2452
  { name = "pinecone", specifier = ">=7.3.0" },
2453
  { name = "pinecone-client", extras = ["grpc"], specifier = ">=6.0.0" },
2454
  { name = "pinecone-text", specifier = ">=0.11.0" },
 
2455
  { name = "python-dotenv", specifier = ">=1.1.1" },
2456
  { name = "uvicorn", specifier = ">=0.37.0" },
2457
  ]
 
7
  "python_full_version >= '4'",
8
  ]
9
 
10
+ [[package]]
11
+ name = "ag-ui-protocol"
12
+ version = "0.1.9"
13
+ source = { registry = "https://pypi.org/simple" }
14
+ dependencies = [
15
+ { name = "pydantic" },
16
+ ]
17
+ sdist = { url = "https://files.pythonhosted.org/packages/7b/d7/a8f8789b3b8b5f7263a902361468e8dfefd85ec63d1d5398579b9175d76d/ag_ui_protocol-0.1.9.tar.gz", hash = "sha256:94d75e3919ff75e0b608a7eed445062ea0e6f11cd33b3386a7649047e0c7abd3", size = 4988, upload-time = "2025-09-19T13:36:26.903Z" }
18
+ wheels = [
19
+ { url = "https://files.pythonhosted.org/packages/39/50/2bb71a2a9135f4d88706293773320d185789b592987c09f79e9bf2f4875f/ag_ui_protocol-0.1.9-py3-none-any.whl", hash = "sha256:44c1238b0576a3915b3a16e1b3855724e08e92ebc96b1ff29379fbd3bfbd400b", size = 7070, upload-time = "2025-09-19T13:36:25.791Z" },
20
+ ]
21
+
22
  [[package]]
23
  name = "agno"
24
  version = "2.0.8"
 
1958
  { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" },
1959
  ]
1960
 
1961
+ [[package]]
1962
+ name = "psycopg2"
1963
+ version = "2.9.10"
1964
+ source = { registry = "https://pypi.org/simple" }
1965
+ sdist = { url = "https://files.pythonhosted.org/packages/62/51/2007ea29e605957a17ac6357115d0c1a1b60c8c984951c19419b3474cdfd/psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", size = 385672, upload-time = "2024-10-16T11:24:54.832Z" }
1966
+ wheels = [
1967
+ { url = "https://files.pythonhosted.org/packages/3d/16/4623fad6076448df21c1a870c93a9774ad8a7b4dd1660223b59082dd8fec/psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", size = 1025113, upload-time = "2024-10-16T11:18:40.148Z" },
1968
+ { url = "https://files.pythonhosted.org/packages/66/de/baed128ae0fc07460d9399d82e631ea31a1f171c0c4ae18f9808ac6759e3/psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", size = 1163951, upload-time = "2024-10-16T11:18:44.377Z" },
1969
+ { url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2", size = 2569060, upload-time = "2025-01-04T20:09:15.28Z" },
1970
+ ]
1971
+
1972
  [[package]]
1973
  name = "ptyprocess"
1974
  version = "0.7.0"
 
2443
  version = "0.1.0"
2444
  source = { virtual = "." }
2445
  dependencies = [
2446
+ { name = "ag-ui-protocol" },
2447
  { name = "agno" },
2448
  { name = "cohere" },
2449
  { name = "ddgs" },
 
2457
  { name = "pinecone" },
2458
  { name = "pinecone-client", extra = ["grpc"] },
2459
  { name = "pinecone-text" },
2460
+ { name = "psycopg2" },
2461
  { name = "python-dotenv" },
2462
  { name = "uvicorn" },
2463
  ]
2464
 
2465
  [package.metadata]
2466
  requires-dist = [
2467
+ { name = "ag-ui-protocol", specifier = ">=0.1.9" },
2468
  { name = "agno", specifier = ">=2.0.8" },
2469
  { name = "cohere", specifier = ">=5.18.0" },
2470
  { name = "ddgs", specifier = ">=9.6.0" },
 
2478
  { name = "pinecone", specifier = ">=7.3.0" },
2479
  { name = "pinecone-client", extras = ["grpc"], specifier = ">=6.0.0" },
2480
  { name = "pinecone-text", specifier = ">=0.11.0" },
2481
+ { name = "psycopg2", specifier = ">=2.9.10" },
2482
  { name = "python-dotenv", specifier = ">=1.1.1" },
2483
  { name = "uvicorn", specifier = ">=0.37.0" },
2484
  ]