From 8a59daae6cfc4a56e771173b448f1adc92155799 Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Fri, 25 Jul 2025 12:05:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D=E4=B8=AD=E6=95=B0=E6=8D=AE=E5=BA=93=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/routers/__pycache__/tweet.cpython-312.pyc | Bin 14671 -> 14722 bytes api/routers/tweet.py | 126 +++++-------- .../database_service.cpython-312.pyc | Bin 42883 -> 45481 bytes .../prompt_builder.cpython-312.pyc | Bin 18609 -> 19764 bytes .../prompt_service.cpython-312.pyc | Bin 31390 -> 32336 bytes .../__pycache__/tweet.cpython-312.pyc | Bin 16419 -> 16888 bytes api/services/prompt_builder.py | 177 +++++++----------- api/services/tweet.py | 106 +++++------ 8 files changed, 170 insertions(+), 239 deletions(-) diff --git a/api/routers/__pycache__/tweet.cpython-312.pyc b/api/routers/__pycache__/tweet.cpython-312.pyc index 8108c724a53a2d5327ea33af3ded41fe6c9c2bb1..a300c8565735e9aa2a2d89ec1ea415a151d72128 100644 GIT binary patch delta 3618 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 h2ZbX3U{K0& tuple: +def _resolve_ids_to_objects(db_service: DatabaseService, + styleIds: Optional[List[int]] = None, + audienceIds: Optional[List[int]] = None, + scenicSpotIds: Optional[List[int]] = None, + productIds: Optional[List[int]] = None) -> tuple: """ - 将ID列表转换为名称列表,并返回ID到名称的映射关系 - - Args: - db_service: 数据库服务 - styleIds: 风格ID列表 - audienceIds: 受众ID列表 - scenicSpotIds: 景区ID列表 - productIds: 产品ID列表 - - Returns: - (styles, audiences, scenic_spots, products, id_name_mappings) 名称列表和映射关系元组 + 将ID列表解析为完整的对象记录列表,并返回ID到名称的映射。 """ - styles = [] - audiences = [] - scenic_spots = [] - products = [] + styles, audiences, scenic_spots, products = [], [], [], [] - # 建立ID到名称的映射字典 id_name_mappings = { - 'style_mapping': {}, # {name: id} - 'audience_mapping': {}, # {name: id} - 'scenic_spot_mapping': {}, # {name: id} - 'product_mapping': {} # {name: id} + 'style_mapping': {}, 'audience_mapping': {}, + 'scenic_spot_mapping': {}, 'product_mapping': {} } - - # 如果数据库服务不可用,返回空列表 + if not db_service or not db_service.is_available(): logger.warning("数据库服务不可用,无法解析ID") return styles, audiences, scenic_spots, products, id_name_mappings - - # 解析风格ID + if styleIds: - style_records = db_service.get_styles_by_ids(styleIds) - for record in style_records: - style_name = record['styleName'] - styles.append(style_name) - id_name_mappings['style_mapping'][style_name] = record['id'] + styles = db_service.get_styles_by_ids(styleIds) + id_name_mappings['style_mapping'] = {record['styleName']: record['id'] for record in styles} - # 解析受众ID if audienceIds: - audience_records = db_service.get_audiences_by_ids(audienceIds) - for record in audience_records: - audience_name = record['audienceName'] - audiences.append(audience_name) - id_name_mappings['audience_mapping'][audience_name] = record['id'] + audiences = db_service.get_audiences_by_ids(audienceIds) + id_name_mappings['audience_mapping'] = {record['audienceName']: record['id'] for record in audiences} - # 解析景区ID if scenicSpotIds: - spot_records = db_service.get_scenic_spots_by_ids(scenicSpotIds) - for record in spot_records: - spot_name = record['name'] - scenic_spots.append(spot_name) - id_name_mappings['scenic_spot_mapping'][spot_name] = record['id'] + scenic_spots = db_service.get_scenic_spots_by_ids(scenicSpotIds) + id_name_mappings['scenic_spot_mapping'] = {record['name']: record['id'] for record in scenic_spots} - # 解析产品ID if productIds: - product_records = db_service.get_products_by_ids(productIds) - for record in product_records: - product_name = record['productName'] # 修改这里,从name改为productName - products.append(product_name) - id_name_mappings['product_mapping'][product_name] = record['id'] + products = db_service.get_products_by_ids(productIds) + id_name_mappings['product_mapping'] = {record['productName']: record['id'] for record in products} return styles, audiences, scenic_spots, products, id_name_mappings @@ -141,7 +108,7 @@ def _resolve_ids_to_names(db_service: DatabaseService, Returns: (styles, audiences, scenic_spots, products) 名称列表元组 """ - styles, audiences, scenic_spots, products, _ = _resolve_ids_to_names_with_mapping( + styles, audiences, scenic_spots, products, _ = _resolve_ids_to_objects( db_service, styleIds, audienceIds, scenicSpotIds, productIds ) return styles, audiences, scenic_spots, products @@ -213,8 +180,8 @@ async def generate_topics( - **productIds**: 产品ID列表 """ try: - # 将ID转换为名称,并获取映射关系 - styles, audiences, scenic_spots, products, id_name_mappings = _resolve_ids_to_names_with_mapping( + # 将ID解析为完整的对象记录 + styles, audiences, scenic_spots, products, id_name_mappings = _resolve_ids_to_objects( db_service, request.styleIds, request.audienceIds, @@ -222,16 +189,26 @@ async def generate_topics( request.productIds ) + # 从对象中提取名称列表,用于向后兼容 + style_names = [s['styleName'] for s in styles] + audience_names = [a['audienceName'] for a in audiences] + scenic_spot_names = [s['name'] for s in scenic_spots] + product_names = [p['productName'] for p in products] + request_id, topics = await tweet_service.generate_topics( dates=request.dates, numTopics=request.numTopics, - styles=styles, - audiences=audiences, - scenic_spots=scenic_spots, - products=products + styles=style_names, + audiences=audience_names, + scenic_spots=scenic_spot_names, + products=product_names, + # 传递完整的对象以供下游使用 + style_objects=styles, + audience_objects=audiences, + scenic_spot_objects=scenic_spots, + product_objects=products ) - # 为topics添加ID字段 enriched_topics = _add_ids_to_topics(topics, id_name_mappings) return TopicResponse( @@ -260,8 +237,8 @@ async def generate_content( - **autoJudge**: 是否自动进行内容审核 """ try: - # 将ID转换为名称 - styles, audiences, scenic_spots, products = _resolve_ids_to_names( + # 将ID解析为完整的对象记录 + styles, audiences, scenic_spots, products, _ = _resolve_ids_to_objects( db_service, request.styleIds, request.audienceIds, @@ -271,17 +248,14 @@ async def generate_content( request_id, topic_index, content = await tweet_service.generate_content( topic=request.topic, - styles=styles, - audiences=audiences, - scenic_spots=scenic_spots, - products=products, + style_objects=styles, + audience_objects=audiences, + scenic_spot_objects=scenic_spots, + product_objects=products, autoJudge=request.autoJudge ) - # 提取judgeSuccess字段,从content中移除以避免重复 - judge_success = None - if isinstance(content, dict) and 'judgeSuccess' in content: - judge_success = content.pop('judgeSuccess') + judge_success = content.pop('judgeSuccess', None) if isinstance(content, dict) else None return ContentResponse( requestId=request_id, @@ -341,8 +315,8 @@ async def judge_content( - **productIds**: 产品ID列表 """ try: - # 将ID转换为名称 - styles, audiences, scenic_spots, products = _resolve_ids_to_names( + # 将ID解析为完整的对象记录 + styles, audiences, scenic_spots, products, _ = _resolve_ids_to_objects( db_service, request.styleIds, request.audienceIds, @@ -353,10 +327,10 @@ async def judge_content( request_id, topic_index, judged_content, judge_success = await tweet_service.judge_content( topic=request.topic, content=request.content, - styles=styles, - audiences=audiences, - scenic_spots=scenic_spots, - products=products + style_objects=styles, + audience_objects=audiences, + scenic_spot_objects=scenic_spots, + product_objects=products ) return JudgeResponse( diff --git a/api/services/__pycache__/database_service.cpython-312.pyc b/api/services/__pycache__/database_service.cpython-312.pyc index 43360921243422ae6bb5d28a40cec67e03e8cb0e..c79dd35b7469a6bd47db6bd133163525fbd45830 100644 GIT binary patch literal 45481 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 delta 11074 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 diff --git a/api/services/__pycache__/prompt_builder.cpython-312.pyc b/api/services/__pycache__/prompt_builder.cpython-312.pyc index 6b7f46bdd080c4ed89bbf829a93f80808a66a1cc..65f0bb97b04563e2eeb85a0bdf5b851d4f2b8652 100644 GIT binary patch delta 5818 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 delta 3683 zcmaJ^eQZWb|{Xuv|=+G+x#Q|~s8u06?9b(d+v?i@51s_g1X;nOMQ@7RTeO7-|HgUy2 zB?!EJh?iklzt!Ty5&u~!7Am-)!sxpGw*LJRixKDQNaeEveCb>Z!RG(K&1R3JSA;79 z^6|@Bpu64+4fQUIM9?rK~TpzLoWDS4*yl3tAnTU2~ii9_C`k(K~R^ z7KQ&-Rf4r53~%{fFcrjf=Imgyz`+rJg|5JxLA4*!Nop8Au*V~+go_k(eaVbkMm-1i zy4ZxS-r-axnaaQoe*=h)I=&1NjxcN~Uw5ov%p`|K2D>xKg0(1nvVSm{>K;m}+h`G+ zk{s$zDP2q48sb`pdj^udnJy+*OPtaXbyyi)WNk;5x&z*Hgi+Pygd6_s=rH!<(LTk^ z%TUqi1~st$0soqdz6-uf_vM;qeerSYoE*3%SIx*(x8(X6x&B6KRz5f`%^SG#@Wt4L zSiZ7$s%O@}Yuq;H3*Pc=n(=MAl)>KWVPpyh< z$S}5vwy*`^-$Gqjts-hOK{Y{~U@Jn*s)nh5AHhxn<^=t~aDR$h7xu1H>9meiar9YYKO{%TLV-C@$ zM}{+M8_~=g%;bGU?tpj8pXW!ecLuWJ2DTlx*C>t^#7uu?Fd5UUM5&JwuqA5YpB1fc zwhg*l#cdRght5c)`yL|~<~$$i=HG_DMZT1KOK{fWM^S-2$te+^a;dkKudGTC$~zPNTP^*XS+jvr=lKA~Nv`#Q@=T zRZ(&A6)(0ZgA#~>Qj(SM->T`;EwR#pc z-SkFb+_R|Zn!yQH*~zpy6$!QNcA~bj723*BTiGhw%I?zUV20Cj@6q0Z! zn!wB`7jSLQ!rBqE(W80rEQ?1#b1g9!eBqVgCvKC&v$TXeRXi4wI1&r!yo!yE#k*oL zIu;Ka>RH9mrG?nBxX~V2~_0-OV-HDkFsVSsG!Ij z^(%G_W2xfM9EfF_3(={$5nY;${xN6EQFbkK%Fc>%&7etF-B;)Wl8*wKUkgyg?G{<*3>0*G{1FyyXb{24!V@(kL5ciKAr*)XV z(#-Hke=lW;ll^`71e}67o#|FHT?(ZLV-iyV_0(RPycCNHW>U@aN1BBs^3wddKh7S!NewEm`bk*QSCI|L$Dtq9mMqEgXt|L@VhNd(ig-!i>|{w5(eMaon!wn zaIQ!`Qk&n=I_qp3H{LdIrH(n9d(wS=Y%aR#^(QVrF|}hhS~p{_e9=6)Z_Xc@JU2O* zTQ_CQoy*I06XKl1H7QO6F!@|GT`*l*_j=Xks$B1EWZSG5(!cmg~%tul!P?@D8RvvTXW1Vwg`<7^j6FzD33Lo-IRZ&m92WudwUC}Q+Gz%6tPI_WesUj+dL338pBrJD2!>%%= zL@{19ExjXavHA&GHKS(MELY7}^k@uzS({d>*;q|uftz6$blD8>t%fr&c>f{Ga{7tG zPj@ED*zG7*4>&bOrH#0&dW@24F^Mi`pSzQ2K|hclP8CFDbZ8`9%&7Ji8I`58)zWD( zQ_=D)H!i7G**$!M2F_ruxo{ipcpIH~6D645wOwk%f+qbniY!z$yTRAi3EyZMg6Xy5 zb!qqO`sFG@?WCoaiwBiR%xByKt!kS38G_RYizUTbVouuWAza$gjVz1N}H3dgVI~%q0i9H6(0Jf#qa=qf1#QiK5u(&%_FptV+aLZ ze=4Jn5Sb+yBX}BK*cZZI7(d?E9&43B&T(($sazFKYBdh)ssKk zGjq824h_wxAkzN9*zH67aUSRGoNBq@yw*Nb)iBQyBmc0{{f-BpRND^ diff --git a/api/services/__pycache__/prompt_service.cpython-312.pyc b/api/services/__pycache__/prompt_service.cpython-312.pyc index a45a6609589ee49dcd64b8eb33cd36b0726bc0d7..2abfdce7e4ce665d085def2b3ac280933270c3dc 100644 GIT binary patch delta 6912 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 delta 6524 zcmeHL3se(V8lK5yk_?0p$OI+{d658y@KT_HqLpV*A&Lm~2~~ox712o$wPB-nx7ya$ z;P0;5u7W*nmu}D5VrlodkG8b(vfZwsaCRfL+s7Vl_iVe3#cq4LkM6w_!B=h9wrBU8 zJsl3;{qJLL=FWWI|Nr*}{y`r5m{hzH6%|3C>;9}A>l)UbP(YmMpa|Y9*NXVvuvxzh z-kCO^Ool9@5h}Co&|Sm!yc@3~$@}16IaOp&PopMH6q!yE#7-h4A>>MUNT;vYn>&^t zS>9c8c=?Dv-(MDFwTGPT&LCGE)Fd93U!o~>Tu4S3tR3}7>PPe${<5?2`g5G2Bl1XO zAbIYPZAcfe){JtsL1R*9)37n;6BQAcJ*=Jjs8CEC-*IJNx|ndheyJ5W^Lp|&)S>z6be0hnBx=+_ZeYNVk{%vw{OHH z?Rg|IhZMEgpejQRy_RVBr$t6NTav+>!GY2GHmL?ONh2Mj8#Xq(8zE+!8nW_e$Vocy z`zd-n_ZpEvoH#_B{RQ_GghbI%PBIv;JLGNm{vm$aaQw89_#D4DsL^-mj_A6@hjk;G zT)!l!j60-gR|MI`L3Lt)wIW8v#3Dv%O&!J~#t}_wc!<1ReozrqvWFttBM!>nR%y;5 zBF=B0A4n|iH8l0s2TV1iY;90y?o1!nWd+!*5KXY!kb+}o!!ehk=WH@XQlDPB9WJIk zGG}{tIJE;E(eT&8s}? z-2A<`<30p#1_2BwvCxy5<9i3U91HuntEoEwR;XGYR2%#ix6IU{-h5rJJz#Q>v#X`SQ@?3d zQ)2@X^27yo(<1)QIjOuY2XitWR!nC}%*B0@{b>2NYssiEe-#N2A`+%Z!nA{ZdF8$s z#4>lsH>4W!ie~?UuR*l}U2jIfR5i*jLR1SRP9N6g1=zgHUJ=~+4_rmF8W9gY2WOm? zB|d~!&bu*Ok=qYYbxh)JS+i#SHvWg;E8a-%f%l8GYCL!>if>%g$k#VCZ}K#5^6-y? zt|XbA6yF%RW=!l_) zDVRCmNdD-m=&gmLEA3wz!L2x~QesaGm@BL_isC4a7p}95HfZ5owgz;y5x{dS_-86m z1W%xvu0(AKztSt)V1dF)Gm6zet2M%~iG??6m81=(+Er8pn)A6zJt>9PE2kuQV$p(y z%S!Nx*!@k^H>h#^o_lkr`Bpe`x00>IHfkx+N?M3(W)qz0wX?zVPHk>DI{)=6?zY_c zEUx z{G$jyqcSowo)GbS;Ml_XLhx6^=L>124-c?mNX77TFej6$aK7}w3<}$`7>A7-lyD9!HE5-g-GE-l=!q5i$R%;F{i@u_Wit81DhuJZt zVTi}D6T>bH*ZS4V>cAZ@V|WF&EYli)i_I(ydog^WY_0gHlF& zUB$IN|ARUU_V+T`<((50x;1$3C*o_M86~>Nl<=iq2Kjxw>AI(00lO@Y7>{DElLr?_o0K#_q(KOep% zsKB9Oe33Z3l@ufuTkUF3Tx*KZ-qzfEG2PCAi}LDjWES+1RkA!EOSR$WiIshCN}B59|FnrHY9UpS4s3)f&HJVppLt zc0?bE9^q_V=3y?)Prs$&4o^Lh|JKA&-yK1XwbK>g>>mrKeNH){)`brNm!s31!*`(` zG!;4rj38pvha%e}4@TXXbe%(K80rcbW}oz&ToXtr9aWa0)1goVq&2@P>_uWXFIzJY zt@OcSiGv_c#TBxSnM7Y|tRs`^D=4B+WsqPTY1AOYIZ~*>6dK!^v?EVCm?Oo#Gnw*a zBo40JObwZ7WMg4WUf=10;Qv>;=-JWIPr{tmWSNs!p)bP!9-3N{B7@k(I}!dU9BmDY zL@POq8}K1eS`AhSKZZfj$WCnCs1e*MgvbrpT7ltp3^;O)MYtN9R&8?CyVmpkq>hSx zwOB(ReiIE<-I9h@+*Ct%ej6HkX!{vKL%Vk@BmW3*>{vJZe*6aTK_`C%!wVQ>7g;N>b2*i8Hd{1&7eo7b*gziBQ11bpsY gPji6130MCId3>eZtC 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 Tuple[str, str]: """ - 构建内容生成提示词 - - Args: - topic: 选题信息 - step: 当前步骤,用于过滤参考内容 - - Returns: - 系统提示词和用户提示词的元组 + 构建内容生成提示词 (已重构) + 此方法现在依赖于一个预先填充好完整信息的topic对象。 """ - # 获取内容生成配置 content_config = self._ensure_content_config() - - # 加载系统提示词和用户提示词模板 - system_prompt_path = content_config.content_system_prompt - user_prompt_path = content_config.content_user_prompt - - # 创建提示词模板 - template = PromptTemplate(system_prompt_path, user_prompt_path) - - # 获取风格内容 - style_filename = topic.get("style", "") - style_content = self.prompt_service.get_style_content(style_filename) - - # 获取目标受众内容 - demand_filename = topic.get("targetAudience", "") - demand_content = self.prompt_service.get_audience_content(demand_filename) - - # 获取景区信息 - object_name = topic.get("object", "") - object_content = self.prompt_service.get_scenic_spot_info(object_name) - - # 获取产品信息 - product_name = topic.get("product", "") - product_content = self.prompt_service.get_product_info(product_name) - - # 获取参考内容 + template = PromptTemplate(content_config.content_system_prompt, content_config.content_user_prompt) + + # 从预填充的topic对象中直接获取信息,不再调用prompt_service + style_obj = topic.get('style_object', {}) + style_content = f"{style_obj.get('styleName', '')}\n{style_obj.get('description', '')}" + + audience_obj = topic.get('audience_object', {}) + demand_content = f"{audience_obj.get('audienceName', '')}\n{audience_obj.get('description', '')}" + + spot_obj = topic.get('scenic_spot_object', {}) + object_content = f"{spot_obj.get('name', '')}\n{spot_obj.get('description', '')}" + + product_obj = topic.get('product_object', {}) + product_content = f"{product_obj.get('productName', '')}\n{product_obj.get('detailedDescription', '')}" + + # 获取通用的参考内容 refer_content = self.prompt_service.get_refer_content(step) - # 构建系统提示词 system_prompt = template.get_system_prompt() - # 构建用户提示词 user_prompt = template.build_user_prompt( - style_content=f"{style_filename}\n{style_content}", - demand_content=f"{demand_filename}\n{demand_content}", - object_content=f"{object_name}\n{object_content}", - product_content=f"{product_name}\n{product_content}", + style_content=style_content, + demand_content=demand_content, + object_content=object_content, + product_content=product_content, refer_content=refer_content ) @@ -229,7 +211,15 @@ class PromptBuilderService: return system_prompt, user_prompt - def build_topic_prompt(self, products: Optional[List[str]] = None, scenic_spots: Optional[List[str]] = None, styles: Optional[List[str]] = None, audiences: Optional[List[str]] = None, dates: Optional[str] = None, numTopics: int = 5) -> Tuple[str, str]: + def build_topic_prompt(self, products: Optional[List[str]] = None, + scenic_spots: Optional[List[str]] = None, + styles: Optional[List[str]] = None, + audiences: Optional[List[str]] = None, + dates: Optional[str] = None, numTopics: int = 5, + style_objects: Optional[List[Dict[str, Any]]] = None, + audience_objects: Optional[List[Dict[str, Any]]] = None, + scenic_spot_objects: Optional[List[Dict[str, Any]]] = None, + product_objects: Optional[List[Dict[str, Any]]] = None) -> Tuple[str, str]: """ 构建选题生成提示词 @@ -253,59 +243,50 @@ class PromptBuilderService: if not system_prompt_path or not user_prompt_path: raise ValueError("选题提示词模板路径不完整") - # 创建提示词模板 template = PromptTemplate(system_prompt_path, user_prompt_path) - # 处理日期 - if dates: - if ' to ' in dates: - start_date, end_date = dates.split(' to ') - month = f"从 {start_date} 到 {end_date}" - elif ',' in dates: - month = ', '.join(dates.split(',')) - else: - month = dates - else: - month = '' - - # 获取风格内容 - style_content = '' - if styles: + month = dates or '' + if dates and ' to ' in dates: + start_date, end_date = dates.split(' to ') + month = f"从 {start_date} 到 {end_date}" + elif dates and ',' in dates: + month = ', '.join(dates.split(',')) + + # 使用传入的完整对象构建内容,避免重复查询 + if style_objects: + style_content = '\n'.join([f"{obj['styleName']}: {obj.get('description', '')}" for obj in style_objects]) + elif styles: style_content = '\n'.join([f"{style}: {self.prompt_service.get_style_content(style)}" for style in styles]) else: all_styles = self.prompt_service.get_all_styles() style_content = "Style文件列表:\n" + "\n".join([f"- {style['name']}" for style in all_styles]) - - # 获取受众内容 - demand_content = '' - if audiences: + + if audience_objects: + demand_content = '\n'.join([f"{obj['audienceName']}: {obj.get('description', '')}" for obj in audience_objects]) + elif audiences: demand_content = '\n'.join([f"{audience}: {self.prompt_service.get_audience_content(audience)}" for audience in audiences]) else: all_audiences = self.prompt_service.get_all_audiences() demand_content = "Demand文件列表:\n" + "\n".join([f"- {audience['name']}" for audience in all_audiences]) - - # 获取参考内容 - refer_content = self.prompt_service.get_refer_content("topic") - - # 获取景区内容 - object_content = '' - if scenic_spots: + + if scenic_spot_objects: + object_content = '\n'.join([f"{obj['name']}: {obj.get('description', '')}" for obj in scenic_spot_objects]) + elif scenic_spots: object_content = '\n'.join([f"{spot}: {self.prompt_service.get_scenic_spot_info(spot)}" for spot in scenic_spots]) else: all_spots = self.prompt_service.get_all_scenic_spots() object_content = "Object信息:\n" + "\n".join([f"- {spot['name']}" for spot in all_spots]) - - # 获取产品内容 - product_content = '' - if products: + + if product_objects: + product_content = '\n'.join([f"{obj['productName']}: {obj.get('detailedDescription', '')}" for obj in product_objects]) + elif products: product_content = '\n'.join([f"{product}: {self.prompt_service.get_product_info(product)}" for product in products]) else: - product_content = '' # 假设没有默认产品列表 - - # 构建系统提示词 + product_content = '' + + refer_content = self.prompt_service.get_refer_content("topic") system_prompt = template.get_system_prompt() - # 构建创作资料 creative_materials = ( f"你拥有的创作资料如下:\n" f"风格信息:\n{style_content}\n\n" @@ -315,7 +296,6 @@ class PromptBuilderService: f"产品信息:\n{product_content}" ) - # 构建用户提示词 user_prompt = template.build_user_prompt( creative_materials=creative_materials, numTopics=numTopics, @@ -326,44 +306,25 @@ class PromptBuilderService: def build_judge_prompt(self, topic: Dict[str, Any], content: Dict[str, Any]) -> Tuple[str, str]: """ - 构建内容审核提示词 - - Args: - topic: 选题信息 - content: 生成的内容 - - Returns: - 系统提示词和用户提示词的元组 + 构建内容审核提示词 (已重构) + 此方法现在依赖于一个预先填充好完整信息的topic对象。 """ - # 获取内容生成配置 content_config = self._ensure_content_config() - - # 从配置中获取审核提示词模板路径 - system_prompt_path = content_config.judger_system_prompt - user_prompt_path = content_config.judger_user_prompt - - # 创建提示词模板 - template = PromptTemplate(system_prompt_path, user_prompt_path) - - # 获取景区信息 - object_name = topic.get("object", "") - object_content = self.prompt_service.get_scenic_spot_info(object_name) - - # 获取产品信息 - product_name = topic.get("product", "") - product_content = self.prompt_service.get_product_info(product_name) - - # 获取参考内容 + template = PromptTemplate(content_config.judger_system_prompt, content_config.judger_user_prompt) + + # 从预填充的topic对象中直接获取信息 + spot_obj = topic.get('scenic_spot_object', {}) + object_content = f"{spot_obj.get('name', '')}\n{spot_obj.get('description', '')}" + + product_obj = topic.get('product_object', {}) + product_content = f"{product_obj.get('productName', '')}\n{product_obj.get('detailedDescription', '')}" + refer_content = self.prompt_service.get_refer_content("judge") - - # 构建系统提示词 system_prompt = template.get_system_prompt() - # 格式化内容 import json tweet_content = json.dumps(content, ensure_ascii=False, indent=4) - # 构建用户提示词 user_prompt = template.build_user_prompt( tweet_content=tweet_content, object_content=object_content, @@ -371,7 +332,7 @@ class PromptBuilderService: refer_content=refer_content ) - return system_prompt, user_prompt + return system_prompt, user_prompt def build_judge_prompt_with_params(self, topic: Dict[str, Any], content: Dict[str, Any], styles: Optional[List[str]] = None, diff --git a/api/services/tweet.py b/api/services/tweet.py index d703297..49fe64c 100644 --- a/api/services/tweet.py +++ b/api/services/tweet.py @@ -48,11 +48,15 @@ class TweetService: self.prompt_service = PromptService(config_manager) self.prompt_builder = PromptBuilderService(config_manager, self.prompt_service) - async def generate_topics(self, dates: Optional[str] = None, numTopics: int = 5, - styles: Optional[List[str]] = None, + async def generate_topics(self, dates: Optional[str] = None, numTopics: int = 5, + styles: Optional[List[str]] = None, audiences: Optional[List[str]] = None, scenic_spots: Optional[List[str]] = None, - products: Optional[List[str]] = None) -> Tuple[str, List[Dict[str, Any]]]: + products: Optional[List[str]] = None, + style_objects: Optional[List[Dict[str, Any]]] = None, + audience_objects: Optional[List[Dict[str, Any]]] = None, + scenic_spot_objects: Optional[List[Dict[str, Any]]] = None, + product_objects: Optional[List[Dict[str, Any]]] = None) -> Tuple[str, List[Dict[str, Any]]]: """ 生成选题 @@ -63,6 +67,10 @@ class TweetService: audiences: 受众列表 scenic_spots: 景区列表 products: 产品列表 + style_objects: 风格对象列表 + audience_objects: 受众对象列表 + scenic_spot_objects: 景区对象列表 + product_objects: 产品对象列表 Returns: 请求ID和生成的选题列表 @@ -82,7 +90,11 @@ class TweetService: styles=styles, audiences=audiences, dates=dates, - numTopics=numTopics + numTopics=numTopics, + style_objects=style_objects, + audience_objects=audience_objects, + scenic_spot_objects=scenic_spot_objects, + product_objects=product_objects ) # 使用预构建的提示词生成选题 @@ -97,11 +109,11 @@ class TweetService: logger.info(f"选题生成完成,请求ID: {requestId}, 数量: {len(topics)}") return requestId, topics - async def generate_content(self, topic: Optional[Dict[str, Any]] = None, - styles: Optional[List[str]] = None, - audiences: Optional[List[str]] = None, - scenic_spots: Optional[List[str]] = None, - products: Optional[List[str]] = None, + async def generate_content(self, topic: Optional[Dict[str, Any]] = None, + style_objects: Optional[List[Dict[str, Any]]] = None, + audience_objects: Optional[List[Dict[str, Any]]] = None, + scenic_spot_objects: Optional[List[Dict[str, Any]]] = None, + product_objects: Optional[List[Dict[str, Any]]] = None, autoJudge: bool = False) -> Tuple[str, str, Dict[str, Any]]: """ 为选题生成内容 @@ -117,25 +129,28 @@ class TweetService: Returns: 请求ID、选题索引和生成的内容(包含judgeSuccess状态) """ - # 如果没有提供topic,创建一个基础的topic if not topic: topic = {"index": "1", "date": "2024-07-01"} topicIndex = topic.get('index', 'N/A') logger.info(f"开始为选题 {topicIndex} 生成内容{'(含审核)' if autoJudge else ''}") - # 创建topic的副本并应用覆盖参数 + # 核心修改:创建一个增强版的topic,将所有需要的信息预先填充好 enhanced_topic = topic.copy() - if styles and len(styles) > 0: - enhanced_topic['style'] = styles[0] # 使用第一个风格 - if audiences and len(audiences) > 0: - enhanced_topic['targetAudience'] = audiences[0] # 使用第一个受众 - if scenic_spots and len(scenic_spots) > 0: - enhanced_topic['object'] = scenic_spots[0] # 使用第一个景区 - if products and len(products) > 0: - enhanced_topic['product'] = products[0] # 使用第一个产品 - - # 使用PromptBuilderService构建提示词 + if style_objects: + enhanced_topic['style_object'] = style_objects[0] + enhanced_topic['style'] = style_objects[0].get('styleName') + if audience_objects: + enhanced_topic['audience_object'] = audience_objects[0] + enhanced_topic['targetAudience'] = audience_objects[0].get('audienceName') + if scenic_spot_objects: + enhanced_topic['scenic_spot_object'] = scenic_spot_objects[0] + enhanced_topic['object'] = scenic_spot_objects[0].get('name') + if product_objects: + enhanced_topic['product_object'] = product_objects[0] + enhanced_topic['product'] = product_objects[0].get('productName') + + # 使用PromptBuilderService构建提示词,现在它只需要enhanced_topic system_prompt, user_prompt = self.prompt_builder.build_content_prompt(enhanced_topic, "content") # 使用预构建的提示词生成内容 @@ -207,61 +222,42 @@ class TweetService: return requestId, topicIndex, content async def judge_content(self, topic: Optional[Dict[str, Any]] = None, content: Dict[str, Any] = {}, - styles: Optional[List[str]] = None, - audiences: Optional[List[str]] = None, - scenic_spots: Optional[List[str]] = None, - products: Optional[List[str]] = None) -> Tuple[str, str, Dict[str, Any], bool]: + style_objects: Optional[List[Dict[str, Any]]] = None, + audience_objects: Optional[List[Dict[str, Any]]] = None, + scenic_spot_objects: Optional[List[Dict[str, Any]]] = None, + product_objects: Optional[List[Dict[str, Any]]] = None) -> Tuple[str, str, Dict[str, Any], bool]: """ - 审核内容 - - Args: - topic: 选题信息 - content: 要审核的内容 - styles: 风格列表 - audiences: 受众列表 - scenic_spots: 景区列表 - products: 产品列表 - - Returns: - 请求ID、选题索引、审核后的内容和审核是否成功 + 审核内容 (已重构) """ - # 如果没有提供topic,创建一个基础的topic if not topic: topic = {"index": "1", "date": "2024-07-01"} - - # 如果没有提供content,返回错误 if not content: content = {"title": "未提供内容", "content": "未提供内容"} topicIndex = topic.get('index', 'unknown') logger.info(f"开始审核选题 {topicIndex} 的内容") - # 创建topic的副本并应用覆盖参数 + # 构建包含所有预取信息的enhanced_topic enhanced_topic = topic.copy() - if styles and len(styles) > 0: - enhanced_topic['style'] = styles[0] # 使用第一个风格 - if audiences and len(audiences) > 0: - enhanced_topic['targetAudience'] = audiences[0] # 使用第一个受众 - if scenic_spots and len(scenic_spots) > 0: - enhanced_topic['object'] = scenic_spots[0] # 使用第一个景区 - if products and len(products) > 0: - enhanced_topic['product'] = products[0] # 使用第一个产品 - - # 使用PromptBuilderService构建提示词 + if style_objects: + enhanced_topic['style_object'] = style_objects[0] + if audience_objects: + enhanced_topic['audience_object'] = audience_objects[0] + if scenic_spot_objects: + enhanced_topic['scenic_spot_object'] = scenic_spot_objects[0] + if product_objects: + enhanced_topic['product_object'] = product_objects[0] + system_prompt, user_prompt = self.prompt_builder.build_judge_prompt(enhanced_topic, content) - # 使用预构建的提示词进行审核 judged_data = await self.content_judger.judge_content_with_prompt(content, enhanced_topic, system_prompt, user_prompt) - # 提取审核是否成功(content_judger返回judge_success字段) judgeSuccess = judged_data.get('judge_success', False) - # 统一字段命名:移除judge_success,添加judgeSuccess if 'judge_success' in judged_data: judged_data = {k: v for k, v in judged_data.items() if k != 'judge_success'} judged_data['judgeSuccess'] = judgeSuccess - # 生成请求ID requestId = f"judge-{datetime.now().strftime('%Y%m%d-%H%M%S')}-{str(uuid.uuid4())[:8]}" logger.info(f"内容审核完成,请求ID: {requestId}, 选题索引: {topicIndex}, 审核结果: {judgeSuccess}")