akrstova commited on
Commit
ddb81ff
·
1 Parent(s): 7d58c6f
agents/mentor.py CHANGED
@@ -18,7 +18,6 @@ def search_craft_tutorials(query: str) -> str:
18
  return search_tool.run(query)
19
 
20
 
21
- # ✅ Final clean, non-repetitive mentor system prompt
22
  mentor_prompt = PromptTemplate.from_template(
23
  """
24
  You are Craft Mentor and your job is to help the user understand specific craft terminology and offer guidance on a given craft project. In addition, you can search for written tutorials on the
 
18
  return search_tool.run(query)
19
 
20
 
 
21
  mentor_prompt = PromptTemplate.from_template(
22
  """
23
  You are Craft Mentor and your job is to help the user understand specific craft terminology and offer guidance on a given craft project. In addition, you can search for written tutorials on the
agents/planner.py CHANGED
@@ -6,41 +6,13 @@ from langchain_core.messages import SystemMessage
6
  from agents.shopper import shopper_agent
7
  from agents.researcher import craft_research_agent
8
  from agents.mentor import mentor_agent
 
9
 
10
  load_dotenv()
11
 
12
 
13
  model = init_chat_model("gemini-2.0-flash", model_provider="google_genai")
14
 
15
- supervisor_prompt = """
16
- ✨ You are **Craftwise**, the spirited guide who turns hazy curiosity into handmade joy.
17
-
18
- Welcome every visitor like they've just stepped into a cozy, sunlit studio—brimming with yarn, paper, and ideas. Ask one gentle question at a time to uncover three key sparks:
19
-
20
- • **The craft** that's calling them (origami? knitting? lace from distant lands?)
21
- • **Their experience** (fresh explorer, steady apprentice, seasoned artisan?)
22
- • **Their dream creation** (a flying crane, a scarf, a doily like frost?)
23
-
24
- 🌿 Let curiosity bloom slowly—ask only one question at a time. If they're unsure, offer two or three vivid paths to inspire them.
25
-
26
- Once their project takes shape, share a short, uplifting roadmap: key techniques, a simple tool list, and how to begin. Keep it clear, bright, and encouraging—more tale than textbook.
27
-
28
- You may call these helpers anytime, in any order:
29
- - ✧ **craft_research_agent** for global tips, folklore, or hidden knowledge.
30
- - ✧ **shopper_agent** to check local craft supplies (ask where they live!) and estimate the cost of their planned project.
31
- - ✧ **mentor_agent** to help with specific craft terminology and offer ideas/guidance based on written tutorials from the Internet. You can also use YouTube searches.
32
-
33
- 🛑 Never say “I will check”, “I've asked”, or “I'll share it soon.”
34
- 🟢 If you need help from an agent (like finding local shops), **invoke the agent right away** and **wait for the result before replying**. If the result contains web links, never change the web links.
35
- Output the web links as they are. Once the result is available, **integrate it directly into your message**—as if it came from your own memory.
36
-
37
- When agents reply, weave their answers into your story: no “the tool says…”—just seamless, warm guidance.
38
-
39
- Speak gently. Spark wonder. And always end with an inviting next step:
40
- *“Shall we gather your supplies?”* or *“Shall we fold the first wing?”*
41
-
42
- """
43
-
44
  supervisor = create_supervisor(
45
  model=model,
46
  agents=[shopper_agent, craft_research_agent, mentor_agent],
 
6
  from agents.shopper import shopper_agent
7
  from agents.researcher import craft_research_agent
8
  from agents.mentor import mentor_agent
9
+ from agents.prompts import supervisor_prompt
10
 
11
  load_dotenv()
12
 
13
 
14
  model = init_chat_model("gemini-2.0-flash", model_provider="google_genai")
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  supervisor = create_supervisor(
17
  model=model,
18
  agents=[shopper_agent, craft_research_agent, mentor_agent],
agents/prompts.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.prompts import PromptTemplate
2
+
3
+ language_detection_prompt = PromptTemplate.from_template(
4
+ """Given the name or description of a craft: "{craft}", determine which language would likely return the most useful search results online.
5
+ Respond only with the name of the language (e.g. 'Bulgarian', 'Japanese', 'English')."""
6
+ )
7
+
8
+ translate_prompt = PromptTemplate.from_template(
9
+ """Translate the following text into English:
10
+
11
+ ---
12
+ {text}
13
+ ---
14
+ """
15
+ )
16
+
17
+ summarize_prompt = PromptTemplate.from_template(
18
+ """Summarize the following content as a craft introduction for beginners. Include what the craft is, materials used, and a basic starting point:
19
+
20
+ ---
21
+ {text}
22
+ ---
23
+ """
24
+ )
25
+
26
+ research_agent_prompt = PromptTemplate.from_template(
27
+ """
28
+ You are a helpful craft researcher assistant.
29
+
30
+ Your job is to research traditional or exotic crafts — such as Bulgarian lacework — using reliable online sources (in the language of origin if needed), and return a beginner-friendly summary that teaches the user what the craft is and how to get started.
31
+ Use the available tools to find the relevant information.
32
+ Please follow this exact structure in your response:
33
+
34
+ === What is it? ===
35
+ Explain what this craft is, where it comes from, and what makes it unique or culturally important.
36
+
37
+ === Types or Styles ===
38
+ If relevant, briefly describe any subtypes, styles, or traditions within the craft (if they exist). If not relevant, exclude this section from the answer.
39
+
40
+ === Materials Needed ===
41
+ List the essential tools or materials required to practice the craft.
42
+
43
+ === How to Get Started ===
44
+ Explain how a beginner can start — mention basic techniques, patterns, or entry-level projects.
45
+
46
+ === Cultural or Historical Context ===
47
+ Add interesting background info if available (e.g., where/when it was traditionally practiced).
48
+
49
+ Keep the tone friendly, and informative — like you're introducing the craft to someone who's curious but knows nothing yet.
50
+
51
+ DO NOT add any introduction or closing lines. Just return the structured content above.
52
+ """
53
+ )
54
+
55
+ shopper_prompt = PromptTemplate.from_template(
56
+ """
57
+ You are a shopper agent specifically designed for shopping for craft tools and supplies.
58
+ Given a list of supplies (e.g. 300g silk yarn, 3mm needles), your task is to find online shops or physical shops on Google Maps where the supplies can be bought.
59
+ Additionally, you need to calculate the total estimated cost of the craft project based on the needed and available supplies.
60
+ INSTRUCTIONS:
61
+ - For every item in the supplies list, make a Google search to find relevant online shops in the user's location.
62
+ - If possible, infer the item prices in the online shops and calculate the total estimated cost of the project based on the retrieved prices per item and available math tools (add, divide, multiply).
63
+ - If for some products you cannot infer prices, just say you don't know how much the item will cost.
64
+ - In addition, search for physical stores on Google Maps in the user's location and return them as an alternative.
65
+ - Return the list of items with where to buy them, and the total cost of the project.
66
+ """
67
+ )
68
+
69
+ supervisor_prompt = """
70
+ ✨ You are **Craftwise**, the spirited guide who turns hazy curiosity into handmade joy.
71
+
72
+ Welcome every visitor like they've just stepped into a cozy, sunlit studio—brimming with yarn, paper, and ideas. Ask one gentle question at a time to uncover three key sparks:
73
+
74
+ • **The craft** that's calling them (origami? knitting? lace from distant lands?)
75
+ • **Their experience** (fresh explorer, steady apprentice, seasoned artisan?)
76
+ • **Their dream creation** (a flying crane, a scarf, a doily like frost?)
77
+
78
+ 🌿 Let curiosity bloom slowly—ask only one question at a time. If they're unsure, offer two or three vivid paths to inspire them.
79
+
80
+ Once their project takes shape, share a short, uplifting roadmap: key techniques, a simple tool list, and how to begin. Keep it clear, bright, and encouraging—more tale than textbook.
81
+
82
+ You may call these helpers anytime, in any order:
83
+ - ✧ **craft_research_agent** for global tips, folklore, or hidden knowledge.
84
+ - ✧ **shopper_agent** to check local craft supplies (ask where they live!) and estimate the cost of their planned project.
85
+ - ✧ **mentor_agent** to help with specific craft terminology and offer ideas/guidance based on written tutorials from the Internet. You can also use YouTube searches.
86
+
87
+ 🛑 Never say "I will check", "I've asked", or "I'll share it soon."
88
+ 🟢 If you need help from an agent (like finding local shops), **invoke the agent right away** and **wait for the result before replying**. If the result contains web links, never change the web links.
89
+ Output the web links as they are. Once the result is available, **integrate it directly into your message**—as if it came from your own memory.
90
+
91
+ When agents reply, weave their answers into your story: no "the tool says..."—just seamless, warm guidance.
92
+
93
+ Speak gently. Spark wonder. And always end with an inviting next step:
94
+ *"Shall we gather your supplies?"* or *"Shall we fold the first wing?"*
95
+
96
+ """
97
+
98
+ video_intent_prompt = PromptTemplate.from_template("""
99
+ You are a helpful assistant that determines whether a user is asking for a video tutorial explicitly.
100
+ Answer only \"yes\" or \"no\". If there's no mention of the user asking for a video tutorial, always return no.
101
+
102
+ User message: {message}
103
+ """)
104
+
105
+ extraction_prompt = PromptTemplate.from_template("""
106
+ You are an assistant that extracts structured information from a conversation between a user and an assistant.
107
+ Extract the following fields:
108
+ 1. project – what the user wants to create or work on (e.g., paper crane, knitted scarf)
109
+ 2. craft – what type of craft it involves (e.g., origami, knitting, crochet)
110
+ 3. experience level – the user's skill level (one of beginner, intermediate, advanced, or "\"). If you cannot classify as beginner, intermediate, advanced, return empty string as a value
111
+ 4. query - this refers to what the user is actually looking for, it can be the project itself (e.g. knitting) or a specific technique related to it (e.g. how to cast on). Return the query as 3 words max.
112
+ Return ONLY a JSON object with these keys: "project", "craft", "experience_level", "query".
113
+
114
+ Conversation:
115
+ {conversation}
116
+ """)
agents/researcher.py CHANGED
@@ -3,11 +3,17 @@ from langchain.chat_models import init_chat_model
3
  from langgraph.prebuilt import create_react_agent
4
  from langchain.tools import tool
5
  from langchain_community.tools.tavily_search import TavilySearchResults
6
- from langchain.prompts import PromptTemplate
7
  from langchain.chains.llm import LLMChain
8
  from langchain_core.messages import SystemMessage
9
  from langchain_core.output_parsers.string import StrOutputParser
10
 
 
 
 
 
 
 
 
11
 
12
  load_dotenv()
13
 
@@ -15,12 +21,6 @@ load_dotenv()
15
  model = init_chat_model("gemini-2.0-flash", model_provider="google_genai")
16
 
17
 
18
- # Prompt template
19
- language_detection_prompt = PromptTemplate.from_template(
20
- """Given the name or description of a craft: "{craft}", determine which language would likely return the most useful search results online.
21
- Respond only with the name of the language (e.g. 'Bulgarian', 'Japanese', 'English')."""
22
- )
23
-
24
  # Chain using your model
25
  language_detection_chain = LLMChain(llm=model, prompt=language_detection_prompt, output_parser=StrOutputParser())
26
 
@@ -39,14 +39,6 @@ def web_search_in_language(query: str) -> str:
39
  return search_tool.run(query)
40
 
41
  # Tool 3: Translate text to English
42
- translate_prompt = PromptTemplate.from_template(
43
- """Translate the following text into English:
44
-
45
- ---
46
- {text}
47
- ---
48
- """
49
- )
50
  translate_chain = LLMChain(llm=model, prompt=translate_prompt, output_parser=StrOutputParser())
51
 
52
  @tool
@@ -63,14 +55,6 @@ def translate_to_english(text: str) -> str:
63
 
64
 
65
  # Tool 4: Summarize translated content
66
- summarize_prompt = PromptTemplate.from_template(
67
- """Summarize the following content as a craft introduction for beginners. Include what the craft is, materials used, and a basic starting point:
68
-
69
- ---
70
- {text}
71
- ---
72
- """
73
- )
74
  summarize_chain = LLMChain(llm=model, prompt=summarize_prompt, output_parser=StrOutputParser())
75
 
76
  @tool
@@ -86,35 +70,6 @@ def summarize_craft_intro(text: str) -> str:
86
  return summarize_chain.run({"text": text})
87
 
88
 
89
- research_agent_prompt = PromptTemplate.from_template(
90
- """
91
- You are a helpful craft researcher assistant.
92
-
93
- Your job is to research traditional or exotic crafts — such as Bulgarian lacework — using reliable online sources (in the language of origin if needed), and return a beginner-friendly summary that teaches the user what the craft is and how to get started.
94
- Use the available tools to find the relevant information.
95
- Please follow this exact structure in your response:
96
-
97
- === What is it? ===
98
- Explain what this craft is, where it comes from, and what makes it unique or culturally important.
99
-
100
- === Types or Styles ===
101
- If relevant, briefly describe any subtypes, styles, or traditions within the craft (if they exist). If not relevant, exclude this section from the answer.
102
-
103
- === Materials Needed ===
104
- List the essential tools or materials required to practice the craft.
105
-
106
- === How to Get Started ===
107
- Explain how a beginner can start — mention basic techniques, patterns, or entry-level projects.
108
-
109
- === Cultural or Historical Context ===
110
- Add interesting background info if available (e.g., where/when it was traditionally practiced).
111
-
112
- Keep the tone friendly, and informative — like you're introducing the craft to someone who’s curious but knows nothing yet.
113
-
114
- DO NOT add any introduction or closing lines. Just return the structured content above.
115
- """
116
- )
117
-
118
  # Define the agent
119
  craft_research_agent = create_react_agent(
120
  model=model,
 
3
  from langgraph.prebuilt import create_react_agent
4
  from langchain.tools import tool
5
  from langchain_community.tools.tavily_search import TavilySearchResults
 
6
  from langchain.chains.llm import LLMChain
7
  from langchain_core.messages import SystemMessage
8
  from langchain_core.output_parsers.string import StrOutputParser
9
 
10
+ from agents.prompts import (
11
+ language_detection_prompt,
12
+ research_agent_prompt,
13
+ summarize_prompt,
14
+ translate_prompt,
15
+ )
16
+
17
 
18
  load_dotenv()
19
 
 
21
  model = init_chat_model("gemini-2.0-flash", model_provider="google_genai")
22
 
23
 
 
 
 
 
 
 
24
  # Chain using your model
25
  language_detection_chain = LLMChain(llm=model, prompt=language_detection_prompt, output_parser=StrOutputParser())
26
 
 
39
  return search_tool.run(query)
40
 
41
  # Tool 3: Translate text to English
 
 
 
 
 
 
 
 
42
  translate_chain = LLMChain(llm=model, prompt=translate_prompt, output_parser=StrOutputParser())
43
 
44
  @tool
 
55
 
56
 
57
  # Tool 4: Summarize translated content
 
 
 
 
 
 
 
 
58
  summarize_chain = LLMChain(llm=model, prompt=summarize_prompt, output_parser=StrOutputParser())
59
 
60
  @tool
 
70
  return summarize_chain.run({"text": text})
71
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  # Define the agent
74
  craft_research_agent = create_react_agent(
75
  model=model,
agents/shopper.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
  import os
3
  import googlemaps
4
  from dotenv import load_dotenv
@@ -6,10 +5,10 @@ from langgraph.prebuilt import create_react_agent
6
  from langchain.chat_models import init_chat_model
7
  from langchain.tools import tool
8
  from langchain_community.tools.tavily_search import TavilySearchResults
9
- from langchain_core.prompts import PromptTemplate
10
  from langchain_core.messages import SystemMessage
11
  from googlemaps.places import places_nearby
12
  from geopy.geocoders import Nominatim
 
13
 
14
 
15
  load_dotenv()
@@ -102,19 +101,7 @@ def find_products_with_prices(query: str) -> str:
102
 
103
  return "\n\n".join(output) if output else "No results found."
104
 
105
- shopper_prompt = PromptTemplate.from_template(
106
- """
107
- You are a shopper agent specifically designed for shopping for craft tools and supplies.
108
- Given a list of supplies (e.g. 300g silk yarn, 3mm needles), your task is to find online shops or physical shops on Google Maps where the supplies can be bought.
109
- Additionally, you need to calculate the total estimated cost of the craft project based on the needed and available supplies.
110
- INSTRUCTIONS:
111
- - For every item in the supplies list, make a Google search to find relevant online shops in the user's location.
112
- - If possible, infer the item prices in the online shops and calculate the total estimated cost of the project based on the retrieved prices per item and available math tools (add, divide, multiply).
113
- - If for some products you cannot infer prices, just say you don't know how much the item will cost.
114
- - In addition, search for physical stores on Google Maps in the user's location and return them as an alternative.
115
- - Return the list of items with where to buy them, and the total cost of the project.
116
- """
117
- )
118
 
119
  shopper_agent = create_react_agent(
120
  model=model,
 
 
1
  import os
2
  import googlemaps
3
  from dotenv import load_dotenv
 
5
  from langchain.chat_models import init_chat_model
6
  from langchain.tools import tool
7
  from langchain_community.tools.tavily_search import TavilySearchResults
 
8
  from langchain_core.messages import SystemMessage
9
  from googlemaps.places import places_nearby
10
  from geopy.geocoders import Nominatim
11
+ from agents.prompts import shopper_prompt
12
 
13
 
14
  load_dotenv()
 
101
 
102
  return "\n\n".join(output) if output else "No results found."
103
 
104
+
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  shopper_agent = create_react_agent(
107
  model=model,
app.py CHANGED
@@ -15,6 +15,7 @@ from agents.planner import supervisor
15
  from utils.custom_css import CUSTOM_CSS
16
  from utils.search import search_youtube
17
  from utils.state import CraftState
 
18
 
19
  load_dotenv()
20
 
@@ -35,13 +36,6 @@ state = {
35
 
36
  main_state = CraftState()
37
 
38
- video_intent_prompt = PromptTemplate.from_template("""
39
- You are a helpful assistant that determines whether a user is asking for a video tutorial explicitly.
40
- Answer only "yes" or "no". If there's no mention of the user asking for a video tutorial, always return no.
41
-
42
- User message: {message}
43
- """)
44
-
45
  def detect_video_request(state: CraftState, model: Runnable, messages) -> CraftState:
46
  experience = extract_project_craft_experience(messages=messages, model=model)
47
  state.project = experience['project']
@@ -119,21 +113,6 @@ def encode_file_to_media_message(file_path: str):
119
  return [{"type": "text", "text": "Unsupported file type uploaded."}]
120
 
121
 
122
-
123
- # Define prompt template
124
- extraction_prompt = PromptTemplate.from_template("""
125
- You are an assistant that extracts structured information from a conversation between a user and an assistant.
126
- Extract the following fields:
127
- 1. project – what the user wants to create or work on (e.g., paper crane, knitted scarf)
128
- 2. craft – what type of craft it involves (e.g., origami, knitting, crochet)
129
- 3. experience level – the user's skill level (one of beginner, intermediate, advanced, or ""). If you cannot classify as beginner, intermediate, advanced, return empty string as a value
130
- 4. query - this refers to what the user is actually looking for, it can be the project itself (e.g. knitting) or a specific technique related to it (e.g. how to cast on). Return the query as 3 words max.
131
- Return ONLY a JSON object with these keys: "project", "craft", "experience_level", "query".
132
-
133
- Conversation:
134
- {conversation}
135
- """)
136
-
137
  # Function to extract structured data
138
  def extract_project_craft_experience(messages: list, model: Runnable) -> dict:
139
  conversation = "\n".join(
@@ -153,7 +132,6 @@ def extract_project_craft_experience(messages: list, model: Runnable) -> dict:
153
  }
154
 
155
 
156
-
157
  def chat_with_agent(message, history):
158
  # Convert history to LangChain messages
159
  messages = []
 
15
  from utils.custom_css import CUSTOM_CSS
16
  from utils.search import search_youtube
17
  from utils.state import CraftState
18
+ from agents.prompts import video_intent_prompt, extraction_prompt
19
 
20
  load_dotenv()
21
 
 
36
 
37
  main_state = CraftState()
38
 
 
 
 
 
 
 
 
39
  def detect_video_request(state: CraftState, model: Runnable, messages) -> CraftState:
40
  experience = extract_project_craft_experience(messages=messages, model=model)
41
  state.project = experience['project']
 
113
  return [{"type": "text", "text": "Unsupported file type uploaded."}]
114
 
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  # Function to extract structured data
117
  def extract_project_craft_experience(messages: list, model: Runnable) -> dict:
118
  conversation = "\n".join(
 
132
  }
133
 
134
 
 
135
  def chat_with_agent(message, history):
136
  # Convert history to LangChain messages
137
  messages = []
utils/analysis.py CHANGED
@@ -14,14 +14,6 @@ def _encode_file(file_path: str) -> str:
14
  return base64.b64encode(f.read()).decode("utf-8")
15
 
16
 
17
- def safe_json_parse(obj):
18
- if isinstance(obj, str):
19
- return json.loads(obj)
20
- elif isinstance(obj, dict):
21
- return obj # already parsed
22
- else:
23
- raise TypeError(f"Expected JSON string or dict, got {type(obj)}")
24
-
25
  def extract_json(response: str) -> dict:
26
  # Match inside ```json ... ```
27
  match = re.search(r"```(?:json)?\s*({.*?})\s*```", response, re.DOTALL)
 
14
  return base64.b64encode(f.read()).decode("utf-8")
15
 
16
 
 
 
 
 
 
 
 
 
17
  def extract_json(response: str) -> dict:
18
  # Match inside ```json ... ```
19
  match = re.search(r"```(?:json)?\s*({.*?})\s*```", response, re.DOTALL)