6 Commits

5 changed files with 160 additions and 21 deletions

View File

@@ -1,4 +1,5 @@
import datetime
import json
import logging
from langchain.prompts import ChatPromptTemplate
@@ -12,6 +13,7 @@ class ExtractPeopleAgent(BaseAgent):
self.prompt = ChatPromptTemplate.from_messages([
(
"system",
f"现在是{datetime.datetime.now().strftime('%Y-%m-%d')}"
"你是一个专业的婚姻、交友助手,善于从一段文字描述中,精确获取用户的以下信息:\n"
"姓名 name\n"
"性别 gender\n"
@@ -19,7 +21,9 @@ class ExtractPeopleAgent(BaseAgent):
"身高(cm) height\n"
"婚姻状况 marital_status\n"
"择偶要求 match_requirement\n"
"以上信息需要严格按照 JSON 格式输出 字段名与条目中英文保持一致。\n"
"以上信息需要严格按照 JSON 格式输出 字段名与条目中英文保持一致; 若未识别到以上的某项,则不返回该字段,不要自行填写“未知”,“未填写”等类似字眼\n"
"其中,'年龄 age''身高(cm) height' 必须是一个整数,不能是一个字符串;\n"
"并且,'性别 gender' 根据识别结果,必须从 男,女,未知 三选一填写。\n"
"除了上述基本信息,还有一个字段\n"
"个人介绍 introduction\n"
"其余的信息需要按照字典的方式进行提炼和总结,都放在个人介绍字段中\n"
@@ -34,8 +38,15 @@ class ExtractPeopleAgent(BaseAgent):
response = self.llm.invoke(prompt)
logging.info(f"llm response: {response.content}")
try:
return People.from_dict(json.loads(response.content))
people = People.from_dict(json.loads(response.content))
err = people.validate()
if not err.success:
raise ValueError(f"Failed to validate people info: {err.info}")
return people
except json.JSONDecodeError:
logging.error(f"Failed to parse JSON from LLM response: {response.content}")
return None
except ValueError as e:
logging.error(f"Failed to validate people info: {e}")
return None
pass

View File

@@ -2,9 +2,12 @@
# created by mmmy on 2025-09-30
import json
import logging
from typing import Dict
from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, DateTime, func
from utils.rldb import RLDBBaseModel
from utils.error import ErrorCode, error
class PeopleRLDBModel(RLDBBaseModel):
__tablename__ = 'peoples'
@@ -24,6 +27,37 @@ class PeopleRLDBModel(RLDBBaseModel):
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True)
class Comment:
# 评论内容
content: str
# 评论人
author: str
# 创建时间
created_at: datetime
# 更新时间
updated_at: datetime
def __init__(self, **kwargs):
self.content = kwargs.get('content', '')
self.author = kwargs.get('author', '')
self.created_at = kwargs.get('created_at', datetime.now())
self.updated_at = kwargs.get('updated_at', datetime.now())
def to_dict(self) -> dict:
return {
'content': self.content,
'author': self.author,
'created_at': int(self.created_at.timestamp()),
'updated_at': int(self.updated_at.timestamp()),
}
@classmethod
def from_dict(cls, data: dict):
data['created_at'] = datetime.fromtimestamp(data['created_at'])
data['updated_at'] = datetime.fromtimestamp(data['updated_at'])
return cls(**data)
class People:
# 数据库 ID
id: str
@@ -44,9 +78,11 @@ class People:
# 个人介绍
introduction: Dict[str, str]
# 总结评价
comments: Dict[str, str]
comments: Dict[str, "Comment"]
# 封面
cover: str = None
# 创建时间
created_at: datetime = None
def __init__(self, **kwargs):
# 初始化所有属性从kwargs中获取值如果不存在则设置默认值
@@ -61,19 +97,17 @@ class People:
self.introduction = kwargs.get('introduction', {}) if kwargs.get('introduction', {}) is not None else {}
self.comments = kwargs.get('comments', {}) if kwargs.get('comments', {}) is not None else {}
self.cover = kwargs.get('cover', None) if kwargs.get('cover', None) is not None else None
self.created_at = kwargs.get('created_at', None)
def __str__(self) -> str:
# 返回对象的字符串表示,包含所有属性
return (f"People(id={self.id}, name={self.name}, contact={self.contact}, gender={self.gender}, "
f"age={self.age}, height={self.height}, marital_status={self.marital_status}, "
f"match_requirement={self.match_requirement}, introduction={self.introduction}, "
f"comments={self.comments}, cover={self.cover})")
f"comments={self.comments}, cover={self.cover}, created_at={self.created_at})")
@classmethod
def from_dict(cls, data: dict):
if 'created_at' in data:
# 移除 created_at 字段,避免类型错误
del data['created_at']
if 'updated_at' in data:
# 移除 updated_at 字段,避免类型错误
del data['updated_at']
@@ -95,8 +129,9 @@ class People:
marital_status=data.marital_status,
match_requirement=data.match_requirement,
introduction=json.loads(data.introduction) if data.introduction else {},
comments=json.loads(data.comments) if data.comments else {},
comments={k: Comment.from_dict(v) for k, v in json.loads(data.comments).items()} if data.comments else {},
cover=data.cover,
created_at=data.created_at,
)
def to_dict(self) -> dict:
@@ -111,8 +146,9 @@ class People:
'marital_status': self.marital_status,
'match_requirement': self.match_requirement,
'introduction': self.introduction,
'comments': self.comments,
'comments': {k: v.to_dict() for k, v in self.comments.items()},
'cover': self.cover,
'created_at': int(self.created_at.timestamp()) if self.created_at else None,
}
def to_rldb_model(self) -> PeopleRLDBModel:
@@ -127,6 +163,22 @@ class People:
marital_status=self.marital_status,
match_requirement=self.match_requirement,
introduction=json.dumps(self.introduction, ensure_ascii=False),
comments=json.dumps(self.comments, ensure_ascii=False),
comments=json.dumps({k: v.to_dict() for k, v in self.comments.items()}, ensure_ascii=False),
cover=self.cover,
)
)
def validate(self) -> error:
err = error(ErrorCode.SUCCESS, "")
if not self.name:
logging.error("Name is required, use default")
self.name = ""
if not self.gender in ['', '', '未知']:
logging.error("Gender must be '', '', or '未知', use default")
self.gender = "未知"
if not isinstance(self.age, int) or self.age < 0:
logging.error("Age must be an integer and greater than 0, use default")
self.age = 0
if not isinstance(self.height, int) or self.height < 0:
logging.error("Height must be an integer and greater than 0, use default")
self.height = 0
return err

View File

@@ -1,9 +1,11 @@
import logging
import uuid
from models.people import People, PeopleRLDBModel
from utils.error import error
from models.people import People, PeopleRLDBModel, Comment
from datetime import datetime
from utils.error import ErrorCode, error
from utils import rldb
@@ -25,7 +27,7 @@ class PeopleService:
people_orm = people.to_rldb_model()
self.rldb.upsert(people_orm)
return people.id, error(0, "")
return people.id, error(ErrorCode.SUCCESS, "")
def delete(self, people_id: str) -> error:
"""
@@ -36,9 +38,9 @@ class PeopleService:
"""
people_orm = self.rldb.get(PeopleRLDBModel, people_id)
if not people_orm:
return error(1, f"people {people_id} not found")
return error(ErrorCode.RLDB_ERROR, f"people {people_id} not found")
self.rldb.delete(people_orm)
return error(0, "")
return error(ErrorCode.SUCCESS, "")
def get(self, people_id: str) -> (People, error):
"""
@@ -49,8 +51,8 @@ class PeopleService:
"""
people_orm = self.rldb.get(PeopleRLDBModel, people_id)
if not people_orm:
return None, error(1, f"people {people_id} not found")
return People.from_rldb_model(people_orm), error(0, "")
return None, error(ErrorCode.MODEL_ERROR, f"people {people_id} not found")
return People.from_rldb_model(people_orm), error(ErrorCode.SUCCESS, "")
def list(self, conds: dict = {}, limit: int = 10, offset: int = 0) -> (list[People], error):
"""
@@ -64,7 +66,53 @@ class PeopleService:
people_orms = self.rldb.query(PeopleRLDBModel, **conds)
peoples = [People.from_rldb_model(people_orm) for people_orm in people_orms]
return peoples, error(0, "")
return peoples, error(ErrorCode.SUCCESS, "")
def save_remark(self, people_id: str, content: str) -> error:
"""
为人物添加或更新备注
:param people_id: 人物ID
:param content: 备注内容
:return: 错误对象
"""
people: People
err: error
people, err = self.get(people_id)
logging.info(f"get people before save remark: {people}")
if not err.success:
return err
remark = people.comments.get("remark", None)
if remark is not None:
remark.content = content
remark.updated_at = datetime.now()
else:
people.comments["remark"] = Comment(content=content)
logging.info(f"save remark for people {people}")
_, err = self.save(people)
return err
def delete_remark(self, people_id: str) -> error:
"""
删除人物备注
:param people_id: 人物ID
:return: 错误对象
"""
people: People
err: error
people, err = self.get(people_id)
if not err.success:
return err
if "remark" in people.comments:
del people.comments["remark"]
_, err = self.save(people)
return err
return error(ErrorCode.SUCCESS, "")
people_service = None

View File

@@ -1,14 +1,21 @@
from enum import Enum
import logging
from typing import Protocol
class ErrorCode(Enum):
SUCCESS = 0
MODEL_ERROR = 1000
RLDB_ERROR = 2100
class error(Protocol):
_error_code: int = 0
_error_info: str = ""
def __init__(self, error_code: int, error_info: str):
self._error_code = error_code
def __init__(self, error_code: ErrorCode, error_info: str):
self._error_code = int(error_code.value)
self._error_info = error_info
logging.info(f"errorcode: {type(self._error_code)}")
def __str__(self) -> str:
return f"{self.__class__.__name__}({self._error_code}, {self._error_info})"

View File

@@ -150,3 +150,24 @@ async def get_peoples(
peoples = [people.to_dict() for people in results]
return BaseResponse(error_code=0, error_info="success", data=peoples)
class RemarkRequest(BaseModel):
content: str
@api.post("/people/{people_id}/remark")
async def post_remark(people_id: str, request: RemarkRequest):
service = get_people_service()
error = service.save_remark(people_id, request.content)
if not error.success:
return BaseResponse(error_code=error.code, error_info=error.info)
return BaseResponse(error_code=0, error_info="success")
@api.delete("/people/{people_id}/remark")
async def delete_remark(people_id: str):
service = get_people_service()
error = service.delete_remark(people_id)
if not error.success:
return BaseResponse(error_code=error.code, error_info=error.info)
return BaseResponse(error_code=0, error_info="success")