🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

{% vimeo 1123091554 %}

完整指南:從零到生產


目錄

  1. 介紹

  2. 這個專案為何重要

  3. 技術堆疊概述

  4. 專案架構

  5. 先決條件

  6. 專案設定

  7. 了解環境變數

  8. 程式碼實現

  9. 執行應用程式

  10. 測試您的 API

  11. 常見問題及解決方案

  12. 後續步驟

  13. GitHub 倉庫


介紹

本指南將指導您建立MediCare AI - 一款可投入生產的醫療助理,它可以:

  • 用英語和法語回答醫療問題

  • 從醫療記錄(手寫或列印)中提取文本

  • 分析實驗室結果並提供見解

  • 搜尋最新醫學研究

  • 提供個人化的健康建議

我們將LangChain 表達語言 (LCEL)Google Gemini 2.0結合使用 - 最新的 AI 模型,完全免費


這個專案為何重要

喀麥隆的問題

  1. 醫療保健服務有限—許多農村地區缺乏醫生

  2. 語言障礙-英語和法語人士需要支持

  3. 醫學素養-人們難以理解醫療報告

  4. 成本-醫療諮詢費用昂貴

我們的解決方案

免費的人工智慧醫療助理,可離線工作(本地部署後),懂英語和法語,簡單解釋醫學術語,提供基於證據的訊息,並尊重隱私。


技術堆疊概述

| 技術 | 目的 | 我們為什麼選擇它 |

|------------|---------|-----------------|

| FastAPI | 後端框架 | 快速、現代、自動文件 |

| LangChain | AI 編排 | 輕鬆鏈上 AI 操作 |

| Google Gemini 2.0 | 人工智慧模型 | 免費、多模態(文字+視覺)|

| Tavily AI | 醫學研究 | 每月 1000 次免費搜尋 |

| Pydantic | 資料驗證 | 類型安全,自動驗證 |

| Python 3.11+ | 程式語言 | 現代、可讀、廣泛的函式庫 |

LangChain 幫助我們將多個 AI 操作連結在一起,使用模板進行一致的提示,獲取結構化輸出(JSON),並優雅地處理重試和錯誤。


專案架構

backend/
├── app/
│   ├── __init__.py
│   ├── main.py                 # FastAPI application entry
│   ├── config.py               # Environment & settings
│   ├── routes/
│   │   ├── health.py           # Health check endpoints
│   │   ├── analysis.py         # Medical analysis endpoints
│   │   └── research.py         # Research endpoints
│   ├── services/
│   │   ├── gemini_service.py   # Gemini Vision operations
│   │   └── tavily_service.py   # Medical research
│   ├── chains/
│   │   ├── chat_chain.py       # LangChain chat flows
│   │   └── analysis_chain.py   # LangChain analysis flows
│   └── models/
│       └── schemas.py          # Pydantic data models
├── requirements.txt
├── .env.example
└── README.md

先決條件

在開始之前,請確保您已:

  • 已安裝 Python 3.11+

  • pip(Python 套件管理器)

  • 文字編輯器(建議使用 VS Code)

  • API 金鑰(免費):

  • Google Gemini API 金鑰:在此處取得

  • Tavily API 金鑰:在此處獲取


專案設定

步驟 1:建立專案結構

mkdir medicare-ai
cd medicare-ai
mkdir backend
cd backend
mkdir -p app/routes app/services app/chains app/models

第 2 步:建立虛擬環境

python -m venv venv

# Activate it
# On Windows:
venv\Scripts\activate
# On Mac/Linux:
source venv/bin/activate

步驟3:安裝依賴項

建立requirements.txt

fastapi
uvicorn
python-dotenv
python-multipart
pydantic
pydantic-settings
langchain
langchain-core
langchain-google-genai
tavily-python
Pillow

安裝所有內容:

pip install --upgrade pip
pip install -r requirements.txt

了解環境變數

環境變數將敏感資訊(API 金鑰)與程式碼隔離。這對於安全性(API 金鑰永遠不會進入版本控制)、靈活性(更改設定而不更改程式碼)和部署(開發/預發布/生產環境的不同配置)至關重要。

我們使用Pydantic Settings而不是os.getenv() ,因為它提供自動類型驗證、內建預設值、欄位描述、完整的 IDE 自動完成支援和詳細的錯誤訊息。

建立.env文件

backend/資料夾中建立.env

GOOGLE_API_KEY=AIzaSy...your_key_here
TAVILY_API_KEY=tvly-...your_key_here
HOST=0.0.0.0
PORT=8000
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
GEMINI_MODEL=gemini-2.0-flash-exp
TEMPERATURE=0.7
MAX_TOKENS=2048

重要提示:.env加入您的.gitignore中:

echo ".env" >> .gitignore

建立.env.example (用於文件):

GOOGLE_API_KEY=your_gemini_api_key_here
TAVILY_API_KEY=your_tavily_api_key_here
HOST=0.0.0.0
PORT=8000
CORS_ORIGINS=http://localhost:3000
GEMINI_MODEL=gemini-2.0-flash-exp
TEMPERATURE=0.7
MAX_TOKENS=2048

程式碼實現

配置管理

建立app/config.py

該檔案管理所有設定並使用 Pydantic 設定建立我們的 AI 模型實例,以實現自動驗證和類型安全。

import os
from pydantic_settings import BaseSettings
from pydantic import Field
from langchain_google_genai import ChatGoogleGenerativeAI
from functools import lru_cache

class Settings(BaseSettings):
    google_api_key: str = Field(..., description="Google Gemini API key")
    tavily_api_key: str = Field(..., description="Tavily API key")
    host: str = Field(default="0.0.0.0")
    port: int = Field(default=8000)
    cors_origins: str = Field(default="http://localhost:3000")
    gemini_model: str = Field(default="gemini-2.0-flash-exp")
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)
    max_tokens: int = Field(default=2048, ge=100, le=8192)
    max_file_size: int = Field(default=10 * 1024 * 1024)

    class Config:
        env_file = ".env"
        case_sensitive = False

    @property
    def cors_origins_list(self):
        return [origin.strip() for origin in self.cors_origins.split(",")]

settings = Settings()

@lru_cache()
def load_google_llm():
    return ChatGoogleGenerativeAI(
        model=settings.gemini_model,
        google_api_key=settings.google_api_key,
        temperature=settings.temperature,
        max_output_tokens=settings.max_tokens,
        convert_system_message_to_human=True
    )

@lru_cache()
def load_google_vision_llm():
    return ChatGoogleGenerativeAI(
        model=settings.gemini_model,
        google_api_key=settings.google_api_key,
        temperature=0.5,
        max_output_tokens=settings.max_tokens,
        convert_system_message_to_human=True
    )

這裡發生了什麼: Pydantic 設定會自動讀取.env文件,驗證字段類型和範圍,並且@lru_cache裝飾器會建立一次模型並重複使用以獲得更好的性能。我們有兩個 LLM 函數:一個用於溫度較高的聊天(更具創意),另一個用於溫度較低的視覺(更一致的文字擷取)。


使用 Pydantic 的資料模型

建立app/models/schemas.py

這些模型定義了傳入請求和傳出回應的資料形狀。 FastAPI 使用它們進行自動驗證和文件產生。

from pydantic import BaseModel, Field
from datetime import datetime

class HealthCheckResponse(BaseModel):
    status: str
    timestamp: datetime
    message: str

class ChatRequest(BaseModel):
    message: str = Field(..., min_length=1, max_length=1000)
    language: str = Field(default="en", pattern="^(en|fr)$")

class ChatResponse(BaseModel):
    response: str
    language: str
    timestamp: datetime

class AnalysisRequest(BaseModel):
    text: str = Field(..., min_length=1)
    context: str = Field(default="")
    language: str = Field(default="en")

class MedicalAnalysis(BaseModel):
    summary: str
    key_findings: list[str]
    recommendations: list[str]
    next_steps: list[str]

class AnalysisResponse(BaseModel):
    summary: str
    key_findings: list[str]
    recommendations: list[str]
    next_steps: list[str]
    disclaimer: str
    language: str
    timestamp: datetime

class ImageAnalysisResponse(BaseModel):
    extracted_text: str
    analysis: AnalysisResponse

class ResearchRequest(BaseModel):
    query: str = Field(..., min_length=3, max_length=200)
    max_results: int = Field(default=5, ge=1, le=10)
    language: str = Field(default="en")

class ResearchResult(BaseModel):
    title: str
    url: str
    content: str
    score: float

class ResearchResponse(BaseModel):
    query: str
    results: list[ResearchResult]
    summary: str
    timestamp: datetime

重點: FastAPI 會自動檢查min_lengthmax_lengthpattern的有效性。 LangChain 的PydanticOutputParser使用MedicalAnalysis模型強制 AI 輸出結構化的 JSON 格式。巢狀模型(例如包含AnalysisResponseImageAnalysisResponse允許使用複雜的資料結構。


LangChain 鏈條

建立app/chains/chat_chain.py

這使用 LangChain 表達式語言 (LCEL) 實現了聊天鏈。此鏈以清晰易讀的方式連接提示範本、LLM 和輸出解析器。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from app.config import load_google_llm

def create_chat_chain(language: str = "en"):
    llm = load_google_llm()

    if language == "fr":
        system_message = """Vous êtes MediCare AI, un assistant médical IA pour le Cameroun.

Vos responsabilités:
- Fournir des informations médicales précises et basées sur des preuves
- Expliquer les concepts médicaux en termes simples
- Toujours recommander de consulter un professionnel de santé qualifié
- Être culturellement sensible au contexte camerounais

IMPORTANT: Vous n'êtes PAS un médecin. Ne donnez jamais de diagnostic définitif."""
    else:
        system_message = """You are MediCare AI, a medical AI assistant for Cameroon.

Your responsibilities:
- Provide accurate, evidence-based medical information
- Explain medical concepts in simple terms
- Always recommend consulting qualified healthcare professionals
- Be culturally sensitive to the Cameroonian context

IMPORTANT: You are NOT a doctor. Never provide definitive diagnoses."""

    prompt = ChatPromptTemplate.from_messages([
        ("system", system_message),
        ("user", "{user_question}")
    ])

    parser = StrOutputParser()
    chain = prompt | llm | parser

    return chain

def get_chat_response(message: str, language: str = "en"):
    chain = create_chat_chain(language)
    response = chain.invoke({"user_question": message})
    return response

LCEL 解釋:管道運算prompt | llm | parser會建立一個鏈,其中每個步驟的輸出都會成為下一個步驟的輸入。這比手動呼叫每個元件更簡潔,並且提供了內建的非同步支援、錯誤處理和串流功能。


建立app/chains/analysis_chain.py

該鏈使用PydanticOutputParser產生結構化的 JSON 輸出,這迫使 LLM 輸出與我們的MedicalAnalysis模型匹配的資料。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from app.config import load_google_llm
from app.models.schemas import MedicalAnalysis

def create_analysis_chain(language: str = "en"):
    llm = load_google_llm()
    parser = PydanticOutputParser(pydantic_object=MedicalAnalysis)
    format_instructions = parser.get_format_instructions()

    if language == "fr":
        system_message = """Vous êtes un assistant médical IA analysant des dossiers médicaux.
Fournissez des informations claires, précises et actionnables.
Restez objectif et recommandez toujours une consultation médicale professionnelle."""

        user_template = """Analysez ce dossier médical et fournissez une analyse structurée:

Dossier Médical:
{medical_text}

Contexte Additionnel:
{context}

{format_instructions}

Répondez UNIQUEMENT en JSON valide."""
    else:
        system_message = """You are a medical AI assistant analyzing medical records.
Provide clear, accurate, and actionable insights.
Stay objective and always recommend professional medical consultation."""

        user_template = """Analyze this medical record and provide a structured analysis:

Medical Record:
{medical_text}

Additional Context:
{context}

{format_instructions}

Respond ONLY with valid JSON."""

    prompt = ChatPromptTemplate.from_messages([
        ("system", system_message),
        ("user", user_template)
    ])

    prompt = prompt.partial(format_instructions=format_instructions)
    chain = prompt | llm | parser

    return chain

def analyze_medical_record(text: str, context: str = "", language: str = "en"):
    chain = create_analysis_chain(language)

    try:
        result = chain.invoke({
            "medical_text": text,
            "context": context if context else "No additional context provided"
        })
        return result
    except Exception as e:
        print(f"Analysis error: {e}")
        return MedicalAnalysis(
            summary=f"Analysis completed but encountered formatting issues: {str(e)[:200]}",
            key_findings=["Analysis was performed but results need manual review"],
            recommendations=["Consult with a healthcare professional for detailed interpretation"],
            next_steps=["Schedule appointment with your doctor", "Keep this record for your medical history"]
        )

PydanticOutputParser 的魔力:它會自動產生詳細的 JSON 模式指令,告知 LLM 如何格式化其回應。解析器隨後會驗證輸出並將其轉換為 Python 物件。 try-except 程式碼區塊確保即使 JSON 解析失敗,我們也能始終傳回有用的訊息。


服務層

建立app/services/gemini_service.py

該服務處理特定於視覺的任務,例如從醫療記錄圖像中提取文字。

from langchain_core.messages import HumanMessage
from app.config import load_google_vision_llm
import base64
import json

class GeminiService:
    def __init__(self):
        self.vision_llm = load_google_vision_llm()

    def extract_text_from_image(self, image_bytes: bytes):
        try:
            image_b64 = base64.b64encode(image_bytes).decode('utf-8')

            extraction_prompt = """You are a medical text extractor. Extract ALL text from this medical document/record.

Include:
- Patient information
- Test results
- Doctor's notes
- Prescriptions
- Dates and measurements
- Any handwritten text

Format the output clearly and preserve the structure. If text is unclear, indicate with [unclear].

Extract all text now:"""

            message = HumanMessage(
                content=[
                    {"type": "text", "text": extraction_prompt},
                    {"type": "image_url", "image_url": f"data:image/jpeg;base64,{image_b64}"}
                ]
            )

            response = self.vision_llm.invoke([message])
            return response.content

        except Exception as e:
            raise Exception(f"Image text extraction error: {str(e)}")

    def analyze_image_directly(self, image_bytes: bytes, language: str = "en"):
        try:
            image_b64 = base64.b64encode(image_bytes).decode('utf-8')

            if language == "fr":
                prompt = """Analysez cette image de dossier médical et fournissez une analyse au format JSON avec ces clés:
- summary: Aperçu bref de ce que vous voyez
- key_findings: Liste des résultats importants
- recommendations: Recommandations de santé
- next_steps: Actions suggérées

Répondez UNIQUEMENT en JSON valide."""
            else:
                prompt = """Analyze this medical record image and provide analysis in JSON format with these keys:
- summary: Brief overview of what you see
- key_findings: List of important findings
- recommendations: Health recommendations
- next_steps: Suggested actions

Respond ONLY with valid JSON."""

            message = HumanMessage(
                content=[
                    {"type": "text", "text": prompt},
                    {"type": "image_url", "image_url": f"data:image/jpeg;base64,{image_b64}"}
                ]
            )

            response = self.vision_llm.invoke([message])
            result = json.loads(response.content)
            return result

        except json.JSONDecodeError:
            return {
                "summary": response.content[:500],
                "key_findings": ["Analysis completed - see summary"],
                "recommendations": ["Consult with a healthcare professional"],
                "next_steps": ["Schedule appointment with your doctor"]
            }
        except Exception as e:
            raise Exception(f"Image analysis error: {str(e)}")

gemini_service = GeminiService()

為什麼要使用 base64? LangChain 要求圖片採用 base64 格式,這是將二進位資料編碼為文字的標準方法。 HumanMessage 格式type: "image_url"是 LangChain 處理多模態內容(文字 + 圖片HumanMessage的方式。


建立app/services/tavily_service.py

該服務使用 Tavily 的人工智慧搜尋引擎處理醫學研究搜尋。

from tavily import TavilyClient
from app.config import settings

class TavilyService:
    def __init__(self):
        self.client = TavilyClient(api_key=settings.tavily_api_key)

    def search_medical_research(self, query: str, max_results: int = 5):
        try:
            response = self.client.search(
                query=f"medical research {query}",
                search_depth="advanced",
                max_results=max_results,
                include_domains=[
                    "pubmed.ncbi.nlm.nih.gov",
                    "nih.gov",
                    "who.int",
                    "cdc.gov",
                    "mayoclinic.org",
                    "webmd.com",
                    "healthline.com",
                    "medicalnewstoday.com"
                ]
            )
            return response
        except Exception as e:
            raise Exception(f"Research search error: {str(e)}")

    def format_results(self, raw_results):
        formatted = []
        for result in raw_results.get("results", []):
            formatted.append({
                "title": result.get("title", "Untitled"),
                "url": result.get("url", ""),
                "content": result.get("content", "")[:500],
                "score": result.get("score", 0.0)
            })
        return formatted

tavily_service = TavilyService()

網域過濾:透過指定include_domains ,我們確保結果僅來自可信的醫療來源,避免部落格、論壇或不可靠的網站。


API 路由

建立app/routes/health.py

from fastapi import APIRouter
from app.models.schemas import HealthCheckResponse
from datetime import datetime

router = APIRouter(prefix="/api", tags=["Health"])

@router.get("/health", response_model=HealthCheckResponse)
async def health_check():
    return HealthCheckResponse(
        status="healthy",
        timestamp=datetime.now(),
        message="MediCare AI Backend is running!"
    )

建立app/routes/analysis.py

from fastapi import APIRouter, UploadFile, File, Form, HTTPException
from app.models.schemas import (
    ChatRequest, ChatResponse,
    AnalysisRequest, AnalysisResponse,
    ImageAnalysisResponse
)
from app.chains.chat_chain import get_chat_response
from app.chains.analysis_chain import analyze_medical_record
from app.services.gemini_service import gemini_service
from datetime import datetime

router = APIRouter(prefix="/api", tags=["Analysis"])

@router.post("/chat", response_model=ChatResponse)
async def chat_with_ai(request: ChatRequest):
    try:
        response_text = get_chat_response(
            message=request.message,
            language=request.language
        )
        return ChatResponse(
            response=response_text,
            language=request.language,
            timestamp=datetime.now()
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Chat error: {str(e)}")

@router.post("/analyze-text", response_model=AnalysisResponse)
async def analyze_medical_text(request: AnalysisRequest):
    try:
        analysis = analyze_medical_record(
            text=request.text,
            context=request.context,
            language=request.language
        )

        disclaimer = (
            "This analysis is for informational purposes only. "
            "Always consult qualified healthcare professionals for medical advice."
        )

        return AnalysisResponse(
            summary=analysis.summary,
            key_findings=analysis.key_findings,
            recommendations=analysis.recommendations,
            next_steps=analysis.next_steps,
            disclaimer=disclaimer,
            language=request.language,
            timestamp=datetime.now()
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Analysis error: {str(e)}")

@router.post("/analyze-image", response_model=ImageAnalysisResponse)
async def analyze_medical_image(
    file: UploadFile = File(...),
    language: str = Form(default="en"),
    extract_text_only: bool = Form(default=False)
):
    if not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="File must be an image")

    try:
        image_bytes = await file.read()
        extracted_text = gemini_service.extract_text_from_image(image_bytes)

        if extract_text_only:
            return ImageAnalysisResponse(
                extracted_text=extracted_text,
                analysis=AnalysisResponse(
                    summary="Text extraction completed",
                    key_findings=[],
                    recommendations=[],
                    next_steps=["Review the extracted text", "Analyze if needed"],
                    disclaimer="Text extraction only - no analysis performed",
                    language=language,
                    timestamp=datetime.now()
                )
            )

        analysis = analyze_medical_record(text=extracted_text, language=language)
        disclaimer = (
            "This analysis is for informational purposes only. "
            "Always consult qualified healthcare professionals for medical advice."
        )

        return ImageAnalysisResponse(
            extracted_text=extracted_text,
            analysis=AnalysisResponse(
                summary=analysis.summary,
                key_findings=analysis.key_findings,
                recommendations=analysis.recommendations,
                next_steps=analysis.next_steps,
                disclaimer=disclaimer,
                language=language,
                timestamp=datetime.now()
            )
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Image analysis error: {str(e)}")

@router.post("/extract-text")
async def extract_text_from_image(file: UploadFile = File(...)):
    if not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="File must be an image")

    try:
        image_bytes = await file.read()
        extracted_text = gemini_service.extract_text_from_image(image_bytes)
        return {
            "extracted_text": extracted_text,
            "timestamp": datetime.now()
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Text extraction error: {str(e)}")

兩步驟影像處理:我們首先使用 Gemini Vision (OCR) 提取文本,然後使用我們的 LangChain 分析鏈對文本進行分析。這比單步分析能提供更可靠的結果。


建立app/routes/research.py

from fastapi import APIRouter, HTTPException
from app.models.schemas import ResearchRequest, ResearchResponse, ResearchResult
from app.services.tavily_service import tavily_service
from app.chains.chat_chain import get_chat_response
from datetime import datetime

router = APIRouter(prefix="/api", tags=["Research"])

@router.post("/research", response_model=ResearchResponse)
async def search_medical_research(request: ResearchRequest):
    try:
        raw_results = tavily_service.search_medical_research(
            query=request.query,
            max_results=request.max_results
        )

        formatted_results = tavily_service.format_results(raw_results)

        results_text = "\n\n".join([
            f"Source: {r['title']}\n{r['content']}"
            for r in formatted_results[:3]
        ])

        summary_prompt = f"""Based on these medical research results, provide a brief summary in 2-3 sentences:

{results_text}

Focus on the key takeaways and most important information."""

        summary = get_chat_response(summary_prompt, request.language)

        research_results = [
            ResearchResult(
                title=r["title"],
                url=r["url"],
                content=r["content"],
                score=r["score"]
            )
            for r in formatted_results
        ]

        return ResearchResponse(
            query=request.query,
            results=research_results,
            summary=summary,
            timestamp=datetime.now()
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Research error: {str(e)}")

為什麼要將 Tavily 與 LangChain 結合? Tavily 可以從可靠的醫學資料庫中找到準確且有引證的資料,而 LangChain 則產生易於閱讀的摘要。使用者既可以獲得詳細的資料來源,又可以獲得快速概覽。


主要應用

建立app/main.py

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.routes import health, analysis, research

app = FastAPI(
    title="MediCare AI Backend",
    description="Medical AI Assistant API for Cameroon - Powered by LangChain",
    version="2.0.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins_list,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(health.router)
app.include_router(analysis.router)
app.include_router(research.router)

@app.get("/")
async def root():
    return {
        "message": "Welcome to MediCare AI Backend",
        "version": "2.0.0",
        "powered_by": "LangChain + Google Gemini",
        "docs": "/docs",
        "status": "running"
    }

CORS 解釋:跨域資源共享允許你的前端(React、Next.js)向後端 API 發出請求。如果沒有 CORS 中間件,瀏覽器會出於安全性原因阻止這些請求。


執行應用程式

最終設定步驟

  1. 確保你位於backend目錄中

  2. 啟動你的虛擬環境

  3. 驗證您的.env檔案是否具有有效的 API 金鑰

  4. 啟動伺服器:

uvicorn app.main:app --reload

--reload標誌可在開發過程中啟用熱重加載,因此當您變更程式碼時伺服器會自動重新啟動。

您應該看到如下輸出:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process
INFO:     Application startup complete.

存取 API

打開瀏覽器:

  1. 互動式文件http://localhost:8000/docs(Swagger UI)

  2. 替代文件http://localhost:8000/redoc (ReDoc)

  3. 根端點http://localhost:8000/

/docs端點特別有用 - 它提供了一個互動式介面,您可以在瀏覽器中直接測試所有端點。


使用 Swagger UI 測試您的 API

透過 FastAPI 的 Swagger UI 自動產生的互動式文件,測試 API 變得前所未有的輕鬆。內建的測試介面讓您無需編寫任何程式碼或使用命令列工具即可探索和測試每個端點。

造訪 Swagger UI

伺服器執行後,打開瀏覽器並導航至:

http://localhost:8000/docs

您將看到一個美觀的互動式介面,其中列出了您所有的 API 端點,並按標籤(健康、分析、研究)進行組織。此文件會根據您的程式碼自動生成,包含所有請求/回應模型、欄位驗證和描述。

了解 Swagger 介面

Swagger UI 顯示:

  • 端點路徑- 每個 API 路由的 URL

  • HTTP 方法- GET、POST 等,採用顏色編碼,以便於辨識

  • 請求模式- 包含欄位類型和驗證規則的預期輸入格式

  • 回應模式- 您將收到的內容,包括狀態程式碼

  • 試試看- 互動式測試功能

測試每個端點

測試 1:健康檢查(GET)

讓我們從最簡單的端點開始驗證一切是否正常:

  1. 找到“健康”部分

  2. 點擊GET /api/health展開它

  3. 點選「試用」按鈕(右上)

  4. 點擊藍色的「執行」按鈕

  5. 向下滾動查看回复

您應該會看到如下 JSON 回應:

{
  "status": "healthy",
  "timestamp": "2025-09-30T14:23:45.123456",
  "message": "MediCare AI Backend is running!"
}

這告訴您:您的 FastAPI 伺服器正在正確執行,並且基本路由正在正常工作。

測驗 2:醫療聊天(POST)

現在讓我們來測試一下對話式AI功能:

  1. “分析”部分下找到POST /api/chat

  2. 點擊展開,然後點擊“試用”

  3. 您將看到一個帶有範例 JSON 的請求正文文字區域

  4. 用您自己的問題替換範例:

{
  "message": "What are the early signs of diabetes?",
  "language": "en"
}
  1. 點擊“執行”

  2. 檢查下面的回复

預期響應結構:

{
  "response": "Early signs of diabetes include frequent urination...",
  "language": "en",
  "timestamp": "2025-09-30T14:25:30.456789"
}

也試試看:透過改變"language": "fr"並用法語詢問同樣的問題來測試雙語能力。

測驗3:文本分析(POST)

此端點分析醫學文本並提供結構化的見解:

  1. 導航到POST /api/analyze-text

  2. 點擊“試用”

  3. 輸入樣本醫療資料:

{
  "text": "Patient presents with elevated blood pressure (150/95 mmHg), fasting blood sugar of 126 mg/dL, and BMI of 32. Patient reports frequent headaches and fatigue.",
  "context": "45-year-old male, sedentary lifestyle",
  "language": "en"
}
  1. 點擊“執行”

在回應中要注意什麼:

  • 摘要- 醫學發現的簡要概述

  • key_findings - 重要觀察結果的結構化列表

  • 建議- 基於資料的健康建議

  • next_steps - 病人可採取的行動

  • 免責聲明- 法律/醫療免責聲明

這展示了 LangChain 的PydanticOutputParser實際作用——強制 AI 每次都會返回完美結構化的 JSON。

測試4:圖像文字提取(POST)

這就是 Gemini Vision 的閃光點——從醫療記錄、處方或實驗室報告中提取文本:

  1. 查找POST /api/extract-text

  2. 點擊“試用”

  3. 您會看到一個檔案上傳按鈕 - 點擊即可選擇圖像

  4. 選擇醫療文件影像(處方、實驗室報告,甚至手寫筆記)

  5. 點擊“執行”

  6. 等待幾秒鐘進行處理

回覆格式:

{
  "extracted_text": "Patient Name: John Doe\nDate: 2025-09-28\nBlood Test Results:\nHemoglobin: 14.2 g/dL\nWhite Blood Cells: 7,500/μL\n...",
  "timestamp": "2025-09-30T14:30:15.789012"
}

專業提示:使用不同類型的影像(列印文件、手寫處方、醫療記錄照片)進行測試,以查看 OCR 功能的實際效果。

測試 5:完整影像分析(POST)

這將文本提取與人工智慧分析結合起來,實現完整的醫療記錄解釋:

  1. 前往POST /api/analyze-image

  2. 點擊“試用”

  3. 使用檔案參數上傳病歷影像

  4. 語言設定為“en”(或法語的“fr”)

  5. extract_text_only保留為false (未選取)

  6. 點擊“執行”

響應結構:

{
  "extracted_text": "The complete OCR text from your image...",
  "analysis": {
    "summary": "This blood test shows...",
    "key_findings": [
      "Hemoglobin levels are within normal range",
      "Slightly elevated glucose levels detected"
    ],
    "recommendations": [
      "Monitor blood sugar levels regularly",
      "Consider dietary modifications"
    ],
    "next_steps": [
      "Schedule follow-up in 3 months",
      "Consult with an endocrinologist"
    ],
    "disclaimer": "This analysis is for informational purposes only...",
    "language": "en",
    "timestamp": "2025-09-30T14:35:42.345678"
  }
}

幕後發生的事:

  1. Gemini Vision 從圖像中提取所有文字(OCR)

  2. 提取的文字被輸入到分析鏈

  3. LangChain 將輸出格式化為結構化 JSON

  4. 您可以獲得原始文本和智慧分析

測試 6:醫學研究搜尋(POST)

此端點搜尋可信賴的醫療資料庫並總結發現:

  1. 找到POST /api/research

  2. 點擊“試用”

  3. 輸入研究查詢:

{
  "query": "latest treatments for hypertension",
  "max_results": 5,
  "language": "en"
}
  1. 點擊“執行”

  2. 等待搜尋完成(可能需要 5-10 秒)

響應細分:

{
  "query": "latest treatments for hypertension",
  "results": [
    {
      "title": "New Guidelines for Hypertension Treatment",
      "url": "https://pubmed.ncbi.nlm.nih.gov/...",
      "content": "Recent studies show that combination therapy...",
      "score": 0.95
    }
  ],
  "summary": "Recent research indicates that combination therapy with ACE inhibitors and calcium channel blockers shows promising results...",
  "timestamp": "2025-09-30T14:40:18.901234"
}

主要特點:

  • 可信來源-僅來自醫學資料庫(PubMed、WHO、CDC 等)的結果

  • 相關性分數- 分數越高,結果越相關

  • AI 產生的摘要- LangChain 讀取排名靠前的結果並建立簡潔的摘要

  • 來源引用- 每個結果都包含原始 URL

理解回應程式碼

Swagger UI 顯示每個回應的 HTTP 狀態碼:

  • 200 OK - 請求成功,這是您的資料

  • 400 錯誤請求- 您的輸入未通過驗證(檢查欄位要求)

  • 422 無法處理的實體- JSON 結構錯誤或缺少必填字段

  • 500 內部伺服器錯誤- 伺服器出現問題(檢查日誌)

當您看到錯誤時,Swagger UI 會顯示錯誤詳細訊息,使偵錯變得簡單。

進階測驗技巧

測試場驗證

FastAPI 會根據你的 Pydantic 模型自動驗證輸入。試著故意破壞一些事情:

  1. 測試最小長度:嘗試僅使用“hi”發送聊天訊息(低於最小長度)

  2. 測試最大長度:傳送一則 2000 個字元的訊息(超過 max_length)

  3. 測試無效語言:將語言設為“es”而不是“en”或“fr”

  4. 測試無效的文件類型:將 PDF 上傳到圖像端點

每個測試都會回傳詳細的驗證錯誤,準確地解釋出了什麼問題。

測試替代文件

FastAPI 也正在http://localhost:8000/redoc產生 ReDoc 文件。這提供了:

  • 更清晰、更易讀的佈局

  • 大型 API 的導航更加輕鬆

  • 更適合與前端開發人員分享

  • 無測試能力(僅限文件)

使用 Swagger 的模式模型

點擊 Swagger UI 頁面底部的任意Schema ,即可查看請求和回應的完整資料結構。這對於建立前端或理解 API 契約非常有用。

監控伺服器日誌

在 Swagger UI 中測試時,觀察 uvicorn 執行的終端。你會看到:

  • 傳入的 HTTP 方法和路徑請求

  • 回應狀態程式碼

  • 任何錯誤回溯

  • 每個請求的處理時間

這種即時回饋可以幫助您了解內部發生的情況。

常見測試場景

場景 1:患者症狀檢查器

  1. 使用/api/chat進行聊天:“我持續咳嗽和發燒 3 天了”

  2. 觀察人工智慧如何提供資訊並推薦專業諮詢

情境 2:實驗報告分析

  1. 將血液檢測圖像上傳到/api/analyze-image

  2. 審查提取的值和 AI 解釋

  3. 檢查它是否標記了key_findings中的異常結果

場景三:就診前做好調查

  1. 使用/api/research進行“2 型糖尿病的治療方案”

  2. 查閱醫學期刊的引用來源

  3. 閱讀 AI 產生的摘要以快速理解

場景 4:藥物訊息

  1. 將處方圖像上傳到/api/extract-text

  2. 複製提取的藥物名稱

  3. 使用/api/research了解每種藥物

排除測試失敗故障

“未找到模組”錯誤:

  • 確保所有相依性都已安裝: pip install -r requirements.txt

  • 驗證虛擬環境是否已激活

「無效的 API 金鑰」錯誤:

  • 檢查你的.env檔案是否有有效的密鑰

  • 如有必要,重新產生金鑰(先決條件部分中的連結)

圖片上傳失敗:

  • 驗證檔案是否確實是映像(JPEG、PNG)

  • 檢查檔案大小是否太大(預設限制:10MB)

  • 確保檔案未損壞

反應時間慢:

  • 視覺和研究終點需要 5-15 秒(正常)

  • 檢查您的網路連線以進行 Tavily 搜尋

  • 如果重複測試,請監控 API 速率限制

JSON解析錯誤:

  • 有時 LLM 會傳回無效的 JSON

  • 程式碼中有針對此問題的後備處理程序

  • 嘗試重新表達您的查詢以獲得更好的結果


學習資源

現在您已經建立並測試了可投入生產的 AI 醫療助理,這裡有精選的資源可以幫助您加深理解並擴展技能。

官方文件

核心技術

FastAPI - 用於建立 API 的現代、快速 Web 框架

LangChain - 由 LLM 支援的應用程式開發框架

Pydantic - 使用 Python 類型註解進行資料驗證

人工智慧服務

Google Gemini - 多模式 AI 模型(文字 + 視覺)

Tavily AI - 開發人員的人工智慧搜尋 API

影片教學

FastAPI 精通

LangChain 深度探索

人工智慧集成

互動學習平台

LangChain 學院(免費)

Google AI Studio (免費)

FastAPI 互動式教學課程(免費)

書籍和書面指南

Martin Kleppmann 撰寫的《設計資料密集型應用程式》

  • 了解系統架構

  • 建立可擴展的後端

  • 對於生產系統至關重要

《快速工程指南》 (免費線上)

《程式設計師修練之道》作者:安迪‧亨特與戴夫‧湯瑪斯

  • 軟體工程最佳實踐

  • 編寫可維護的程式碼

  • 生涯參考書

GitHub 儲存庫研究

LangChain 模板

FastAPI 最佳實踐

生產就緒的 FastAPI

雙子座食譜

社群和論壇

LangChain Discord

FastAPI GitHub 討論

r/LangChain(Reddit)

堆疊溢位

  • 搜尋: [fastapi][langchain][pydantic]

  • 詢問具體的技術問題

進階主題探索

掌握基礎知識後

1. 向量資料庫和 RAG

  • 將醫療文件儲存在向量資料庫中

  • 實現檢索增強生成

  • LangChain + Pinecone/Chroma/FAISS 教學

2. 串流響應

  • 即時產生 AI 回應

  • FastAPI 伺服器發送事件(SSE)

  • LangChain 流式回調

3. 身份驗證和授權

  • 使用 JWT 令牌保護您的 API

  • 使用者管理和權限

  • FastAPI 安全文件

4. 生產部署

  • Docker 容器化

  • 雲端部署(AWS、GCP、Azure)

  • 使用 GitHub Actions 的 CI/CD 管道

5. 監控與可觀察性

  • LangSmith 用於 LangChain 追踪

  • 應用程式效能監控(APM)

  • 使用 Sentry 進行錯誤追蹤

6. 測試與品質保證

  • 使用 pytest 進行單元測試

  • API 端點的整合測試

  • LangChain 測試實用程式

關注這些專家

Twitter/X:

  • @langchainai - LangChain 更新

  • @tiangolo——FastAPI 創作者

  • @samuelcolvin——Pydantic 創作者

  • @GoogleDeepMind - 雙子座新聞

YouTube頻道:

  • LangChain官方

  • ArjanCodes(Python最佳實踐)

  • Krish Naik(AI 教學)

  • Patrick Loeber(Python 和 AI)

免費 API 積分和工具

Google Cloud 免費套餐

塔維利·艾

Vercel/鐵路/渲染

  • 業餘愛好專案的免費託管

  • 免費部署 FastAPI 應用

  • 輕鬆整合 CI/CD


最後的建議

從小處著手,從大處著眼:從簡單的功能著手,逐步提升複雜度。我們所建構的架構能夠從原型擴展到生產環境。

閱讀他人程式碼:提升程式碼品質的最佳方法是研究生產程式碼庫。以上列出的所有程式碼庫都是優秀的學習資源。

公開建立:在 Twitter、LinkedIn 或 GitHub 上分享你的進度。社群會給予你支持,你會得到寶貴的回饋。

專注於基礎:在追逐最新的 AI 模型或框架之前,先掌握 Python、HTTP、API 和資料結構。這些永遠不會過時。

持續學習:人工智慧科技日新月異。每週抽出時間閱讀文件、觀看教學課程或嘗試新功能。

回饋貢獻:一旦適應了,就可以為 LangChain 或 FastAPI 等開源專案做出貢獻。這是深入掌握一門技術的最佳途徑。

Github 倉庫

程式碼在這裡_______________

Github 倉庫

祝您在建立人工智慧應用程式的旅途中好運!


原文出處:https://dev.to/fonyuygita/building-a-production-ready-medical-ai-assistant-with-python-fastapi-tavili-gemini-langchain-5693


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝17   💬8   ❤️4
680
🥈
我愛JS
📝3   💬11   ❤️7
214
🥉
AppleLily
📝1   💬3   ❤️1
69
#4
御魂
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付