首次提交by MimoCode
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class VisionResult(BaseModel):
|
||||
generic_name: Optional[str] = None
|
||||
brand_name: Optional[str] = None
|
||||
manufacturer: Optional[str] = None
|
||||
specification: Optional[str] = None
|
||||
|
||||
|
||||
class DateResult(BaseModel):
|
||||
production_date: Optional[str] = None
|
||||
expiry_date: Optional[str] = None
|
||||
|
||||
|
||||
class LeafletResult(BaseModel):
|
||||
indications: str
|
||||
adult_dose: str
|
||||
child_dose: Optional[str] = None
|
||||
contraindications: str
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class VisionProvider(ABC):
|
||||
@abstractmethod
|
||||
async def recognize_medicine(self, image_bytes: bytes) -> VisionResult:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def recognize_dates(self, image_bytes: bytes) -> DateResult:
|
||||
pass
|
||||
|
||||
|
||||
class TextProvider(ABC):
|
||||
@abstractmethod
|
||||
async def summarize_leaflet(self, text: str) -> LeafletResult:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def natural_language_search(self, query: str, medicines: list) -> list:
|
||||
pass
|
||||
@@ -0,0 +1,34 @@
|
||||
from typing import Optional
|
||||
from app.ai.base import VisionProvider, TextProvider
|
||||
|
||||
|
||||
class AIManager:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if self._initialized:
|
||||
return
|
||||
self.vision_providers: dict[str, VisionProvider] = {}
|
||||
self.text_providers: dict[str, TextProvider] = {}
|
||||
self._initialized = True
|
||||
|
||||
def register_vision_provider(self, name: str, provider: VisionProvider):
|
||||
self.vision_providers[name] = provider
|
||||
|
||||
def register_text_provider(self, name: str, provider: TextProvider):
|
||||
self.text_providers[name] = provider
|
||||
|
||||
def get_vision_provider(self, name: str) -> Optional[VisionProvider]:
|
||||
return self.vision_providers.get(name)
|
||||
|
||||
def get_text_provider(self, name: str) -> Optional[TextProvider]:
|
||||
return self.text_providers.get(name)
|
||||
|
||||
|
||||
ai_manager = AIManager()
|
||||
@@ -0,0 +1,153 @@
|
||||
import base64
|
||||
import json
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
from app.ai.base import VisionProvider, TextProvider, VisionResult, DateResult, LeafletResult
|
||||
from app.config import settings
|
||||
|
||||
|
||||
class OpenAIVisionProvider(VisionProvider):
|
||||
def __init__(self):
|
||||
self.client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
|
||||
self.model = settings.OPENAI_MODEL
|
||||
|
||||
async def recognize_medicine(self, image_bytes: bytes) -> VisionResult:
|
||||
base64_image = base64.b64encode(image_bytes).decode('utf-8')
|
||||
|
||||
response = await self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": """请识别这张药品包装图片中的信息,返回JSON格式:
|
||||
{
|
||||
"generic_name": "通用名称",
|
||||
"brand_name": "商品名称",
|
||||
"manufacturer": "生产厂家",
|
||||
"specification": "规格"
|
||||
}
|
||||
只提取图片中真实出现的内容,不要猜测。"""
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
|
||||
result = json.loads(response.choices[0].message.content)
|
||||
return VisionResult(**result)
|
||||
|
||||
async def recognize_dates(self, image_bytes: bytes) -> DateResult:
|
||||
base64_image = base64.b64encode(image_bytes).decode('utf-8')
|
||||
|
||||
response = await self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": """请识别这张图片中的日期信息,返回JSON格式:
|
||||
{
|
||||
"production_date": "生产日期(YYYY-MM-DD格式,如果无法识别则为null)",
|
||||
"expiry_date": "有效期/过期日期(YYYY-MM-DD格式,如果无法识别则为null)"
|
||||
}
|
||||
只提取图片中真实出现的日期,不要猜测或推理。"""
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
|
||||
result = json.loads(response.choices[0].message.content)
|
||||
return DateResult(**result)
|
||||
|
||||
|
||||
class OpenAITextProvider(TextProvider):
|
||||
def __init__(self):
|
||||
self.client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
|
||||
self.model = "gpt-4"
|
||||
|
||||
async def summarize_leaflet(self, text: str) -> LeafletResult:
|
||||
response = await self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "你是一个医疗信息提取助手。请从药品说明书中提取关键信息。"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"""请从以下药品说明书中提取关键信息,返回JSON格式:
|
||||
{{
|
||||
"indications": "适应症",
|
||||
"adult_dose": "成人用法用量",
|
||||
"child_dose": "儿童用法用量(如果没有则为null)",
|
||||
"contraindications": "禁忌",
|
||||
"notes": "注意事项(如果有)"
|
||||
}}
|
||||
|
||||
说明书内容:
|
||||
{text}"""
|
||||
}
|
||||
],
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
|
||||
result = json.loads(response.choices[0].message.content)
|
||||
return LeafletResult(**result)
|
||||
|
||||
async def natural_language_search(self, query: str, medicines: list) -> list:
|
||||
medicines_text = "\n".join([
|
||||
f"- {m['name']}: {m.get('indications', '')}"
|
||||
for m in medicines
|
||||
])
|
||||
|
||||
response = await self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "你是一个药品搜索助手。根据用户描述的症状,从药品列表中找出可能适用的药品。"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"""用户描述:{query}
|
||||
|
||||
可用药品列表:
|
||||
{medicines_text}
|
||||
|
||||
请返回JSON格式的搜索结果:
|
||||
{{
|
||||
"results": [
|
||||
{{
|
||||
"medicine_id": 药品ID,
|
||||
"name": "药品名称",
|
||||
"reason": "匹配原因"
|
||||
}}
|
||||
]
|
||||
}}"""
|
||||
}
|
||||
],
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
|
||||
result = json.loads(response.choices[0].message.content)
|
||||
return result.get('results', [])
|
||||
Reference in New Issue
Block a user