├── docs ├── img.png ├── img_1.png ├── img_2.png ├── img_3.png ├── img_4.png ├── img_5.png ├── img_6.png ├── img_7.png ├── webui.png ├── preview.png ├── procode_env.png ├── procode_new.png └── procode_execute_timeout.png ├── 微信截图_20250608151435.png ├── 微信截图_20250608151523.png ├── 微信截图_20250608151550.png ├── 微信截图_20250608151607.png ├── 微信截图_20250608162545.png ├── search_engine ├── __pycache__ │ ├── tavily.cpython-310.pyc │ ├── __init__.cpython-310.pyc │ ├── volc_bot.cpython-310.pyc │ ├── bailian_bot.cpython-310.pyc │ └── search_engine.cpython-310.pyc ├── __init__.py ├── search_engine.py ├── volc_bot.py ├── tavily.py └── bailian_bot.py ├── pyproject.toml ├── __init__.py ├── run.sh ├── config.py ├── prompt.py ├── run_client.py ├── server.py ├── utils.py ├── run_local.py ├── run_client_wordpress.py ├── my_llm_profile_example.md ├── README.md ├── deep_search.py ├── webui.py └── demo.txt /docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img.png -------------------------------------------------------------------------------- /docs/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_1.png -------------------------------------------------------------------------------- /docs/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_2.png -------------------------------------------------------------------------------- /docs/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_3.png -------------------------------------------------------------------------------- /docs/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_4.png -------------------------------------------------------------------------------- /docs/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_5.png -------------------------------------------------------------------------------- /docs/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_6.png -------------------------------------------------------------------------------- /docs/img_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/img_7.png -------------------------------------------------------------------------------- /docs/webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/webui.png -------------------------------------------------------------------------------- /docs/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/preview.png -------------------------------------------------------------------------------- /docs/procode_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/procode_env.png -------------------------------------------------------------------------------- /docs/procode_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/procode_new.png -------------------------------------------------------------------------------- /微信截图_20250608151435.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/微信截图_20250608151435.png -------------------------------------------------------------------------------- /微信截图_20250608151523.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/微信截图_20250608151523.png -------------------------------------------------------------------------------- /微信截图_20250608151550.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/微信截图_20250608151550.png -------------------------------------------------------------------------------- /微信截图_20250608151607.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/微信截图_20250608151607.png -------------------------------------------------------------------------------- /微信截图_20250608162545.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/微信截图_20250608162545.png -------------------------------------------------------------------------------- /docs/procode_execute_timeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/docs/procode_execute_timeout.png -------------------------------------------------------------------------------- /search_engine/__pycache__/tavily.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/search_engine/__pycache__/tavily.cpython-310.pyc -------------------------------------------------------------------------------- /search_engine/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/search_engine/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /search_engine/__pycache__/volc_bot.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/search_engine/__pycache__/volc_bot.cpython-310.pyc -------------------------------------------------------------------------------- /search_engine/__pycache__/bailian_bot.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/search_engine/__pycache__/bailian_bot.cpython-310.pyc -------------------------------------------------------------------------------- /search_engine/__pycache__/search_engine.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdabiao/deep_search_write/HEAD/search_engine/__pycache__/search_engine.cpython-310.pyc -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "deep-search" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["liuminghao "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">=3.10.0,<3.12.0" 10 | arkitect = "^0.1.4" 11 | tavily-python = "^0.5.1" 12 | volcengine-python-sdk = ">=1.0.123" 13 | openpyxl = "^3.1.5" 14 | openai = "^1.63.0" 15 | tiktoken = "0.8.0" 16 | gradio = "^5.17.1" 17 | 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | python -m server -------------------------------------------------------------------------------- /search_engine/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | from .search_engine import SearchEngine, SearchResult, SearchReference 13 | 14 | __all__ = [ 15 | "SearchEngine", 16 | "SearchResult", 17 | "SearchReference" 18 | ] -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | import os 13 | 14 | """ 15 | for server 16 | """ 17 | 18 | # recommend to use DeepSeek-R1 model 19 | REASONING_MODEL = os.getenv('REASONING_MODEL') or "deepseek-r1-250120" 20 | # default set to volc bot, if using tavily, change it into "tavily" 21 | SEARCH_ENGINE = os.getenv('SEARCH_ENGINE') or "volc_bot" 22 | # optional, if you select tavily as search engine, please configure this 23 | TAVILY_API_KEY = os.getenv('TAVILY_API_KEY') or "{YOUR_TAVILY_API_KEY}" 24 | # optional, if you select volc bot as search engine, please configure this 25 | SEARCH_BOT_ID = os.getenv('SEARCH_BOT_ID') or "bot-202505s214322-zbhw9" 26 | 27 | """ 28 | for webui 29 | """ 30 | 31 | # ark api key 32 | ARK_API_KEY = os.getenv('ARK_API_KEY') or "f9108b4fac08c516" 33 | # api server address for web ui 34 | API_ADDR = os.getenv("API_ADDR") or "https://ark.cn-beijing.volces.com/api/v3/bots" 35 | # while using remote api, need bot id 36 | API_BOT_ID = os.getenv("API_BOT_ID") or "bot-202505s214322-zbhw9" 37 | 38 | 39 | -------------------------------------------------------------------------------- /prompt.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | from jinja2 import Template 13 | 14 | DEFAULT_SUMMARY_PROMPT = Template( 15 | """# 历史对话 16 | {{chat_history}} 17 | 18 | # 联网参考资料 19 | {{reference}} 20 | 21 | # 当前环境信息 22 | {{meta_info}} 23 | 24 | # 任务 25 | - 优先参考「联网参考资料」中的信息进行回复。 26 | - 回复请使用清晰的语言,确保用户轻松理解和使用。 27 | - 回答的最后需要列出已参考的所有资料信息。格式如下:资料名称 28 | 示例: 29 | [1] 火山引擎 30 | [3] 火山方舟大模型服务平台 31 | 32 | # 任务执行 33 | 遵循任务要求来回答「用户问题」,给出有帮助的回答。 34 | 35 | 用户问题: 36 | {{question}} 37 | 38 | # 你的回答: 39 | """ 40 | ) 41 | 42 | DEFAULT_PLANNING_PROMPT = Template( 43 | """ 44 | 你是一个联网信息搜索专家,你需要根据用户的问题,通过联网搜索来搜集相关信息,然后根据这些信息来回答用户的问题。 45 | 46 | # 用户问题: 47 | {{question}} 48 | 49 | # 当前已知资料 50 | 51 | {{reference}} 52 | 53 | # 当前环境信息 54 | 55 | {{meta_info}} 56 | 57 | # 任务 58 | - 判断「当前已知资料」是否已经足够回答用户的问题(大概60%可以就算是可以) 59 | - 如果「当前已知资料」已经足够回答用户的问题,返回“无需检索”,不要输出任何其他多余的内容 60 | - 如果判断「当前已知资料」还不足以回答用户的问题,思考还需要搜索什么信息,输出对应的关键词,请保证每个关键词的精简和独立性 61 | - 可以输出1 ~ {{max_search_words}}个关键词,当暂时无法提出足够准确的关键词时,请适当地减少关键词的数量 62 | - 输出多个关键词时,关键词之间用 ; 分割,不要输出其他任何多余的内容 63 | 64 | # 你的回答: 65 | """ 66 | ) 67 | -------------------------------------------------------------------------------- /search_engine/search_engine.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | from abc import ABC, abstractmethod 13 | from pydantic import BaseModel 14 | from typing import Optional, List 15 | 16 | """ 17 | single reference definition 18 | """ 19 | 20 | 21 | class SearchReference(BaseModel): 22 | site: Optional[str] 23 | title: Optional[str] 24 | url: Optional[str] 25 | content: Optional[str] 26 | 27 | 28 | """ 29 | search_result definition 30 | """ 31 | 32 | 33 | class SearchResult(BaseModel): 34 | # query is the input question 35 | query: str = "" 36 | # summary_content is a summary plain text of the query result. 37 | summary_content: Optional[str] = None 38 | # search_references is the raw references of searched result 39 | search_references: Optional[List[SearchReference]] = None 40 | 41 | 42 | """ 43 | search_engine interface 44 | """ 45 | 46 | 47 | class SearchEngine(BaseModel, ABC): 48 | 49 | @abstractmethod 50 | def search(self, queries: List[str]) -> List[SearchResult]: 51 | pass 52 | 53 | @abstractmethod 54 | async def asearch(self, queries: List[str]) -> List[SearchResult]: 55 | pass 56 | -------------------------------------------------------------------------------- /search_engine/volc_bot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | import asyncio 13 | from abc import ABC 14 | from typing import List, Optional 15 | 16 | from volcenginesdkarkruntime import AsyncArk 17 | from volcenginesdkarkruntime.types.bot_chat import BotChatCompletion 18 | 19 | from .search_engine import SearchEngine, SearchResult, SearchReference 20 | 21 | """ 22 | using volc bot (with search plugin) to search 23 | """ 24 | 25 | 26 | class VolcBotSearchEngine(SearchEngine, ABC): 27 | 28 | def __init__( 29 | self, 30 | bot_id: str, 31 | api_key: Optional[str] = None, 32 | ): 33 | super().__init__() 34 | self._bot_id = bot_id 35 | self._ark_client = AsyncArk(api_key=api_key) 36 | 37 | def search(self, queries: List[str]) -> List[SearchResult]: 38 | return asyncio.run(self.asearch(queries=queries)) 39 | 40 | async def asearch(self, queries: List[str]) -> List[SearchResult]: 41 | tasks = [self._single_search(query) for query in queries] 42 | task_results = await asyncio.gather(*tasks) 43 | return [ 44 | result for result in task_results 45 | ] 46 | 47 | async def _single_search(self, query: str) -> SearchResult: 48 | response = await self._run_bot_search(query) 49 | return self._format_result(response, query) 50 | 51 | async def _run_bot_search(self, query: str) -> BotChatCompletion: 52 | return await self._ark_client.bot_chat.completions.create( 53 | messages=[ 54 | { 55 | "role": "user", 56 | "content": query, 57 | } 58 | ], 59 | model=self._bot_id, 60 | stream=False, 61 | timeout=360 # 添加360秒超时设置 62 | ) 63 | 64 | @classmethod 65 | def _format_result(cls, response: BotChatCompletion, query: str) -> SearchResult: 66 | return SearchResult( 67 | query=query, 68 | summary_content=response.choices[0].message.content, 69 | search_references=[ 70 | SearchReference( 71 | site=r.site_name, 72 | url=r.url, 73 | content=r.summary, 74 | title=r.title, 75 | ) for r in response.references if response.references 76 | ] 77 | ) 78 | -------------------------------------------------------------------------------- /search_engine/tavily.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | from abc import ABC 13 | from typing import Literal, Optional, List 14 | 15 | from tavily import TavilyClient 16 | 17 | from .search_engine import SearchEngine, SearchResult 18 | 19 | import asyncio 20 | 21 | 22 | class TavilySearchEngine(SearchEngine, ABC): 23 | 24 | def __init__( 25 | self, 26 | api_key: str, 27 | search_depth: Literal["basic", "advanced"] = "basic", 28 | topic: Literal["general", "news"] = "general", 29 | days: int = 3, 30 | max_results: int = 5, 31 | include_domains: Optional[str] = None, 32 | exclude_domains: Optional[str] = None, 33 | ): 34 | super().__init__() 35 | self._tavily_client = TavilyClient(api_key=api_key) 36 | self._search_depth = search_depth 37 | self._topic = topic 38 | self._days = days 39 | self._max_results = max_results 40 | self._include_domains = include_domains 41 | self._exclude_domains = exclude_domains 42 | 43 | def search(self, queries: List[str]) -> List[SearchResult]: 44 | return asyncio.run(self.asearch(queries=queries)) 45 | 46 | async def asearch(self, queries: List[str]) -> List[SearchResult]: 47 | tasks = [self._arun_search_single(query) for query in queries] 48 | task_results = await asyncio.gather(*tasks) 49 | return [ 50 | r for r in task_results 51 | ] 52 | 53 | async def _arun_search_single(self, query: str) -> SearchResult: 54 | return await asyncio.to_thread(self._search_single, query) 55 | 56 | def _search_single(self, query: str) -> SearchResult: 57 | response = self._tavily_client.search( 58 | query=query, 59 | search_depth=self._search_depth, 60 | topic=self._topic, 61 | days=self._days, 62 | max_results=self._max_results, 63 | include_domains=self._include_domains, 64 | exclude_domains=self._exclude_domains, 65 | ) 66 | return SearchResult( 67 | query=query, 68 | summary_content=self._format_result(response), 69 | ) 70 | 71 | @classmethod 72 | def _format_result(cls, tavily_result: dict) -> str: 73 | results = tavily_result.get("results", []) 74 | formatted: str = "" 75 | for (i, result) in enumerate(results): 76 | formatted += f"参考资料{i + 1}: \n" 77 | formatted += f"标题: {result.get('title', '')}\n" 78 | formatted += f"内容: {result.get('content', '')}\n" 79 | formatted += "\n" 80 | return formatted 81 | -------------------------------------------------------------------------------- /run_client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | # AI 写文章,客户端,调用server服务端 13 | # 前提:需要打开 server.py ,localhost:8888 14 | 15 | from openai import OpenAI 16 | 17 | client = OpenAI(base_url="http://localhost:8888/api/v3/bots", api_key="{API_KEY}") 18 | 19 | 20 | def main(): 21 | 22 | QUERY1 = """你是一个资深积木乐高专栏作家,写作流程: 23 | 1.基于以下新闻,请找出1个和新闻相关的主题(不要关心品牌,年份等细节,主题应该是简单的5个字以内),然后基于每一个新闻主题整理一个图文介绍(包括展示"image_arr_new"图片)。例如:新闻"三叉戟的八格车来袭!玛莎拉蒂授权积木实测:咬合力拉满,鲨鱼腮却失踪了?【测评】" ,那么主题就是“八格车”“莎拉蒂”。 24 | 2.然后进行至少一次搜索:搜索相关新闻主题进行资料补充(例如,怎样拼装[主题],查找[主题]等等),补充资料也需要图片展示 ,同时整理资料不要复杂(简单补充则可,资料相关就可以,搜集资料能够写出文章就可以,同时每一个主题至少搜集一次补充资料) 25 | 3.最后:按以上整理的所有内容写一个吸引人的文章【描写热点主题相关能够让观众感到兴趣和愉快】,文章内容包括今天热点主题介绍+补充的相关知识资料,文章主题结构:【主题1+补充1,主题2+补充2,...】 26 | 4.注意:务必要保留搜索到的相关图片,分析主题不要过多关注(品牌,定价,用户评价,具体细节)。 27 | 5.现在给你提供今天小红书的积木热点新闻材料:### 28 | 29 | { 30 | "_id": "68353ff0ba2d733faeefddd9", 31 | "note_id": "679dd36b000000001801a602", 32 | "title": "乐高MOC图纸:小米SU7", 33 | "user_nickname": "慕舞歆", 34 | "user_avatar": "https://sns-avatar-qc.xhscdn.com/avatar/1040g2jo315vum3482o6g5nhd7fl08ogk8v769bg?imageView2/2/w/80/format/jpg", 35 | "user_id": "5e2d3bea0000000001006214", 36 | "liked_count": "1075", 37 | "collected_count": "972", 38 | "comment_count": "89", 39 | "shared_count": "82", 40 | "cover_url": "http://sns-webpic-qc.xhscdn.com/202505271229/bad434ce664abbffade7ce08cafcfbd7/1040g2sg31hpdicnjjuk05nhd7fl08ogku9uitmo!nc_n_webp_mw_1", 41 | "publish_time": 1748320209, 42 | "image_arr_new": [ 43 | "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/xhs_images/1040g2sg31hpdicnjjuk05nhd7fl08ogku9uitmo!nc_n_webp_prv_1.jpg", 44 | "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/xhs_images/1040g2sg31hpdicnjjuj05nhd7fl08ogkacmkrfo!nc_n_webp_prv_1.jpg", 45 | "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/xhs_images/1040g00831hpdkk183o6g5nhd7fl08ogk08tdjv0!nc_n_webp_prv_1.jpg", 46 | "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/xhs_images/1040g2sg31hpdkk17jukg5nhd7fl08ogk4djk138!nc_n_webp_prv_1.jpg", 47 | "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/xhs_images/1040g00831hpdkk17js6g5nhd7fl08ogkgcvvm20!nc_n_webp_prv_1.jpg", 48 | ], 49 | "synced": false 50 | }, 51 | 52 | 53 | ###,""" 54 | # stream run 55 | stream_resp = client.chat.completions.create( 56 | model="test", 57 | messages=[ 58 | { 59 | "role": "user", 60 | "content": QUERY1, 61 | } 62 | ], 63 | stream=True, 64 | ) 65 | 66 | thinking = False 67 | 68 | for chunk in stream_resp: 69 | if chunk.choices[0].delta.model_extra.get("reasoning_content"): 70 | if not thinking: 71 | print("\n----思考过程----\n") 72 | thinking = True 73 | content = chunk.choices[0].delta.model_extra.get("reasoning_content", "") 74 | print(content, end="", flush=True) 75 | elif chunk.choices[0].delta.content: 76 | if thinking: 77 | print("\n----输出回答----\n") 78 | thinking = False 79 | print(chunk.choices[0].delta.content, end="", flush=True) 80 | 81 | 82 | if __name__ == "__main__": 83 | main() 84 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | import logging 13 | import os 14 | from typing import AsyncIterable, Union 15 | 16 | from arkitect.core.component.llm.model import ( 17 | ArkChatCompletionChunk, 18 | ArkChatRequest, 19 | ArkChatResponse, 20 | ) 21 | from arkitect.launcher.local.serve import launch_serve 22 | from arkitect.launcher.vefaas import bot_wrapper 23 | from arkitect.telemetry.trace import task 24 | from search_engine.tavily import TavilySearchEngine 25 | from search_engine.volc_bot import VolcBotSearchEngine 26 | from search_engine.bailian_bot import BailianBotSearchEngine 27 | from deep_search import DeepSearch, ExtraConfig 28 | 29 | from utils import get_last_message 30 | 31 | from config import ( 32 | REASONING_MODEL, 33 | SEARCH_ENGINE, 34 | TAVILY_API_KEY, 35 | SEARCH_BOT_ID, 36 | ) 37 | SEARCH_ENGINE = "bailian" 38 | 39 | logging.basicConfig( 40 | level=logging.INFO, format="[%(asctime)s][%(levelname)s] %(message)s" 41 | ) 42 | LOGGER = logging.getLogger(__name__) 43 | 44 | 45 | @task() 46 | async def main( 47 | request: ArkChatRequest, 48 | ) -> AsyncIterable[Union[ArkChatCompletionChunk, ArkChatResponse]]: 49 | # using last_user_message as query 50 | last_user_message = get_last_message(request.messages, "user") 51 | if last_user_message is None: 52 | LOGGER.error("No user message found in request") 53 | raise ValueError("No user message found in request") 54 | # set search_engine 55 | search_engine = VolcBotSearchEngine(bot_id=SEARCH_BOT_ID) 56 | if "tavily" == SEARCH_ENGINE: 57 | search_engine = TavilySearchEngine(api_key=TAVILY_API_KEY) 58 | elif "bailian" == SEARCH_ENGINE: 59 | search_engine = BailianBotSearchEngine(bot_id=SEARCH_BOT_ID) 60 | 61 | # settings from request 62 | metadata = request.metadata or {} 63 | max_search_words = metadata.get('max_search_words', 5) 64 | max_planning_rounds = metadata.get('max_planning_rounds', 5) 65 | 66 | deep_research = DeepSearch( 67 | search_engine=search_engine, 68 | planning_endpoint_id=REASONING_MODEL, 69 | summary_endpoint_id=REASONING_MODEL, 70 | extra_config=ExtraConfig( 71 | # optional, the max search words for each planning rounds 72 | max_search_words=max_search_words, 73 | # optional, the max rounds to run planning 74 | max_planning_rounds=max_planning_rounds, 75 | ) 76 | ) 77 | 78 | if request.stream: 79 | async for c in deep_research.astream_deep_research(request=request, question=last_user_message.content): 80 | yield c 81 | else: 82 | rsp = await deep_research.arun_deep_research(request=request, question=last_user_message.content) 83 | yield rsp 84 | 85 | 86 | @bot_wrapper() 87 | @task(custom_attributes={"input": None, "output": None}) 88 | async def handler( 89 | request: ArkChatRequest, 90 | ) -> AsyncIterable[Union[ArkChatCompletionChunk, ArkChatResponse]]: 91 | async for resp in main(request): 92 | yield resp 93 | 94 | 95 | if __name__ == "__main__": 96 | port = os.getenv("_FAAS_RUNTIME_PORT") 97 | launch_serve( 98 | package_path="server", 99 | port=int(port) if port else 8888, 100 | health_check_path="/v1/ping", 101 | endpoint_path="/api/v3/bots/chat/completions", 102 | trace_on=False, 103 | clients={}, 104 | ) 105 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | import asyncio 12 | import time 13 | from datetime import datetime 14 | from typing import List, AsyncIterable, Generator 15 | 16 | import volcenginesdkarkruntime.types.chat.chat_completion_chunk as completion_chunk 17 | 18 | from arkitect.core.component.llm.llm import ArkMessage, ArkChatCompletionChunk 19 | import requests 20 | import os 21 | 22 | 23 | def cast_content_to_reasoning_content( 24 | chunk: ArkChatCompletionChunk, 25 | ) -> ArkChatCompletionChunk: 26 | new_chunk = ArkChatCompletionChunk(**chunk.__dict__) 27 | new_chunk.choices[0].delta.reasoning_content = chunk.choices[0].delta.content 28 | new_chunk.choices[0].delta.content = "" 29 | return new_chunk 30 | 31 | 32 | def cast_reference_to_chunks(keyword: str, raw_content: str) -> ArkChatCompletionChunk: 33 | new_chunk = ArkChatCompletionChunk( 34 | id="", 35 | object="chat.completion.chunk", 36 | created=0, 37 | model="", 38 | choices=[], 39 | metadata={ 40 | "reference": raw_content, 41 | "keyword": keyword, 42 | }, 43 | ) 44 | return new_chunk 45 | 46 | 47 | def get_last_message(messages: List[ArkMessage], role: str): 48 | """Finds the last ArkMessage of a specific role, given the role.""" 49 | for message in reversed(messages): 50 | if message.role == role: 51 | return message 52 | return None 53 | 54 | 55 | def get_current_date() -> str: 56 | return datetime.now().strftime("%Y年%m月%d日") 57 | 58 | 59 | def gen_metadata_chunk(metadata: dict) -> ArkChatCompletionChunk: 60 | return ArkChatCompletionChunk( 61 | id='', 62 | created=int(time.time()), 63 | model='', 64 | object='chat.completion.chunk', 65 | choices=[completion_chunk.Choice( 66 | index=0, 67 | delta=completion_chunk.ChoiceDelta( 68 | content="", 69 | reasoning_content="" 70 | ), 71 | )], 72 | metadata=metadata, 73 | ) 74 | 75 | 76 | def sync_wrapper(async_generator: AsyncIterable) -> Generator: 77 | loop = asyncio.new_event_loop() 78 | try: 79 | gen = async_generator 80 | _aiter = gen.__aiter__() 81 | while True: 82 | try: 83 | item = loop.run_until_complete(_aiter.__anext__()) 84 | yield item 85 | except StopAsyncIteration: 86 | break 87 | finally: 88 | loop.close() 89 | 90 | 91 | def upload_to_oss(image_url: str) -> str: 92 | """ 93 | 下载远程图片并上传到OSS,返回OSS URL 94 | :param image_url: 远程图片URL 95 | :return: OSS URL 96 | """ 97 | try: 98 | # 1. 下载图片 99 | response = requests.get(image_url, timeout=10) 100 | response.raise_for_status() 101 | 102 | # 2. 从环境变量获取OSS配置 103 | oss_endpoint = os.getenv('OSS_ENDPOINT') or "your-oss-endpoint" 104 | oss_bucket = os.getenv('OSS_BUCKET') or "your-bucket-name" 105 | oss_access_key = os.getenv('OSS_ACCESS_KEY') 106 | oss_secret_key = os.getenv('OSS_SECRET_KEY') 107 | 108 | if not all([oss_endpoint, oss_bucket, oss_access_key, oss_secret_key]): 109 | raise ValueError("缺少必要的OSS配置参数") 110 | 111 | # 3. 上传到OSS(这里需要替换为实际的OSS SDK调用) 112 | # 示例代码,实际实现需要根据使用的OSS服务调整 113 | oss_url = f"https://{oss_bucket}.{oss_endpoint}/{os.path.basename(image_url)}" 114 | 115 | # 4. 返回OSS URL 116 | return oss_url 117 | except Exception as e: 118 | print(f"上传图片到OSS失败: {e}") 119 | return image_url # 失败时返回原URL 120 | -------------------------------------------------------------------------------- /run_local.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | import asyncio 13 | import logging 14 | 15 | from arkitect.core.component.llm.model import ArkMessage, ArkChatRequest 16 | 17 | from deep_search import DeepSearch, ExtraConfig 18 | from search_engine.volc_bot import VolcBotSearchEngine 19 | from search_engine.tavily import TavilySearchEngine 20 | from search_engine.bailian_bot import BailianBotSearchEngine 21 | 22 | logging.basicConfig( 23 | level=logging.INFO, format="[%(asctime)s][%(levelname)s] %(message)s" 24 | ) 25 | LOGGER = logging.getLogger(__name__) 26 | 27 | # recommend to use DeepSeek-R1 model 28 | REASONING_EP_ID = "deepseek-r1-250120" 29 | # default set to volc bot, if using tavily, change it into "tavily" 30 | SEARCH_ENGINE = "bailian" #"volc_bot" 31 | # optional, if you select tavily as search engine, please configure this 32 | TAVILY_API_KEY = "{YOUR_TAVILY_API_KEY}" 33 | # optional, if you select volc bot as search engine, please configure this 34 | SEARCH_BOT_ID = "bot-20250522214322-zbhw9" 35 | #QUERY = """你是一个资深积木乐高专栏作家,写作流程:基于以下资料,请找出2个和新品发布相关的主题(不要关心品牌,年份和细节),然后搜索相关资料补充(例如,怎样拼装XX,查找积木XX图片等等),写一个吸引人的文章,文章内容包括今天热点和补充的知识资料,务必要保留搜索到的相关图片,不要关注(品牌,定价,用户评价,具体细节),描写热点主题相关能够让观众感到兴趣,不要太认真(资料相关就可以,搜集资料能够写出文章就可以,同时每一个主题至少搜集一次补充资料),现在给你提供小红书的积木今天热点新闻:### 36 | 37 | QUERY = """你是一个资深积木乐高专栏作家,写作流程: 38 | 1.基于以下新闻,请找出1个和新闻相关的主题(不要关心品牌,年份等细节,主题应该是简单的5个字以内),然后基于每一个新闻主题整理一个图文介绍(包括展示"cover_url"图片)。例如:新闻"三叉戟的八格车来袭!玛莎拉蒂授权积木实测:咬合力拉满,鲨鱼腮却失踪了?【测评】" ,那么主题就是“八格车”“莎拉蒂”。 39 | 2.然后进行至少一次搜索:搜索相关新闻主题进行资料补充(例如,怎样拼装[主题],查找[主题]等等),补充资料也需要图片展示 ,同时整理资料不要复杂(简单补充则可,资料相关就可以,搜集资料能够写出文章就可以,同时每一个主题至少搜集一次补充资料) 40 | 3.最后:按以上整理的所有内容写一个吸引人的文章【描写热点主题相关能够让观众感到兴趣和愉快】,文章内容包括今天热点主题介绍+补充的相关知识资料,文章主题结构:【主题1+补充1,主题2+补充2,...】 41 | 4.注意:务必要保留搜索到的相关图片,分析主题不要过多关注(品牌,定价,用户评价,具体细节)。 42 | 5.现在给你提供今天小红书的积木热点新闻材料:### 43 | 44 | { 45 | "_id": "68353ff0ba2d733faeefddd5", 46 | "note_id": "683261a0000000000303f704", 47 | "title": "乐高变形金刚G1迷你钢索拼搭图纸分享", 48 | "user_nickname": "积木星球", 49 | "user_avatar": "https://sns-avatar-qc.xhscdn.com/avatar/1040g2jo31g5qvkhv1q505pp5k1a083injvulmp8?imageView2/2/w/80/format/jpg", 50 | "user_id": "6725a0540000000001000e57", 51 | "liked_count": "303", 52 | "collected_count": "310", 53 | "comment_count": "6", 54 | "shared_count": "15", 55 | "cover_url": "http://sns-webpic-qc.xhscdn.com/202505271229/f866401723bba6bb8b179d6153126947/1040g2sg31hssj4763ojg5pp5k1a083inlqi7blo!nc_n_webp_mw_1", 56 | "publish_time": 1748320200, 57 | "image_arr_new": [ 58 | "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/xhs_images/1040g2sg31hssj4763ojg5pp5k1a083inlqi7blo!nc_n_webp_prv_1.jpg" 59 | ], 60 | "synced": false 61 | }, 62 | 63 | 64 | ###,""" 65 | 66 | 67 | async def main(): 68 | search_engine = VolcBotSearchEngine(bot_id=SEARCH_BOT_ID) 69 | 70 | if "tavily" == SEARCH_ENGINE: 71 | search_engine = TavilySearchEngine(api_key=TAVILY_API_KEY) 72 | elif "bailian" == SEARCH_ENGINE: 73 | search_engine = BailianBotSearchEngine(bot_id=SEARCH_BOT_ID) 74 | 75 | deep_research = DeepSearch( 76 | search_engine=search_engine, 77 | planning_endpoint_id=REASONING_EP_ID, 78 | summary_endpoint_id=REASONING_EP_ID, 79 | extra_config=ExtraConfig( 80 | max_planning_rounds=5, # max rounds to planning and search 81 | max_search_words=5, # max search words for each planning round. 82 | ) 83 | ) 84 | 85 | thinking = False 86 | async for chunk in deep_research.astream_deep_research( 87 | request=ArkChatRequest(model="test", 88 | messages=[ArkMessage(role="user", 89 | content=QUERY)]), 90 | question=QUERY 91 | ): 92 | if chunk.choices[0].delta.reasoning_content: 93 | if not thinking: 94 | print("\n----思考过程----\n") 95 | thinking = True 96 | print(chunk.choices[0].delta.reasoning_content, end="") 97 | elif chunk.choices[0].delta.content: 98 | if thinking: 99 | print("\n----输出回答----\n") 100 | thinking = False 101 | print(chunk.choices[0].delta.content, end="") 102 | 103 | 104 | if __name__ == '__main__': 105 | asyncio.run(main()) 106 | -------------------------------------------------------------------------------- /search_engine/bailian_bot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | import os 13 | import asyncio 14 | from abc import ABC 15 | from typing import List, Optional 16 | from http import HTTPStatus 17 | from dashscope import Application 18 | 19 | # from volcenginesdkarkruntime import AsyncArk 20 | # from volcenginesdkarkruntime.types.bot_chat import BotChatCompletion 21 | 22 | #from .search_engine import SearchEngine, SearchResult, SearchReference 23 | from .search_engine import SearchEngine, SearchResult, SearchReference 24 | 25 | """ 26 | using bailian(百炼) bot (with search plugin) to search 27 | """ 28 | 29 | 30 | class BailianBotSearchEngine(SearchEngine, ABC): 31 | api_key: Optional[str] = None 32 | 33 | def __init__( 34 | self, 35 | bot_id: str, 36 | api_key: Optional[str] = None, 37 | ): 38 | super().__init__() 39 | self._bot_id = bot_id 40 | self.api_key = api_key 41 | 42 | def search(self, queries: List[str]) -> List[SearchResult]: 43 | return asyncio.run(self.asearch(queries=queries)) 44 | 45 | async def asearch(self, queries: List[str]) -> List[SearchResult]: 46 | tasks = [self._single_search(query) for query in queries] 47 | task_results = await asyncio.gather(*tasks) 48 | return [ 49 | result for result in task_results 50 | ] 51 | 52 | async def _single_search(self, query: str) -> SearchResult: 53 | response = await self._run_bot_search(query) 54 | return self._format_result(response, query) 55 | 56 | async def _run_bot_search(self, query: str): 57 | max_retries = 3 58 | retry_count = 0 59 | while retry_count < max_retries: 60 | try: 61 | response = Application.call( 62 | # 若没有配置环境变量,可用百炼API Key将下行替换为:api_key="sk-xxx"。但不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。 63 | api_key="sk-cacafff2c8d344be6702adf4f", 64 | app_id='dbce22d58d7b4c7b74d357b362b',# 替换为实际的应用 ID 65 | prompt=query, 66 | timeout=160) 67 | 68 | if response.status_code != HTTPStatus.OK: 69 | print(f'request_id={response.request_id}') 70 | print(f'code={response.status_code}') 71 | print(f'message={response.message}') 72 | print(f'请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code') 73 | if retry_count < max_retries - 1: 74 | retry_count += 1 75 | await asyncio.sleep(1) 76 | continue 77 | raise ValueError(f"API request failed: {response.message}") 78 | break 79 | except Exception as e: 80 | if retry_count < max_retries - 1: 81 | retry_count += 1 82 | await asyncio.sleep(1) 83 | continue 84 | raise 85 | 86 | if not response.output or not hasattr(response.output, 'text'): 87 | raise ValueError("Invalid response format from API") 88 | 89 | #print(response.output.text) 90 | return response.output 91 | 92 | @classmethod 93 | def _format_result(cls, response, query: str) -> SearchResult: 94 | if not response or not hasattr(response, 'text') or not response.text: 95 | raise ValueError("Invalid response content") 96 | 97 | return SearchResult( 98 | query=query, 99 | summary_content=response.text, 100 | search_references=[ 101 | SearchReference( 102 | site='lego', 103 | url='lego', 104 | content='lego', 105 | title='lego', 106 | ) 107 | ] 108 | ) 109 | 110 | 111 | if __name__ == '__main__': 112 | # 测试查询 113 | test_queries = ["火箭积木列出", "怎样拼装屋顶"] 114 | 115 | # 实例化搜索引擎 116 | search_engine = BailianBotSearchEngine( 117 | bot_id='dbce22d58d7b4357b362b', 118 | api_key="sk-cacafff2c8d3446fbe6702adf4f" 119 | ) 120 | 121 | # 执行搜索 122 | results = search_engine.search(test_queries) 123 | 124 | # 打印结果 125 | for result in results: 126 | print(f"查询: {result.query}") 127 | print(f"结果: {result.summary_content}") 128 | print("---") 129 | -------------------------------------------------------------------------------- /run_client_wordpress.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | # AI 写文章,直接发布到wordpress 13 | # 前提:需要打开 server.py ,localhost:8888 14 | # api.tikhub 是自动抓取最新的小红书,公众号搜索新闻,如果需要,则配置自己的key 15 | 16 | from openai import OpenAI 17 | import requests 18 | import json 19 | import base64 20 | import random 21 | 22 | client = OpenAI(base_url="http://localhost:8888/api/v3/bots", api_key="{API_KEY}") 23 | WORDPRESS_URL = "https://liangdabiao.com/wp-json/wp/v2" 24 | WORDPRESS_USER = "liangdabiao" 25 | WORDPRESS_PASSWORD = "pkHR alPS Goff 1K" 26 | 27 | def post_to_wordpress(title, content): 28 | """ 29 | 将文章发布到WordPress 30 | :param title: 文章标题 31 | :param content: markdown格式的文章内容 32 | :return: 响应结果 33 | """ 34 | # 将markdown转换为HTML 35 | try: 36 | import markdown 37 | html_content = markdown.markdown(content) 38 | except ImportError: 39 | print("警告: markdown模块未安装,将直接发送原始内容") 40 | html_content = content 41 | 42 | headers = { 43 | 'Content-Type': 'application/json', 44 | 'Authorization': 'Basic ' + base64.b64encode(f"{WORDPRESS_USER}:{WORDPRESS_PASSWORD}".encode()).decode() 45 | } 46 | 47 | data = { 48 | 'title': title, 49 | 'content': html_content, 50 | 'status': 'publish' 51 | } 52 | 53 | response = requests.post( 54 | f"{WORDPRESS_URL}/posts", 55 | headers=headers, 56 | data=json.dumps(data) 57 | ) 58 | 59 | return response.json() 60 | 61 | 62 | def main(): 63 | 64 | QUERY1 = """你是一个资深积木乐高专栏作家,写作流程: 65 | 1.基于以下新闻,请找出1个和新闻相关的主题(不要关心品牌,年份等细节,主题应该是简单的5个字以内),然后基于每一个新闻主题整理一个图文介绍(包括展示"CoverImgUrl"图片)。例如:新闻"三叉戟的八格车来袭!玛莎拉蒂授权积木实测:咬合力拉满,鲨鱼腮却失踪了?【测评】" ,那么主题就是“八格车”“莎拉蒂”。 66 | 2.然后进行至少一次搜索:搜索相关新闻主题进行资料补充(例如,怎样拼装[主题],查找[主题]等等),补充资料也需要图片展示 ,同时整理资料不要复杂(简单补充则可,资料相关就可以,搜集资料能够写出文章就可以,同时每一个主题至少搜集一次补充资料) 67 | 3.最后:按以上整理的所有内容写一个吸引人的文章【描写热点主题相关能够让观众感到兴趣和愉快】,文章内容包括今天热点主题介绍+补充的相关知识资料,文章主题结构:【主题1+补充1,主题2+补充2,...】 68 | 4.注意:务必要保留搜索到的相关图片,分析主题不要过多关注(品牌,定价,用户评价,具体细节)。 69 | 5.现在给你提供今天小红书的积木热点新闻材料:### 70 | 71 | {value} 72 | 73 | 74 | ###,""" 75 | 76 | headers_api = { 77 | 'accept': 'application/json', 78 | 'Authorization': 'Bearer xq5oa0Zj+GMAjRQnzU2evyUdwCoyuYHj7+1Uw==' 79 | } 80 | url = 'https://api.tikhub.io/api/v1/wechat_mp/web/fetch_mp_article_list?ghid=gh_fde65db448ac&page=1' 81 | print(f"请求URL:{url}") 82 | 83 | try: 84 | response = requests.get(url, headers=headers_api, verify=False, timeout=30) 85 | response.raise_for_status() 86 | except requests.exceptions.RequestException as e: 87 | print(f"请求失败: {e}") 88 | return None 89 | response.raise_for_status() 90 | data = response.json() 91 | if data.get("code") != 200: 92 | print(f"API请求失败,状态码:{data.get('code')},信息:{data.get('message')}") 93 | exit() 94 | items = data.get("data", {}).get("list", []) 95 | # items 是一个包含10个item的数组,请随机选择一个 96 | item = random.choice(items) 97 | # TODO 修改item, 只保留Title, Digest, ContentUrl, CoverImgUrl 98 | # 下载远程图片到OSS并获取新URL 99 | oss_url = upload_to_oss(item['CoverImgUrl']) 100 | 101 | item = { 102 | 'Title': item['Title'], 103 | 'Digest': item['Digest'], 104 | 'ContentUrl': item['ContentUrl'], 105 | 'CoverImgUrl': oss_url 106 | } 107 | # json转为 string 108 | item_str = json.dumps(item, ensure_ascii=False) 109 | 110 | # 把item_str 替换到QUERY1中 value 111 | item_str = QUERY1.replace("{value}", item_str) 112 | 113 | print(item_str) 114 | 115 | # stream run 116 | stream_resp = client.chat.completions.create( 117 | model="test", 118 | messages=[ 119 | { 120 | "role": "user", 121 | "content": item_str, 122 | } 123 | ], 124 | stream=True, 125 | ) 126 | 127 | thinking = False 128 | 129 | article_content = "" 130 | for chunk in stream_resp: 131 | if chunk.choices[0].delta.model_extra.get("reasoning_content"): 132 | if not thinking: 133 | print("\n----思考过程----\n") 134 | thinking = True 135 | content = chunk.choices[0].delta.model_extra.get("reasoning_content", "") 136 | print(content, end="", flush=True) 137 | elif chunk.choices[0].delta.content: 138 | if thinking: 139 | print("\n----输出回答----\n") 140 | thinking = False 141 | print(chunk.choices[0].delta.content, end="", flush=True) 142 | article_content += chunk.choices[0].delta.content 143 | 144 | # 发布到WordPress 145 | if article_content: 146 | # 使用文章内容的前三行作为标题 147 | title = ' '.join([line.strip() for line in article_content.split('\n')[:3] if line.strip()]) 148 | print("\n\n正在发布到WordPress...") 149 | result = post_to_wordpress(title, article_content) 150 | print(f"发布结果: {result}") 151 | 152 | 153 | if __name__ == "__main__": 154 | main() 155 | 156 | -------------------------------------------------------------------------------- /my_llm_profile_example.md: -------------------------------------------------------------------------------- 1 | Personal Background 2 | 3 | I'm Hasan, a tech expert turned digital entrepreneur with over 15 years in technology and 9+ years building online businesses. My journey began as an IT/Network Administrator before transforming into a successful digital entrepreneur. 4 | 5 | ## Core Mission 6 | 7 | I help people master technical and business skills needed for today's digital world, specializing in combining software development, digital marketing, and business automation. 8 | 9 | ## Teaching Style & Philosophy 10 | 11 | - I explain complex concepts using simple English for global accessibility 12 | - My content follows: Hook → Steps → Advanced Section → Conclusion → Call to Action 13 | - I emphasize daily dedication (30-60 minutes) and taking consistent action 14 | - I focus on real solutions, avoiding "lazy" quick fixes 15 | - I always share from personal experience, not theoretical knowledge 16 | - I believe in honest, straightforward communication without fluff 17 | 18 | ## Technical Expertise 19 | 20 | - **Advanced WordPress development (SaaS/microSaaS architecture)** 21 | - **Full-stack API development and monetization** 22 | - **Python programming with AI integration** 23 | - **Multiple programming languages (.NET, Java, C++, C#, VB, ASP)** 24 | - **System administration (Windows Server, IIS, RDS, SMTP)** 25 | - **Cloud infrastructure and virtualization** 26 | - **Network administration (Cisco CCNP certified)** 27 | - **Security expertise (CEH certified)** 28 | - **Database design and SQL Server administration** 29 | 30 | ## Business & Marketing Skills 31 | 32 | - Digital business strategy and scaling 33 | - Online product development and monetization 34 | - Content marketing strategy 35 | - SEO optimization and automation 36 | - Marketing automation systems 37 | - Community building and management 38 | - Multi-platform content distribution 39 | - Course creation and monetization 40 | 41 | ## Main Platforms & Projects 42 | 43 | ### 1. LearnWithHasan.com 44 | 45 | Premium Courses: 46 | 47 | - How To Build SaaS on WordPress 48 | - Build and Sell APIs (Including FastAPI implementation) 49 | - Build a Digital Business in 6 Weeks 50 | - GenAI/Prompt Engineering with Python 51 | - Marketing Automation (No-code and code solutions) 52 | 53 | ### 2. PromoterKit.com (AI-Powered Marketing Suite) 54 | 55 | ## SEO Tools 56 | 57 | 1. Keyword Research 58 | - Purpose: Discover high-performing keywords for content optimization 59 | - Key Feature: AI-powered insights for strategic SEO decisions 60 | 2. Bulk Keyword SEO Metrics 61 | - Purpose: Analyze multiple keywords simultaneously 62 | - Features: Search volume, SEO difficulty, paid competitors, CPC analysis 63 | 3. AI SEO Audit 64 | - Purpose: Comprehensive website SEO analysis 65 | - Features: Detailed analysis and actionable recommendations 66 | 4. Site Keywords Analysis 67 | - Purpose: Track website ranking for target keywords 68 | - Features: Rank tracking and performance analysis 69 | 5. Competitor Finder 70 | - Purpose: Identify and analyze top SEO competitors 71 | - Features: Competitive analysis and market positioning 72 | 6. Questions Keywords 73 | - Purpose: Find question-based keywords 74 | - Features: Content relevance optimization 75 | 7. Keyword Suggestions 76 | - Purpose: Generate relevant keyword ideas 77 | - Features: Content strategy expansion 78 | 8. Domain Authority Checker 79 | - Purpose: Track website domain authority 80 | - Features: SEO performance evaluation 81 | 82 | ## YouTube Tools 83 | 84 | 1. YouTube Title Generator 85 | - Purpose: Generate engaging video titles 86 | - Features: CTR optimization 87 | 2. YouTube Popular Videos Database 88 | - Purpose: Discover high-performing content 89 | - Features: Trend analysis and content inspiration 90 | 3. YouTube Channel Search 91 | - Purpose: Analyze competitors and discover influencers 92 | - Features: Niche research and competitor analysis 93 | 94 | ## Email Tools 95 | 96 | 1. Bulk Email Verifier 97 | - Purpose: Validate email lists 98 | - Features: 98% accuracy, export options 99 | 2. Subject Line Generator 100 | - Purpose: Create engaging email subjects 101 | - Features: AI-powered engagement optimization 102 | 3. Subject Line Grader 103 | - Purpose: Analyze subject line effectiveness 104 | - Features: Performance scoring 105 | 4. Subject Lines Database 106 | - Purpose: Access proven subject lines 107 | - Features: 100+ winning templates 108 | 109 | ## AI Tools 110 | 111 | 1. AI Image Generator 112 | - Purpose: Create unique images 113 | - Features: Advanced AI image creation 114 | 2. Logo Generator 115 | - Purpose: Design professional logos 116 | - Features: Brand identity creation 117 | 3. Hook Generator 118 | - Purpose: Create engaging content hooks 119 | - Features: CTR optimization 120 | 4. Blog Title Generator 121 | - Purpose: Generate SEO-optimized titles 122 | - Features: Engagement optimization 123 | 5. SEO Meta Description Generator 124 | - Purpose: Create optimized meta descriptions 125 | - Features: SEO enhancement 126 | 6. Title Generator 127 | - Purpose: Create catchy content titles 128 | - Features: AI-powered title optimization 129 | 7. LinkedIn Carousel Generator 130 | - Purpose: Create engaging LinkedIn content 131 | - Features: Professional presentation creation 132 | 8. Domain Name Generator 133 | - Purpose: Generate business domain names 134 | - Features: Brand-aligned domain suggestions 135 | 136 | ### 3. Additional Projects 137 | 138 | - TubeDigest: AI-powered YouTube content transformation 139 | - DefineWise: Visual AI dictionary for children 140 | - PyDive: Interactive Python learning platform 141 | 142 | ### 4. Content Platforms 143 | 144 | - YouTube Channel (950K+ subscribers) 145 | - Active presence on Twitter, LinkedIn, Reddit, and Medium 146 | - Technical courses on Udemy (Server administration, IIS, RDS, SMTP) 147 | 148 | ## Educational Background 149 | 150 | - Masters in Computer Science 151 | - Multiple certifications including MCSA, MCSE, VMware VCP/VCAP 152 | - Cisco CCNP, CEH, and various programming certifications 153 | 154 | ## Content Focus Areas 155 | 156 | 1. Marketing Automation 157 | - No-code automation systems 158 | - Python-based automation with AI 159 | - Marketing tool development 160 | 2. Digital Business Development 161 | - MicroSaaS and SaaS creation 162 | - Course development 163 | - Digital product creation 164 | 3. Technical Education 165 | - Programming tutorials 166 | - System administration 167 | - API development 168 | - WordPress development 169 | 4. Growth Strategies 170 | - Marketing optimization 171 | - Business automation 172 | - Technical infrastructure 173 | 174 | ## Development Environment 175 | 176 | - VS Code with AI integration (Claude API) 177 | - Local WordPress development using LocalWP 178 | - Git for version control 179 | - Various AI tools for productivity 180 | 181 | ## Community Engagement 182 | 183 | - Regular content creation across all platforms 184 | - Active community support and mentoring 185 | - Focus on practical, implementable advice 186 | - Emphasis on building sustainable online businesses 187 | 188 | # Content Generation Guidelines for AI 189 | 190 | - Use simple, clear English 191 | - Focus on practical, step-by-step instructions 192 | - Include real examples from my projects 193 | - Maintain technical accuracy while ensuring accessibility 194 | - Reference relevant tools and resources when appropriate 195 | - Include calls to action to relevant courses or communities 196 | - Emphasize automation and efficiency in solutions 197 | - Share actual experiences and tested strategies 198 | - Maintain a balance between technical depth and practical application -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 开源的AI写作小工具方案:让2个智能体合作写出真正可用的图文并茂的帖子 3 | 4 | 这是我开发的开源的AI写作小工具方案:让2个智能体合作写出真正可用的图文并茂的帖子(微信公众号,小红书,博客)。1,写作智能体,2,知识库智能体。原理如下: 5 | 6 | ![img.png](微信截图_20250608162545.png) 7 | 8 | 教程地址: https://juejin.cn/post/7512966459166801960 9 | 10 | ## 演示效果: 11 | 12 | 1,首先抓取热点新闻资讯(这里展示是抓取乐高积木今天的热点新闻) 13 | 14 | ![img.png](微信截图_20250608151523.png) 15 | 16 | 2,告知AI按热点新闻,写一个文章帖子。AI开始思考: 17 | ``` 18 | ---好的,我需要解决用户的问题。用户希望作为积木乐高专栏作家,根据提供的新闻材料,生成两个主题,并进行相关资料搜索,最后撰写文章。--- 19 | 20 | 21 | 2,AI发现需要搜索这些资料: 乐高大型动物园搭建;底板拼接技巧;乐高MOC动物园案例;多底板设计创意;乐高动物园细节设计。 然后开始搜索。 22 | ``` 23 | 这里的搜索是基于我开发的另一个知识智能体提供相关搜索知识: 24 | 25 | ![img.png](微信截图_20250608151550.png) 26 | 27 | 3,当AI发现知识还不足够写文章帖子,AI会形成新的关键词补充知识,再次搜索,循环思考和搜索: 28 | 29 | ![img.png](微信截图_20250608151607.png) 30 | 31 | 4,完成文章效果: 32 | 33 | https://mp.weixin.qq.com/s/YmgB3veZ0FgSOB_gqKYQog 34 | 35 | 可配置定时任务,直接发文章发表。 36 | 37 | ## 环境准备(务必利用poetry环境安装) 38 | 39 | - Python - 版本要求大于等于 3.9.0,小于 3.12 40 | - Poetry 1.6.1 版本,可参考以下命令安装 41 | 42 | ``` 43 | pip install poetry==1.6.1 44 | ``` 45 | - poetry install 46 | 47 | # 重要配置: 48 | 49 | 运行时配置: 50 | windows : set ARK_API_KEY=XXX , 51 | linux: export ARK_API_KEY=XXX 52 | 53 | windows运行: 54 | (zhijie310) D:\ai-app-lab\ai-app-lab-main\demohouse\deep_search>poetry run python -m webui 55 | 56 | (zhijie310) D:\ai-app-lab\ai-app-lab-main\demohouse\deep_search>python run_local.py 57 | 58 | linux运行: 59 | 60 | cd /www/wwwroot/deep_search && poetry run python -m run_client_wordpress 61 | 62 | cd /www/wwwroot/deep_search && export ARK_API_KEY=f9108b8d-a0738c516 && poetry run python -m server 63 | 64 | ## 调用百炼智能体: 65 | 66 | bailian_bot.py 修改key 67 | 68 | ## 调用tavily搜索 69 | 70 | tavily.py 配置key 71 | 72 | ## 调用字节大模型智能体: 73 | VolcBotSearchEngine 配置key 74 | 75 | 76 | # 原理介绍:深度推理 Deep Search 77 | 这里是介绍Deep Search的原理,同时本项目也是基于字节的Deep Search原理,但是加入了写作智能体和知识库智能体的合作。进行了大量改写和调试,目前是可以正常写作帖子。可用,可以试试。免费开源。 78 | 79 | ## 应用介绍 80 | 81 | Deep Search 是一款专为应对复杂问题而设计的高效工具,利用 DeepSeek-R1 大模型对复杂问题进行多角度分析,并辅助互联网资料,快速生成最合适用户的解决方案。 82 | 无论是在学术研究、企业决策还是产品调研中,Deep Search 都能够有效地协助用户深入挖掘,提出切实可行的解决策略。 83 | 84 | ![img.png](docs/preview.png) 85 | 86 | ## 费用说明 87 | 88 | - 推理总结模型计费 89 | 90 | - DeepSeek-R1 大模型:开通赠送500,000 tokens 免费额度,超过部分按 token 使用量付费,详见 计费说明 91 | 92 | 93 | - 联网搜索服务计费,您可自由选择使用的联网搜索服务 94 | 95 | - 选择一:使用火山方舟零代码联网应用作为搜索引擎,计费项如下 96 | 97 | - Doubao-pro-32k/Doubao-1.5-pro-32k,开通赠送500,000 tokens 免费额度,超过部分按 token 使用量付费,价格详见 计费说明 98 | 99 | - 联网内容插件,价格详见 计费说明 100 | 101 | - 选择二:使用开源搜索引擎 Tavily,由 Tavily 官方进行收费,计费规则详见 计费说明 102 | 103 | - 选择三:其他 Search API 或者企业内部 Search 接口,火山方舟侧不收取费用 104 | 105 | ## 环境准备 106 | 107 | - Python - 版本要求大于等于 3.9.0,小于 3.12 108 | - Poetry 1.6.1 版本,可参考以下命令安装 109 | 110 | ``` 111 | pip install poetry==1.6.1 112 | ``` 113 | 114 | - 获取火山方舟 API KEY | 参考文档 115 | - 在开通管理页开通 DeepSeek-R1 模型。 116 | - (可选)如果通过火山方舟高代码应用部署本demo,需获取火山引擎 AK SK | 参考文档 117 | - 搜索引擎选择:以下方式任选其一 118 | - 使用火山方舟零代码联网应用作为搜索引擎,推荐配置参见【附录】,操作步骤详情见 参考文档 119 | - 使用开源搜索引擎 Tavily,需获取 Tavily APIKEY 参考文档 120 | 121 | ## 快速入门 122 | 123 | 本文为您介绍如何在本地以及利用火山方舟高代码应用快速部署 Deep search 项目,本项目可以通过以下两种方式进行部署: 124 | 125 | | **方式** | **说明** | **适用场景** | 126 | |-------------|-------------------------|---------------------------| 127 | | 本地部署运行 | 本地启动服务和webui | 本地测试和局域网内部署API服务 | 128 | | 部署火山方舟高代码应用 | 使用火山方舟高代码应用方式托管部署 | 低成本快速实现云端API服务部署 + 对话界面体验 | 129 | 130 | 131 | ### 方式一:本地部署运行 132 | 133 | 1. 下载代码库 134 | 135 | ```shell 136 | git clone https://github.com/volcengine/ai-app-lab.git 137 | cd demohouse/deep_search 138 | ``` 139 | 140 | 2. 设置环境变量 141 | 142 | - 如果使用火山方舟零代码联网应用作为搜索引擎,需要设置如下环境变量 143 | 144 | ```shell 145 | # 填写火山方舟API KEY 146 | export ARK_API_KEY=xxx-xxxx-xxx-xxx 147 | 148 | # 思考模型名称,填写deepseek-r1-250120 149 | export REASONING_MODEL=deepseek-r1-250120 150 | 151 | # 设置使用火山方舟零代码联网应用作为搜索引擎 152 | export SEARCH_ENGINE=volc_bot 153 | 154 | # 配置火山方舟零代码联网应用的bot id 155 | export SEARCH_BOT_ID=bot-xxxxxx-xx 156 | ``` 157 | 158 | - 如果使用 tavily 作为搜索引擎,需要设置如下环境变量 159 | 160 | ```shell 161 | # 填写火山方舟API KEY 162 | export ARK_API_KEY=xxx-xxxx-xxx-xxx 163 | 164 | # 思考模型名称,填写deepseek-r1-250120 165 | export REASONING_MODEL=deepseek-r1-250120 166 | 167 | # 设置使用tavily作为搜索引擎 168 | export SEARCH_ENGINE=tavily 169 | 170 | # 配置tavily的APIKEY 171 | export TAVILY_API_KEY=xxx-xxx-xxx-xxx 172 | ``` 173 | 174 | 3. 安装项目依赖并运行 175 | 176 | > 说明 177 | > 178 | > 默认在 localhost:8888 提供符合 openAI 规范的 chatAPI 服务 179 | 180 | ```shell 181 | python -m venv .venv 182 | source .venv/bin/activate 183 | 184 | poetry install 185 | poetry run python -m server 186 | ``` 187 | 188 | 4. 启动webui 189 | 190 | ```shell 191 | # 需要先设置连接本地服务器 192 | export API_ADDR=http://localhost:8888/api/v3/bots 193 | 194 | python -m venv .venv 195 | source .venv/bin/activate 196 | poetry install 197 | 198 | # 启动web ui 199 | poetry run python -m webui 200 | ``` 201 | 5. 使用浏览器访问 `http://localhost:7860/` 即可使用 202 | 203 | ![img.png](docs/webui.png) 204 | 205 | ### 方式二:部署火山方舟高代码应用 206 | 207 | 1. 在火山方舟控制台创建高代码应用 | 参考文档,完成首次部署后点击【更新代码】进入函数详情页面 208 | 209 | ![img.png](docs/procode_new.png) 210 | 211 | 2. 在本地下载代码包 212 | 213 | ```shell 214 | git clone https://github.com/volcengine/ai-app-lab.git 215 | cd demohouse/deep_search 216 | ``` 217 | 218 | 3. 打包本地代码为 `code.zip` 219 | 220 | ```shell 221 | cd demohouse/deep_search 222 | zip -r code.zip * 223 | ``` 224 | 225 | 4. 上传`code.zip`,更新高代码应用的代码 226 | 227 | 5. 执行【安装依赖】 228 | 229 | ![img.png](docs/img_3.png) 230 | 231 | 6. 在【函数配置】中按需调整环境变量: 232 | 233 | - 如果使用火山方舟零代码联网应用作为搜索引擎,需要设置如下环境变量 234 | - `ARK_API_KEY`: 填写环境准备章节获取的火山方舟API KEY 235 | - `REASONING_MODEL`: 填写`deepseek-r1-250120` 236 | - `SEARCH_ENGINE`: 填写`volc_bot` 237 | - `SEARCH_BOT_ID`: 填写火山方舟零代码联网应用的 bot id(可参考附录获取) 238 | - 如果使用 tavily 作为搜索引擎,需要设置如下环境变量 239 | - `ARK_API_KEY`: 填写环境准备章节获取的火山方舟API KEY 240 | - `REASONING_EP_ID`: 填写`deepseek-r1-250120` 241 | - `SEARCH_ENGINE`: 填写`tavily` 242 | - `TAVILY_API_KEY`: 配置tavily的APIKEY 243 | 244 | ![img.png](docs/procode_env.png) 245 | 246 | 7. (建议)在【函数配置】中调整函数执行超时时间为 900 s 247 | 248 | ![img.png](docs/procode_execute_timeout.png) 249 | 250 | 8. 单击右上角【发布】,重新发布应用 251 | 252 | ![img.png](docs/img_5.png) 253 | 254 | 9. 应用发布完成后,支持通过以下两种方式使用: 255 | 256 | - 通过高代码应用的控制台界面直接体验 257 | 258 | ![img.png](docs/img_6.png) 259 | 260 | - 在本地通过 webui 体验 261 | 262 | 1. 执行以下命令,在本地启动 webui,并连接远程服务。 263 | 264 | ```shell 265 | cd demohouse/deep_search 266 | # 安装项目依赖 267 | poetry install 268 | # 设置环境变量为前面步骤中的高代码应用botID 269 | export API_BOT_ID=bot-xxxx-xxx 270 | # 运行webui 271 | poetry run python -m webui 272 | ``` 273 | 2. 启动成功后可访问`http://localhost:7860`体验服务。 274 | 275 | ![img](docs/webui.png) 276 | 277 | ## 技术实现 278 | 279 | 本项目结合深度思考大模型和联网搜索能力,并向上封装成标准的 Chat Completion API Server。 280 | 281 | ![img.png](docs/img.png) 282 | 283 | 在接收到用户的原始问题后,会进行两个阶段的处理: 284 | 285 | 1. **思考阶段(循环进行)** 286 | 287 | DeepSeek-R1 根据用户问题不断地使用搜索引擎,获取参考资料,直至模型认为收集到的参考资料已经足够解决用户问题。 288 | 289 | 290 | 2. **总结阶段** 291 | 292 | DeepSeek-R1 根据上一阶段产出的所有参考资料和思考过程中的模型输出,对用户的问题进行总结性回答。 293 | 294 | 其中,思考阶段的模型输出会整合至`reasoning_content`字段中,总结阶段的模型输出会整合至`content`字段中。该架构严格遵循 295 | OpenAI 296 | Chat Completion API 规范设计,因此开发者可直接使用 OpenAI 标准 SDK 或兼容接口实现服务的无缝对接,显著降低了技术集成的复杂度。 297 | 298 | ## 目录结构 299 | 300 | ``` 301 | ├── README.md 302 | ├── __init__.py 303 | ├── config.py 304 | ├── deep_search.py # 项目核心逻辑实现 305 | ├── docs 306 | ├── poetry.lock 307 | ├── prompt.py # 思考/总结 prompt 308 | ├── pyproject.toml 309 | ├── requirements.txt 310 | ├── run.sh # 用于部署火山方舟高代码应用,启动入口 311 | ├── search_engine 312 | │   ├── __init__.py 313 | │   ├── search_engine.py 314 | │   ├── tavily.py # tavily 搜索引擎实现 315 | │   └── volc_bot.py # 火山方舟零代码联网应用 搜索引擎实现 316 | ├── server.py # 服务启动入口 317 | ├── utils.py 318 | └── webui.py # webui启动入口 319 | ``` 320 | 321 | ## 附录 322 | 323 | ### 创建推荐配置的零代码联网应用 324 | 325 | 创建零代码应用时,推荐以下配置: 326 | 327 | - 推理接入点:Doubao-pro-32k / Doubao-pro-1.5-32k 328 | - 联网内容插件:开启 329 | - 智能改写:关闭 330 | - 调用方式:选择强制开启 331 | - 参考资料回复:选择自定义回复,推荐回复示例如下: 332 | 333 | ``` 334 | - 不需要回复用户的问题,仅对「联网」中的信息进行总结,总结需要全面,但不要添加自己额外的信息。 335 | - 回复请使用清晰、结构化(序号/分段等)的语言,确保可被用户理解和使用。 336 | ``` 337 | 338 | 339 | ![img.png](docs/img_7.png) 340 | 341 | -------------------------------------------------------------------------------- /deep_search.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | from typing import Dict, List, AsyncIterable 13 | 14 | from jinja2 import Template 15 | from pydantic import BaseModel, Field 16 | from typing_extensions import Optional 17 | 18 | from arkitect.core.component.llm import BaseChatLanguageModel 19 | from arkitect.core.component.llm.model import ArkMessage, ArkChatRequest, ArkChatResponse, ArkChatCompletionChunk 20 | from arkitect.core.component.prompts import CustomPromptTemplate 21 | from arkitect.telemetry.logger import INFO 22 | 23 | from search_engine import SearchEngine, SearchResult 24 | from search_engine.volc_bot import VolcBotSearchEngine 25 | from prompt import DEFAULT_PLANNING_PROMPT, DEFAULT_SUMMARY_PROMPT 26 | from utils import get_current_date, cast_content_to_reasoning_content, gen_metadata_chunk 27 | 28 | """ 29 | ResultsSummary is using to store the result searched so far 30 | """ 31 | 32 | 33 | class ResultsSummary(BaseModel): 34 | """ 35 | key: query 36 | values: list of searched references for this query 37 | """ 38 | ref_dict: Dict[str, List[SearchResult]] = Field(default_factory=dict) 39 | 40 | def add_result(self, query: str, results: List[SearchResult]) -> None: 41 | if query not in self.ref_dict: 42 | self.ref_dict[query] = results.copy() 43 | else: 44 | extended_references = self.ref_dict.get(query, []) 45 | extended_references.extend(results) 46 | self.ref_dict[query] = extended_references 47 | 48 | def to_plaintext(self) -> str: 49 | output = "" 50 | 51 | for key, value in self.ref_dict.items(): 52 | output += f"\n【查询 “{key}” 得到的相关资料】" 53 | output += "\n".join([v.summary_content for v in value]) 54 | 55 | return output 56 | 57 | 58 | class ExtraConfig(BaseModel): 59 | # max_planning_rounds 60 | max_planning_rounds: int = 5 61 | # max_search_words 62 | max_search_words: int = 5 63 | # planning_template (prompt) 64 | planning_template: Template = DEFAULT_PLANNING_PROMPT 65 | # summary_template (prompt) 66 | summary_template: Template = DEFAULT_SUMMARY_PROMPT 67 | 68 | class Config: 69 | """Configuration for this pydantic object.""" 70 | 71 | arbitrary_types_allowed = True 72 | 73 | 74 | """ 75 | DeepSearch 76 | """ 77 | 78 | 79 | class DeepSearch(BaseModel): 80 | search_engine: SearchEngine = Field(default_factory=VolcBotSearchEngine) 81 | planning_endpoint_id: str = Field(default_factory="") 82 | summary_endpoint_id: str = Field(default_factory="") 83 | extra_config: ExtraConfig = Field(default_factory=ExtraConfig) 84 | 85 | async def arun_deep_research(self, request: ArkChatRequest, question: str) -> ArkChatResponse: 86 | references = ResultsSummary() 87 | buffered_reasoning_content = "" 88 | 89 | # 1. run reasoning 90 | reasoning_stream = self.astream_planning( 91 | request=request, 92 | question=question, 93 | references=references, 94 | ) 95 | 96 | async for reasoning_chunk in reasoning_stream: 97 | buffered_reasoning_content += reasoning_chunk.choices[0].delta.reasoning_content 98 | 99 | # 2. run summary 100 | # append the reasoning content as an assistant message to help summary 101 | request.messages.append( 102 | ArkMessage( 103 | role="assistant", 104 | content=buffered_reasoning_content, 105 | ) 106 | ) 107 | resp = await self.arun_summary( 108 | request=request, 109 | question=question, 110 | references=references 111 | ) 112 | # append the reasoning buffer 113 | resp.choices[0].message.reasoning_content = ( 114 | buffered_reasoning_content + resp.choices[0].message.reasoning_content) 115 | return resp 116 | 117 | async def astream_deep_research(self, request: ArkChatRequest, question: str) \ 118 | -> AsyncIterable[ArkChatCompletionChunk]: 119 | if not question or not question.strip(): 120 | raise ValueError("Question cannot be empty") 121 | 122 | references = ResultsSummary() 123 | buffered_reasoning_content = "" 124 | 125 | # 1. stream reasoning 126 | reasoning_stream = self.astream_planning( 127 | request=request, 128 | question=question, 129 | references=references, 130 | ) 131 | 132 | async for reasoning_chunk in reasoning_stream: 133 | buffered_reasoning_content += reasoning_chunk.choices[0].delta.reasoning_content 134 | yield reasoning_chunk 135 | 136 | # 2. stream summary 137 | # append the reasoning content as an assistant message to help summary 138 | request.messages.append( 139 | ArkMessage( 140 | role="assistant", 141 | content=buffered_reasoning_content, 142 | ) 143 | ) 144 | summary_stream = self.astream_summary( 145 | request=request, 146 | question=question, 147 | references=references, 148 | ) 149 | 150 | async for summary_chunk in summary_stream: 151 | yield summary_chunk 152 | 153 | async def astream_planning( 154 | self, 155 | request: ArkChatRequest, 156 | question: str, 157 | references: ResultsSummary 158 | ) -> AsyncIterable[ArkChatCompletionChunk]: 159 | 160 | planned_rounds = 1 161 | while planned_rounds <= self.extra_config.max_planning_rounds: 162 | planned_rounds += 1 163 | 164 | llm = BaseChatLanguageModel( 165 | endpoint_id=self.planning_endpoint_id, 166 | template=CustomPromptTemplate(template=self.extra_config.planning_template or DEFAULT_PLANNING_PROMPT), 167 | messages=request.messages, 168 | ) 169 | 170 | stream = llm.astream( 171 | reference=references.to_plaintext(), # pass the search result to prompt template 172 | question=question, 173 | max_search_words=self.extra_config.max_search_words, 174 | meta_info=f"当前时间:{get_current_date()}" 175 | ) 176 | 177 | planning_result = "" 178 | 179 | async for chunk in stream: 180 | if not chunk or not chunk.choices or not chunk.choices[0].delta: 181 | LOGGER.error("Received empty or invalid chunk from LLM") 182 | continue 183 | if chunk.choices[0].delta.reasoning_content: 184 | yield chunk 185 | elif chunk.choices[0].delta.content: 186 | planning_result += chunk.choices[0].delta.content 187 | # cast the content into reasoning content 188 | yield cast_content_to_reasoning_content(chunk) 189 | 190 | INFO(f"got planning_result: {planning_result}") 191 | 192 | new_queries = self.check_query(planning_result) 193 | if not new_queries: 194 | # YIELD state with metadata 195 | yield gen_metadata_chunk( 196 | metadata={ 197 | 'search_state': 'finished' 198 | } 199 | ) 200 | INFO("planning finished") 201 | break 202 | else: 203 | INFO(f"searching: {new_queries}") 204 | # YIELD state with metadata 205 | yield gen_metadata_chunk( 206 | metadata={ 207 | 'search_rounds': planned_rounds, 208 | 'search_state': 'searching', 209 | 'search_keywords': new_queries 210 | } 211 | ) 212 | search_results = await self.search_engine.asearch(new_queries) 213 | INFO(f"search result: {search_results}") 214 | # YIELD state with metadata 215 | yield gen_metadata_chunk( 216 | metadata={ 217 | 'search_rounds': planned_rounds, 218 | 'search_state': 'searched', 219 | 'search_keywords': new_queries, 220 | 'search_results': search_results 221 | } 222 | ) 223 | for search_result in search_results: 224 | references.add_result(query=search_result.query, results=[search_result]) 225 | 226 | async def arun_summary(self, request: ArkChatRequest, question: str, references: ResultsSummary) -> ArkChatResponse: 227 | llm = BaseChatLanguageModel( 228 | endpoint_id=self.summary_endpoint_id, 229 | template=CustomPromptTemplate(template=self.extra_config.summary_template), 230 | messages=request.messages, 231 | ) 232 | 233 | return await llm.arun( 234 | reference=references.to_plaintext(), 235 | question=question, 236 | meta_info=f"当前时间:{get_current_date()}" 237 | ) 238 | 239 | async def astream_summary(self, request: ArkChatRequest, question: str, references: ResultsSummary) \ 240 | -> AsyncIterable[ArkChatCompletionChunk]: 241 | llm = BaseChatLanguageModel( 242 | endpoint_id=self.summary_endpoint_id, 243 | template=CustomPromptTemplate(template=self.extra_config.summary_template or DEFAULT_SUMMARY_PROMPT), 244 | messages=request.messages, 245 | ) 246 | 247 | INFO("----- 联网资料 -----") 248 | INFO(f"{references.to_plaintext()}") 249 | 250 | stream = llm.astream( 251 | reference=references.to_plaintext(), 252 | question=question, 253 | meta_info=f"当前时间:{get_current_date()}" 254 | ) 255 | 256 | async for chunk in stream: 257 | if not chunk or not chunk.choices or not chunk.choices[0].delta: 258 | LOGGER.error("Received empty or invalid chunk from LLM") 259 | continue 260 | yield chunk 261 | 262 | @classmethod 263 | def check_query(cls, output: str) -> Optional[List[str]]: 264 | if '无需' in output: 265 | return None 266 | return [o.strip() for o in output.split(';')] 267 | -------------------------------------------------------------------------------- /webui.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Bytedance Ltd. and/or its affiliates 2 | # Licensed under the 【火山方舟】原型应用软件自用许可协议 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # https://www.volcengine.com/docs/82379/1433703 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | import asyncio 13 | import logging 14 | import json 15 | import gradio as gr 16 | from gradio import ChatMessage 17 | from pydantic import BaseModel 18 | from openai import OpenAI 19 | from typing import Any, Generator, AsyncGenerator 20 | 21 | from config import (ARK_API_KEY, API_ADDR, API_BOT_ID) 22 | from arkitect.core.component.llm.model import ArkChatRequest, ArkMessage 23 | from search_engine.volc_bot import VolcBotSearchEngine 24 | from search_engine.tavily import TavilySearchEngine 25 | from search_engine.bailian_bot import BailianBotSearchEngine 26 | 27 | # recommend to use DeepSeek-R1 model 28 | REASONING_EP_ID = "deepseek-r1-250120" 29 | # default set to volc bot, if using tavily, change it into "tavily" 30 | SEARCH_ENGINE = "bailian" #"volc_bot" 31 | # optional, if you select tavily as search engine, please configure this 32 | TAVILY_API_KEY = "{YOUR_TAVILY_API_KEY}" 33 | # optional, if you select volc bot as search engine, please configure this 34 | SEARCH_BOT_ID = "bot-20250522214322-zbhw9" 35 | 36 | from deep_search import DeepSearch, ExtraConfig 37 | 38 | client = OpenAI(base_url=API_ADDR, api_key=ARK_API_KEY) 39 | search_engine = VolcBotSearchEngine(bot_id=SEARCH_BOT_ID) 40 | 41 | if "tavily" == SEARCH_ENGINE: 42 | search_engine = TavilySearchEngine(api_key=TAVILY_API_KEY) 43 | elif "bailian" == SEARCH_ENGINE: 44 | search_engine = BailianBotSearchEngine(bot_id=SEARCH_BOT_ID) 45 | 46 | deep_research = DeepSearch( 47 | search_engine=search_engine, 48 | planning_endpoint_id=REASONING_EP_ID, 49 | summary_endpoint_id=REASONING_EP_ID, 50 | extra_config=ExtraConfig( 51 | max_planning_rounds=5, 52 | max_search_words=5, 53 | ) 54 | ) 55 | 56 | logging.basicConfig( 57 | level=logging.INFO, format="[%(asctime)s][%(levelname)s] %(message)s" 58 | ) 59 | LOGGER = logging.getLogger(__name__) 60 | def clean_json_string(json_str): 61 | # 允许的控制字符:制表符(0x09)、换行符(0x0A)、回车符(0x0D) 62 | allowed = {9, 10, 13} 63 | return ''.join( 64 | c for c in json_str 65 | if ord(c) >= 32 or ord(c) in allowed 66 | ) 67 | # store the search records 68 | search_records = [] 69 | 70 | global_css = """ 71 | .search-panel { 72 | height: 800px; 73 | overflow: auto; 74 | border: 1px solid #ccc; 75 | padding: 10px; 76 | } 77 | 78 | .chat-col { 79 | height: 100%; 80 | } 81 | """ 82 | 83 | 84 | def update_search_panel(): 85 | """生成右侧搜索面板的HTML内容""" 86 | html = "
" 87 | for i, record in enumerate(search_records, 1): 88 | if isinstance(record, BaseModel): 89 | record = record.model_dump(exclude_none=True, exclude_unset=True) 90 | html += f""" 91 |
92 | 🔍search [{record.get('query')}] 93 |
94 | 📖results: ({len(record.get('search_references', []))}): 95 |
    {"".join([f"
  1. {res.get('title', '')}
  2. " 96 | for res in record.get('search_references', [])])}
97 |
98 |
99 | """ 100 | html += "
" 101 | return gr.HTML(value=html) 102 | 103 | 104 | async def stream_chat(message: str, 105 | history: list, 106 | ) -> AsyncGenerator[list[tuple[str, str]], None]: 107 | global search_records 108 | 109 | message = f"""你是一个资深积木乐高专栏作家,写作流程: 110 | 1.基于新闻,请找出2个和新闻相关的主题(不要关心品牌,年份等细节,主题应该是简单的5个字以内)。例如:新闻"三叉戟的八格车来袭!玛莎拉蒂授权积木实测:咬合力拉满,鲨鱼腮却失踪了?【测评】" ,那么主题就是“八格车”“莎拉蒂”。 111 | 2.然后基于关键词进行至少一次搜索资料补充:搜索相关新闻主题进行资料补充(例如,怎样拼装[主题],查找[主题]等等), 整理资料不要复杂(简单补充则可,资料相关就可以,搜集资料能够写出文章就可以,同时每一个主题至少搜集一次补充资料) 112 | 3.最后:按以上整理的所有内容写一个吸引人的文章【描写热点主题相关能够让观众感到兴趣和愉快】,文章内容包括今天热点主题介绍+补充的相关知识资料,文章主题结构:【主题1+补充1,主题2+补充2,...】 113 | 4.注意:务必要保留搜索到的相关图片,分析主题不要过多关注(品牌,定价,用户评价,具体细节)。 114 | 115 | ## 格式示例: 116 | ``` 117 | # 今日乐高MOC热点主题速递:XXX 118 | ## 主题1:双头鲨鱼 119 | - 具体介绍主题1:双头鲨鱼的情况。 120 | 尽可能详细介绍主题1,包括: 主题1的特点,主题1的应用场景, 主题1的相关图片等等。 121 | 列出图片( 如"image_arr_new"或"cover_url"图片),图片格式能够展示给用户看到 122 | - 主题1相关补充资料: 123 | 补充资料的图片展示:图片1,图片2...相关图片都展示给用户看到 , 124 | 125 | 主题2:XXXX 126 | ...... 127 | 文章结语 128 | XXX 129 | ``` 130 | 最后:给你提供今天小红书的积木热点新闻材料:### 131 | 132 | {message} 133 | 134 | 135 | ###,""" 136 | 137 | history.clear() 138 | search_records = [] 139 | 140 | sum_reasoning_content = "" 141 | sum_content = "" 142 | sum_search_content = "" 143 | planning_rounds = 0 144 | 145 | thinking_msg = ChatMessage(content=sum_reasoning_content, 146 | metadata={"title": f"🤔 thinking round {planning_rounds}", 147 | "id": f"thinking-round-{planning_rounds}", "status": "pending"}) 148 | 149 | searching_msg = ChatMessage(content="", 150 | metadata={"title": f"🔍 searching round {planning_rounds}", 151 | "id": f"searching-round-{planning_rounds}", "status": "pending"}) 152 | history.append(thinking_msg) 153 | 154 | # chat_completion = client.chat.completions.create( 155 | # model=API_BOT_ID, 156 | # messages=[ 157 | # { 158 | # "role": "user", 159 | # "content": message, 160 | # } 161 | # ], 162 | # stream=True, 163 | # ) 164 | request = ArkChatRequest( 165 | model=API_BOT_ID, 166 | messages=[ 167 | ArkMessage( 168 | role="user", 169 | content=message 170 | ) 171 | ] 172 | ) 173 | async for rsp in deep_research.astream_deep_research( 174 | request=request, 175 | question=message, 176 | ): 177 | # round vars 178 | reasoning_content = rsp.choices[0].delta.reasoning_content if hasattr(rsp.choices[0].delta, 179 | 'reasoning_content') else '' 180 | content = rsp.choices[0].delta.content if hasattr(rsp.choices[0].delta, 'content') else '' 181 | metadata = getattr(rsp, 'metadata', {}) 182 | 183 | if metadata: 184 | logging.info(f"metadata: {rsp.metadata}") 185 | search_state = metadata.get('search_state', '') 186 | search_keywords = metadata.get('search_keywords', []) 187 | search_results = metadata.get('search_results', []) 188 | 189 | if search_state == 'searching': 190 | # think round ended 191 | thinking_msg.metadata.update({"status": "done"}) 192 | yield history, update_search_panel() 193 | # clear thinking content 194 | sum_reasoning_content = "" 195 | 196 | # search round started 197 | sum_search_content += "\n【搜索关键词】\n" + "\n\n".join(search_keywords) + "\n" 198 | searching_msg.content = sum_search_content 199 | history.append(searching_msg) 200 | yield history, update_search_panel() 201 | elif search_state == 'searched': 202 | sum_search_content += "\n【搜索总结】\n\n" 203 | for search_result in search_results: 204 | sum_search_content += f"\n {search_result.summary_content} \n" 205 | searching_msg.content = sum_search_content 206 | searching_msg.metadata.update({"status": "done"}) 207 | search_records += search_results 208 | yield history, update_search_panel() 209 | sum_search_content = "" 210 | sum_reasoning_content = "" 211 | planning_rounds += 1 212 | # new msgs 213 | thinking_msg = ChatMessage(content=sum_reasoning_content, 214 | metadata={"title": f"🤔 thinking round {planning_rounds}", 215 | "id": f"thinking-round-{planning_rounds}", "status": "pending"}) 216 | searching_msg = ChatMessage(content="", 217 | metadata={"title": f"🔍 searching round {planning_rounds}", 218 | "id": f"searching-round-{planning_rounds}", "status": "pending"}) 219 | history.append(thinking_msg) 220 | yield history, update_search_panel() 221 | 222 | if reasoning_content: 223 | sum_reasoning_content += reasoning_content 224 | thinking_msg.content = sum_reasoning_content 225 | thinking_msg.metadata.update({"status": "pending"}) 226 | yield history, update_search_panel() 227 | elif content: 228 | thinking_msg.metadata.update({"status": "done"}) 229 | sum_content += content 230 | yield [*history, ChatMessage( 231 | content=sum_content, 232 | role="assistant", 233 | )], update_search_panel() 234 | 235 | 236 | if __name__ == "__main__": 237 | with gr.Blocks(css=global_css) as demo: 238 | references = gr.HTML(render=False) 239 | with gr.Row(min_height=900, height=900): 240 | with gr.Column(scale=2, elem_classes='chat-col'): 241 | gr.Markdown("

🤖 积木乐高写作 Deep Research

") 242 | gr.Markdown("> **1,直接在对话框提供你的积木相关信息,AI就会帮忙写文章。2,快捷按钮,点击以下ABC按钮,AI给你选题,试试。") 243 | 244 | # 添加3个按钮 245 | with gr.Row(): 246 | btn_a = gr.Button("A") 247 | btn_b = gr.Button("B") 248 | btn_c = gr.Button("C") 249 | 250 | # 按钮点击事件处理 251 | def handle_button_click(btn_id): 252 | import requests 253 | try: 254 | response = """ 255 | { 256 | "count": 30, 257 | "result": [ { 258 | "_id": "6834b41a167bb9d9e1b12a5a", 259 | "note_id": "68312154000000002101b45f", 260 | "title": "用25块底板搭建超大型乐高动物园", 261 | "user_nickname": "有趣砖块", 262 | "user_avatar": "https://sns-avatar-qc.xhscdn.com/avatar/1040g2jo30tgb84qaks005navkvbg85pbisl7ek8?imageView2/2/w/80/format/jpg", 263 | "user_id": "5d5fa7d7000000000100172b", 264 | "liked_count": "3216", 265 | "collected_count": "2956", 266 | "comment_count": "28", 267 | "shared_count": "109", 268 | "cover_url": "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2018_12_04_08_02_46_97672400_b8a9141f755f62e1c0ce61244dcd9e7a.jpg", 269 | "publish_time": 1748284440, 270 | "synced": false 271 | }, 272 | { 273 | "_id": "6834b41a167bb9d9e1b12a55", 274 | "note_id": "6830456f000000000f03b145", 275 | "title": "乐高积木载人机甲 飞翼式动力背包设计预告", 276 | "user_nickname": "灰白jedi", 277 | "user_avatar": "https://sns-avatar-qc.xhscdn.com/avatar/641da09da41e0aa3a670021c.jpg?imageView2/2/w/80/format/jpg", 278 | "user_id": "5e933a8f00000000010071cd", 279 | "liked_count": "255", 280 | "collected_count": "146", 281 | "comment_count": "44", 282 | "shared_count": "1", 283 | "cover_url": "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2018_12_04_08_02_46_97672400_b8a9141f755f62e1c0ce61244dcd9e7a.jpg", 284 | "publish_time": 1748284434, 285 | "synced": false 286 | }, 287 | { 288 | "_id": "6834b41a167bb9d9e1b12a53", 289 | "note_id": "6831d0b80000000022006a89", 290 | "title": "把“幻影忍者”们送上太空拯救银河系", 291 | "user_nickname": "有趣砖块", 292 | "user_avatar": "https://sns-avatar-qc.xhscdn.com/avatar/1040g2jo30tgb84qaks005navkvbg85pbisl7ek8?imageView2/2/w/80/format/jpg", 293 | "user_id": "5d5fa7d7000000000100172b", 294 | "liked_count": "1120", 295 | "collected_count": "928", 296 | "comment_count": "32", 297 | "shared_count": "17", 298 | "cover_url": "https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2018_12_04_08_02_46_97672400_b8a9141f755f62e1c0ce61244dcd9e7a.jpg", 299 | "publish_time": 1748284433, 300 | "synced": false 301 | } ] 302 | } 303 | """ 304 | response = clean_json_string(response) 305 | try: 306 | data = json.loads(response) 307 | except json.JSONDecodeError as e: 308 | print(f"错误位置: {e.pos}") 309 | print(f"错误附近的字s符: {response[e.pos-20:e.pos+20]}") 310 | data = json.loads(response) 311 | print(data) 312 | if not data or "result" not in data or len(data["result"]) < 3: 313 | return "API返回数据格式不正确或数据不足" 314 | 315 | if btn_id == "A": 316 | selected = data["result"][0] 317 | btn_text = selected['title'] 318 | elif btn_id == "B": 319 | selected = data["result"][1] 320 | btn_text = selected['title'] 321 | else: 322 | selected = data["result"][2] 323 | btn_text = selected['title'] 324 | 325 | return { 326 | 'title': selected['title'], 327 | 'content': selected 328 | } 329 | except requests.exceptions.RequestException as e: 330 | return f"请求API出错: {str(e)}" 331 | except ValueError as e: 332 | return f"解析JSON出错: {str(e)}" 333 | 334 | # 按钮hover事件处理 335 | def handle_button_hover(btn_id): 336 | result = handle_button_click(btn_id) 337 | if isinstance(result, dict) and 'title' in result: 338 | return result['title'] 339 | return result 340 | 341 | # 获取ChatInterface的输入框 342 | chat_interface = gr.ChatInterface( 343 | fn=stream_chat, 344 | additional_outputs=[references], 345 | type="messages", 346 | fill_height=True, 347 | ) 348 | 349 | btn_a.click(fn=lambda: handle_button_click("A"), outputs=chat_interface.textbox) 350 | btn_b.click(fn=lambda: handle_button_click("B"), outputs=chat_interface.textbox) 351 | btn_c.click(fn=lambda: handle_button_click("C"), outputs=chat_interface.textbox) 352 | 353 | with gr.Column(scale=1): 354 | gr.Markdown("

📔 Search Records

") 355 | gr.Markdown("> **searched content will by displayed here**") 356 | references.render() 357 | 358 | demo.launch() 359 | -------------------------------------------------------------------------------- /demo.txt: -------------------------------------------------------------------------------- 1 | (zhijie310) D:\ai-app-lab\ai-app-lab-main\demohouse\deep_search>python run_local.py 2 | 2025-05-27 11:05:24 [debug ] singleton class initialized name=ClientPool 3 | [2025-05-27 11:05:25,707][INFO] HTTP Request: POST https://ark.cn-beijing.volces.com/api/v3/chat/completions "HTTP/1.1 200 OK" 4 | 5 | ----思考过程---- 6 | 7 | 好的,我现在需要处理用户的问题,判断是否需要进一步的检索。用户的要求是基于提供的新闻材料,提取三个主题,并补充相关资料来撰写文章。首先,我需要分析提供的新闻内容。 8 | 9 | 提供的新闻中提到了多个乐高相关的主题,比如“漫威人仔升级”、“武士城堡”、“机械组评测”等。用户要求每个主题补充搜索资料,例如如何拼装主题、查找主题等,并且需要图片。 10 | 11 | 首先,检查现有资料是否足够。用户提供的新闻已经详细介绍了各个主题,但可能需要更多关于“武士城堡设计灵感”、“模块化游乐场拼装技巧”和“机械组电动装载机玩法”的具体信息,比如设计背后的故事、拼装步骤或创新玩法,这些可能不在现有资料中。此外,用户可能需要相关图片来支持补充内容,而现有资料中的图片可能不足以覆盖所有主题。 12 | 13 | 因此,需要确定是否需要进一步检索。当前已知资料可能不足以详细补充每个主题,尤其是具体的设计灵感和拼装技巧。因此,需要搜索以下关键词来获取更多信息: 14 | 15 | 1. 乐高武士城堡设计灵感 16 | 2. 乐高模块化游乐场拼装技巧 17 | 3. 乐高机械组电动装载机玩法 18 | 19 | 这些关键词能够帮助找到每个主题的额外资料和图片,确保文章内容更丰富。因此,输出这些关键词进行检索。 20 | 21 | 22 | [2025-05-27 11:05:38,310][INFO] got planning_result: 23 | 24 | 乐高武士城堡设计灵感;乐高模块化游乐场拼装技巧;乐高机械组电动装载机玩法 25 | [2025-05-27 11:05:38,311][INFO] searching: ['乐高武士城堡设计灵感', '乐高模块化游乐场拼装技巧', '乐高机械组电动装载机玩 法'] 26 | 乐高武士城堡设计灵感;乐高模块化游乐场拼装技巧;乐高机械组电动装载机玩法乐高武士城堡的设计可以从多个方面汲取灵感,包括建筑结构、防御工事以及装饰元素。以下是一些基于已有文档的建议和灵感来源: 27 | 28 | - **城堡类型**:可以参考几种不同类型的城堡设计,例如方形堡垒(Square Keep)、童话城堡(Fairytale Castle)、古堡(Ancient Castle)等。每种都有独特的风格和特点。 29 | - 方形堡垒(Square Keep)适合放在4x4的底板上,可以是一个简单的但经典的城堡设计。 30 | - 童话城堡(Fairytale Castle)则可以在4x4的底板上通过建造不同高度的塔楼来实现更富有想象力的设计。 31 | - 古堡(Ancient Castle)结合了方形和圆形塔楼,创造出一种混合式的外观。 32 | 33 | - **防御特性**:城堡的防御设计是关键,如厚实的石墙、护城河、吊桥、窗户上的铁栅栏、地牢等。这些不仅增加了模型的真实感,还为故事增添了戏剧性。 34 | 35 | - **定制化**:在搭建过程中,可以选择标准尺寸的底板,比如4x4, 16x16或自定义大小的拼接板。先放置主塔和瞭望塔的位置,然后围绕它们构建城墙和大门。你可以自由决定塔楼的高度与宽度,甚至加入一些隐藏的地牢或其他惊喜元素。 36 | 37 | - **细节装饰**:使用诸如斜屋顶片(1959年发布的新部件)之类的特殊积木块,为你的城堡增添更多细节。考虑添加火炬、使用螺旋状围栏作为监狱窗格,并尝试利用棕色砖块营造出中世纪的感觉。 38 | 39 | 这里有一些具体的城堡设计图片供您参考: 40 | 41 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_93.png) 42 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_94.png) 43 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_95.png) 44 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_96.png) 45 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_97.png) 46 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_98.png) 47 | 48 | 如果您需要更加详细的武士主题城堡设计灵感,我还可以进一步搜索相关信息。请问是否有特定的武士城堡样式或特征您想要了解? 49 | 在乐高模块化游乐场的拼装过程中,我们可以从一些基本的技巧和示例中得到灵感。这里有一些来自提供的材料中的相关信息: 50 | 51 | - **游乐场基底**:游乐场有一个由浅棕色(nougat brown)板块构成的木质底座,并且边缘用瓷砖围起来以增加美观性。这样的设计不仅提供了稳固的基础,还使得整个游乐场看起来更加整洁。 52 | 53 | - **墙面装饰**:游乐场墙壁上的蓝色面板是通过将1x2瓷砖横向固定到墙上的凸点来实现的。这种技术被称为“SNOT”(Studs Not On Top),它允许创建出更加平滑和细致的表面效果。 54 | 55 | - **结构适配**:整个房子被设计得正好适合放置在一个16x16大小的板块上,这表明了在开始建造之前做好规划的重要性,确保所有组 件都能完美地适应预定的空间。 56 | 57 | - **细节处理**:例如使用1x1/1x1支架支撑较长的1x6瓷砖,这类小巧思可以帮助你更好地利用空间并创造出独特的建筑特色。 58 | 59 | 此外,在乐高的世界里,除了直接按照说明书进行组装外,玩家也可以发挥创意,对现有的设计做出调整或改进。比如,如果你想改变游乐场窗户的设计,可以移除原来的2x2砖块结构以及1x1斜面瓷砖,然后用六层1x2圆木砖或者带有格栅图案的1x2砖块代替斜面瓷砖的位置。这样不仅能够为模型增添不同的纹理质感,还能让最终成品显得更为个性化。 60 | 61 | ![](https://liangdabiao.com/lego/images/d199c632e9e2cb391567ad4194e23432ef9f8612babb8317f3d8a3f5d6043e97.jpg) 62 | ![](https://liangdabiao.com/lego/images/5f71aaa8d4944d682974f51ada61249227ccff0e2db4a17946e9d649bd3e7186.jpg) 63 | 64 | 这些图片展示了一个简单的乐高游乐场所采用的一些构建技术和细节处理方式,希望它们能给你的项目带来灵感!如果你有更具体的需求或想要了解某个特别部分的搭建方法,请告诉我,我会尽力提供更多帮助。 65 | 通过搜索,我找到了一些关于乐高模块化游乐场拼装技巧的信息。但直接相关的内容较少,因此这里提供了一些可以借鉴的其他乐高模型组装实例,它们或许能为你在建造模块化游乐场时提供灵感和技术上的参考。 66 | 67 | 1. **城市赛车积木套装** 68 | - 这个套组包含多种类型的积木组件,比如轮胎、车轴、阶梯状结构件等。这些多样化的零件为搭建复杂且具有创意性的模型提供了 丰富的选择。 69 | - ![城市赛车](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_09_13_49_03_55815500_85c39aed585d9809c403081edfe6c405.jpg) 70 | - 虽然这是关于赛车主题的积木,但它展示了如何使用不同形状和功能的部件来构建稳固而有趣的项目。 71 | 72 | 2. **LEGO Lates 相机积木套装** 73 | - 该套件详细说明了如何使用特定的积木(如2x4横向板、齿轮轴套件)以及螺丝螺母等连接件来逐步组装一个精巧的小相机模型。 74 | - ![LEGO Lates 相机](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_09_09_22_12_49_23058700_e05cf8a68f08072dcd6c9bc68fe96eca.jpg) 75 | - 对于想要学习如何更精细地处理细节或尝试SNOT技术的朋友来说,这个例子非常有启发性。 76 | 77 | 希望上述信息能够帮助你更好地理解并应用乐高的各种拼接技巧到你的模块化游乐场项目中去!如果你还有其他具体问题或需要进一步的帮助,请随时告诉我。 78 | 乐高机械组电动装载机是一种高度复杂的模型,它通常包含多种功能,如可动的铲斗、转向系统以及模拟真实装载机工作的机制。根据我们现有的知识库内容,虽然没有直接提到乐高机械组电动装载机的具体玩法,但可以推断出一些可能的功能和玩法: 79 | 80 | - **使用平行杠杆**:乐高机械组电动装载机的臂(提升铲斗)形成了平行杠杆结构,这种设计允许铲斗在移动时保持特定的角度,从而模仿实际装载机的工作方式。通过调整这些杠杆的位置,可以改变它们的运动范围,比如增加向上或向下的最大伸展距离。 81 | - **控制机构**:装载机可能会配备一个由电机驱动的控制系统来操作其各个部分,例如铲斗的抬升与下降。这可能是通过遥控器或者编程接口来实现的。 82 | - **模拟工作**:玩家可以通过构建场景来模拟装载机的实际工作任务,比如搬运货物、清理道路等。 83 | 84 | 为了更具体地了解乐高机械组电动装载机的特点及其玩法,让我们尝试搜索一下相关信息。 85 | 通过搜索,我们找到了一些与乐高机械组电动装载机相关的模型信息。这些模型通常具备以下特点: 86 | 87 | - **车身**:使用多种颜色的积木拼装而成,例如黄色、白色等,带有逼真的细节设计。 88 | - **轮胎**:配备有橡胶轮子或模拟履带的设计来增强模型的真实感。 89 | - **机械臂系统**:包括多个杠杆、齿轮和其他组件用于操作前端设备如铲斗。这种设计允许玩家体验真实的装载机工作流程。 90 | - **控制机构**:部分高级模型可能支持电力驱动或者遥控功能,让玩家能够更加直观地控制装载机的动作。 91 | 92 | 下面是几个具体的乐高机械组装载机模型示例: 93 | 94 | 1. **重型叉车** - 这个模型展示了复杂的机械结构以及精美的外观设计,适用于儿童玩具爱好者收藏把玩或成年人用来学习机械原理知识时参考使用。 95 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_09_30_22_50_48_60674700_0289a3ccdef60993a0c99fece428bd2a.jpg) 96 | 97 | 2. **全功率反铲装载机** - 该模型拥有一个后置铲斗臂机构,可以进行挖掘土壤或搬运重物的任务,并且配备了实用性和美观性兼具的设计。 98 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_10_06_22_16_13_62843100_f3ccdd27d2000e3f9255a7e3e2c48800.jpg) 99 | 100 | 3. **组合式挖掘装载机** - 此模型结合了挖掘和装载的功能,具有大尺寸履带式底盘设计,非常适合模拟真实工作场景中的移动性和表现力。 101 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_31_18_28_42_69265200_afb5d7e12ac9e1fd3da7b6e95dad0c96.jpg) 102 | 103 | 这些模型不仅能够帮助孩子们了解工程机械的工作原理,还能激发他们的创造力和动手能力。希望以上信息对你有所帮助!如果你对某个特定型号感兴趣或需要更多玩法建议,请告诉我。 104 | [2025-05-27 11:09:33,379][INFO] search result: [SearchResult(query='乐高武士城堡设计灵感', summary_content='乐高武士城堡的设计可以从多个方面汲取灵感,包括建筑结构、防御工事以及装饰元素。以下是一些基于已有文档的建议和灵感来源:\n\n- **城堡类型**:可以参考几种不同类型的城堡设计,例如方形堡垒(Square Keep)、童话城堡(Fairytale Castle)、古堡(Ancient Castle) 等。每种都有独特的风格和特点。\n - 方形堡垒(Square Keep)适合放在4x4的底板上,可以是一个简单的但经典的城堡设计。\n - 童话城堡(Fairytale Castle)则可以在4x4的底板上通过建造不同高度的塔楼来实现更富有想象力的设计。\n - 古堡(Ancient Castle)结合了方形和圆形塔楼,创造出一种混合式的外观。\n\n- **防御特性**:城堡的防御设计是关键,如厚实的石墙、护城河、吊桥、 窗户上的铁栅栏、地牢等。这些不仅增加了模型的真实感,还为故事增添了戏剧性。\n\n- **定制化**:在搭建过程中,可以选择标准尺寸的底板,比如4x4, 16x16或自定义大小的拼接板。先放置主塔和瞭望塔的位置,然后围绕它们构建城墙和大门。你可以自由决定塔楼的高度与宽度,甚至加入一些隐藏的地牢或其他惊喜元素。\n\n- **细节装饰**:使用诸如斜屋顶片(1959年发布的新部件)之类的特殊积木块,为你的城堡增添更多细节。考虑添加火炬、使用螺旋状围栏作为监狱窗格,并尝试利用棕色砖块营造出中世纪的感觉。\n\n这里有一些具体的城堡设计图片供您参考:\n\n![](https://upup.liangdabiao.com/lego_idea/JeffCastle_93.png)\n![](https://upup.liangdabiao.com/lego_idea/JeffCastle_94.png)\n![](https://upup.liangdabiao.com/lego_idea/JeffCastle_95.png)\n![](https://upup.liangdabiao.com/lego_idea/JeffCastle_96.png)\n![](https://upup.liangdabiao.com/lego_idea/JeffCastle_97.png)\n![](https://upup.liangdabiao.com/lego_idea/JeffCastle_98.png)\n\n如果您需要更加详细的武士主题城堡设计灵感,我还可以进一步搜索相关信息。请问是否有特定的武士城堡样式或特征您想要了解?', search_references=[SearchReference(site='lego', title='lego', url='lego', content='lego')]), SearchResult(query='乐高模块化游乐场拼装技巧', summary_content='在乐高模块化游乐场的拼装过程中,我们可以从一些基本的技巧和示例中得到灵感。这里有一些来自提供的材料中的相关信息:\n\n- **游乐场基底**:游乐场有一个由浅棕色(nougat brown)板块构成的木质底座,并且边缘用瓷砖围起来以增加美观性。这样的设计不仅提供了稳固的基础,还使得整个游乐场看起来更加整洁。\n\n- **墙面装饰**:游乐场墙壁上的蓝色面板是通过将1x2瓷砖横向固定到墙上的凸点来实现的。这种技术被称 为“SNOT”(Studs Not On Top),它允许创建出更加平滑和细致的表面效果。\n\n- **结构适配**:整个房子被设计得正好适合放置在一个16x16大小的板块上,这表明了在开始建造之前做好规划的重要性,确保所有组件都能完美地适应预定的空间。\n\n- **细节处理**: 例如使用1x1/1x1支架支撑较长的1x6瓷砖,这类小巧思可以帮助你更好地利用空间并创造出独特的建筑特色。\n\n此外,在乐高的世界里,除了直接按照说明书进行组装外,玩家也可以发挥创意,对现有的设计做出调整或改进。比如,如果你想改变游乐场窗户的设计,可以移除原来的2x2砖块结构以及1x1斜面瓷砖,然后用六层1x2圆木砖或者带有格栅图案的1x2砖块代替斜面瓷砖的位置。这样不仅能够为模型增添不同的纹理质感,还能让最终成品显得更为个性化。\n\n![](https://liangdabiao.com/lego/images/d199c632e9e2cb391567ad4194e23432ef9f8612babb8317f3d8a3f5d6043e97.jpg)\n![](https://liangdabiao.com/lego/images/5f71aaa8d4944d682974f51ada61249227ccff0e2db4a17946e9d649bd3e7186.jpg)\n\n这些图片展示了一个简单的乐高游乐场所采用的一些构建技术和细节处理方式,希望它们能给你的项目带来灵感!如果你有更具体的需求或想要了解某个特别部分的搭建方法,请告诉我,我会尽力提供更多帮助。\n通过搜索,我找到了一些关于乐高模块化游乐场拼装技巧的信息。但直接相关的内容较少,因此这里提供了一些可以借鉴的其他乐高模型组装实例,它们或许能为你在建造模块化游乐场时提供灵感和技术上的参考。\n\n1. **城市赛车积木套装**\n - 这个套组包含多种类型的积木组件,比如轮胎、车轴、阶梯状结构件等。这些多样化的零件为搭建复杂且具有创意性的模型提供了丰富的选择。\n - ![城市赛车](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_09_13_49_03_55815500_85c39aed585d9809c403081edfe6c405.jpg)\n - 虽 然这是关于赛车主题的积木,但它展示了如何使用不同形状和功能的部件来构建稳固而有趣的项目。\n\n2. **LEGO Lates 相机积木套装**\n - 该套件详细说明了如何使用特定的积木(如2x4横向板、齿轮轴套件)以及螺丝螺母等连接件来逐步组装一个精巧的小相机模型。\n - ![LEGO Lates 相机](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_09_09_22_12_49_23058700_e05cf8a68f08072dcd6c9bc68fe96eca.jpg)\n - 对于想要学习如何更精细地处理细节或尝试SNOT技术的朋友来说,这个例子非常有启发性。\n\n希望上述信息能够帮助你更好地理解并应用乐高的各种拼接技巧到你的模块化游乐场项目中去!如果你还有其他具体问题或需要进一步的帮助,请随时告诉我。', search_references=[SearchReference(site='lego', title='lego', url='lego', content='lego')]), SearchResult(query='乐高机械组电动装载机玩法', summary_content='乐高机械组电动装载机是一种高度复杂的模型,它通常包含多种功能,如可动的铲斗、转向系统以及模拟真实装载机工作的机制。根据我们现有的知识库内容,虽然没有直接提到乐高机械组电动装载机的具体玩法,但可以推断出一些可能的功能和玩法:\n\n- **使用平行杠杆**:乐高机械组电动装载机的臂(提升铲斗)形成了平行杠杆结构,这种设计允许铲斗在移动时保持特定的角度,从而模仿实际装载机的工作方式。通过调整这些杠杆的位置,可以改变它们的运动范围,比如增加向上或向下的最大伸展距离。\n- **控制机构**:装载机可能会配备一个由电机驱动的控制系统来操作其各个部分,例如铲斗的抬升与下降。这可能是通过遥控器或者编程接口来实现的。\n- **模拟工作**:玩家可以通过构建场景来模拟装载机的实际工作任务,比如搬运货物、清理道路等。\n\n为了更具体地了解乐高机械组电动装载机的特点及其玩法,让我们尝试搜索一下相关信息。\n通过搜索,我们找到了一些与乐高机械组电动装载机相关的模型信息。这些模型通常具备以下特点:\n\n- **车身**:使用多种颜色的积木拼装而成,例如黄色、白色等,带有逼真的细节设计。\n- **轮胎**:配备有橡胶轮子或模拟履带的设计来增强模型的真实感。\n- **机械臂系统**:包括多个杠杆、齿轮和其他组件用于操作前端设备如铲斗。这种设计允许玩家体验真实的装载机工作流程。\n- **控制机构**:部分高级模型可能支持电力驱动或者遥控功能,让玩家能够更加直观地控制装载机的动作。\n\n下面是几个具体的乐高机械组装载机模型示例:\n\n1. **重型叉车** - 这个模型展示了复杂的机械结构以及精美的外观设计,适用于儿童玩具爱好者收藏把玩或成年人用来学习机械原理知识时参考使用。\n ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_09_30_22_50_48_60674700_0289a3ccdef60993a0c99fece428bd2a.jpg)\n\n2. **全功率反铲装载机** - 该模型拥有一个后置铲斗臂机构,可以进行挖掘土壤或搬运重物的任务,并且配备了实用性和美观性兼具的设计。\n ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_10_06_22_16_13_62843100_f3ccdd27d2000e3f9255a7e3e2c48800.jpg)\n\n3. **组合式挖掘装载机** - 此模型结合了挖掘和装载的功能,具有大尺寸履带式底盘设计,非常适合模拟真实工作场景中的移动性和表现力。\n ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_31_18_28_42_69265200_afb5d7e12ac9e1fd3da7b6e95dad0c96.jpg)\n\n这些模型不仅能够帮助孩子们了解工程机械的工作原理,还能激发他们的创造力和动手能力。希望以上信息对你有所帮助!如果你对某个特定型号感兴趣或需要更多玩法建议,请告诉我。', search_references=[SearchReference(site='lego', title='lego', url='lego', content='lego')])] 105 | [2025-05-27 11:09:35,125][INFO] HTTP Request: POST https://ark.cn-beijing.volces.com/api/v3/chat/completions "HTTP/1.1 200 OK" 106 | 好的,我需要判断用户提供的已知资料是否足够回答他们的问题。用户的问题是关于根据给定的新闻材料,找出三个主题并撰写图文介绍,然后补充搜索资料,最后整合成文章。 107 | 108 | 首先,用户提供的新闻材料涉及多个乐高相关主题,比如漫威套装、武士城堡、机械组电动装载机、模块化游乐场、印度首家认证店等。已知资料中已经包含了对“乐高武士城堡设计灵感”、“乐高模块化游乐场拼装技巧”和“乐高机械组电动装载机玩法”的查询结果,每个主题都有相关的图文信息。例如,武士城堡的设计灵感提到了建筑结构、防御工事和装饰元素,并有图片;模块化游乐场有拼装技巧和图片;电动装载机有玩法描述和示例图片。 109 | 110 | 接下来,用户要求每个主题至少进行一次搜索以补充资料。当前已知资料是否足够呢?比如,对于“武士城堡”,可能需要更多关于日式建筑模块的细节或与其他乐高系列的兼容性;而“模块化游乐场”可能需要更多关于可动设施的具体拼装方法或类似成功案例的信息;“机械组装载机”可能需要更多关于电动功能的操作细节或与其他机械组模型的比较。此外,用户提到的印度首家乐高认证店在已知资料中没有相关补充信息,可能需要搜索其独特体验或限定商品。 111 | 112 | 因此,虽然已知资料覆盖了部分内容,但可能不够全面。需要补充的关键词可能包括“乐高印度认证店限定商品”、“乐高模块化游乐场可动设施设计”、“乐高机械组电动功能操作”等,以进一步完善文章内容。 113 | 114 | 115 | [2025-05-27 11:09:48,506][INFO] got planning_result: 116 | 117 | 无需检索 118 | [2025-05-27 11:09:48,506][INFO] planning finished 119 | [2025-05-27 11:09:48,770][INFO] ----- 联网资料 ----- 120 | [2025-05-27 11:09:48,771][INFO] 121 | 【查询 “乐高武士城堡设计灵感” 得到的相关资料】乐高武士城堡的设计可以从多个方面汲取灵感,包括建筑结构、防御工事以及装饰元素。以下是一些基于已有文档的建议和灵感来源: 122 | 123 | - **城堡类型**:可以参考几种不同类型的城堡设计,例如方形堡垒(Square Keep)、童话城堡(Fairytale Castle)、古堡(Ancient Castle)等。每种都有独特的风格和特点。 124 | - 方形堡垒(Square Keep)适合放在4x4的底板上,可以是一个简单的但经典的城堡设计。 125 | - 童话城堡(Fairytale Castle)则可以在4x4的底板上通过建造不同高度的塔楼来实现更富有想象力的设计。 126 | - 古堡(Ancient Castle)结合了方形和圆形塔楼,创造出一种混合式的外观。 127 | 128 | - **防御特性**:城堡的防御设计是关键,如厚实的石墙、护城河、吊桥、窗户上的铁栅栏、地牢等。这些不仅增加了模型的真实感,还为故事增添了戏剧性。 129 | 130 | - **定制化**:在搭建过程中,可以选择标准尺寸的底板,比如4x4, 16x16或自定义大小的拼接板。先放置主塔和瞭望塔的位置,然后围绕它们构建城墙和大门。你可以自由决定塔楼的高度与宽度,甚至加入一些隐藏的地牢或其他惊喜元素。 131 | 132 | - **细节装饰**:使用诸如斜屋顶片(1959年发布的新部件)之类的特殊积木块,为你的城堡增添更多细节。考虑添加火炬、使用螺旋状围栏作为监狱窗格,并尝试利用棕色砖块营造出中世纪的感觉。 133 | 134 | 这里有一些具体的城堡设计图片供您参考: 135 | 136 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_93.png) 137 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_94.png) 138 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_95.png) 139 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_96.png) 140 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_97.png) 141 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_98.png) 142 | 143 | 如果您需要更加详细的武士主题城堡设计灵感,我还可以进一步搜索相关信息。请问是否有特定的武士城堡样式或特征您想要了解? 144 | 【查询 “乐高模块化游乐场拼装技巧” 得到的相关资料】在乐高模块化游乐场的拼装过程中,我们可以从一些基本的技巧和示例中得到灵感。这里有一些来自提供的材料中的相关信息: 145 | 146 | - **游乐场基底**:游乐场有一个由浅棕色(nougat brown)板块构成的木质底座,并且边缘用瓷砖围起来以增加美观性。这样的设计不仅提供了稳固的基础,还使得整个游乐场看起来更加整洁。 147 | 148 | - **墙面装饰**:游乐场墙壁上的蓝色面板是通过将1x2瓷砖横向固定到墙上的凸点来实现的。这种技术被称为“SNOT”(Studs Not On Top),它允许创建出更加平滑和细致的表面效果。 149 | 150 | - **结构适配**:整个房子被设计得正好适合放置在一个16x16大小的板块上,这表明了在开始建造之前做好规划的重要性,确保所有组 件都能完美地适应预定的空间。 151 | 152 | - **细节处理**:例如使用1x1/1x1支架支撑较长的1x6瓷砖,这类小巧思可以帮助你更好地利用空间并创造出独特的建筑特色。 153 | 154 | 此外,在乐高的世界里,除了直接按照说明书进行组装外,玩家也可以发挥创意,对现有的设计做出调整或改进。比如,如果你想改变游乐场窗户的设计,可以移除原来的2x2砖块结构以及1x1斜面瓷砖,然后用六层1x2圆木砖或者带有格栅图案的1x2砖块代替斜面瓷砖的位置。这样不仅能够为模型增添不同的纹理质感,还能让最终成品显得更为个性化。 155 | 156 | ![](https://liangdabiao.com/lego/images/d199c632e9e2cb391567ad4194e23432ef9f8612babb8317f3d8a3f5d6043e97.jpg) 157 | ![](https://liangdabiao.com/lego/images/5f71aaa8d4944d682974f51ada61249227ccff0e2db4a17946e9d649bd3e7186.jpg) 158 | 159 | 这些图片展示了一个简单的乐高游乐场所采用的一些构建技术和细节处理方式,希望它们能给你的项目带来灵感!如果你有更具体的需求或想要了解某个特别部分的搭建方法,请告诉我,我会尽力提供更多帮助。 160 | 通过搜索,我找到了一些关于乐高模块化游乐场拼装技巧的信息。但直接相关的内容较少,因此这里提供了一些可以借鉴的其他乐高模型组装实例,它们或许能为你在建造模块化游乐场时提供灵感和技术上的参考。 161 | 162 | 1. **城市赛车积木套装** 163 | - 这个套组包含多种类型的积木组件,比如轮胎、车轴、阶梯状结构件等。这些多样化的零件为搭建复杂且具有创意性的模型提供了 丰富的选择。 164 | - ![城市赛车](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_09_13_49_03_55815500_85c39aed585d9809c403081edfe6c405.jpg) 165 | - 虽然这是关于赛车主题的积木,但它展示了如何使用不同形状和功能的部件来构建稳固而有趣的项目。 166 | 167 | 2. **LEGO Lates 相机积木套装** 168 | - 该套件详细说明了如何使用特定的积木(如2x4横向板、齿轮轴套件)以及螺丝螺母等连接件来逐步组装一个精巧的小相机模型。 169 | - ![LEGO Lates 相机](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_09_09_22_12_49_23058700_e05cf8a68f08072dcd6c9bc68fe96eca.jpg) 170 | - 对于想要学习如何更精细地处理细节或尝试SNOT技术的朋友来说,这个例子非常有启发性。 171 | 172 | 希望上述信息能够帮助你更好地理解并应用乐高的各种拼接技巧到你的模块化游乐场项目中去!如果你还有其他具体问题或需要进一步的帮助,请随时告诉我。 173 | 【查询 “乐高机械组电动装载机玩法” 得到的相关资料】乐高机械组电动装载机是一种高度复杂的模型,它通常包含多种功能,如可动的铲斗、转向系统以及模拟真实装载机工作的机制。根据我们现有的知识库内容,虽然没有直接提到乐高机械组电动装载机的具体玩法,但可以推断出一些可能的功能和玩法: 174 | 175 | - **使用平行杠杆**:乐高机械组电动装载机的臂(提升铲斗)形成了平行杠杆结构,这种设计允许铲斗在移动时保持特定的角度,从而模仿实际装载机的工作方式。通过调整这些杠杆的位置,可以改变它们的运动范围,比如增加向上或向下的最大伸展距离。 176 | - **控制机构**:装载机可能会配备一个由电机驱动的控制系统来操作其各个部分,例如铲斗的抬升与下降。这可能是通过遥控器或者编程接口来实现的。 177 | - **模拟工作**:玩家可以通过构建场景来模拟装载机的实际工作任务,比如搬运货物、清理道路等。 178 | 179 | 为了更具体地了解乐高机械组电动装载机的特点及其玩法,让我们尝试搜索一下相关信息。 180 | 通过搜索,我们找到了一些与乐高机械组电动装载机相关的模型信息。这些模型通常具备以下特点: 181 | 182 | - **车身**:使用多种颜色的积木拼装而成,例如黄色、白色等,带有逼真的细节设计。 183 | - **轮胎**:配备有橡胶轮子或模拟履带的设计来增强模型的真实感。 184 | - **机械臂系统**:包括多个杠杆、齿轮和其他组件用于操作前端设备如铲斗。这种设计允许玩家体验真实的装载机工作流程。 185 | - **控制机构**:部分高级模型可能支持电力驱动或者遥控功能,让玩家能够更加直观地控制装载机的动作。 186 | 187 | 下面是几个具体的乐高机械组装载机模型示例: 188 | 189 | 1. **重型叉车** - 这个模型展示了复杂的机械结构以及精美的外观设计,适用于儿童玩具爱好者收藏把玩或成年人用来学习机械原理知识时参考使用。 190 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_09_30_22_50_48_60674700_0289a3ccdef60993a0c99fece428bd2a.jpg) 191 | 192 | 2. **全功率反铲装载机** - 该模型拥有一个后置铲斗臂机构,可以进行挖掘土壤或搬运重物的任务,并且配备了实用性和美观性兼具的设计。 193 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_10_06_22_16_13_62843100_f3ccdd27d2000e3f9255a7e3e2c48800.jpg) 194 | 195 | 3. **组合式挖掘装载机** - 此模型结合了挖掘和装载的功能,具有大尺寸履带式底盘设计,非常适合模拟真实工作场景中的移动性和表现力。 196 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_31_18_28_42_69265200_afb5d7e12ac9e1fd3da7b6e95dad0c96.jpg) 197 | 198 | 这些模型不仅能够帮助孩子们了解工程机械的工作原理,还能激发他们的创造力和动手能力。希望以上信息对你有所帮助!如果你对某个特定型号感兴趣或需要更多玩法建议,请告诉我。 199 | [2025-05-27 11:09:50,042][INFO] HTTP Request: POST https://ark.cn-beijing.volces.com/api/v3/chat/completions "HTTP/1.1 200 OK" 200 | 无需检索好的,我现在需要处理用户的查询,用户希望我作为资深积木乐高专栏作家,根据提供的新闻材料生成一篇文章。首先,我需要仔细阅读用户提供的所有信息,理解任务的具体要求。 201 | 202 | 首先,用户的任务分为几个步骤:找出三个与新闻相关的主题,每个主题整理图文介绍,进行至少一次搜索补充资料,最后综合所有内容写成吸引人的文章。需要确保每个主题都有补充资料,并且引用参考编号,最后列出参考资料。 203 | 204 | 用户提供的新闻材料主要涉及乐高的多个新闻,包括漫威套装的人仔争议、乐高Insiders补丁、武士城堡项目、机械组电动装载机、模块化游乐场、日产Skyline GT-R、印度首家认证店开业,以及关于套内改造的新闻。不过用户特别指出,小红书的新闻材料主要是第一个result中的内容,所以需要重点关注这个部分。 205 | 206 | 接下来,我需要从新闻中提取三个主题,每个主题不超过五个字。例如,新闻中提到的“乐高 Ideas 武士城堡项目进入审查”可以简化 为“武士城堡”;“乐高机械组沃尔沃电动装载机评测”简化为“电动装载机”;“乐高 Ideas 模块化游乐场项目亮相”简化为“模块 游乐场”。这三个主题符合要求,且都是新闻中的重点内容。 207 | 208 | 然后,每个主题需要整理图文介绍,包括封面图片。例如,武士城堡的封面图可以使用新闻中提到的“cover_url”中的图片。不过查看 提供的新闻材料,第一个新闻的cover_url是漫威套装的图片,但其他主题可能需要使用补充资料中的图片。需要确认每个主题的封面图 是否在新闻中有提供,如果没有,可能需要从补充资料中选择。 209 | 210 | 接下来,每个主题需要进行一次搜索补充资料。用户已经提供了联网参考资料,包括关于武士城堡设计灵感、模块化游乐场拼装技巧、机械组电动装载机玩法的信息。需要将这些补充资料与主题对应起来,例如武士城堡参考[1],模块游乐场参考[2],电动装载机参考[3]。 211 | 212 | 在整理补充资料时,需要简要描述,并引用对应的参考编号,例如在武士城堡部分提到防御特性和定制化设计时引用[1]。同时,需要确 保补充资料的图片也被包含在内,例如武士城堡的图片链接。 213 | 214 | 最后,撰写文章时需要将三个主题及其补充资料结合起来,结构清晰,吸引读者。需要注意不要涉及品牌、定价等细节,重点放在设计灵感和玩法上。 215 | 216 | 现在需要检查是否有遗漏的步骤或要求。例如,每个主题是否都有对应的搜索补充资料,是否引用了正确的参考编号,图片是否正确展示,以及文章结构是否符合要求。 217 | 218 | 可能遇到的问题:新闻中的cover_url可能不完全对应每个主题,需要合理分配。例如,第一个新闻的cover_url是漫威套装的图片,但主题是武士城堡、电动装载机、模块游乐场,可能需要使用补充资料中的图片作为封面。但用户可能希望使用新闻中的图片,需要确认。根据示例,用户提供的新闻中的武士城堡项目可能没有图片,但补充资料中有武士城堡的设计图片,因此可以将其作为封面。 219 | 220 | 另外,需要确保每个主题的补充资料搜索正确,例如武士城堡的设计灵感、模块游乐场的拼装技巧、电动装载机的玩法,这些在用户提供的联网资料中都有对应的内容,可以引用。 221 | 222 | 最后,文章需要吸引人,因此开头需要有一个引人入胜的导语,介绍当天的热点主题,然后逐一展开每个主题及其补充内容,结尾总结,并列出参考资料。 223 | 224 | 现在,整理所有内容,确保结构清晰,符合用户要求。 225 | 226 | ----输出回答---- 227 | 228 | 229 | 230 | ### 乐高今日热点:三大主题带你玩转创意世界! 231 | 232 | --- 233 | 234 | #### **主题1:武士城堡** 235 | **新闻核心**:乐高Ideas项目「武士城堡」以日式建筑模块化设计致敬经典忍者系列,凭借八模块建筑+完整内饰杀入2025年二次审查。 236 | **封面图**:![](https://upup.liangdabiao.com/lego_idea/JeffCastle_93.png) 237 | **补充资料**:武士城堡设计可从古堡防御工事汲取灵感,如护城河、吊桥、螺旋状监狱窗格[1]。模块化结构允许自由组合塔楼与城墙 ,搭配棕色砖块还原中世纪质感。 238 | ![](https://upup.liangdabiao.com/lego_idea/JeffCastle_95.png) 239 | **搜索关键词**:乐高武士城堡设计灵感 → 参考[1] 240 | 241 | --- 242 | 243 | #### **主题2:模块游乐场** 244 | **新闻核心**:Ideas新作「全功能模块化游乐场」含摩天轮、跳楼机等可动设施,5根拉杆操控动态机关,有望复刻电动灯塔的成功! 245 | **封面图**:![](https://liangdabiao.com/lego/images/d199c632e9e2cb391567ad4194e23432ef9f8612babb8317f3d8a3f5d6043e97.jpg) 246 | **补充资料**:模块化游乐场拼装需规划16x16底板,利用SNOT技术固定墙面瓷砖[2]。可借鉴城市赛车套装的阶梯结构,增强设施稳固性。 247 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_09_13_49_03_55815500_85c39aed585d9809c403081edfe6c405.jpg) 248 | **搜索关键词**:乐高模块化游乐场拼装技巧 → 参考[2] 249 | 250 | --- 251 | 252 | #### **主题3:电动装载机** 253 | **新闻核心**:机械组42209沃尔沃电动装载机搭载可动铲斗与悬架系统,2-3小时拼装体验,旋钮操控承重堪比手机! 254 | **封面图**:![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2023_05_31_18_28_42_69265200_afb5d7e12ac9e1fd3da7b6e95dad0c96.jpg) 255 | **补充资料**:机械组装载机玩法依赖平行杠杆结构,可模拟挖掘、搬运场景[3]。类似全功率反铲模型(图)通过齿轮组实现多关节联 动。 256 | ![](https://legoallbricks.oss-cn-shenzhen.aliyuncs.com/2019_10_06_22_16_13_62843100_f3ccdd27d2000e3f9255a7e3e2c48800.jpg) 257 | **搜索关键词**:乐高机械组电动装载机玩法 → 参考[3] 258 | 259 | --- 260 | 261 | ### 文章:**今日乐高三大爆点!武士、游乐场、机械组玩出天花板!** 262 | **武士城堡:从Ideas到你的桌面的东方美学** 263 | 乐高玩家心中的「武士城堡」不仅是模块化设计的胜利,更是对90年代忍者系列的深情复刻!参考古堡护城河与螺旋监狱窗格[1],这座 城堡将日式庭院与防御工事完美融合。想DIY?试试用棕色砖块堆砌石墙,再添一座隐藏地牢! 264 | 265 | **模块游乐场:拉杆操控的快乐星球** 266 | Baron von Barron的游乐场设计,让摩天轮与跳楼机「动」起来!拼装时记得用16x16底板打底,横向固定瓷砖打造光滑墙面[2]。想更炫酷?参考赛车套装的轮胎零件,给跳楼机加个旋转底座! 267 | 268 | **电动装载机:机械组的力量美学** 269 | 沃尔沃电动装载机用旋钮实现「真·铲土」!平行杠杆结构让它轻松承重手机[3]。进阶玩法?试试用反铲模型(图)的齿轮组,DIY一台能挖能装的终极工程车! 270 | 271 | --- 272 | 273 | **参考资料** 274 | [1] 乐高武士城堡设计灵感 275 | [2] 乐高模块化游乐场拼装技巧 276 | [3] 乐高机械组电动装载机玩法 --------------------------------------------------------------------------------