{% vimeo 1123091554 %}
本指南將指導您建立MediCare AI - 一款可投入生產的醫療助理,它可以:
用英語和法語回答醫療問題
從醫療記錄(手寫或列印)中提取文本
分析實驗室結果並提供見解
搜尋最新醫學研究
提供個人化的健康建議
我們將LangChain 表達語言 (LCEL)與Google Gemini 2.0結合使用 - 最新的 AI 模型,完全免費。
醫療保健服務有限—許多農村地區缺乏醫生
語言障礙-英語和法語人士需要支持
醫學素養-人們難以理解醫療報告
成本-醫療諮詢費用昂貴
免費的人工智慧醫療助理,可離線工作(本地部署後),懂英語和法語,簡單解釋醫學術語,提供基於證據的訊息,並尊重隱私。
| 技術 | 目的 | 我們為什麼選擇它 |
|------------|---------|-----------------|
| 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 金鑰:在此處獲取
mkdir medicare-ai
cd medicare-ai
mkdir backend
cd backend
mkdir -p app/routes app/services app/chains app/models
python -m venv venv
# Activate it
# On Windows:
venv\Scripts\activate
# On Mac/Linux:
source venv/bin/activate
建立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 函數:一個用於溫度較高的聊天(更具創意),另一個用於溫度較低的視覺(更一致的文字擷取)。
建立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_length
、 max_length
和pattern
的有效性。 LangChain 的PydanticOutputParser
使用MedicalAnalysis
模型強制 AI 輸出結構化的 JSON 格式。巢狀模型(例如包含AnalysisResponse
的ImageAnalysisResponse
允許使用複雜的資料結構。
建立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
,我們確保結果僅來自可信的醫療來源,避免部落格、論壇或不可靠的網站。
建立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 中間件,瀏覽器會出於安全性原因阻止這些請求。
確保你位於backend
目錄中
啟動你的虛擬環境
驗證您的.env
檔案是否具有有效的 API 金鑰
啟動伺服器:
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.
打開瀏覽器:
互動式文件:http://localhost:8000/docs(Swagger UI)
替代文件:http://localhost:8000/redoc (ReDoc)
/docs
端點特別有用 - 它提供了一個互動式介面,您可以在瀏覽器中直接測試所有端點。
透過 FastAPI 的 Swagger UI 自動產生的互動式文件,測試 API 變得前所未有的輕鬆。內建的測試介面讓您無需編寫任何程式碼或使用命令列工具即可探索和測試每個端點。
伺服器執行後,打開瀏覽器並導航至:
您將看到一個美觀的互動式介面,其中列出了您所有的 API 端點,並按標籤(健康、分析、研究)進行組織。此文件會根據您的程式碼自動生成,包含所有請求/回應模型、欄位驗證和描述。
Swagger UI 顯示:
端點路徑- 每個 API 路由的 URL
HTTP 方法- GET、POST 等,採用顏色編碼,以便於辨識
請求模式- 包含欄位類型和驗證規則的預期輸入格式
回應模式- 您將收到的內容,包括狀態程式碼
試試看- 互動式測試功能
讓我們從最簡單的端點開始驗證一切是否正常:
找到“健康”部分
點擊GET /api/health展開它
點選「試用」按鈕(右上)
點擊藍色的「執行」按鈕
向下滾動查看回复
您應該會看到如下 JSON 回應:
{
"status": "healthy",
"timestamp": "2025-09-30T14:23:45.123456",
"message": "MediCare AI Backend is running!"
}
這告訴您:您的 FastAPI 伺服器正在正確執行,並且基本路由正在正常工作。
現在讓我們來測試一下對話式AI功能:
在“分析”部分下找到POST /api/chat
點擊展開,然後點擊“試用”
您將看到一個帶有範例 JSON 的請求正文文字區域
用您自己的問題替換範例:
{
"message": "What are the early signs of diabetes?",
"language": "en"
}
點擊“執行”
檢查下面的回复
預期響應結構:
{
"response": "Early signs of diabetes include frequent urination...",
"language": "en",
"timestamp": "2025-09-30T14:25:30.456789"
}
也試試看:透過改變"language": "fr"
並用法語詢問同樣的問題來測試雙語能力。
此端點分析醫學文本並提供結構化的見解:
導航到POST /api/analyze-text
點擊“試用”
輸入樣本醫療資料:
{
"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"
}
在回應中要注意什麼:
摘要- 醫學發現的簡要概述
key_findings - 重要觀察結果的結構化列表
建議- 基於資料的健康建議
next_steps - 病人可採取的行動
免責聲明- 法律/醫療免責聲明
這展示了 LangChain 的PydanticOutputParser
實際作用——強制 AI 每次都會返回完美結構化的 JSON。
這就是 Gemini Vision 的閃光點——從醫療記錄、處方或實驗室報告中提取文本:
查找POST /api/extract-text
點擊“試用”
您會看到一個檔案上傳按鈕 - 點擊即可選擇圖像
選擇醫療文件影像(處方、實驗室報告,甚至手寫筆記)
點擊“執行”
等待幾秒鐘進行處理
回覆格式:
{
"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 功能的實際效果。
這將文本提取與人工智慧分析結合起來,實現完整的醫療記錄解釋:
前往POST /api/analyze-image
點擊“試用”
使用檔案參數上傳病歷影像
將語言設定為“en”(或法語的“fr”)
將extract_text_only保留為false
(未選取)
點擊“執行”
響應結構:
{
"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"
}
}
幕後發生的事:
Gemini Vision 從圖像中提取所有文字(OCR)
提取的文字被輸入到分析鏈
LangChain 將輸出格式化為結構化 JSON
您可以獲得原始文本和智慧分析
此端點搜尋可信賴的醫療資料庫並總結發現:
找到POST /api/research
點擊“試用”
輸入研究查詢:
{
"query": "latest treatments for hypertension",
"max_results": 5,
"language": "en"
}
點擊“執行”
等待搜尋完成(可能需要 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 模型自動驗證輸入。試著故意破壞一些事情:
測試最小長度:嘗試僅使用“hi”發送聊天訊息(低於最小長度)
測試最大長度:傳送一則 2000 個字元的訊息(超過 max_length)
測試無效語言:將語言設為“es”而不是“en”或“fr”
測試無效的文件類型:將 PDF 上傳到圖像端點
每個測試都會回傳詳細的驗證錯誤,準確地解釋出了什麼問題。
FastAPI 也正在http://localhost:8000/redoc產生 ReDoc 文件。這提供了:
更清晰、更易讀的佈局
大型 API 的導航更加輕鬆
更適合與前端開發人員分享
無測試能力(僅限文件)
點擊 Swagger UI 頁面底部的任意Schema ,即可查看請求和回應的完整資料結構。這對於建立前端或理解 API 契約非常有用。
在 Swagger UI 中測試時,觀察 uvicorn 執行的終端。你會看到:
傳入的 HTTP 方法和路徑請求
回應狀態程式碼
任何錯誤回溯
每個請求的處理時間
這種即時回饋可以幫助您了解內部發生的情況。
使用/api/chat
進行聊天:“我持續咳嗽和發燒 3 天了”
觀察人工智慧如何提供資訊並推薦專業諮詢
將血液檢測圖像上傳到/api/analyze-image
審查提取的值和 AI 解釋
檢查它是否標記了key_findings
中的異常結果
使用/api/research
進行“2 型糖尿病的治療方案”
查閱醫學期刊的引用來源
閱讀 AI 產生的摘要以快速理解
將處方圖像上傳到/api/extract-text
複製提取的藥物名稱
使用/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 支援的應用程式開發框架
文件:https://python.langchain.com/docs/get\_started/introduction
LCEL 指南:https://python.langchain.com/docs/expression\_language/
Pydantic - 使用 Python 類型註解進行資料驗證
Google Gemini - 多模式 AI 模型(文字 + 視覺)
API 文件:https://ai.google.dev/docs
Gemini API 手冊:https://github.com/google-gemini/cookbook
Tavily AI - 開發人員的人工智慧搜尋 API
Python SDK:https://github.com/tavily-ai/tavily-python
freeCodeCamp 的FastAPI 完整課程(4 小時):https://www.youtube.com/watch?v=0sOvCWFmrtA
ArjanCodes使用 FastAPI 建立生產 API :https://www.youtube.com/watch?v=SORiTsvnU28
與 Roby 一起編碼的FastAPI + React 全端:https://www.youtube.com/watch?v=yWThSl1LVTs
Krish Naik 的LangChain 速成課程:https://www.youtube.com/watch?v=\_FpT1cwcSLg
LangChain 的LangChain 表達語言 (LCEL) :https://www.youtube.com/watch?v=gKUoHhHvAZE
Sam Witteveen 撰寫的《建構生產 LangChain 應用程式》 :https://www.youtube.com/watch?v=k8TzFKmj56A
Google Gemini API 教學:https://www.youtube.com/watch?v=9PKbgWDrN8c
使用 Gemini 建立 AI 應用程式:https://www.youtube.com/watch?v=5qiJGY9vshQ
視覺 LLM 的多模式 AI :https://www.youtube.com/watch?v=wZcxuS8V\_N8
LangChain 學院(免費)
LangChain 基礎知識互動課程
使用真實程式碼進行實踐練習
Google AI Studio (免費)
直接使用 Gemini 模型進行實驗
無需編寫程式碼即可測試提示
FastAPI 互動式教學課程(免費)
透過建構實際專案來學習
內建於 FastAPI 文件中
Martin Kleppmann 撰寫的《設計資料密集型應用程式》
了解系統架構
建立可擴展的後端
對於生產系統至關重要
《快速工程指南》 (免費線上)
AI提示綜合指南
LangChain 特有的技術
《程式設計師修練之道》作者:安迪‧亨特與戴夫‧湯瑪斯
軟體工程最佳實踐
編寫可維護的程式碼
生涯參考書
LangChain 模板
FastAPI 最佳實踐
生產就緒的 FastAPI
雙子座食譜
Gemini API 的程式碼範例
LangChain Discord
活躍的開發者社群
取得有關特定問題的協助
FastAPI GitHub 討論
官方 FastAPI 社群
功能請求和討論
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)
Google Cloud 免費套餐
新用戶可享 300 美元信用額度
提供免費的 Gemini API 層
塔維利·艾
每月 1000 次免費搜尋
無需信用卡
Vercel/鐵路/渲染
業餘愛好專案的免費託管
免費部署 FastAPI 應用
輕鬆整合 CI/CD
從小處著手,從大處著眼:從簡單的功能著手,逐步提升複雜度。我們所建構的架構能夠從原型擴展到生產環境。
閱讀他人程式碼:提升程式碼品質的最佳方法是研究生產程式碼庫。以上列出的所有程式碼庫都是優秀的學習資源。
公開建立:在 Twitter、LinkedIn 或 GitHub 上分享你的進度。社群會給予你支持,你會得到寶貴的回饋。
專注於基礎:在追逐最新的 AI 模型或框架之前,先掌握 Python、HTTP、API 和資料結構。這些永遠不會過時。
持續學習:人工智慧科技日新月異。每週抽出時間閱讀文件、觀看教學課程或嘗試新功能。
回饋貢獻:一旦適應了,就可以為 LangChain 或 FastAPI 等開源專案做出貢獻。這是深入掌握一門技術的最佳途徑。
程式碼在這裡_______________
祝您在建立人工智慧應用程式的旅途中好運!