| | import gradio as gr |
| | from langchain_community.vectorstores import Chroma |
| | from langchain_core.documents import Document |
| | from langchain_huggingface import HuggingFaceEmbeddings |
| | from openai import OpenAI |
| | import json |
| | import os |
| |
|
| | |
| | |
| | |
| | OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "sk-your-key-here") |
| |
|
| | |
| | |
| | |
| | def clean_metadata(metadata): |
| | """Convert list values to comma-separated strings for ChromaDB compatibility""" |
| | cleaned = {} |
| | for key, value in metadata.items(): |
| | if isinstance(value, list): |
| | cleaned[key] = ", ".join(str(v) for v in value) |
| | elif isinstance(value, (str, int, float, bool)) or value is None: |
| | cleaned[key] = value |
| | else: |
| | cleaned[key] = str(value) |
| | return cleaned |
| |
|
| | print("Loading documents...") |
| | docs = [] |
| | with open("helpwildlife_rag.jsonl", "r", encoding="utf-8") as f: |
| | for line in f: |
| | entry = json.loads(line) |
| | metadata = entry.get("metadata", {}) |
| | docs.append(Document( |
| | page_content=entry["text"], |
| | metadata=clean_metadata(metadata) |
| | )) |
| | print(f"β Loaded {len(docs)} documents") |
| |
|
| | print("Loading embedding model...") |
| | embeddings = HuggingFaceEmbeddings( |
| | model_name="sentence-transformers/all-MiniLM-L6-v2" |
| | ) |
| |
|
| | print("Building vector store...") |
| | try: |
| | vectorstore = Chroma.from_documents(docs, embedding=embeddings) |
| | except TypeError: |
| | vectorstore = Chroma.from_documents(docs, embedding_function=embeddings) |
| |
|
| | retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) |
| | print("β Vector store created") |
| |
|
| | |
| | client = OpenAI(api_key=OPENAI_API_KEY) |
| | print("β OpenAI client ready") |
| |
|
| | |
| | SYSTEM_PROMPT = """You are a compassionate wildlife advice assistant having a conversation with someone concerned about an animal. |
| | |
| | CRITICAL FORMATTING RULES: |
| | - Write in short, conversational paragraphs (2-3 sentences each) |
| | - NEVER use asterisks (*) or bullet points |
| | - Use numbered lists ONLY when giving step-by-step instructions |
| | - Keep your initial response brief and focused |
| | - Ask 1-2 clarifying questions to better understand the situation |
| | |
| | Your approach: |
| | 1. First, acknowledge their concern warmly |
| | 2. Ask specific questions to assess the situation (e.g., "Is the animal bleeding or visibly injured?", "Can you see the parents nearby?", "Is it in immediate danger from traffic or predators?") |
| | 3. Based on the context provided, give concise, practical first steps |
| | 4. Always emphasize: contact a local wildlife rescue or 24-hour animal hospital for the safest outcome |
| | |
| | Key principles: |
| | - Never suggest killing or harming any animal |
| | - Discourage people from handling wildlife themselves |
| | - Note that regular vets may euthanize wild animals - wildlife rescues are better |
| | - All animals deserve care and respect - no animal is a "pest" |
| | - Use empathy and clarity |
| | |
| | Use ONLY the information in the CONTEXT section below. If the context doesn't have enough information, say so and suggest contacting a wildlife rescue.""" |
| |
|
| | |
| | |
| | |
| | def chat_wildlife(message, history): |
| | """Process a wildlife question with conversation history""" |
| | if not message.strip(): |
| | return history + [["", "Please tell me about the animal you've found, and I'll help you figure out what to do."]] |
| | |
| | try: |
| | |
| | retrieved_docs = retriever.invoke(message) |
| | |
| | |
| | context = "\n\n".join([doc.page_content for doc in retrieved_docs]) |
| | |
| | |
| | messages = [{"role": "system", "content": SYSTEM_PROMPT}] |
| | |
| | |
| | for user_msg, assistant_msg in history: |
| | if user_msg: |
| | messages.append({"role": "user", "content": user_msg}) |
| | if assistant_msg: |
| | messages.append({"role": "assistant", "content": assistant_msg}) |
| | |
| | |
| | current_message = f"""CONTEXT (from HelpWildlife guidance): |
| | {context} |
| | |
| | QUESTION: {message}""" |
| | |
| | messages.append({"role": "user", "content": current_message}) |
| | |
| | |
| | response = client.chat.completions.create( |
| | model="gpt-4o-mini", |
| | messages=messages, |
| | temperature=0.3, |
| | max_tokens=300 |
| | ) |
| | |
| | answer = response.choices[0].message.content |
| | history.append([message, answer]) |
| | return history |
| | |
| | except Exception as e: |
| | error_msg = f"I'm having trouble connecting right now. Please check that the OpenAI API key is set correctly, or try again in a moment." |
| | history.append([message, error_msg]) |
| | return history |
| |
|
| | |
| | |
| | |
| | custom_css = """ |
| | /* Import ChatGPT-like font */ |
| | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); |
| | |
| | * { |
| | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important; |
| | } |
| | |
| | .gradio-container { |
| | max-width: 900px !important; |
| | margin: 0 auto !important; |
| | } |
| | |
| | /* Main chat area styling */ |
| | .chatbot { |
| | border-radius: 12px !important; |
| | border: 1px solid #e5e7eb !important; |
| | } |
| | |
| | /* Message bubbles */ |
| | .message-wrap { |
| | padding: 20px !important; |
| | } |
| | |
| | .message { |
| | font-size: 15px !important; |
| | line-height: 1.6 !important; |
| | color: #374151 !important; |
| | } |
| | |
| | /* User message */ |
| | .user { |
| | background: #f7f7f8 !important; |
| | border-radius: 12px !important; |
| | padding: 12px 16px !important; |
| | } |
| | |
| | /* Assistant message */ |
| | .bot { |
| | background: transparent !important; |
| | padding: 12px 16px !important; |
| | } |
| | |
| | /* Input box */ |
| | .input-box { |
| | border-radius: 12px !important; |
| | border: 1px solid #d1d5db !important; |
| | font-size: 15px !important; |
| | padding: 12px 16px !important; |
| | } |
| | |
| | /* Buttons */ |
| | button { |
| | border-radius: 8px !important; |
| | font-weight: 500 !important; |
| | font-size: 14px !important; |
| | transition: all 0.2s !important; |
| | } |
| | |
| | button:hover { |
| | transform: translateY(-1px) !important; |
| | box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important; |
| | } |
| | |
| | /* Primary button (Send) */ |
| | .primary { |
| | background: #10a37f !important; |
| | color: white !important; |
| | border: none !important; |
| | } |
| | |
| | .primary:hover { |
| | background: #0d8a6a !important; |
| | } |
| | |
| | /* Secondary buttons */ |
| | .secondary { |
| | background: white !important; |
| | border: 1px solid #d1d5db !important; |
| | color: #374151 !important; |
| | } |
| | |
| | /* Example buttons */ |
| | .sm { |
| | background: white !important; |
| | border: 1px solid #d1d5db !important; |
| | color: #374151 !important; |
| | padding: 8px 16px !important; |
| | } |
| | |
| | /* Headers */ |
| | h1, h2, h3 { |
| | font-weight: 600 !important; |
| | color: #111827 !important; |
| | } |
| | |
| | h1 { |
| | font-size: 32px !important; |
| | margin-bottom: 8px !important; |
| | } |
| | |
| | h3 { |
| | font-size: 16px !important; |
| | font-weight: 500 !important; |
| | color: #6b7280 !important; |
| | margin-top: 24px !important; |
| | margin-bottom: 12px !important; |
| | } |
| | |
| | /* Markdown text */ |
| | .markdown { |
| | color: #374151 !important; |
| | font-size: 15px !important; |
| | line-height: 1.6 !important; |
| | } |
| | """ |
| |
|
| | |
| | |
| | |
| | with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo: |
| | gr.Markdown( |
| | """ |
| | # π¦ Wildlife Rescue Assistant |
| | |
| | Found an animal that might need help? I'm here to guide you with compassionate, evidence-based advice. |
| | |
| | **β οΈ For emergencies:** Contact your local wildlife rescue or 24-hour animal hospital immediately. |
| | """ |
| | ) |
| | |
| | chatbot = gr.Chatbot( |
| | height=500, |
| | show_label=False, |
| | avatar_images=(None, "π¦"), |
| | bubble_full_width=False |
| | ) |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=9): |
| | msg = gr.Textbox( |
| | placeholder="Describe what you've found (e.g., 'I found a baby bird on the ground')...", |
| | show_label=False, |
| | container=False |
| | ) |
| | with gr.Column(scale=1, min_width=80): |
| | submit = gr.Button("Send", variant="primary") |
| | |
| | gr.Markdown( |
| | """ |
| | ### Common situations: |
| | """ |
| | ) |
| | |
| | with gr.Row(): |
| | example1 = gr.Button("π¦ Baby bird on ground", size="sm") |
| | example2 = gr.Button("π¦ Hedgehog in daytime", size="sm") |
| | example3 = gr.Button("π¦ Injured fox", size="sm") |
| | |
| | clear = gr.Button("π Start new conversation", variant="secondary") |
| | |
| | |
| | def respond(message, chat_history): |
| | return chat_wildlife(message, chat_history), "" |
| | |
| | msg.submit(respond, [msg, chatbot], [chatbot, msg]) |
| | submit.click(respond, [msg, chatbot], [chatbot, msg]) |
| | |
| | example1.click(lambda: ("I found a baby bird on the ground", []), None, [msg, chatbot]) |
| | example2.click(lambda: ("I found a hedgehog out during the day", []), None, [msg, chatbot]) |
| | example3.click(lambda: ("There's an injured fox in my garden", []), None, [msg, chatbot]) |
| | |
| | clear.click(lambda: [], None, chatbot) |
| | |
| | gr.Markdown( |
| | """ |
| | --- |
| | *This tool provides general guidance based on wildlife rescue best practices. |
| | Always consult with a wildlife rescue professional for specific situations.* |
| | """ |
| | ) |
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |