Spaces:
Sleeping
Sleeping
| import base64 | |
| import io | |
| import os | |
| import sys | |
| import traceback | |
| # from http.cookiejar import domain_match | |
| from typing import List | |
| import gradio as gr | |
| from PIL import Image | |
| import time | |
| from langchain.agents import AgentExecutor, create_react_agent | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from model import * | |
| from gradio import ChatMessage, on, HTML | |
| from tools import * | |
| from prompt import * | |
| import sys | |
| from gradio.components.chatbot import MessageDict | |
| from langchain_community.llms import Tongyi | |
| from io import BytesIO | |
| from css import * | |
| from concurrent.futures import ThreadPoolExecutor | |
| from TrustedUrl import * | |
| from hashlib import md5 | |
| import random | |
| os.environ["OPENAI_API_KEY"] = "sb-6a683cb3bd63a9b72040aa2dd08feff8b68f08a0e1d959f5" | |
| os.environ['OPENAI_BASE_URL'] = "https://api.openai-sb.com/v1/" | |
| os.environ["SERPAPI_API_KEY"] = "dcc98b22d5f7d413979a175ff7d75b721c5992a3ee1e2363020b2bbdf4f82404" | |
| os.environ['TAVILY_API_KEY'] = "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" # you | |
| os.environ["REPLICATE_API_TOKEN"] = "r8_IYJpjwjrxegcUfBeBbyUxErJXXsnHDM4AlSQQ" | |
| os.environ["DASHSCOPE_API_KEY"] = "sk-8159f0ed38994c3b96b4527404ea1cda" | |
| class ImageProcessor: | |
| """Handles image processing and analysis""" | |
| def resize_image(image: Image.Image) -> Image.Image: | |
| """Resize image to appropriate dimensions""" | |
| width, height = image.size | |
| while width >= 500 or height >= 400: | |
| width = width * 0.8 | |
| height = height * 0.8 | |
| return image.resize((int(width), int(height))) | |
| def encode_image(image: Image.Image) -> str: | |
| """Encode image to base64 string""" | |
| buffered = io.BytesIO() | |
| image.save(buffered, format="PNG") | |
| return base64.b64encode(buffered.getvalue()).decode("utf-8") | |
| def load_image(image_path_or_url: str) -> Optional[Image.Image]: | |
| """Load image from path or URL""" | |
| if image_path_or_url.startswith(('http://', 'https://')): | |
| try: | |
| response = requests.get(image_path_or_url, timeout=10) | |
| response.raise_for_status() | |
| return Image.open(BytesIO(response.content)) | |
| except Exception as e: | |
| print(f"Failed to download image: {image_path_or_url} | Error: {e}") | |
| return None | |
| else: | |
| try: | |
| return Image.open(image_path_or_url) | |
| except Exception as e: | |
| print(f"Failed to open local image: {image_path_or_url} | Error: {e}") | |
| return None | |
| def analyze_image(image: Image.Image, prompt: str) -> str: | |
| base64_image = ImageProcessor.encode_image(image) | |
| client = OpenAI( | |
| api_key=os.getenv('DASHSCOPE_API_KEY'), | |
| base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", | |
| ) | |
| completion = client.chat.completions.create( | |
| model="qwen-vl-plus", | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": [{"type": "text", "text": f"{prompt}"}]}, | |
| { | |
| "role": "user", | |
| "content": [ | |
| { | |
| "type": "image_url", | |
| "image_url": {"url": f"data:image/png;base64,{base64_image}"}, | |
| }, | |
| {"type": "text", "text": "请分析该图片"}, | |
| ], | |
| } | |
| ], | |
| ) | |
| return completion.choices[0].message.content | |
| class FactChecker: | |
| """Main fact-checking agent class""" | |
| def __init__(self): | |
| self.tools = None | |
| self.selected_language = "ch" | |
| self.image_summaries = "" | |
| self.query = "" | |
| self.llm = Tongyi(model_name="qwen-plus", temperature=0.1) | |
| self.selected_contrast = "qwen" | |
| self.selected_model = "自研" | |
| self.contrast_model_prompt = "" | |
| self.fact_checking = False | |
| self.deep_search = 2 | |
| self.trusted_url = {"TRUSTED_DOMAINS_ABOARD":TRUSTED_DOMAINS_ABOARD,"TRUSTED_DOMAINS_DOMESTIC":TRUSTED_DOMAINS_DOMESTIC,"TRUSTED_SUFFIXES":TRUSTED_SUFFIXES,"TRUSTED_PERSON":TRUSTED_PERSON} | |
| dashscope.api_key = "sk-8159f0ed38994c3b96b4527404ea1cda" | |
| def set_language(self, language: str): | |
| """Set the language for responses""" | |
| self.selected_language = "ch" if language == "中文" else "en" | |
| def baidu_translate(self,query): | |
| appid = '20250629002392938' | |
| appkey = 'CAk49yEcWE0MTWzFdSK0' | |
| from_lang = 'zh' | |
| to_lang = 'en' | |
| endpoint = 'http://api.fanyi.baidu.com' | |
| path = '/api/trans/vip/translate' | |
| url = endpoint + path | |
| def make_md5(s, encoding='utf-8'): | |
| return md5(s.encode(encoding)).hexdigest() | |
| salt = random.randint(32768, 65536) | |
| sign = make_md5(appid + query + str(salt) + appkey) | |
| headers = {'Content-Type': 'application/x-www-form-urlencoded'} | |
| payload = {'appid': appid, 'q': query, 'from': from_lang, 'to': to_lang, 'salt': salt, 'sign': sign} | |
| r = requests.post(url, params=payload, headers=headers) | |
| result = r.json()["trans_result"][0]["dst"] | |
| return result | |
| def get_prompt(self) -> ChatPromptTemplate: | |
| if self.selected_language == "en": | |
| num_marker = "(This 'Thought/Action/Action Input/Observation' loop can repeat up to 3 times)" | |
| title_marker = "### 6. Historical records:" | |
| if self.fact_checking: | |
| prompt_with_title = en_prompt.replace( | |
| title_marker, | |
| f"{title_marker}\n### 7. When encountering personal professional titles (positions), it is important to carefully identify them\n" | |
| ) | |
| prompt_with_num = prompt_with_title.replace( | |
| num_marker, | |
| "(This 'Thought/Action/Action Input/Observation' loop can repeat up to 5 times)" if self.deep_search == 3 else "(This 'Thought/Action/Action Input/Observation' loop can repeat up to 3 times)" | |
| ) | |
| else: | |
| prompt_with_num = en_prompt | |
| print(prompt_with_num) | |
| return ChatPromptTemplate.from_template(prompt_with_num) | |
| else: | |
| num_marker = "(这个 'Thought/Action/Action Input/Observation' 逻辑可以最多重复3次)" | |
| title_marker = "### 6. 以下为历史记录: " | |
| if self.fact_checking: | |
| prompt_with_title = ch_prompt.replace( | |
| title_marker, | |
| f"{title_marker}\n### 7. 当遇到个人职称(职位)时要仔细鉴别\n" | |
| ) | |
| prompt_with_num = prompt_with_title.replace( | |
| num_marker, | |
| "(这个 'Thought/Action/Action Input/Observation' 逻辑可以最多重复5次)" if self.deep_search == 3 else "(这个 'Thought/Action/Action Input/Observation' 逻辑可以最多重复3次)" | |
| ) | |
| else: | |
| prompt_with_num = ch_prompt | |
| print(prompt_with_num) | |
| return ChatPromptTemplate.from_template(prompt_with_num) | |
| def get_image_prompt(self) -> str: | |
| if self.selected_language == "ch": | |
| return image_ch_prompt | |
| else: | |
| return image_en_prompt | |
| def process_image(self, image_path_or_url: str) -> Optional[str]: | |
| """Process and analyze an image""" | |
| if not image_path_or_url: | |
| return None | |
| image = ImageProcessor.load_image(image_path_or_url) | |
| if not image: | |
| return None | |
| image = ImageProcessor.resize_image(image) | |
| prompt = self.get_image_prompt() | |
| analysis = ImageProcessor.analyze_image(image, prompt) | |
| return analysis.replace('*', '').replace('\n', ' ').strip() | |
| def process_image_load(self, image: Image.Image) -> Optional[str]: | |
| if not image: | |
| return None | |
| image = ImageProcessor.resize_image(image) | |
| prompt = self.get_image_prompt() | |
| analysis = ImageProcessor.analyze_image(image, prompt) | |
| return analysis.replace('*', '').replace('\n', ' ').strip() | |
| def extract_agent_thoughts(self, intermediate_steps: List) -> List[ChatMessage]: | |
| """Extract agent thoughts from intermediate steps""" | |
| agent_thought = [] | |
| pattern1 = r"Pre Thought:(.*?)Thought: (.*?)\nAction: (.*?)\nAction Input: (.*)" | |
| pattern2 = r"Thought: (.*?)\nAction: (.*?)\nAction Input: (.*)" | |
| for index in intermediate_steps: | |
| match = re.search(pattern1, index[0].log, re.S) | |
| before_thought = match.group(1).strip() if match else "" | |
| thought = match.group(2).strip() if match else "" | |
| action = match.group(3).strip() if match else "" | |
| action_input = match.group(4).strip() if match else "" | |
| if self.image_summaries != "": | |
| agent_thought.append( | |
| ChatMessage( | |
| role="assistant", | |
| content=self.image_summaries, | |
| metadata={"title": "图片分析结果:"} | |
| ) | |
| ) | |
| if not match: | |
| match = re.search(pattern2, index[0].log, re.S) | |
| before_thought = match.group(1).strip() if match else "" | |
| thought = match.group(2).strip() if match else "" | |
| action_input = match.group(3).strip() if match else "" | |
| if len(before_thought) >= 3: | |
| # agent_thought.append({"title": "预思考:", "content": before_thought}) | |
| agent_thought.append( | |
| ChatMessage( | |
| role="assistant", | |
| content=before_thought, | |
| metadata={"title": "预思考:"} | |
| ) | |
| ) | |
| if len(action_input) >= 3: | |
| # agent_thought.append({"title": f"工具调用:{action}", "content": action_input}) | |
| web_search_tools = {"BoCha Webs Search","Image Search","Baidu News Search","tavily_search_results_json"} | |
| if action in web_search_tools or action == "": | |
| action = "网页搜索" | |
| agent_thought.append( | |
| ChatMessage( | |
| role="assistant", | |
| content=action_input, | |
| metadata={"title": f"工具调用:{action}"} | |
| ) | |
| ) | |
| return agent_thought | |
| def process_news_results(self, intermediate_steps: List) -> List[Dict]: | |
| news_results = [] | |
| if not intermediate_steps: | |
| return news_results | |
| first_step = intermediate_steps[0][0] | |
| if first_step.tool == "tavily_search_results_json": | |
| for step1 in intermediate_steps[:1]: # Only first step | |
| try: | |
| for step2 in step1[1][:3]: # First 3 results | |
| news_results.append({ | |
| "title": step2['content'][:28] + "...", | |
| "url": step2['url'], | |
| "source": "", | |
| "image": "", | |
| "time": "" | |
| }) | |
| except: | |
| continue | |
| elif first_step.tool == "BoCha Webs Search": | |
| for step1 in intermediate_steps: | |
| try: | |
| for i in range(3): | |
| news_results.append({ | |
| "title": step1[1]['data']['webPages']['value'][i]['snippet'][:28] + "...", | |
| "url": step1[1]['data']['webPages']['value'][i]['url'], | |
| "source": step1[1]['data']['webPages']['value'][i]['siteName'], | |
| "image": step1[1]['data']['images']['value'][i]['contentUrl'], | |
| "time": step1[1]['data']['webPages']['value'][i]['dateLastCrawled'] | |
| }) | |
| except: | |
| continue | |
| elif first_step.tool == "Baidu News Search": | |
| for step1 in intermediate_steps: | |
| try: | |
| for i in range(3): | |
| news_results.append({ | |
| "title": step1[1][i]['title'][:28] + "...", | |
| "url": step1[1][i]['link'], | |
| "source": step1[1][i]['source'], | |
| "image": "", | |
| "time": "" | |
| }) | |
| except: | |
| continue | |
| return news_results | |
| def set_tools(self): | |
| self.tools = [ | |
| BoChaSearchTool(selected_language=self.selected_language,fact_checking=self.fact_checking,trusted_url=self.trusted_url), | |
| ChineseTavilySearchResults(selected_language=self.selected_language,fact_checking=self.fact_checking,trusted_url=self.trusted_url), | |
| ImageSearchTool(selected_language=self.selected_language), | |
| WeatherCrossing(selected_language=self.selected_language), | |
| GetHoliday(selected_language=self.selected_language), | |
| GetLocation(selected_language=self.selected_language), | |
| CurrencyConversion(selected_language=self.selected_language), | |
| SafeCodeExecutor(selected_language=self.selected_language), | |
| SafeExpressionEvaluator(selected_language=self.selected_language), | |
| RegionInquiryTool(selected_language=self.selected_language), | |
| HTMLTextExtractor(selected_language=self.selected_language) | |
| ] | |
| def format_reply(reply:Dict) -> HTML: | |
| formatted_text = "" | |
| news_list = reply.get("news", []) | |
| container_html = '<div class="news-container">' # 添加容器 | |
| for news in news_list[:5]: | |
| card_html = f""" | |
| <div class="news-card"> | |
| <img src="{news['image']}" alt="新闻图片"> | |
| <div class="title"><a href="{news['url']}" target="_blank">{news['title']}</a></div> | |
| <div class="source">{news['source']}</div> | |
| </div> | |
| """ | |
| container_html += card_html | |
| container_html += '</div>' # 关闭容器 | |
| formatted_text += container_html | |
| formatted_text += reply.get("text", "") | |
| return gr.HTML(formatted_text) | |
| def contract(self)-> str | None: | |
| tool = BoChaSearchTool(self.selected_language) | |
| web_information = tool._run(self.query) | |
| information = [] | |
| for step in web_information['data']['webPages']['value']: | |
| information.append(step['snippet']) | |
| contract_query = self.query + "搜索到的相关新闻:" + str(information) | |
| if self.selected_contrast == "qwen": | |
| result = qwen(contract_query) | |
| elif self.selected_contrast == "llama": | |
| result = llama(contract_query) | |
| elif self.selected_contrast == "glm": | |
| result = glm(contract_query) | |
| elif self.selected_contrast == "doubao": | |
| result = doubao(contract_query) | |
| elif self.selected_contrast == "deepseek": | |
| result = deepseek(contract_query) | |
| else: | |
| result = baichuan(contract_query) | |
| return result | |
| def SelectLanguage(self,option:str): | |
| if option == "英文": | |
| self.selected_language = "en" | |
| else: | |
| self.selected_language = "ch" | |
| def SelectModel(self,option:str): | |
| if option == "自研": | |
| self.selected_model = "gpt" | |
| elif option == "llama3-70b": | |
| self.selected_model = "llama3-70b" | |
| elif option == "llama3-8b": | |
| self.selected_model = "llama3-8b" | |
| else: | |
| self.selected_model = "mistral" | |
| def SelectMode(self,option:str): | |
| if option == "否": | |
| self.fact_checking = False | |
| else: | |
| self.fact_checking = True | |
| def SelectDeep(self,option:str): | |
| if option == "普通检测": | |
| self.deep_search = 2 | |
| elif option == "深度检测": | |
| self.deep_search = 3 | |
| else: | |
| self.deep_search = 1 | |
| def SelectContrast(self,option:str): | |
| if option == "qwen": | |
| self.selected_contrast = "qwen" | |
| elif option == "llama": | |
| self.selected_contrast = "llama" | |
| elif option == "glm": | |
| self.selected_contrast = "glm" | |
| elif option == "doubao": | |
| self.selected_contrast = "doubao" | |
| elif option == "deepseek": | |
| self.selected_contrast = "deepseek" | |
| else: | |
| self.selected_contrast = "baichuan" | |
| def update_image_summaries(self): | |
| return self.image_summaries | |
| def ModelPrompt(self,prompt): | |
| self.contrast_model_prompt = prompt | |
| def AddTrustedSource(self,trusted_source): | |
| try: | |
| urls = trusted_source.split('\n') | |
| # print(urls) | |
| for url in urls: | |
| extracted = tldextract.extract(url) | |
| # subdomain = extracted.subdomain.lower() | |
| domain = extracted.domain.lower() | |
| suffix = extracted.suffix.lower() | |
| if domain not in self.trusted_url['TRUSTED_DOMAINS_DOMESTIC']: | |
| # print(domain) | |
| self.trusted_url['TRUSTED_DOMAINS_DOMESTIC'].add(domain) | |
| return "添加成功" | |
| except: | |
| return "添加失败,请重新尝试" | |
| def qwen_tool(self, query: str,language: str): | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": """ | |
| 记住, 你是一个虚假信息鉴定工具, 由哈尔滨工业大学的SCIR实验室开发。 | |
| 你的任务是鉴定输入内容的真实性,可调用相关工具来搜集证据,最终给出判断标签及判断理由。 | |
| ## 注意事项: | |
| ### 1. 鉴定结果的各个标签定义如下: | |
| (1) 虚假:内容中的所有细节或大部分细节都能被事实证明为虚假的。 | |
| (2) 真假参半:内容中约一半是真实的,一半是虚假的。 | |
| (3) 真实:内容中的所有细节或大部分细节都能被事实证明为真实的。 | |
| (4) 无法鉴定:缺乏事实来验证,且完全无法推测其真实性。 | |
| 或者无法理解输入内容,输入无完整语义,歧义太大。 | |
| 或者为不可鉴定的内容(例如:未来状态,观点)。 | |
| 或者无法归纳到其它标签。 | |
| ### 2. 按照以下专家经验判断: | |
| (1) 一步一步地思考,来生成解释,逻辑清晰,步骤详细,最好能体现思考及推理过程,并引用相关的证据,而不是简单的复述证据。 | |
| (2) 如果输入内容包含多个细节,会逐一验证。 | |
| (3) 虚假信息验证时的线索除了与事实(常识,自身知识,证据)冲突之外,还有语言学特征: | |
| - 词汇层面:情绪化词汇过多,模糊性词汇频繁出现 | |
| - 语义层面: 逻辑矛盾,内容前后表述自相矛盾; 缺乏合理因果关系,随意编造因果关联,把并无实质联系的两件事强行说成因果关系; 过度夸张或违背常识. | |
| - 风格层面: 语言风格与来源不符, 例声称是官方发布的信息,却采用随意、口语化且不规范的语言风格;模仿痕迹明显 | |
| - 语用层面: 意图引导性过强,不是客观呈现信息,带有强烈主观引导意图的内容; 语境不匹配 | |
| ### 3. 执行鉴定的过程需要使用中文。 | |
| ## 要求遵循如下格式来执行鉴定: | |
| Question: 需要被鉴定的内容。 | |
| Image_information: 对输入的图片的鉴定结果。 | |
| Pre Thought: 简要理解要验证的内容,是否包含多个要验证的细节部分,做好规划。 | |
| Thought: 为了验证第一个细节部分, 你应该怎么做. | |
| Action: 工具列表({tool_names})之一的工具名,不能有其他内容 | |
| Action Input: Action的输入参数. | |
| Observation: Action的执行结果. | |
| Thought: 为了验证第二个细节, 你应该怎么做. | |
| ... (这个 'Thought/Action/Action Input/Observation' 逻辑可以最多重复3次) | |
| Thought: 我现在知道最后的答案了。 | |
| Final Answer: 我的最终回答. 要求包含一段鉴定过程的总结, 判断标签及判断理由。 | |
| """, | |
| }, | |
| { | |
| "role": "user", | |
| "content": query, | |
| }, | |
| ] if language == "ch" else [ | |
| { | |
| "role": "system", | |
| "content": """ | |
| Remember, you are a misinformation identification tool developed by the SCIR Lab at Harbin Institute of Technology. | |
| Your task is to verify the authenticity of input content by calling relevant tools to gather evidence, and ultimately provide a judgment label with reasoning. | |
| ## Important Notes: | |
| ### 1. Definition of judgment labels: | |
| (1) False: All details or Most details in the content can be proven false with factual evidence. | |
| (3) Mixed: Approximately half of the content is true and half is false. | |
| (5) True: All details or Most details in the content can be proven true with factual evidence. | |
| (8) Cannot_Determine: | |
| - Lacks factual verification and cannot speculate authenticity | |
| - Or cannot understand input (incomplete semantics/ambiguous) | |
| - Or non-verifiable content (e.g., future states, opinions) | |
| - Or cannot be categorized into other labels | |
| ### 2. Follow these expert guidelines: | |
| (1) Think step-by-step to generate explanations with clear logic and detailed reasoning. Cite relevant evidence rather than simply restating it. | |
| (2) If input contains multiple claims, verify each one individually. | |
| (3) Misinformation clues include not only factual conflicts (common sense, knowledge, evidence) but also linguistic features: | |
| - Lexical level: Excessive emotional/ambiguous vocabulary | |
| - Semantic level: Logical contradictions, inconsistent statements; Lack of reasonable causation (forced correlations); Exaggeration or violation of common sense | |
| - Stylistic level: Language style inconsistent with claimed source (e.g., claiming official information but using informal language) | |
| - Pragmatic level: Strong intentional bias (non-objective presentation); Context mismatch | |
| ### 3. The verification process must be conducted in English. | |
| ## Required response format: | |
| Question: Content to be verified. | |
| Image_information: Verification results of input images. | |
| Pre Thought: Briefly understand the content to verify and plan verification steps for multiple claims if present. | |
| Thought: What should you do to verify the first claim. | |
| Action: Exactly one tool name from ({tool_names}), nothing else | |
| Action Input: Parameters for the Action. | |
| Observation: Result from the Action. | |
| Thought: What should you do to verify the second claim. | |
| ... (This 'Thought/Action/Action Input/Observation' loop can repeat up to 3 times) | |
| Thought: I now know the final answer. | |
| Final Answer: Must include: | |
| - Summary of verification process | |
| - Judgment label | |
| - Reasoning | |
| """, | |
| }, | |
| { | |
| "role": "user", | |
| "content": query, | |
| }, | |
| ] | |
| client = OpenAI( | |
| api_key="sk-8159f0ed38994c3b96b4527404ea1cda", | |
| base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", # 填写DashScope SDK的base_url | |
| ) | |
| tools = [ | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "bocha_search", | |
| "description": "首选搜索引擎,当需要检索信息时调用", | |
| # 因为获取当前时间无需输入参数,因此parameters为空字典 | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "query": { | |
| "type": "string", | |
| "description": "要检索的问题。", | |
| } | |
| }, | |
| "required": ["query"], | |
| }, | |
| }, | |
| } | |
| ] | |
| try: | |
| completion = client.chat.completions.create( | |
| model="qwen-plus", | |
| messages=messages, | |
| tools=tools, | |
| ) | |
| result_data = json.loads(completion.model_dump_json()) | |
| result = result_data["choices"][0]["message"]["content"] | |
| separator = "Final Answer:" | |
| if separator in result: | |
| # 分割文本并取最后一部分(防止多次出现) | |
| parts = result.split(separator) | |
| answer = parts[-1].strip() | |
| print(answer) | |
| return answer | |
| else: | |
| print("none") | |
| return "未检索到内容,请稍后重试" | |
| except: | |
| return "请求失败" | |
| def check_facts_chat(self,dict, space): | |
| """Main fact-checking method""" | |
| topic = dict['text'] | |
| image = dict['files'] | |
| if not image: | |
| self.image_summaries = "" | |
| else: | |
| image_path = image[0] | |
| image = Image.open(image_path) | |
| self.image_summaries = self.process_image_load(image) | |
| if not topic: | |
| return "无输入内容" | |
| self.query = topic | |
| topic_en = self.baidu_translate(topic) | |
| if self.deep_search == 1: | |
| result_ch = self.qwen_tool(topic,"ch") | |
| result_en = self.qwen_tool(topic_en,"en") | |
| return result_ch + "\n" + result_en | |
| # Create agent | |
| prompt = self.get_prompt() | |
| prompt_en =ChatPromptTemplate.from_template(en_prompt) | |
| self.set_tools() | |
| agent = create_react_agent(self.llm, self.tools, prompt) | |
| agent_en = create_react_agent(self.llm, self.tools, prompt_en) | |
| # Redirect stdout to capture agent thoughts | |
| captured_output = io.StringIO() | |
| sys.stdout = captured_output | |
| cur_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) | |
| # Execute agent | |
| agent_executor = AgentExecutor( | |
| agent=agent, | |
| tools=self.tools, | |
| verbose=True, | |
| handle_parsing_errors=True, | |
| return_intermediate_steps=True | |
| ) | |
| agent_executor_en = AgentExecutor( | |
| agent=agent_en, | |
| tools=self.tools, | |
| verbose=False, | |
| handle_parsing_errors=True, | |
| return_intermediate_steps=True | |
| ) | |
| def run_agent(agent_executor, input_data): | |
| return agent_executor.invoke(input_data) | |
| try: | |
| with ThreadPoolExecutor() as executor: | |
| future1 = executor.submit(run_agent, agent_executor, { | |
| "current_time": cur_time, | |
| "input": topic, | |
| "image_information": self.image_summaries | |
| }) | |
| future2 = executor.submit(run_agent, agent_executor_en, { | |
| "current_time": cur_time, | |
| "input": topic_en, | |
| "image_information": self.image_summaries | |
| }) | |
| response1 = future1.result() | |
| response2 = future2.result() | |
| # try: | |
| # response = agent_executor.invoke({ | |
| # "current_time": cur_time, | |
| # "input": topic, | |
| # "image_information": self.image_summaries | |
| # }) | |
| sys.stdout = sys.__stdout__ | |
| captured_content = str(captured_output.getvalue()) | |
| captured_content = re.sub(r'\x1b\[[0-9;]*m', '', captured_content) | |
| # Extract explanation from captured output | |
| explain = "" | |
| try: | |
| match = re.search(r'\{.*\}(.*)', captured_content, re.DOTALL) | |
| if match: | |
| extracted_content = match.group(1).strip() | |
| match2 = re.search(r'Summary:(.*)Final Answer:', extracted_content, re.DOTALL) | |
| if match2: | |
| explain = match2.group(1).strip() | |
| except Exception as e: | |
| print(f"Error extracting explanation: {e}") | |
| # Format response | |
| text = re.sub(r'<.*?>','',response1['output'].replace('*','').strip()) + '<br><br>' +re.sub(r'<.*?>','',response2['output'].replace('*','').strip()) | |
| reply_explain = { | |
| 'text': text, | |
| 'news': self.process_news_results(response1['intermediate_steps']) + self.process_news_results(response2['intermediate_steps']) | |
| } | |
| print(response1['intermediate_steps']) | |
| formatted_reply = FactChecker.format_reply(reply_explain) | |
| return [formatted_reply] + self.extract_agent_thoughts(response1['intermediate_steps']) | |
| except ValueError as e: | |
| if "DataInspectionFailed" in str(e): | |
| return "您的输入可能包含敏感内容,请重新描述。" | |
| else: | |
| return "出现错误,请稍后再试。" | |
| class History: | |
| def generate_chat_title(conversation: list[MessageDict]) -> str: | |
| title = "" | |
| for message in conversation: | |
| if message["role"] == "user": | |
| if isinstance(message["content"], str): | |
| title += message["content"] | |
| break | |
| else: | |
| title += "📎 " | |
| if len(title) > 40: | |
| title = title[:40] + "..." | |
| # print(title) | |
| return title or "Conversation" | |
| def load_chat_history(conversations): | |
| return gr.Dataset( | |
| samples=[ | |
| [History.generate_chat_title(conv)] | |
| for conv in conversations or [] | |
| if conv | |
| ] | |
| ) | |
| def save_conversation( | |
| index: int | None, | |
| conversation: list[MessageDict], | |
| saved_conversations: list[list[MessageDict]], | |
| ): | |
| if index is not None: | |
| saved_conversations[index] = conversation | |
| else: | |
| saved_conversations.append(conversation) | |
| index = len(saved_conversations) - 1 | |
| return index, saved_conversations | |
| def load_conversation( | |
| index: int, | |
| conversations: list[list[MessageDict]], | |
| ): | |
| return ( | |
| index, | |
| gr.Chatbot( | |
| value=conversations[index], # type: ignore | |
| feedback_value=[], | |
| type="messages" | |
| ), | |
| ) | |
| def build_interface() -> gr.Blocks: | |
| with gr.Blocks(css=css, theme='soft') as demo: | |
| gr.HTML( | |
| "<h1 style='font-size: 36px; text-align: center; color: #333333; margin-bottom: 20px;'>惊堂木-虚假信息检测系统</h1>") | |
| with gr.Tab(label='Chat'): | |
| with gr.Row(): | |
| with gr.Sidebar(): | |
| with gr.Column(scale=1): | |
| gr.Textbox(visible=False) | |
| with gr.Column(scale=1): | |
| gr.Textbox(visible=False) | |
| with gr.Column(scale=1): | |
| new_chat_button = gr.Button( | |
| "New chat", | |
| variant="primary", | |
| size="md", | |
| icon="plus.svg", | |
| ) | |
| chat_history_dataset = gr.Dataset( | |
| components=[gr.Textbox(visible=False)], | |
| show_label=False, | |
| layout="table", | |
| type="index", | |
| ) | |
| language_select = gr.Dropdown(["中文", "英文"], label="请选择要使用的语言", scale=1, value="中文") | |
| # model_select = gr.Dropdown(["自研", "llama3-70b", "llama3-8b", "mistral"], | |
| # label="请选择要使用的大模型", | |
| # scale=1, value="自研") | |
| mode_select = gr.Dropdown(["否","是"],label= "是否过滤可信信源",scale= 1, value= "否") | |
| deep_select = gr.Dropdown(["轻度检测","普通检测","深度检测"],label= "选择检测模式",scale= 1, value= "普通检测") | |
| with gr.Column(scale=3): | |
| bot = gr.ChatInterface( | |
| fn=factChecker.check_facts_chat, | |
| examples=[ | |
| { | |
| "text": "9月19日,马来西亚最高元首 Ibrahim 应邀对中国进行为期8天国事访问,亦是2024年1月上任以来首次访问东盟外国家。"}, | |
| { | |
| "text": "据最新天文研究,火星的轨道将逐渐接近地球,最终成为地球的“第二月亮”。天文学家预测这一变化将在2025年发生,届时火星将在夜空中与月亮一样明亮,影响全球潮汐和生态平衡。"} | |
| ], | |
| chatbot=gr.Chatbot(label='惊堂木', | |
| avatar_images=("./image/user.png", "./image/logo.png"), | |
| type="messages", | |
| height=600, | |
| layout="bubble", | |
| show_copy_button=True, | |
| show_copy_all_button=True, | |
| resizable=True | |
| ), | |
| multimodal=True, | |
| show_progress='full', | |
| type="messages", | |
| flagging_mode='manual', | |
| cache_examples=False, | |
| example_icons=["./image/search.png", "./image/search.png"], | |
| ) | |
| with gr.Column(scale=1): | |
| with gr.Accordion("添加可信信源", open=False): | |
| # img_info = gr.Textbox(label="分析结果", lines=5) | |
| trusted_source = gr.Textbox(label="请输入需要添加的可信信源",interactive=True,show_copy_button=True,placeholder="请用回车隔开,输入示例:url1\nurl2",lines=5) | |
| # contrast_model = gr.Textbox(label="对比模型", lines=5) | |
| # with gr.Accordion("展示对比模型prompt", open=False): | |
| # model_prompt = gr.Textbox(label="对比模型prompt", lines=5, placeholder=constract_model_prompt, interactive=True) | |
| # contrast_select = gr.Dropdown(["qwen", "llama", "glm", "doubao", "deepseek", "baichuan"], | |
| # label="请选择使用的对比模型", scale=1, value="qwen") | |
| # contrast_bn = gr.Button("展示对比模型") | |
| # prompt_bn = gr.Button("确定更改提示词") | |
| trusted_source_bn = gr.Button("确定添加可信信源") | |
| # img_bn.click(factChecker.update_image_summaries, [], img_info) | |
| language_select.change(factChecker.SelectLanguage, language_select, []) | |
| # model_select.change(factChecker.SelectModel, model_select, []) | |
| mode_select.change(factChecker.SelectMode, mode_select, []) | |
| trusted_source_bn.click(factChecker.AddTrustedSource,trusted_source,trusted_source) | |
| deep_select.change(factChecker.SelectDeep, deep_select, []) | |
| # contrast_select.change(factChecker.SelectContrast, contrast_select, []) | |
| # contrast_bn.click(factChecker.contract, [], contrast_model) | |
| # prompt_bn.click(factChecker.ModelPrompt,model_prompt, []) | |
| new_chat_button.click( | |
| lambda x: x, | |
| [bot.chatbot], | |
| [bot.chatbot_state], | |
| show_api=False, | |
| queue=False, | |
| ).then( | |
| History.save_conversation, | |
| [bot.conversation_id, bot.chatbot_state, bot.saved_conversations], | |
| [bot.conversation_id, bot.saved_conversations] | |
| ).then( | |
| lambda: (None, []), | |
| None, | |
| [bot.conversation_id, bot.chatbot], | |
| show_api=False, | |
| queue=False, | |
| ).then( | |
| lambda x: x, | |
| [bot.chatbot], | |
| [bot.chatbot_state], | |
| show_api=False, | |
| queue=False, | |
| ) | |
| on( | |
| triggers=[demo.load, bot.saved_conversations.change], | |
| fn=History.load_chat_history, | |
| inputs=bot.saved_conversations, | |
| outputs=chat_history_dataset, | |
| show_api=False, | |
| queue=False, | |
| ) | |
| chat_history_dataset.click( | |
| lambda: [], | |
| None, | |
| [bot.chatbot], | |
| show_api=False, | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| History.load_conversation, | |
| [chat_history_dataset, bot.saved_conversations], | |
| [bot.conversation_id, bot.chatbot], | |
| show_api=False, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| factChecker = FactChecker() | |
| demo = build_interface() | |
| demo.launch() |