diff --git a/.gitignore b/.gitignore index 0152705..3848c44 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ embedding_models jupyter_work model_config.py server_config.py +internal_start.py code_base .DS_Store .idea +data +tests diff --git a/Dockerfile b/Dockerfile index 0ef02c7..f213f38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,8 @@ RUN apt-get install -y iputils-ping telnetd net-tools vim tcpdump # RUN service inetutils-inetd start # service inetutils-inetd status +RUN wget https://oss-cdn.nebula-graph.com.cn/package/3.6.0/nebula-graph-3.6.0.ubuntu1804.amd64.deb +RUN dpkg -i nebula-graph-3.6.0.ubuntu1804.amd64.deb RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple RUN pip install -r /home/user/docker_requirements.txt diff --git a/README.md b/README.md index 4166303..8f2db38 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@

-本项目是一个开源的 AI 智能助手,专为软件开发的全生命周期而设计,涵盖设计、编码、测试、部署和运维等阶段。通过知识检索、代码检索,工具使用和沙箱执行,Codefuse-ChatBot 能解答您开发过程中的各种专业问题、问答操作周边独立分散平台。 +本项目是一个开源的 AI 智能助手,专为软件开发的全生命周期而设计,涵盖设计、编码、测试、部署和运维等阶段。通过知识检索、代码检索,工具使用和沙箱执行,Codefuse-ChatBot不仅能回答您在开发过程中遇到的专业问题,还能通过对话界面协调多个独立分散的平台。 ## 🔔 更新 +- [2023.12.01] Multi-Agent和代码库检索功能开放 - [2023.11.15] 增加基于本地代码库的问答增强模式 - [2023.09.15] 本地/隔离环境的沙盒功能开放,基于爬虫实现指定url知识检索 @@ -29,13 +30,12 @@ 💡 本项目旨在通过检索增强生成(Retrieval Augmented Generation,RAG)、工具学习(Tool Learning)和沙盒环境来构建软件开发全生命周期的AI智能助手,涵盖设计、编码、测试、部署和运维等阶段。 逐渐从各处资料查询、独立分散平台操作的传统开发运维模式转变到大模型问答的智能化开发运维模式,改变人们的开发运维习惯。 -- 📚 知识库管理:DevOps专业高质量知识库 + 企业级知识库自助构建 + 对话实现快速检索开源/私有技术文档 -- ⌨️ 代码知识库管理:支持本地代码库导入和代码结构解析 + 对话实现快速检索本地代码 -- 🐳 隔离沙盒环境:实现代码的快速编译执行测试 -- 🔄 React范式:支撑代码的自我迭代、自动执行 -- 🛠️ Prompt管理:实现各种开发、运维任务的prompt管理 -- 🔌 丰富的领域插件:执行各种定制开发任务 -- 🚀 对话驱动:需求设计、系分设计、代码生成、开发测试、部署运维自动化 +本项目核心差异技术、功能点: +- **🧠 智能调度核心:** 构建了体系链路完善的调度核心,支持多模式一键配置,简化操作流程。 +- **💻 代码整库分析:** 实现了仓库级的代码深入理解,以及项目文件级的代码编写与生成,提升了开发效率。 +- **📄 文档分析增强:** 融合了文档知识库与知识图谱,通过检索和推理增强,为文档分析提供了更深层次的支持。 +- **🔧 垂类专属知识:** 为DevOps领域定制的专属知识库,支持垂类知识库的自助一键构建,便捷实用。 +- **🤖 垂类模型兼容:** 针对DevOps领域的小型模型,保证了与DevOps相关平台的兼容性,促进了技术生态的整合。 🌍 依托于开源的 LLM 与 Embedding 模型,本项目可实现基于开源模型的离线私有部署。此外,本项目也支持 OpenAI API 的调用。 @@ -57,26 +57,26 @@ ## 🧭 技术路线
- 图片 + 图片
-- 🕷️ **Web Crawl**:实现定期网络文档爬取,确保数据的及时性,并依赖于开源社区的持续补充。 -- 🗂️ **DocLoader & TextSplitter**:对从多种来源爬取的数据进行数据清洗、去重和分类,并支持私有文档的导入。 -- 🗄️ **Vector Database**:结合Text Embedding模型对文档进行Embedding并在Milvus中存储。 -- 🔌 **Connector**:作为调度中心,负责LLM与Vector Database之间的交互调度,基于Langchain技术实现。 -- 📝 **Prompt Control**:从开发和运维角度设计,为不同问题分类并为Prompt添加背景,确保答案的可控性和完整性。 -- 💬 **LLM**:默认使用GPT-3.5-turbo,并为私有部署和其他涉及隐私的场景提供专有模型选择。 -- 🔤 **Text Embedding**:默认采用OpenAI的Text Embedding模型,支持私有部署和其他隐私相关场景,并提供专有模型选择。 -- 🚧 **SandBox**:对于生成的输出,如代码,为帮助用户判断其真实性,提供了一个交互验证环境(基于FaaS),并支持用户进行调整。 - +- 🧠 **Multi-Agent Schedule Core:** 多智能体调度核心,简易配置即可打造交互式智能体。 +- 🕷️ **Multi Source Web Crawl:** 多源网络爬虫,提供对指定 URL 的爬取功能,以搜集所需信息。 +- 🗂️ **Data Processor:** 数据处理器,轻松完成文档载入、数据清洗,及文本切分,整合不同来源的数据。 +- 🔤 **Text Embedding & Index:**:文本嵌入索引,用户可以轻松上传文件进行文档检索,优化文档分析过程。 +- 🗄️ **Vector Database & Graph Database:** 向量与图数据库,提供灵活强大的数据管理解决方案。 +- 📝 **Prompt Control & Management:**:Prompt 控制与管理,精确定义智能体的上下文环境。 +- 🚧 **SandBox:**:沙盒环境,安全地执行代码编译和动作。 +- 💬 **LLM:**:智能体大脑,支持多种开源模型和 LLM 接口。 +- 🛠️ **API Management::** API 管理工具,实现对开源组件和运维平台的快速集成。 具体实现明细见:[技术路线明细](sources/readme_docs/roadmap.md) -## 模型接入 +## 🌐 模型接入 -有需要接入的model,可以提issue +如果您需要集成特定的模型,请通过提交issue来告知我们您的需求。 | model_name | model_size | gpu_memory | quantize | HFhub | ModelScope | | ------------------ | ---------- | ---------- | -------- | ----- | ---------- | @@ -193,4 +193,4 @@ python start.py ## 🤗 致谢 -本项目基于[langchain-chatchat](https://github.com/chatchat-space/Langchain-Chatchat)和[codebox-api](https://github.com/shroominic/codebox-api),在此深深感谢他们的开源贡献! +本项目基于[langchain-chatchat](https://github.com/chatchat-space/Langchain-Chatchat)和[codebox-api](https://github.com/shroominic/codebox-api),在此深深感谢他们的开源贡献! \ No newline at end of file diff --git a/README_en.md b/README_en.md index 5a60a0f..ad7c0f6 100644 --- a/README_en.md +++ b/README_en.md @@ -9,16 +9,18 @@

-This project is an open-source AI intelligent assistant, specifically designed for the entire lifecycle of software development, covering design, coding, testing, deployment, and operations. Through knowledge retrieval, tool utilization, and sandbox execution, Codefuse-ChatBot can answer various professional questions during your development process and perform question-answering operations on standalone, disparate platforms. +This project is an open-source AI intelligent assistant, specifically designed for the entire lifecycle of software development, covering design, coding, testing, deployment, and operations. Through knowledge retrieval, tool utilization, and sandbox execution, Codefuse-ChatBot can not only answer professional questions you encounter during the development process but also coordinate multiple independent, dispersed platforms through a conversational interface. ## 🔔 Updates -- [2023.09.15] Sandbox features for local/isolated environments are now available, implementing specified URL knowledge retrieval based on web crawling. +- [2023.12.01] Release of Multi-Agent and codebase retrieval functionalities. +- [2023.11.15] Addition of Q&A enhancement mode based on the local codebase. +- [2023.09.15] Launch of sandbox functionality for local/isolated environments, enabling knowledge retrieval from specified URLs using web crawlers. ## 📜 Contents - [🤝 Introduction](#-introduction) - [🧭 Technical Route](#-technical-route) -- [🌐 模型接入](#-模型接入) +- [🌐 Model Integration](#-model-integration) - [🚀 Quick Start](#-quick-start) - [🤗 Acknowledgements](#-acknowledgements) @@ -26,11 +28,11 @@ This project is an open-source AI intelligent assistant, specifically designed f 💡 The aim of this project is to construct an AI intelligent assistant for the entire lifecycle of software development, covering design, coding, testing, deployment, and operations, through Retrieval Augmented Generation (RAG), Tool Learning, and sandbox environments. It transitions gradually from the traditional development and operations mode of querying information from various sources and operating on standalone, disparate platforms to an intelligent development and operations mode based on large-model Q&A, changing people's development and operations habits. -- 📚 Knowledge Base Management: Professional high-quality Codefuse knowledge base + enterprise-level knowledge base self-construction + dialogue-based fast retrieval of open-source/private technical documents. -- 🐳 Isolated Sandbox Environment: Enables quick compilation, execution, and testing of code. -- 🔄 React Paradigm: Supports code self-iteration and automatic execution. -- 🛠️ Prompt Management: Manages prompts for various development and operations tasks. -- 🚀 Conversation Driven: Automates requirement design, system analysis design, code generation, development testing, deployment, and operations. +- **🧠 Intelligent Scheduling Core:** Constructed a well-integrated scheduling core system that supports multi-mode one-click configuration, simplifying the operational process. +- **💻 Comprehensive Code Repository Analysis:** Achieved in-depth understanding at the repository level and coding and generation at the project file level, enhancing development efficiency. +- **📄 Enhanced Document Analysis:** Integrated document knowledge bases with knowledge graphs, providing deeper support for document analysis through enhanced retrieval and reasoning. +- **🔧 Industry-Specific Knowledge:** Tailored a specialized knowledge base for the DevOps domain, supporting the self-service one-click construction of industry-specific knowledge bases for convenience and practicality. +- **🤖 Compatible Models for Specific Verticals:** Designed small models specifically for the DevOps field, ensuring compatibility with related DevOps platforms and promoting the integration of the technological ecosystem. 🌍 Relying on open-source LLM and Embedding models, this project can achieve offline private deployments based on open-source models. Additionally, this project also supports the use of the OpenAI API. @@ -47,24 +49,25 @@ This project is an open-source AI intelligent assistant, specifically designed f ## 🧭 Technical Route
- Image + Image
-- 🕷️ **Web Crawl**: Implements periodic web document crawling to ensure data timeliness and relies on continuous supplementation from the open-source community. -- 🗂️ **DocLoader & TextSplitter**: Cleans, deduplicates, and categorizes data crawled from various sources and supports the import of private documents. -- 🗄️ **Vector Database**: Integrates Text Embedding models to embed documents and store them in Milvus. -- 🔌 **Connector**: Acts as the scheduling center, responsible for coordinating interactions between LLM and Vector Database, implemented based on Langchain technology. -- 📝 **Prompt Control**: Designs from development and operations perspectives, categorizes different problems, and adds backgrounds to prompts to ensure the controllability and completeness of answers. -- 💬 **LLM**: Uses GPT-3.5-turbo by default and provides proprietary model options for private deployments and other privacy-related scenarios. -- 🔤 **Text Embedding**: Uses OpenAI's Text Embedding model by default, supports private deployments and other privacy-related scenarios, and provides proprietary model options. -- 🚧 **SandBox**: For generated outputs, like code, to help users judge their authenticity, an interactive verification environment is provided (based on FaaS), allowing user adjustments. +- 🧠 **Multi-Agent Schedule Core:** Easily configurable to create interactive intelligent agents. +- 🕷️ **Multi Source Web Crawl:** Offers the capability to crawl specified URLs for collecting the required information. +- 🗂️ **Data Processor:** Effortlessly handles document loading, data cleansing, and text segmentation, integrating data from different sources. +- 🔤 **Text Embedding & Index:**:Users can easily upload files for document retrieval, optimizing the document analysis process. +- 🗄️ **Vector Database & Graph Database:** Provides flexible and powerful data management solutions. +- 📝 **Prompt Control & Management:**:Precisely defines the contextual environment for intelligent agents. +- 🚧 **SandBox:**:Safely executes code compilation and actions. +- 💬 **LLM:**:Supports various open-source models and LLM interfaces. +- 🛠️ **API Management::** Enables rapid integration of open-source components and operational platforms. For implementation details, see: [Technical Route Details](sources/readme_docs/roadmap.md) -## 模型接入 +## 🌐 Model Integration -有需要接入的model,可以提issue +If you need to integrate a specific model, please inform us of your requirements by submitting an issue. | model_name | model_size | gpu_memory | quantize | HFhub | ModelScope | | ------------------ | ---------- | ---------- | -------- | ----- | ---------- | diff --git a/configs/__init__.py b/configs/__init__.py index dd01d3c..9898291 100644 --- a/configs/__init__.py +++ b/configs/__init__.py @@ -1,4 +1,4 @@ from .model_config import * from .server_config import * -VERSION = "v0.0.1" \ No newline at end of file +VERSION = "v0.1.0" \ No newline at end of file diff --git a/configs/model_config.py.example b/configs/model_config.py.example index 753bbc9..29b22ea 100644 --- a/configs/model_config.py.example +++ b/configs/model_config.py.example @@ -1,6 +1,10 @@ import os +import sys import logging import torch +import openai +import base64 +from .utils import is_running_in_docker # 日志格式 LOG_FORMAT = "%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s" logger = logging.getLogger() @@ -8,8 +12,11 @@ logger.setLevel(logging.INFO) logging.basicConfig(format=LOG_FORMAT) # os.environ["OPENAI_PROXY"] = "socks5h://127.0.0.1:13659" +os.environ["API_BASE_URL"] = "http://openai.com/v1/chat/completions" os.environ["OPENAI_API_KEY"] = "" os.environ["DUCKDUCKGO_PROXY"] = "socks5://127.0.0.1:13659" +os.environ["BAIDU_OCR_API_KEY"] = "" +os.environ["BAIDU_OCR_SECRET_KEY"] = "" import platform system_name = platform.system() @@ -38,6 +45,7 @@ LOCAL_MODEL_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(_ embedding_model_dict = {k: f"/home/user/chatbot/embedding_models/{v}" if is_running_in_docker() else f"{LOCAL_MODEL_DIR}/{v}" for k, v in embedding_model_dict.items()} # 选用的 Embedding 名称 +EMBEDDING_ENGINE = 'openai' EMBEDDING_MODEL = "text2vec-base" # Embedding 模型运行设备 @@ -99,6 +107,20 @@ llm_model_dict = { } +LOCAL_LLM_MODEL_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "llm_models") +llm_model_dict_c = {} +for k, v in llm_model_dict.items(): + v_c = {} + for kk, vv in v.items(): + if k=="local_model_path": + v_c[kk] = f"/home/user/chatbot/llm_models/{vv}" if is_running_in_docker() else f"{LOCAL_LLM_MODEL_DIR}/{vv}" + else: + v_c[kk] = vv + llm_model_dict_c[k] = v_c + +llm_model_dict = llm_model_dict_c + + # LLM 名称 LLM_MODEL = "gpt-3.5-turbo" USE_FASTCHAT = "gpt" not in LLM_MODEL # 判断是否进行fastchat @@ -129,7 +151,10 @@ JUPYTER_WORK_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath # WEB_CRAWL存储路径 WEB_CRAWL_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "sources/docs") -for _path in [LOG_PATH, SOURCE_PATH, KB_ROOT_PATH, NLTK_DATA_PATH, JUPYTER_WORK_PATH, WEB_CRAWL_PATH]: +# NEBULA_DATA存储路径 +NELUBA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data/neluba_data") + +for _path in [LOG_PATH, SOURCE_PATH, KB_ROOT_PATH, NLTK_DATA_PATH, JUPYTER_WORK_PATH, WEB_CRAWL_PATH, NELUBA_PATH]: if not os.path.exists(_path): os.mkdir(_path) @@ -194,6 +219,11 @@ CODE_PROMPT_TEMPLATE = """【指令】根据已知信息来回答问题。 【问题】{question}""" +# 代码解释模版 +CODE_INTERPERT_TEMPLATE = '''{code} + +解释一下这段代码''' + # API 是否开启跨域,默认为False,如果需要开启,请设置为True # is open cross domain OPEN_CROSS_DOMAIN = False diff --git a/configs/server_config.py.example b/configs/server_config.py.example index b355dcd..8f3dd7a 100644 --- a/configs/server_config.py.example +++ b/configs/server_config.py.example @@ -45,6 +45,21 @@ FSCHAT_OPENAI_API = { "docker_port": 8888, # model_config.llm_model_dict中模型配置的api_base_url需要与这里一致。 } +# nebula conf +NEBULA_HOST = DEFAULT_BIND_HOST +NEBULA_PORT = 9669 +NEBULA_STORAGED_PORT = 9779 +NEBULA_USER = 'root' +NEBULA_PASSWORD = '' +NEBULA_GRAPH_SERVER = { + "host": DEFAULT_BIND_HOST, + "port": NEBULA_PORT, + "docker_port": NEBULA_PORT +} + +# chroma conf +CHROMA_PERSISTENT_PATH = '/home/user/chatbot/data/chroma_data' + # sandbox api server SANDBOX_CONTRAINER_NAME = "devopsgpt_sandbox" SANDBOX_IMAGE_NAME = "devopsgpt:py39" diff --git a/dev_opsgpt/chat/__init__.py b/dev_opsgpt/chat/__init__.py index 737d802..adb51e6 100644 --- a/dev_opsgpt/chat/__init__.py +++ b/dev_opsgpt/chat/__init__.py @@ -2,12 +2,10 @@ from .base_chat import Chat from .knowledge_chat import KnowledgeChat from .llm_chat import LLMChat from .search_chat import SearchChat -from .tool_chat import ToolChat -from .data_chat import DataChat from .code_chat import CodeChat from .agent_chat import AgentChat __all__ = [ - "Chat", "KnowledgeChat", "LLMChat", "SearchChat", "ToolChat", "DataChat", "CodeChat", "AgentChat" + "Chat", "KnowledgeChat", "LLMChat", "SearchChat", "CodeChat", "AgentChat" ] diff --git a/dev_opsgpt/chat/agent_chat.py b/dev_opsgpt/chat/agent_chat.py index 2ec4261..95ebc9e 100644 --- a/dev_opsgpt/chat/agent_chat.py +++ b/dev_opsgpt/chat/agent_chat.py @@ -1,10 +1,11 @@ from fastapi import Body, Request from fastapi.responses import StreamingResponse -from typing import List +from typing import List, Union, Dict from loguru import logger import importlib import copy import json +from pathlib import Path from configs.model_config import ( llm_model_dict, LLM_MODEL, PROMPT_TEMPLATE, @@ -18,12 +19,12 @@ from dev_opsgpt.tools import ( from dev_opsgpt.connector.phase import BasePhase from dev_opsgpt.connector.agents import BaseAgent, ReactAgent from dev_opsgpt.connector.chains import BaseChain -from dev_opsgpt.connector.connector_schema import ( +from dev_opsgpt.connector.schema import ( Message, load_phase_configs, load_chain_configs, load_role_configs ) -from dev_opsgpt.connector.shcema import Memory - +from dev_opsgpt.connector.schema import Memory +from dev_opsgpt.utils.common_utils import file_normalize from dev_opsgpt.chat.utils import History, wrap_done from dev_opsgpt.connector.configs import PHASE_CONFIGS, AGETN_CONFIGS, CHAIN_CONFIGS @@ -41,6 +42,7 @@ class AgentChat: ) -> None: self.top_k = top_k self.stream = stream + self.chatPhase_dict: Dict[str, BasePhase] = {} def chat( self, @@ -67,7 +69,8 @@ class AgentChat: custom_chain_configs: dict = Body({}, description="自定义chain配置"), custom_role_configs: dict = Body({}, description="自定义role配置"), history_node_list: List = Body([], description="代码历史相关节点"), - isDetaild: bool = Body([], description="是否输出完整的agent相关内容"), + isDetailed: bool = Body(False, description="是否输出完整的agent相关内容"), + upload_file: Union[str, Path, bytes] = "", **kargs ) -> Message: @@ -83,11 +86,21 @@ class AgentChat: # choose tools tools = toLangchainTools([TOOL_DICT[i] for i in choose_tools if i in TOOL_DICT]) + logger.debug(f"upload_file: {upload_file}") + + if upload_file: + upload_file_name = upload_file if upload_file and isinstance(upload_file, str) else upload_file.name + for _filename_idx in range(len(upload_file_name), 0, -1): + if upload_file_name[:_filename_idx] in query: + query = query.replace(upload_file_name[:_filename_idx], upload_file_name) + break + input_message = Message( role_content=query, role_type="human", role_name="user", input_query=query, + origin_query=query, phase_name=phase_name, chain_name=chain_name, do_search=do_search, @@ -101,7 +114,7 @@ class AgentChat: tools=tools ) # history memory mangemant - history = Memory([ + history = Memory(messages=[ Message(role_name=i["role"], role_type=i["role"], role_content=i["content"]) for i in history ]) @@ -128,14 +141,18 @@ class AgentChat: # "figures": output_message.figures # } - def chat_iterator(message: Message, local_memory: Memory, isDetaild=False): + def chat_iterator(message: Message, local_memory: Memory, isDetailed=False): + step_content = local_memory.to_str_messages(content_key='step_content', filter_roles=["user"]) + final_content = message.role_content result = { "answer": "", "db_docs": [str(doc) for doc in message.db_docs], "search_docs": [str(doc) for doc in message.search_docs], "code_docs": [str(doc) for doc in message.code_docs], "related_nodes": [doc.get_related_node() for idx, doc in enumerate(message.code_docs) if idx==0], - "figures": message.figures + "figures": message.figures, + "step_content": step_content, + "final_content": final_content, } @@ -146,8 +163,8 @@ class AgentChat: related_nodes.append(node) result["related_nodes"] = related_nodes - # logger.debug(f"{result['figures'].keys()}") - message_str = local_memory.to_str_messages(content_key='step_content') if isDetaild else message.role_content + # logger.debug(f"{result['figures'].keys()}, isDetailed: {isDetailed}") + message_str = step_content if self.stream: for token in message_str: result["answer"] = token @@ -157,7 +174,139 @@ class AgentChat: result["answer"] += token yield json.dumps(result, ensure_ascii=False) - return StreamingResponse(chat_iterator(output_message, local_memory, isDetaild), media_type="text/event-stream") + return StreamingResponse(chat_iterator(output_message, local_memory, isDetailed), media_type="text/event-stream") + + + def achat( + self, + query: str = Body(..., description="用户输入", examples=["hello"]), + phase_name: str = Body(..., description="执行场景名称", examples=["chatPhase"]), + chain_name: str = Body(..., description="执行链的名称", examples=["chatChain"]), + history: List[History] = Body( + [], description="历史对话", + examples=[[{"role": "user", "content": "我们来玩成语接龙,我先来,生龙活虎"}]] + ), + doc_engine_name: str = Body(..., description="知识库名称", examples=["samples"]), + search_engine_name: str = Body(..., description="搜索引擎名称", examples=["duckduckgo"]), + code_engine_name: str = Body(..., description="代码引擎名称", examples=["samples"]), + cb_search_type: str = Body(..., description="代码查询模式", examples=["tag"]), + top_k: int = Body(VECTOR_SEARCH_TOP_K, description="匹配向量数"), + score_threshold: float = Body(SCORE_THRESHOLD, description="知识库匹配相关度阈值,取值范围在0-1之间,SCORE越小,相关度越高,取到1相当于不筛选,建议设置在0.5左右", ge=0, le=1), + stream: bool = Body(False, description="流式输出"), + local_doc_url: bool = Body(False, description="知识文件返回本地路径(true)或URL(false)"), + choose_tools: List[str] = Body([], description="选择tool的集合"), + do_search: bool = Body(False, description="是否进行搜索"), + do_doc_retrieval: bool = Body(False, description="是否进行知识库检索"), + do_code_retrieval: bool = Body(False, description="是否执行代码检索"), + do_tool_retrieval: bool = Body(False, description="是否执行工具检索"), + custom_phase_configs: dict = Body({}, description="自定义phase配置"), + custom_chain_configs: dict = Body({}, description="自定义chain配置"), + custom_role_configs: dict = Body({}, description="自定义role配置"), + history_node_list: List = Body([], description="代码历史相关节点"), + isDetailed: bool = Body(False, description="是否输出完整的agent相关内容"), + upload_file: Union[str, Path, bytes] = "", + **kargs + ) -> Message: + + # update configs + phase_configs, chain_configs, agent_configs = self.update_configs( + custom_phase_configs, custom_chain_configs, custom_role_configs) + # choose tools + tools = toLangchainTools([TOOL_DICT[i] for i in choose_tools if i in TOOL_DICT]) + logger.debug(f"upload_file: {upload_file}") + + if upload_file: + upload_file_name = upload_file if upload_file and isinstance(upload_file, str) else upload_file.name + for _filename_idx in range(len(upload_file_name), 0, -1): + if upload_file_name[:_filename_idx] in query: + query = query.replace(upload_file_name[:_filename_idx], upload_file_name) + break + + input_message = Message( + role_content=query, + role_type="human", + role_name="user", + input_query=query, + origin_query=query, + phase_name=phase_name, + chain_name=chain_name, + do_search=do_search, + do_doc_retrieval=do_doc_retrieval, + do_code_retrieval=do_code_retrieval, + do_tool_retrieval=do_tool_retrieval, + doc_engine_name=doc_engine_name, + search_engine_name=search_engine_name, + code_engine_name=code_engine_name, + cb_search_type=cb_search_type, + score_threshold=score_threshold, top_k=top_k, + history_node_list=history_node_list, + tools=tools + ) + # history memory mangemant + history = Memory(messages=[ + Message(role_name=i["role"], role_type=i["role"], role_content=i["content"]) + for i in history + ]) + # start to execute + if phase_configs[input_message.phase_name]["phase_type"] not in self.chatPhase_dict: + phase_class = getattr(PHASE_MODULE, phase_configs[input_message.phase_name]["phase_type"]) + phase = phase_class(input_message.phase_name, + task = input_message.task, + phase_config = phase_configs, + chain_config = chain_configs, + role_config = agent_configs, + do_summary=phase_configs[input_message.phase_name]["do_summary"], + do_code_retrieval=input_message.do_code_retrieval, + do_doc_retrieval=input_message.do_doc_retrieval, + do_search=input_message.do_search, + ) + self.chatPhase_dict[phase_configs[input_message.phase_name]["phase_type"]] = phase + else: + phase = self.chatPhase_dict[phase_configs[input_message.phase_name]["phase_type"]] + + def chat_iterator(message: Message, local_memory: Memory, isDetailed=False): + step_content = local_memory.to_str_messages(content_key='step_content', filter_roles=["user"]) + step_content = "\n\n".join([f"{v}" for parsed_output in local_memory.get_parserd_output_list() for k, v in parsed_output.items() if k not in ["Action Status"]]) + final_content = message.role_content + result = { + "answer": "", + "db_docs": [str(doc) for doc in message.db_docs], + "search_docs": [str(doc) for doc in message.search_docs], + "code_docs": [str(doc) for doc in message.code_docs], + "related_nodes": [doc.get_related_node() for idx, doc in enumerate(message.code_docs) if idx==0], + "figures": message.figures, + "step_content": step_content, + "final_content": final_content, + } + + + related_nodes, has_nodes = [], [ ] + for nodes in result["related_nodes"]: + for node in nodes: + if node not in has_nodes: + related_nodes.append(node) + result["related_nodes"] = related_nodes + + # logger.debug(f"{result['figures'].keys()}, isDetailed: {isDetailed}") + message_str = step_content + if self.stream: + for token in message_str: + result["answer"] = token + yield json.dumps(result, ensure_ascii=False) + else: + for token in message_str: + result["answer"] += token + yield json.dumps(result, ensure_ascii=False) + + + for output_message, local_memory in phase.astep(input_message, history): + + # logger.debug(f"output_message: {output_message.role_content}") + # output_message = Message(**output_message) + # local_memory = Memory(**local_memory) + for result in chat_iterator(output_message, local_memory, isDetailed): + yield result + def _chat(self, ): pass diff --git a/dev_opsgpt/chat/code_chat.py b/dev_opsgpt/chat/code_chat.py index a7df657..9068bfb 100644 --- a/dev_opsgpt/chat/code_chat.py +++ b/dev_opsgpt/chat/code_chat.py @@ -53,23 +53,23 @@ class CodeChat(Chat): def _process(self, query: str, history: List[History], model): '''process''' + codes_res = search_code(query=query, cb_name=self.engine_name, code_limit=self.code_limit, + search_type=self.cb_search_type, history_node_list=self.history_node_list) - codes = codes_res['related_code'] - nodes = codes_res['related_node'] + context = codes_res['context'] + related_vertices = codes_res['related_vertices'] # update node names - node_names = [node[0] for node in nodes] - self.history_node_list.extend(node_names) - self.history_node_list = list(set(self.history_node_list)) + # node_names = [node[0] for node in nodes] + # self.history_node_list.extend(node_names) + # self.history_node_list = list(set(self.history_node_list)) - context = "\n".join(codes) source_nodes = [] - for inum, node_info in enumerate(nodes[0:5]): - node_name, node_type, node_score = node_info[0], node_info[1], node_info[2] - source_nodes.append(f'{inum + 1}. 节点名为 {node_name}, 节点类型为 `{node_type}`, 节点得分为 `{node_score}`') + for inum, node_name in enumerate(related_vertices[0:5]): + source_nodes.append(f'{inum + 1}. 节点名: `{node_name}`') logger.info('history={}'.format(history)) logger.info('message={}'.format([i.to_msg_tuple() for i in history] + [("human", CODE_PROMPT_TEMPLATE)])) @@ -90,6 +90,7 @@ class CodeChat(Chat): ), engine_name: str = Body(..., description="知识库名称", examples=["samples"]), code_limit: int = Body(1, examples=['1']), + cb_search_type: str = Body('', examples=['1']), stream: bool = Body(False, description="流式输出"), local_doc_url: bool = Body(False, description="知识文件返回本地路径(true)或URL(false)"), request: Request = None, @@ -100,6 +101,7 @@ class CodeChat(Chat): self.stream = stream if isinstance(stream, bool) else stream.default self.local_doc_url = local_doc_url if isinstance(local_doc_url, bool) else local_doc_url.default self.request = request + self.cb_search_type = cb_search_type return self._chat(query, history, **kargs) def _chat(self, query: str, history: List[History], **kargs): diff --git a/dev_opsgpt/chat/data_chat.py b/dev_opsgpt/chat/data_chat.py deleted file mode 100644 index 448aaf1..0000000 --- a/dev_opsgpt/chat/data_chat.py +++ /dev/null @@ -1,229 +0,0 @@ -import asyncio -from typing import List - -from langchain import LLMChain -from langchain.callbacks import AsyncIteratorCallbackHandler -from langchain.prompts.chat import ChatPromptTemplate -from langchain.agents import AgentType, initialize_agent - -from dev_opsgpt.tools import ( - WeatherInfo, WorldTimeGetTimezoneByArea, Multiplier, - toLangchainTools, get_tool_schema - ) -from .utils import History, wrap_done -from .base_chat import Chat -from loguru import logger -import json, re - -from dev_opsgpt.sandbox import PyCodeBox, CodeBoxResponse -from configs.server_config import SANDBOX_SERVER - -def get_tool_agent(tools, llm): - return initialize_agent( - tools, - llm, - agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, - verbose=True, - ) - -PROMPT_TEMPLATE = """ -`角色` -你是一个数据分析师,借鉴下述步骤,逐步完成数据分析任务的拆解和代码编写,尽可能帮助和准确地回答用户的问题。 - -数据文件的存放路径为 `./` - -`数据分析流程` -- 判断文件是否存在,并读取文件数据 -- 输出数据的基本信息,包括但不限于字段、文本、数据类型等 -- 输出数据的详细统计信息 -- 判断是否需要画图分析,选择合适的字段进行画图 -- 判断数据是否需要进行清洗 -- 判断数据或图片是否需要保存 -... -- 结合数据统计分析结果和画图结果,进行总结和分析这份数据的价值 - -`要求` -- 每轮选择一个数据分析流程,需要综合考虑上轮和后续的可能影响 -- 数据分析流程只提供参考,不要拘泥于它的具体流程,要有自己的思考 -- 使用JSON blob来指定一个计划,通过提供task_status关键字(任务状态)、plan关键字(数据分析计划)和code关键字(可执行代码)。 - -合法的 "task_status" 值: "finished" 表明当前用户问题已被准确回答 或者 "continued" 表明用户问题仍需要进一步分析 - -`$JSON_BLOB如下所示` -``` -{{ - "task_status": $TASK_STATUS, - "plan": $PLAN, - "code": ```python\n$CODE``` -}} -``` - -`跟随如下示例` -问题: 输入待回答的问题 -行动:$JSON_BLOB - -... (重复 行动 N 次,每次只生成一个行动) - -行动: -``` -{{ - "task_status": "finished", - "plan": 我已经可以回答用户问题了,最后回答用户的内容 -}} - -``` - -`数据分析,开始` - -问题:{query} -""" - - -PROMPT_TEMPLATE_2 = """ -`角色` -你是一个数据分析师,借鉴下述步骤,逐步完成数据分析任务的拆解和代码编写,尽可能帮助和准确地回答用户的问题。 - -数据文件的存放路径为 `./` - -`数据分析流程` -- 判断文件是否存在,并读取文件数据 -- 输出数据的基本信息,包括但不限于字段、文本、数据类型等 -- 输出数据的详细统计信息 -- 判断数据是否需要进行清洗 -- 判断是否需要画图分析,选择合适的字段进行画图 -- 判断清洗后数据或图片是否需要保存 -... -- 结合数据统计分析结果和画图结果,进行总结和分析这份数据的价值 - -`要求` -- 每轮选择一个数据分析流程,需要综合考虑上轮和后续的可能影响 -- 数据分析流程只提供参考,不要拘泥于它的具体流程,要有自己的思考 -- 使用JSON blob来指定一个计划,通过提供task_status关键字(任务状态)、plan关键字(数据分析计划)和code关键字(可执行代码)。 - -合法的 "task_status" 值: "finished" 表明当前用户问题已被准确回答 或者 "continued" 表明用户问题仍需要进一步分析 - -`$JSON_BLOB如下所示` -``` -{{ - "task_status": $TASK_STATUS, - "plan": $PLAN, - "code": ```python\n$CODE``` -}} -``` - -`跟随如下示例` -问题: 输入待回答的问题 -行动:$JSON_BLOB - -... (重复 行动 N 次,每次只生成一个行动) - -行动: -``` -{{ - "task_status": "finished", - "plan": 我已经可以回答用户问题了,最后回答用户的内容 -}} - -`数据分析,开始` - -问题:上传了一份employee_data.csv文件,请对它进行数据分析 - -问题:{query} -{history} - -""" - -class DataChat(Chat): - - def __init__( - self, - engine_name: str = "", - top_k: int = 1, - stream: bool = False, - ) -> None: - super().__init__(engine_name, top_k, stream) - self.tool_prompt = """结合上下文信息,{tools} {input}""" - self.codebox = PyCodeBox( - remote_url=SANDBOX_SERVER["url"], - remote_ip=SANDBOX_SERVER["host"], # "http://localhost", - remote_port=SANDBOX_SERVER["port"], - token="mytoken", - do_code_exe=True, - do_remote=SANDBOX_SERVER["do_remote"] - ) - - def create_task(self, query: str, history: List[History], model): - '''构建 llm 生成任务''' - logger.debug("content:{}".format([i.to_msg_tuple() for i in history] + [("human", PROMPT_TEMPLATE)])) - chat_prompt = ChatPromptTemplate.from_messages( - [i.to_msg_tuple() for i in history] + [("human", PROMPT_TEMPLATE)] - ) - pattern = re.compile(r"```(?:json)?\n(.*?)\n", re.DOTALL) - internal_history = [] - retry_nums = 2 - while retry_nums >= 0: - if len(internal_history) == 0: - chat_prompt = ChatPromptTemplate.from_messages( - [i.to_msg_tuple() for i in history] + [("human", PROMPT_TEMPLATE)] - ) - else: - chat_prompt = ChatPromptTemplate.from_messages( - [i.to_msg_tuple() for i in history] + [("human", PROMPT_TEMPLATE_2)] - ) - - chain = LLMChain(prompt=chat_prompt, llm=model) - content = chain({"query": query, "history": "\n".join(internal_history)})["text"] - - # content = pattern.search(content) - # logger.info(f"content: {content}") - # content = json.loads(content.group(1).strip(), strict=False) - - internal_history.append(f"{content}") - refer_info = "\n".join(internal_history) - logger.info(f"refer_info: {refer_info}") - try: - content = content.split("行动:")[-1].split("行动:")[-1] - content = json.loads(content) - except: - content = content.split("行动:")[-1].split("行动:")[-1] - content = eval(content) - - if "finished" == content["task_status"]: - break - elif "code" in content: - # elif "```code" in content or "```python" in content: - # code_text = self.codebox.decode_code_from_text(content) - code_text = content["code"] - codebox_res = self.codebox.chat("```"+code_text+"```", do_code_exe=True) - - if codebox_res is not None and codebox_res.code_exe_status != 200: - logger.warning(f"{codebox_res.code_exe_response}") - internal_history.append(f"观察: 根据这个报错信息 {codebox_res.code_exe_response},进行代码修复") - - if codebox_res is not None and codebox_res.code_exe_status == 200: - if codebox_res.code_exe_type == "image/png": - base_text = f"```\n{code_text}\n```\n\n" - img_html = "".format( - codebox_res.code_exe_response - ) - internal_history.append(f"观察: {img_html}") - # logger.info('```\n'+code_text+'\n```'+"\n\n"+'```\n'+codebox_res.code_exe_response+'\n```') - else: - internal_history.append(f"观察: {codebox_res.code_exe_response}") - # logger.info('```\n'+code_text+'\n```'+"\n\n"+'```\n'+codebox_res.code_exe_response+'\n```') - else: - internal_history.append(f"观察:下一步应该怎么做?") - retry_nums -= 1 - - - return {"answer": "", "docs": ""}, {"text": "\n".join(internal_history)} - - def create_atask(self, query, history, model, callback: AsyncIteratorCallbackHandler): - chat_prompt = ChatPromptTemplate.from_messages( - [i.to_msg_tuple() for i in history] + [("human", PROMPT_TEMPLATE)] - ) - chain = LLMChain(prompt=chat_prompt, llm=model) - task = asyncio.create_task(wrap_done( - chain.acall({"input": query}), callback.done - )) - return task, {"answer": "", "docs": ""} \ No newline at end of file diff --git a/dev_opsgpt/chat/tool_chat.py b/dev_opsgpt/chat/tool_chat.py deleted file mode 100644 index ceb6a86..0000000 --- a/dev_opsgpt/chat/tool_chat.py +++ /dev/null @@ -1,84 +0,0 @@ -import asyncio -from typing import List - -from langchain import LLMChain -from langchain.callbacks import AsyncIteratorCallbackHandler -from langchain.prompts.chat import ChatPromptTemplate -from langchain.agents import AgentType, initialize_agent -import langchain -from langchain.schema import ( - AgentAction - ) - - -# langchain.debug = True - -from dev_opsgpt.tools import ( - TOOL_SETS, TOOL_DICT, - toLangchainTools, get_tool_schema - ) -from .utils import History, wrap_done -from .base_chat import Chat -from loguru import logger - - -def get_tool_agent(tools, llm): - return initialize_agent( - tools, - llm, - agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, - verbose=True, - return_intermediate_steps=True - ) - - -class ToolChat(Chat): - - def __init__( - self, - engine_name: str = "", - top_k: int = 1, - stream: bool = False, - ) -> None: - super().__init__(engine_name, top_k, stream) - self.tool_prompt = """结合上下文信息,{tools} {input}""" - self.tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) - - def create_task(self, query: str, history: List[History], model, **kargs): - '''构建 llm 生成任务''' - logger.debug("content:{}".format([i.to_msg_tuple() for i in history] + [("human", "{query}")])) - # chat_prompt = ChatPromptTemplate.from_messages( - # [i.to_msg_tuple() for i in history] + [("human", "{query}")] - # ) - tools = kargs.get("tool_sets", []) - tools = toLangchainTools([TOOL_DICT[i] for i in tools if i in TOOL_DICT]) - agent = get_tool_agent(tools if tools else self.tools, model) - content = agent(query) - - logger.debug(f"content: {content}") - - s = "" - if isinstance(content, str): - s = content - else: - for i in content["intermediate_steps"]: - for j in i: - if isinstance(j, AgentAction): - s += j.log + "\n" - else: - s += "Observation: " + str(j) + "\n" - - s += "final answer:" + content["output"] - # chain = LLMChain(prompt=chat_prompt, llm=model) - # content = chain({"tools": tools, "input": query}) - return {"answer": "", "docs": ""}, {"text": s} - - def create_atask(self, query, history, model, callback: AsyncIteratorCallbackHandler): - chat_prompt = ChatPromptTemplate.from_messages( - [i.to_msg_tuple() for i in history] + [("human", self.tool_prompt)] - ) - chain = LLMChain(prompt=chat_prompt, llm=model) - task = asyncio.create_task(wrap_done( - chain.acall({"input": query}), callback.done - )) - return task, {"answer": "", "docs": ""} \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/codebase_handler.py b/dev_opsgpt/codebase_handler/codebase_handler.py deleted file mode 100644 index 82e9879..0000000 --- a/dev_opsgpt/codebase_handler/codebase_handler.py +++ /dev/null @@ -1,139 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: codebase_handler.py -@time: 2023/10/23 下午5:05 -@desc: -''' - -from loguru import logger -import time -import os - -from dev_opsgpt.codebase_handler.parser.java_paraser.java_crawler import JavaCrawler -from dev_opsgpt.codebase_handler.parser.java_paraser.java_preprocess import JavaPreprocessor -from dev_opsgpt.codebase_handler.parser.java_paraser.java_dedup import JavaDedup -from dev_opsgpt.codebase_handler.parser.java_paraser.java_parser import JavaParser -from dev_opsgpt.codebase_handler.tagger.tagger import Tagger -from dev_opsgpt.codebase_handler.tagger.tuple_generation import node_edge_update - -from dev_opsgpt.codebase_handler.networkx_handler.networkx_handler import NetworkxHandler -from dev_opsgpt.codebase_handler.codedb_handler.local_codedb_handler import LocalCodeDBHandler - - -class CodeBaseHandler(): - def __init__(self, code_name: str, code_path: str = '', cb_root_path: str = '', history_node_list: list = []): - self.nh = None - self.lcdh = None - self.code_name = code_name - self.code_path = code_path - - self.codebase_path = cb_root_path + os.sep + code_name - self.graph_path = self.codebase_path + os.sep + 'graph.pk' - self.codedb_path = self.codebase_path + os.sep + 'codedb.pk' - - self.tagger = Tagger() - self.history_node_list = history_node_list - - def import_code(self, do_save: bool=False, do_load: bool=False) -> bool: - ''' - import code to codeBase - @param code_path: - @param do_save: - @param do_load: - @return: True as success; False as failure - ''' - if do_load: - logger.info('start load from codebase_path') - load_graph_path = self.graph_path - load_codedb_path = self.codedb_path - - st = time.time() - self.nh = NetworkxHandler(graph_path=load_graph_path) - logger.info('generate graph success, rt={}'.format(time.time() - st)) - - st = time.time() - self.lcdh = LocalCodeDBHandler(db_path=load_codedb_path) - logger.info('generate codedb success, rt={}'.format(time.time() - st)) - else: - logger.info('start load from code_path') - st = time.time() - java_code_dict = JavaCrawler.local_java_file_crawler(self.code_path) - logger.info('crawl success, rt={}'.format(time.time() - st)) - - jp = JavaPreprocessor() - java_code_dict = jp.preprocess(java_code_dict) - - jd = JavaDedup() - java_code_dict = jd.dedup(java_code_dict) - - st = time.time() - j_parser = JavaParser() - parse_res = j_parser.parse(java_code_dict) - logger.info('parse success, rt={}'.format(time.time() - st)) - - st = time.time() - tagged_code = self.tagger.generate_tag(parse_res) - node_list, edge_list = node_edge_update(parse_res.values()) - logger.info('get node and edge success, rt={}'.format(time.time() - st)) - - st = time.time() - self.nh = NetworkxHandler(node_list=node_list, edge_list=edge_list) - logger.info('generate graph success, rt={}'.format(time.time() - st)) - - st = time.time() - self.lcdh = LocalCodeDBHandler(tagged_code) - logger.info('CodeDB load success, rt={}'.format(time.time() - st)) - - if do_save: - save_graph_path = self.graph_path - save_codedb_path = self.codedb_path - self.nh.save_graph(save_graph_path) - self.lcdh.save_db(save_codedb_path) - - def search_code(self, query: str, code_limit: int, history_node_list: list = []): - ''' - search code related to query - @param self: - @param query: - @return: - ''' - # get query tag - query_tag_list = self.tagger.generate_tag_query(query) - - related_node_score_list = self.nh.search_node_with_score(query_tag_list=query_tag_list, - history_node_list=history_node_list) - - score_dict = { - i[0]: i[1] - for i in related_node_score_list - } - related_node = [i[0] for i in related_node_score_list] - related_score = [i[1] for i in related_node_score_list] - - related_code, code_related_node = self.lcdh.search_by_multi_tag(related_node, lim=code_limit) - - related_node = [ - (node, self.nh.get_node_type(node), score_dict[node]) - for node in code_related_node - ] - - related_node.sort(key=lambda x: x[2], reverse=True) - - logger.info('related_node={}'.format(related_node)) - logger.info('related_code={}'.format(related_code)) - logger.info('num of code={}'.format(len(related_code))) - return related_code, related_node - - def refresh_history(self): - self.history_node_list = [] - - - - - - - - - - diff --git a/dev_opsgpt/codebase_handler/codedb_handler/local_codedb_handler.py b/dev_opsgpt/codebase_handler/codedb_handler/local_codedb_handler.py deleted file mode 100644 index e8fb54a..0000000 --- a/dev_opsgpt/codebase_handler/codedb_handler/local_codedb_handler.py +++ /dev/null @@ -1,55 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: local_codedb_handler.py -@time: 2023/10/23 下午5:05 -@desc: -''' -import pickle - - -class LocalCodeDBHandler: - def __init__(self, tagged_code: dict = {}, db_path: str = ''): - if db_path: - with open(db_path, 'rb') as f: - self.data = pickle.load(f) - else: - self.data = {} - for code, tag in tagged_code.items(): - self.data[code] = str(tag) - - def search_by_single_tag(self, tag, lim): - res = list() - for k, v in self.data.items(): - if tag in v and k not in res: - res.append(k) - - if len(res) > lim: - break - return res - - def search_by_multi_tag(self, tag_list, lim=3): - res = list() - res_related_node = [] - for tag in tag_list: - single_tag_res = self.search_by_single_tag(tag, lim) - for code in single_tag_res: - if code not in res: - res.append(code) - res_related_node.append(tag) - if len(res) >= lim: - break - - # reverse order so that most relevant one is close to the query - res = res[0:lim] - res.reverse() - - return res, res_related_node - - def save_db(self, save_path): - with open(save_path, 'wb') as f: - pickle.dump(self.data, f) - - def __len__(self): - return len(self.data) - diff --git a/dev_opsgpt/codebase_handler/networkx_handler/networkx_handler.py b/dev_opsgpt/codebase_handler/networkx_handler/networkx_handler.py deleted file mode 100644 index ffc16be..0000000 --- a/dev_opsgpt/codebase_handler/networkx_handler/networkx_handler.py +++ /dev/null @@ -1,129 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: networkx_handler.py -@time: 2023/10/23 下午5:02 -@desc: -''' - -import networkx as nx -from loguru import logger -import matplotlib.pyplot as plt -import pickle -from collections import defaultdict -import json - -QUERY_SCORE = 10 -HISTORY_SCORE = 5 -RATIO = 0.5 - - -class NetworkxHandler: - def __init__(self, graph_path: str = '', node_list: list = [], edge_list: list = []): - if graph_path: - self.graph_path = graph_path - with open(graph_path, 'r') as f: - self.G = nx.node_link_graph(json.load(f)) - else: - self.G = nx.DiGraph() - self.populate_graph(node_list, edge_list) - logger.debug( - 'number of nodes={}, number of edges={}'.format(self.G.number_of_nodes(), self.G.number_of_edges())) - - self.query_score = QUERY_SCORE - self.history_score = HISTORY_SCORE - self.ratio = RATIO - - def populate_graph(self, node_list, edge_list): - ''' - populate graph with node_list and edge_list - ''' - self.G.add_nodes_from(node_list) - for edge in edge_list: - self.G.add_edge(edge[0], edge[-1], relation=edge[1]) - - def draw_graph(self, save_path: str): - ''' - draw and save to save_path - ''' - sub = plt.subplot(111) - nx.draw(self.G, with_labels=True) - - plt.savefig(save_path) - - def search_node(self, query_tag_list: list, history_node_list: list = []): - ''' - search node by tag_list, search from history_tag neighbors first - > query_tag_list: tag from query - > history_node_list - ''' - node_list = set() - - # search from history_tag_list first, then all nodes - for tag in query_tag_list: - add = False - for history_node in history_node_list: - connect_node_list: list = self.G.adj[history_node] - connect_node_list.insert(0, history_node) - for connect_node in connect_node_list: - node_name_lim = len(connect_node) if '_' not in connect_node else connect_node.index('_') - node_name = connect_node[0:node_name_lim] - if tag.lower() in node_name.lower(): - node_list.add(connect_node) - add = True - if not add: - for node in self.G.nodes(): - if tag.lower() in node.lower(): - node_list.add(node) - return node_list - - def search_node_with_score(self, query_tag_list: list, history_node_list: list = []): - ''' - search node by tag_list, search from history_tag neighbors first - > query_tag_list: tag from query - > history_node_list - ''' - logger.info('query_tag_list={}, history_node_list={}'.format(query_tag_list, history_node_list)) - node_dict = defaultdict(lambda: 0) - - # loop over query_tag_list and add node: - for tag in query_tag_list: - for node in self.G.nodes: - if tag.lower() in node.lower(): - node_dict[node] += self.query_score - - # loop over history_node and add node score - for node in history_node_list: - node_dict[node] += self.history_score - - logger.info('temp_res={}'.format(node_dict)) - - # adj score broadcast - for node in node_dict: - adj_node_list = self.G.adj[node] - for adj_node in adj_node_list: - node_dict[node] += node_dict.get(adj_node, 0) * self.ratio - - # sort - node_list = [(node, node_score) for node, node_score in node_dict.items()] - node_list.sort(key=lambda x: x[1], reverse=True) - return node_list - - def save_graph(self, save_path: str): - to_save = nx.node_link_data(self.G) - with open(save_path, 'w') as f: - json.dump(to_save, f) - - def __len__(self): - return self.G.number_of_nodes() - - def get_node_type(self, node_name): - node_type = self.G.nodes[node_name]['type'] - return node_type - - def refresh_graph(self, ): - with open(self.graph_path, 'r') as f: - self.G = nx.node_link_graph(json.load(f)) - - - diff --git a/dev_opsgpt/codebase_handler/parser/java_paraser/__init__.py b/dev_opsgpt/codebase_handler/parser/java_paraser/__init__.py deleted file mode 100644 index ee1c9a5..0000000 --- a/dev_opsgpt/codebase_handler/parser/java_paraser/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: __init__.py.py -@time: 2023/10/23 下午5:01 -@desc: -''' \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/parser/java_paraser/java_crawler.py b/dev_opsgpt/codebase_handler/parser/java_paraser/java_crawler.py deleted file mode 100644 index 0681033..0000000 --- a/dev_opsgpt/codebase_handler/parser/java_paraser/java_crawler.py +++ /dev/null @@ -1,32 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: java_crawler.py -@time: 2023/10/23 下午5:02 -@desc: -''' - -import os -import glob -from loguru import logger - - -class JavaCrawler: - @staticmethod - def local_java_file_crawler(path: str): - ''' - read local java file in path - > path: path to crawl, must be absolute path like A/B/C - < dict of java code string - ''' - java_file_list = glob.glob('{path}{sep}**{sep}*.java'.format(path=path, sep=os.path.sep), recursive=True) - java_code_dict = {} - - logger.debug('number of file={}'.format(len(java_file_list))) - # logger.debug(java_file_list) - - for java_file in java_file_list: - with open(java_file) as f: - java_code = ''.join(f.readlines()) - java_code_dict[java_file] = java_code - return java_code_dict \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/parser/java_paraser/java_dedup.py b/dev_opsgpt/codebase_handler/parser/java_paraser/java_dedup.py deleted file mode 100644 index c8e88b2..0000000 --- a/dev_opsgpt/codebase_handler/parser/java_paraser/java_dedup.py +++ /dev/null @@ -1,15 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: java_dedup.py -@time: 2023/10/23 下午5:02 -@desc: -''' - - -class JavaDedup: - def __init__(self): - pass - - def dedup(self, java_code_dict): - return java_code_dict \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/parser/java_paraser/java_preprocess.py b/dev_opsgpt/codebase_handler/parser/java_paraser/java_preprocess.py deleted file mode 100644 index c71729f..0000000 --- a/dev_opsgpt/codebase_handler/parser/java_paraser/java_preprocess.py +++ /dev/null @@ -1,14 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: java_preprocess.py -@time: 2023/10/23 下午5:04 -@desc: -''' - -class JavaPreprocessor: - def __init__(self): - pass - - def preprocess(self, java_code_dict): - return java_code_dict \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/tagger/__init__.py b/dev_opsgpt/codebase_handler/tagger/__init__.py deleted file mode 100644 index 05f385f..0000000 --- a/dev_opsgpt/codebase_handler/tagger/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: __init__.py.py -@time: 2023/10/23 下午5:00 -@desc: -''' \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/tagger/tagger.py b/dev_opsgpt/codebase_handler/tagger/tagger.py deleted file mode 100644 index 943c013..0000000 --- a/dev_opsgpt/codebase_handler/tagger/tagger.py +++ /dev/null @@ -1,48 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: tagger.py -@time: 2023/10/23 下午5:01 -@desc: -''' -import re -from loguru import logger - - -class Tagger: - def __init__(self): - pass - - def generate_tag(self, parse_res_dict: dict): - ''' - generate tag from parse_res - ''' - res = {} - for java_code, parse_res in parse_res_dict.items(): - tag = {} - tag['pac_name'] = parse_res.get('pac_name') - tag['class_name'] = set(parse_res.get('class_name_list')) - tag['func_name'] = set() - - for _, func_name_list in parse_res.get('func_name_dict', {}).items(): - tag['func_name'].update(func_name_list) - - res[java_code] = tag - return res - - def generate_tag_query(self, query): - ''' - generate tag from query - ''' - # simple extract english - tag_list = re.findall(r'[a-zA-Z\_\.]+', query) - tag_list = list(set(tag_list)) - return tag_list - - -if __name__ == '__main__': - tagger = Tagger() - logger.debug(tagger.generate_tag_query('com.CheckHolder 有哪些函数')) - - - diff --git a/dev_opsgpt/codebase_handler/tagger/tuple_generation.py b/dev_opsgpt/codebase_handler/tagger/tuple_generation.py deleted file mode 100644 index fa33559..0000000 --- a/dev_opsgpt/codebase_handler/tagger/tuple_generation.py +++ /dev/null @@ -1,51 +0,0 @@ -# encoding: utf-8 -''' -@author: 温进 -@file: tuple_generation.py -@time: 2023/10/23 下午5:01 -@desc: -''' - - -def node_edge_update(parse_res_list: list, node_list: list = list(), edge_list: list = list()): - ''' - generate node and edge by parse_res - < node: list of string node - < edge: (node_st, relation, node_ed) - ''' - node_dict = {i: j for i, j in node_list} - - for single_parse_res in parse_res_list: - pac_name = single_parse_res['pac_name'] - - node_dict[pac_name] = {'type': 'package'} - - # class_name - for class_name in single_parse_res['class_name_list']: - node_dict[class_name] = {'type': 'class'} - edge_list.append((pac_name, 'contain', class_name)) - edge_list.append((class_name, 'inside', pac_name)) - - # func_name - for class_name, func_name_list in single_parse_res['func_name_dict'].items(): - node_list.append(class_name) - for func_name in func_name_list: - node_dict[func_name] = {'type': 'func'} - edge_list.append((class_name, 'contain', func_name)) - edge_list.append((func_name, 'inside', class_name)) - - # depend - for depend_pac_name in single_parse_res['import_pac_name_list']: - if depend_pac_name.endswith('*'): - depend_pac_name = depend_pac_name[0:-2] - - if depend_pac_name in node_dict: - continue - else: - node_dict[depend_pac_name] = {'type': 'unknown'} - edge_list.append((pac_name, 'depend', depend_pac_name)) - edge_list.append((depend_pac_name, 'beDepended', pac_name)) - - node_list = [(i, j) for i, j in node_dict.items()] - - return node_list, edge_list \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/codedb_handler/__init__.py b/dev_opsgpt/codechat/__init__.py similarity index 67% rename from dev_opsgpt/codebase_handler/codedb_handler/__init__.py rename to dev_opsgpt/codechat/__init__.py index 24ed759..35197bf 100644 --- a/dev_opsgpt/codebase_handler/codedb_handler/__init__.py +++ b/dev_opsgpt/codechat/__init__.py @@ -2,6 +2,6 @@ ''' @author: 温进 @file: __init__.py.py -@time: 2023/10/23 下午5:04 +@time: 2023/11/21 下午2:01 @desc: ''' \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/networkx_handler/__init__.py b/dev_opsgpt/codechat/code_analyzer/__init__.py similarity index 67% rename from dev_opsgpt/codebase_handler/networkx_handler/__init__.py rename to dev_opsgpt/codechat/code_analyzer/__init__.py index 05f385f..7704e55 100644 --- a/dev_opsgpt/codebase_handler/networkx_handler/__init__.py +++ b/dev_opsgpt/codechat/code_analyzer/__init__.py @@ -2,6 +2,6 @@ ''' @author: 温进 @file: __init__.py.py -@time: 2023/10/23 下午5:00 +@time: 2023/11/21 下午2:27 @desc: ''' \ No newline at end of file diff --git a/dev_opsgpt/codechat/code_analyzer/code_analyzer.py b/dev_opsgpt/codechat/code_analyzer/code_analyzer.py new file mode 100644 index 0000000..5375c61 --- /dev/null +++ b/dev_opsgpt/codechat/code_analyzer/code_analyzer.py @@ -0,0 +1,219 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: code_analyzer.py +@time: 2023/11/21 下午2:27 +@desc: +''' +import time +from loguru import logger + +from dev_opsgpt.codechat.code_analyzer.code_static_analysis import CodeStaticAnalysis +from dev_opsgpt.codechat.code_analyzer.code_intepreter import CodeIntepreter +from dev_opsgpt.codechat.code_analyzer.code_preprocess import CodePreprocessor +from dev_opsgpt.codechat.code_analyzer.code_dedup import CodeDedup + + +class CodeAnalyzer: + def __init__(self, language: str): + self.code_preprocessor = CodePreprocessor() + self.code_debup = CodeDedup() + self.code_interperter = CodeIntepreter() + self.code_static_analyzer = CodeStaticAnalysis(language=language) + + def analyze(self, code_dict: dict, do_interpret: bool = True): + ''' + analyze code + @param code_dict: {fp: code_text} + @param do_interpret: Whether to get analysis result + @return: + ''' + # preprocess and dedup + st = time.time() + code_dict = self.code_preprocessor.preprocess(code_dict) + code_dict = self.code_debup.dedup(code_dict) + logger.debug('preprocess and dedup rt={}'.format(time.time() - st)) + + # static analysis + st = time.time() + static_analysis_res = self.code_static_analyzer.analyze(code_dict) + logger.debug('static analysis rt={}'.format(time.time() - st)) + + # interpretation + if do_interpret: + logger.info('start interpret code') + st = time.time() + code_list = list(code_dict.values()) + interpretation = self.code_interperter.get_intepretation_batch(code_list) + logger.debug('interpret rt={}'.format(time.time() - st)) + else: + interpretation = {i: '' for i in code_dict.values()} + + return static_analysis_res, interpretation + + +if __name__ == '__main__': + engine = 'openai' + language = 'java' + code_dict = {'1': '''package com.theokanning.openai.client; +import com.theokanning.openai.DeleteResult; +import com.theokanning.openai.OpenAiResponse; +import com.theokanning.openai.audio.TranscriptionResult; +import com.theokanning.openai.audio.TranslationResult; +import com.theokanning.openai.billing.BillingUsage; +import com.theokanning.openai.billing.Subscription; +import com.theokanning.openai.completion.CompletionRequest; +import com.theokanning.openai.completion.CompletionResult; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatCompletionResult; +import com.theokanning.openai.edit.EditRequest; +import com.theokanning.openai.edit.EditResult; +import com.theokanning.openai.embedding.EmbeddingRequest; +import com.theokanning.openai.embedding.EmbeddingResult; +import com.theokanning.openai.engine.Engine; +import com.theokanning.openai.file.File; +import com.theokanning.openai.fine_tuning.FineTuningEvent; +import com.theokanning.openai.fine_tuning.FineTuningJob; +import com.theokanning.openai.fine_tuning.FineTuningJobRequest; +import com.theokanning.openai.finetune.FineTuneEvent; +import com.theokanning.openai.finetune.FineTuneRequest; +import com.theokanning.openai.finetune.FineTuneResult; +import com.theokanning.openai.image.CreateImageRequest; +import com.theokanning.openai.image.ImageResult; +import com.theokanning.openai.model.Model; +import com.theokanning.openai.moderation.ModerationRequest; +import com.theokanning.openai.moderation.ModerationResult; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.*; +import java.time.LocalDate; +public interface OpenAiApi { + @GET("v1/models") + Single> listModels(); + @GET("/v1/models/{model_id}") + Single getModel(@Path("model_id") String modelId); + @POST("/v1/completions") + Single createCompletion(@Body CompletionRequest request); + @Streaming + @POST("/v1/completions") + Call createCompletionStream(@Body CompletionRequest request); + @POST("/v1/chat/completions") + Single createChatCompletion(@Body ChatCompletionRequest request); + @Streaming + @POST("/v1/chat/completions") + Call createChatCompletionStream(@Body ChatCompletionRequest request); + @Deprecated + @POST("/v1/engines/{engine_id}/completions") + Single createCompletion(@Path("engine_id") String engineId, @Body CompletionRequest request); + @POST("/v1/edits") + Single createEdit(@Body EditRequest request); + @Deprecated + @POST("/v1/engines/{engine_id}/edits") + Single createEdit(@Path("engine_id") String engineId, @Body EditRequest request); + @POST("/v1/embeddings") + Single createEmbeddings(@Body EmbeddingRequest request); + @Deprecated + @POST("/v1/engines/{engine_id}/embeddings") + Single createEmbeddings(@Path("engine_id") String engineId, @Body EmbeddingRequest request); + @GET("/v1/files") + Single> listFiles(); + @Multipart + @POST("/v1/files") + Single uploadFile(@Part("purpose") RequestBody purpose, @Part MultipartBody.Part file); + @DELETE("/v1/files/{file_id}") + Single deleteFile(@Path("file_id") String fileId); + @GET("/v1/files/{file_id}") + Single retrieveFile(@Path("file_id") String fileId); + @Streaming + @GET("/v1/files/{file_id}/content") + Single retrieveFileContent(@Path("file_id") String fileId); + @POST("/v1/fine_tuning/jobs") + Single createFineTuningJob(@Body FineTuningJobRequest request); + @GET("/v1/fine_tuning/jobs") + Single> listFineTuningJobs(); + @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}") + Single retrieveFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + @POST("/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel") + Single cancelFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}/events") + Single> listFineTuningJobEvents(@Path("fine_tuning_job_id") String fineTuningJobId); + @Deprecated + @POST("/v1/fine-tunes") + Single createFineTune(@Body FineTuneRequest request); + @POST("/v1/completions") + Single createFineTuneCompletion(@Body CompletionRequest request); + @Deprecated + @GET("/v1/fine-tunes") + Single> listFineTunes(); + @Deprecated + @GET("/v1/fine-tunes/{fine_tune_id}") + Single retrieveFineTune(@Path("fine_tune_id") String fineTuneId); + @Deprecated + @POST("/v1/fine-tunes/{fine_tune_id}/cancel") + Single cancelFineTune(@Path("fine_tune_id") String fineTuneId); + @Deprecated + @GET("/v1/fine-tunes/{fine_tune_id}/events") + Single> listFineTuneEvents(@Path("fine_tune_id") String fineTuneId); + @DELETE("/v1/models/{fine_tune_id}") + Single deleteFineTune(@Path("fine_tune_id") String fineTuneId); + @POST("/v1/images/generations") + Single createImage(@Body CreateImageRequest request); + @POST("/v1/images/edits") + Single createImageEdit(@Body RequestBody requestBody); + @POST("/v1/images/variations") + Single createImageVariation(@Body RequestBody requestBody); + @POST("/v1/audio/transcriptions") + Single createTranscription(@Body RequestBody requestBody); + @POST("/v1/audio/translations") + Single createTranslation(@Body RequestBody requestBody); + @POST("/v1/moderations") + Single createModeration(@Body ModerationRequest request); + @Deprecated + @GET("v1/engines") + Single> getEngines(); + @Deprecated + @GET("/v1/engines/{engine_id}") + Single getEngine(@Path("engine_id") String engineId); + /** + * Account information inquiry: It contains total amount (in US dollars) and other information. + * + * @return + */ + @Deprecated + @GET("v1/dashboard/billing/subscription") + Single subscription(); + /** + * Account call interface consumption amount inquiry. + * totalUsage = Total amount used by the account (in US cents). + * + * @param starDate + * @param endDate + * @return Consumption amount information. + */ + @Deprecated + @GET("v1/dashboard/billing/usage") + Single billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate); +}''', '2': ''' +package com.theokanning.openai; + +/** + * OkHttp Interceptor that adds an authorization token header + * + * @deprecated Use {@link com.theokanning.openai.client.AuthenticationInterceptor} + */ +@Deprecated +public class AuthenticationInterceptor extends com.theokanning.openai.client.AuthenticationInterceptor { + + AuthenticationInterceptor(String token) { + super(token); + } + +} +'''} + + ca = CodeAnalyzer(engine, language) + res = ca.analyze(code_dict) + logger.debug(res) diff --git a/dev_opsgpt/codechat/code_analyzer/code_dedup.py b/dev_opsgpt/codechat/code_analyzer/code_dedup.py new file mode 100644 index 0000000..071bd46 --- /dev/null +++ b/dev_opsgpt/codechat/code_analyzer/code_dedup.py @@ -0,0 +1,31 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: code_dedup.py +@time: 2023/11/21 下午2:27 +@desc: +''' +# encoding: utf-8 +''' +@author: 温进 +@file: java_dedup.py +@time: 2023/10/23 下午5:02 +@desc: +''' + + +class CodeDedup: + def __init__(self): + pass + + def dedup(self, code_dict): + code_dict = self.exact_dedup(code_dict) + return code_dict + + def exact_dedup(self, code_dict): + res = {} + for fp, code_text in code_dict.items(): + if code_text not in res.values(): + res[fp] = code_text + + return res \ No newline at end of file diff --git a/dev_opsgpt/codechat/code_analyzer/code_intepreter.py b/dev_opsgpt/codechat/code_analyzer/code_intepreter.py new file mode 100644 index 0000000..673bc44 --- /dev/null +++ b/dev_opsgpt/codechat/code_analyzer/code_intepreter.py @@ -0,0 +1,229 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: code_intepreter.py +@time: 2023/11/22 上午11:57 +@desc: +''' +from loguru import logger +from langchain.schema import ( + HumanMessage, +) + +from configs.model_config import CODE_INTERPERT_TEMPLATE + +from dev_opsgpt.llm_models.openai_model import getChatModel + + +class CodeIntepreter: + def __init__(self): + pass + + def get_intepretation(self, code_list): + ''' + get intepretion of code + @param code_list: + @return: + ''' + chat_model = getChatModel() + + res = {} + for code in code_list: + message = CODE_INTERPERT_TEMPLATE.format(code=code) + message = [HumanMessage(content=message)] + chat_res = chat_model.predict_messages(message) + content = chat_res.content + res[code] = content + return res + + def get_intepretation_batch(self, code_list): + ''' + get intepretion of code + @param code_list: + @return: + ''' + chat_model = getChatModel() + + res = {} + messages = [] + for code in code_list: + message = CODE_INTERPERT_TEMPLATE.format(code=code) + messages.append(message) + + chat_ress = chat_model.batch(messages) + for chat_res, code in zip(chat_ress, code_list): + res[code] = chat_res.content + return res + + + + +if __name__ == '__main__': + engine = 'openai' + code_list = ['''package com.theokanning.openai.client; +import com.theokanning.openai.DeleteResult; +import com.theokanning.openai.OpenAiResponse; +import com.theokanning.openai.audio.TranscriptionResult; +import com.theokanning.openai.audio.TranslationResult; +import com.theokanning.openai.billing.BillingUsage; +import com.theokanning.openai.billing.Subscription; +import com.theokanning.openai.completion.CompletionRequest; +import com.theokanning.openai.completion.CompletionResult; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatCompletionResult; +import com.theokanning.openai.edit.EditRequest; +import com.theokanning.openai.edit.EditResult; +import com.theokanning.openai.embedding.EmbeddingRequest; +import com.theokanning.openai.embedding.EmbeddingResult; +import com.theokanning.openai.engine.Engine; +import com.theokanning.openai.file.File; +import com.theokanning.openai.fine_tuning.FineTuningEvent; +import com.theokanning.openai.fine_tuning.FineTuningJob; +import com.theokanning.openai.fine_tuning.FineTuningJobRequest; +import com.theokanning.openai.finetune.FineTuneEvent; +import com.theokanning.openai.finetune.FineTuneRequest; +import com.theokanning.openai.finetune.FineTuneResult; +import com.theokanning.openai.image.CreateImageRequest; +import com.theokanning.openai.image.ImageResult; +import com.theokanning.openai.model.Model; +import com.theokanning.openai.moderation.ModerationRequest; +import com.theokanning.openai.moderation.ModerationResult; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.*; +import java.time.LocalDate; +public interface OpenAiApi { + @GET("v1/models") + Single> listModels(); + @GET("/v1/models/{model_id}") + Single getModel(@Path("model_id") String modelId); + @POST("/v1/completions") + Single createCompletion(@Body CompletionRequest request); + @Streaming + @POST("/v1/completions") + Call createCompletionStream(@Body CompletionRequest request); + @POST("/v1/chat/completions") + Single createChatCompletion(@Body ChatCompletionRequest request); + @Streaming + @POST("/v1/chat/completions") + Call createChatCompletionStream(@Body ChatCompletionRequest request); + @Deprecated + @POST("/v1/engines/{engine_id}/completions") + Single createCompletion(@Path("engine_id") String engineId, @Body CompletionRequest request); + @POST("/v1/edits") + Single createEdit(@Body EditRequest request); + @Deprecated + @POST("/v1/engines/{engine_id}/edits") + Single createEdit(@Path("engine_id") String engineId, @Body EditRequest request); + @POST("/v1/embeddings") + Single createEmbeddings(@Body EmbeddingRequest request); + @Deprecated + @POST("/v1/engines/{engine_id}/embeddings") + Single createEmbeddings(@Path("engine_id") String engineId, @Body EmbeddingRequest request); + @GET("/v1/files") + Single> listFiles(); + @Multipart + @POST("/v1/files") + Single uploadFile(@Part("purpose") RequestBody purpose, @Part MultipartBody.Part file); + @DELETE("/v1/files/{file_id}") + Single deleteFile(@Path("file_id") String fileId); + @GET("/v1/files/{file_id}") + Single retrieveFile(@Path("file_id") String fileId); + @Streaming + @GET("/v1/files/{file_id}/content") + Single retrieveFileContent(@Path("file_id") String fileId); + @POST("/v1/fine_tuning/jobs") + Single createFineTuningJob(@Body FineTuningJobRequest request); + @GET("/v1/fine_tuning/jobs") + Single> listFineTuningJobs(); + @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}") + Single retrieveFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + @POST("/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel") + Single cancelFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}/events") + Single> listFineTuningJobEvents(@Path("fine_tuning_job_id") String fineTuningJobId); + @Deprecated + @POST("/v1/fine-tunes") + Single createFineTune(@Body FineTuneRequest request); + @POST("/v1/completions") + Single createFineTuneCompletion(@Body CompletionRequest request); + @Deprecated + @GET("/v1/fine-tunes") + Single> listFineTunes(); + @Deprecated + @GET("/v1/fine-tunes/{fine_tune_id}") + Single retrieveFineTune(@Path("fine_tune_id") String fineTuneId); + @Deprecated + @POST("/v1/fine-tunes/{fine_tune_id}/cancel") + Single cancelFineTune(@Path("fine_tune_id") String fineTuneId); + @Deprecated + @GET("/v1/fine-tunes/{fine_tune_id}/events") + Single> listFineTuneEvents(@Path("fine_tune_id") String fineTuneId); + @DELETE("/v1/models/{fine_tune_id}") + Single deleteFineTune(@Path("fine_tune_id") String fineTuneId); + @POST("/v1/images/generations") + Single createImage(@Body CreateImageRequest request); + @POST("/v1/images/edits") + Single createImageEdit(@Body RequestBody requestBody); + @POST("/v1/images/variations") + Single createImageVariation(@Body RequestBody requestBody); + @POST("/v1/audio/transcriptions") + Single createTranscription(@Body RequestBody requestBody); + @POST("/v1/audio/translations") + Single createTranslation(@Body RequestBody requestBody); + @POST("/v1/moderations") + Single createModeration(@Body ModerationRequest request); + @Deprecated + @GET("v1/engines") + Single> getEngines(); + @Deprecated + @GET("/v1/engines/{engine_id}") + Single getEngine(@Path("engine_id") String engineId); + /** + * Account information inquiry: It contains total amount (in US dollars) and other information. + * + * @return + */ + @Deprecated + @GET("v1/dashboard/billing/subscription") + Single subscription(); + /** + * Account call interface consumption amount inquiry. + * totalUsage = Total amount used by the account (in US cents). + * + * @param starDate + * @param endDate + * @return Consumption amount information. + */ + @Deprecated + @GET("v1/dashboard/billing/usage") + Single billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate); +}''', ''' +package com.theokanning.openai; + +/** + * OkHttp Interceptor that adds an authorization token header + * + * @deprecated Use {@link com.theokanning.openai.client.AuthenticationInterceptor} + */ +@Deprecated +public class AuthenticationInterceptor extends com.theokanning.openai.client.AuthenticationInterceptor { + + AuthenticationInterceptor(String token) { + super(token); + } + +} +'''] + + ci = CodeIntepreter(engine) + res = ci.get_intepretation_batch(code_list) + logger.debug(res) + + + + + diff --git a/dev_opsgpt/codechat/code_analyzer/code_preprocess.py b/dev_opsgpt/codechat/code_analyzer/code_preprocess.py new file mode 100644 index 0000000..50324ba --- /dev/null +++ b/dev_opsgpt/codechat/code_analyzer/code_preprocess.py @@ -0,0 +1,14 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: code_preprocess.py +@time: 2023/11/21 下午2:28 +@desc: +''' + +class CodePreprocessor: + def __init__(self): + pass + + def preprocess(self, code_dict): + return code_dict \ No newline at end of file diff --git a/dev_opsgpt/codechat/code_analyzer/code_static_analysis.py b/dev_opsgpt/codechat/code_analyzer/code_static_analysis.py new file mode 100644 index 0000000..b943795 --- /dev/null +++ b/dev_opsgpt/codechat/code_analyzer/code_static_analysis.py @@ -0,0 +1,26 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: code_static_analysis.py +@time: 2023/11/21 下午2:28 +@desc: +''' +from dev_opsgpt.codechat.code_analyzer.language_static_analysis import * + +class CodeStaticAnalysis: + def __init__(self, language): + self.language = language + + def analyze(self, code_dict): + ''' + analyze code + @param code_list: + @return: + ''' + if self.language == 'java': + analyzer = JavaStaticAnalysis() + else: + raise ValueError('language should be one of [java]') + + analyze_res = analyzer.analyze(code_dict) + return analyze_res diff --git a/dev_opsgpt/codechat/code_analyzer/language_static_analysis/__init__.py b/dev_opsgpt/codechat/code_analyzer/language_static_analysis/__init__.py new file mode 100644 index 0000000..c99e049 --- /dev/null +++ b/dev_opsgpt/codechat/code_analyzer/language_static_analysis/__init__.py @@ -0,0 +1,14 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: __init__.py.py +@time: 2023/11/21 下午4:24 +@desc: +''' + +from .java_static_analysis import JavaStaticAnalysis + + +__all__ = [ + 'JavaStaticAnalysis' + ] \ No newline at end of file diff --git a/dev_opsgpt/codebase_handler/parser/java_paraser/java_parser.py b/dev_opsgpt/codechat/code_analyzer/language_static_analysis/java_static_analysis.py similarity index 75% rename from dev_opsgpt/codebase_handler/parser/java_paraser/java_parser.py rename to dev_opsgpt/codechat/code_analyzer/language_static_analysis/java_static_analysis.py index 23826d5..475da2d 100644 --- a/dev_opsgpt/codebase_handler/parser/java_paraser/java_parser.py +++ b/dev_opsgpt/codechat/code_analyzer/language_static_analysis/java_static_analysis.py @@ -1,26 +1,24 @@ # encoding: utf-8 ''' @author: 温进 -@file: java_parser.py -@time: 2023/10/23 下午5:03 +@file: java_static_analysis.py +@time: 2023/11/21 下午4:25 @desc: ''' -import json -import javalang -import glob import os from loguru import logger +import javalang -class JavaParser: +class JavaStaticAnalysis: def __init__(self): pass - def parse(self, java_code_list): + def analyze(self, java_code_dict): ''' parse java code and extract entity ''' - tree_dict = self.preparse(java_code_list) + tree_dict = self.preparse(java_code_dict) res = self.multi_java_code_parse(tree_dict) return res @@ -38,15 +36,15 @@ class JavaParser: continue if tree.package is not None: - tree_dict[java_code] = tree + tree_dict[fp] = {'code': java_code, 'tree': tree} logger.info('success parse {} files'.format(len(tree_dict))) return tree_dict - def single_java_code_parse(self, tree): + def single_java_code_parse(self, tree, fp): ''' parse single code file > tree: javalang parse result - < {pac_name: '', class_name_list: [], func_name_list: [], import_pac_name_list: []]} + < {pac_name: '', class_name_list: [], func_name_dict: {}, import_pac_name_list: []]} ''' import_pac_name_list = [] @@ -57,25 +55,26 @@ class JavaParser: import_pac_name = import_pac.path import_pac_name_list.append(import_pac_name) - pac_name = tree.package.name + fp_last = fp.split(os.path.sep)[-1] + pac_name = tree.package.name + '#' + fp_last class_name_list = [] func_name_dict = {} for node in tree.types: if type(node) in (javalang.tree.ClassDeclaration, javalang.tree.InterfaceDeclaration): - class_name = pac_name + '.' + node.name + class_name = pac_name + '#' + node.name class_name_list.append(class_name) for node_inner in node.body: if type(node_inner) is javalang.tree.MethodDeclaration: - func_name = class_name + '.' + node_inner.name + func_name = class_name + '#' + node_inner.name # add params name to func_name params_list = node_inner.parameters for params in params_list: params_name = params.type.name - func_name = func_name + '_' + params_name + func_name = func_name + '-' + params_name if class_name not in func_name_dict: func_name_dict[class_name] = [] @@ -97,11 +96,21 @@ class JavaParser: < parse_result_dict ''' res_dict = {} - for java_code, tree in tree_dict.items(): + for fp, value in tree_dict.items(): + java_code = value['code'] + tree = value['tree'] try: - res_dict[java_code] = self.single_java_code_parse(tree) + res_dict[java_code] = self.single_java_code_parse(tree, fp) except Exception as e: logger.debug(java_code) raise ImportError - return res_dict \ No newline at end of file + return res_dict + + + + + + + + diff --git a/dev_opsgpt/codechat/code_crawler/__init__.py b/dev_opsgpt/codechat/code_crawler/__init__.py new file mode 100644 index 0000000..6ddb8ef --- /dev/null +++ b/dev_opsgpt/codechat/code_crawler/__init__.py @@ -0,0 +1,15 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: __init__.py.py +@time: 2023/11/21 下午2:02 +@desc: +''' +from .zip_crawler import ZipCrawler +from .dir_crawler import DirCrawler + + +__all__ = [ + 'ZipCrawler', + 'DirCrawler' + ] diff --git a/dev_opsgpt/codechat/code_crawler/dir_crawler.py b/dev_opsgpt/codechat/code_crawler/dir_crawler.py new file mode 100644 index 0000000..2c27b84 --- /dev/null +++ b/dev_opsgpt/codechat/code_crawler/dir_crawler.py @@ -0,0 +1,39 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: dir_crawler.py +@time: 2023/11/22 下午2:54 +@desc: +''' +from loguru import logger +import os +import glob + + +class DirCrawler: + @staticmethod + def crawl(path: str, suffix: str): + ''' + read local java file in path + > path: path to crawl, must be absolute path like A/B/C + < dict of java code string + ''' + java_file_list = glob.glob('{path}{sep}**{sep}*.{suffix}'.format(path=path, sep=os.path.sep, suffix=suffix), + recursive=True) + java_code_dict = {} + + logger.info(path) + logger.info('number of file={}'.format(len(java_file_list))) + logger.info(java_file_list) + + for java_file in java_file_list: + with open(java_file) as f: + java_code = ''.join(f.readlines()) + java_code_dict[java_file] = java_code + return java_code_dict + + +if __name__ == '__main__': + path = '/Users/bingxu/Desktop/工作/大模型/chatbot/test_code_repo/middleware-alipay-starters-parent' + suffix = 'java' + DirCrawler.crawl(path, suffix) \ No newline at end of file diff --git a/dev_opsgpt/codechat/code_crawler/zip_crawler.py b/dev_opsgpt/codechat/code_crawler/zip_crawler.py new file mode 100644 index 0000000..e5ed23a --- /dev/null +++ b/dev_opsgpt/codechat/code_crawler/zip_crawler.py @@ -0,0 +1,31 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: zip_crawler.py +@time: 2023/11/21 下午2:02 +@desc: +''' +from loguru import logger + +import zipfile +from dev_opsgpt.codechat.code_crawler.dir_crawler import DirCrawler + + +class ZipCrawler: + @staticmethod + def crawl(zip_file, output_path, suffix): + ''' + unzip to output_path + @param zip_file: + @param output_path: + @return: + ''' + logger.info(f'output_path={output_path}') + print(f'output_path={output_path}') + with zipfile.ZipFile(zip_file, 'r') as z: + z.extractall(output_path) + + code_dict = DirCrawler.crawl(output_path, suffix) + return code_dict + + diff --git a/dev_opsgpt/codebase_handler/__init__.py b/dev_opsgpt/codechat/code_search/__init__.py similarity index 67% rename from dev_opsgpt/codebase_handler/__init__.py rename to dev_opsgpt/codechat/code_search/__init__.py index ab840cf..c9e9dbe 100644 --- a/dev_opsgpt/codebase_handler/__init__.py +++ b/dev_opsgpt/codechat/code_search/__init__.py @@ -2,6 +2,6 @@ ''' @author: 温进 @file: __init__.py.py -@time: 2023/10/23 下午4:57 +@time: 2023/11/21 下午2:35 @desc: ''' \ No newline at end of file diff --git a/dev_opsgpt/codechat/code_search/code_search.py b/dev_opsgpt/codechat/code_search/code_search.py new file mode 100644 index 0000000..9a4c7ed --- /dev/null +++ b/dev_opsgpt/codechat/code_search/code_search.py @@ -0,0 +1,179 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: code_search.py +@time: 2023/11/21 下午2:35 +@desc: +''' +import time +from loguru import logger +from collections import defaultdict + +from dev_opsgpt.db_handler.graph_db_handler.nebula_handler import NebulaHandler +from dev_opsgpt.db_handler.vector_db_handler.chroma_handler import ChromaHandler + +from dev_opsgpt.codechat.code_search.cypher_generator import CypherGenerator +from dev_opsgpt.codechat.code_search.tagger import Tagger +from dev_opsgpt.embeddings.get_embedding import get_embedding + +# search_by_tag +VERTEX_SCORE = 10 +HISTORY_VERTEX_SCORE = 5 +VERTEX_MERGE_RATIO = 0.5 + +# search_by_description +MAX_DISTANCE = 0.5 + + +class CodeSearch: + def __init__(self, nh: NebulaHandler, ch: ChromaHandler, limit: int = 3): + ''' + init + @param nh: NebulaHandler + @param ch: ChromaHandler + @param limit: limit of result + ''' + self.nh = nh + self.ch = ch + self.limit = limit + + def search_by_tag(self, query: str): + ''' + search_code_res by tag + @param query: str + @return: + ''' + tagger = Tagger() + tag_list = tagger.generate_tag_query(query) + logger.info(f'query tag={tag_list}') + + # get all verticex + vertex_list = self.nh.get_vertices().get('v', []) + vertex_vid_list = [i.as_node().get_id().as_string() for i in vertex_list] + logger.debug(vertex_vid_list) + + # update score + vertex_score_dict = defaultdict(lambda: 0) + for vid in vertex_vid_list: + for tag in tag_list: + if tag in vid: + vertex_score_dict[vid] += VERTEX_SCORE + + # merge depend adj score + vertex_score_dict_final = {} + for vertex in vertex_score_dict: + cypher = f'''MATCH (v1)-[e]-(v2) where id(v1) == "{vertex}" RETURN v2''' + cypher_res = self.nh.execute_cypher(cypher, self.nh.space_name) + cypher_res_dict = self.nh.result_to_dict(cypher_res) + + adj_vertex_list = [i.as_node().get_id().as_string() for i in cypher_res_dict.get('v2', [])] + + score = vertex_score_dict.get(vertex, 0) + for adj_vertex in adj_vertex_list: + score += vertex_score_dict.get(adj_vertex, 0) * VERTEX_MERGE_RATIO + + if score > 0: + vertex_score_dict_final[vertex] = score + + # get most prominent package tag + package_score_dict = defaultdict(lambda: 0) + for vertex, score in vertex_score_dict.items(): + package = '#'.join(vertex.split('#')[0:2]) + package_score_dict[package] += score + + # get respective code + res = [] + package_score_tuple = list(package_score_dict.items()) + package_score_tuple.sort(key=lambda x: x[1], reverse=True) + + ids = [i[0] for i in package_score_tuple] + chroma_res = self.ch.get(ids=ids, include=['metadatas']) + for vertex, score in package_score_tuple: + index = chroma_res['result']['ids'].index(vertex) + code_text = chroma_res['result']['metadatas'][index]['code_text'] + res.append({ + "vertex": vertex, + "code_text": code_text} + ) + if len(res) >= self.limit: + break + return res + + def search_by_desciption(self, query: str, engine: str): + ''' + search by perform sim search + @param query: + @return: + ''' + query = query.replace(',', ',') + query_emb = get_embedding(engine=engine, text_list=[query]) + query_emb = query_emb[query] + + query_embeddings = [query_emb] + query_result = self.ch.query(query_embeddings=query_embeddings, n_results=self.limit, + include=['metadatas', 'distances']) + logger.debug(query_result) + + res = [] + for idx, distance in enumerate(query_result['result']['distances'][0]): + if distance < MAX_DISTANCE: + vertex = query_result['result']['ids'][0][idx] + code_text = query_result['result']['metadatas'][0][idx]['code_text'] + res.append({ + "vertex": vertex, + "code_text": code_text + }) + + return res + + def search_by_cypher(self, query: str): + ''' + search by generating cypher + @param query: + @param engine: + @return: + ''' + cg = CypherGenerator() + cypher = cg.get_cypher(query) + + if not cypher: + return None + + cypher_res = self.nh.execute_cypher(cypher, self.nh.space_name) + logger.info(f'cypher execution result={cypher_res}') + if not cypher_res.is_succeeded(): + return { + 'cypher': '', + 'cypher_res': '' + } + + res = { + 'cypher': cypher, + 'cypher_res': cypher_res + } + + return res + + +if __name__ == '__main__': + from configs.server_config import NEBULA_HOST, NEBULA_PORT, NEBULA_USER, NEBULA_PASSWORD, NEBULA_STORAGED_PORT + from configs.server_config import CHROMA_PERSISTENT_PATH + + codebase_name = 'testing' + + nh = NebulaHandler(host=NEBULA_HOST, port=NEBULA_PORT, username=NEBULA_USER, + password=NEBULA_PASSWORD, space_name=codebase_name) + nh.add_host(NEBULA_HOST, NEBULA_STORAGED_PORT) + time.sleep(0.5) + + ch = ChromaHandler(path=CHROMA_PERSISTENT_PATH, collection_name=codebase_name) + + cs = CodeSearch(nh, ch) + # res = cs.search_by_tag(tag_list=['createFineTuneCompletion', 'OpenAiApi']) + # logger.debug(res) + + # res = cs.search_by_cypher('代码中一共有多少个类', 'openai') + # logger.debug(res) + + res = cs.search_by_desciption('使用不同的HTTP请求类型(GET、POST、DELETE等)来执行不同的操作', 'openai') + logger.debug(res) diff --git a/dev_opsgpt/codechat/code_search/cypher_generator.py b/dev_opsgpt/codechat/code_search/cypher_generator.py new file mode 100644 index 0000000..16f52e4 --- /dev/null +++ b/dev_opsgpt/codechat/code_search/cypher_generator.py @@ -0,0 +1,63 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: cypher_generator.py +@time: 2023/11/24 上午10:17 +@desc: +''' +from loguru import logger + +from dev_opsgpt.llm_models.openai_model import getChatModel +from dev_opsgpt.utils.postprocess import replace_lt_gt +from langchain.schema import ( + HumanMessage, +) +from langchain.chains.graph_qa.prompts import NGQL_GENERATION_PROMPT + + +schema = ''' +Node properties: [{'tag': 'package', 'properties': []}, {'tag': 'class', 'properties': []}, {'tag': 'method', 'properties': []}] +Edge properties: [{'edge': 'contain', 'properties': []}, {'edge': 'depend', 'properties': []}] +Relationships: ['(:package)-[:contain]->(:class)', '(:class)-[:contain]->(:method)', '(:package)-[:contain]->(:package)'] +''' + + +class CypherGenerator: + def __init__(self): + self.model = getChatModel() + + def get_cypher(self, query: str): + ''' + get cypher from query + @param query: + @return: + ''' + content = NGQL_GENERATION_PROMPT.format(schema=schema, question=query) + + ans = '' + message = [HumanMessage(content=content)] + chat_res = self.model.predict_messages(message) + ans = chat_res.content + + ans = replace_lt_gt(ans) + + ans = self.post_process(ans) + return ans + + def post_process(self, cypher_res: str): + ''' + 判断是否为正确的 cypher + @param cypher_res: + @return: + ''' + if '(' not in cypher_res or ')' not in cypher_res: + return '' + + return cypher_res + + +if __name__ == '__main__': + query = '代码中一共有多少个类' + cg = CypherGenerator(engine='openai') + + cg.get_cypher(query) diff --git a/dev_opsgpt/codechat/code_search/tagger.py b/dev_opsgpt/codechat/code_search/tagger.py new file mode 100644 index 0000000..f7efa72 --- /dev/null +++ b/dev_opsgpt/codechat/code_search/tagger.py @@ -0,0 +1,23 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: tagger.py +@time: 2023/11/24 下午1:32 +@desc: +''' +import re +from loguru import logger + + +class Tagger: + def __init__(self): + pass + + def generate_tag_query(self, query): + ''' + generate tag from query + ''' + # simple extract english + tag_list = re.findall(r'[a-zA-Z\_\.]+', query) + tag_list = list(set(tag_list)) + return tag_list diff --git a/dev_opsgpt/codebase_handler/parser/__init__.py b/dev_opsgpt/codechat/codebase_handler/__init__.py similarity index 67% rename from dev_opsgpt/codebase_handler/parser/__init__.py rename to dev_opsgpt/codechat/codebase_handler/__init__.py index 05f385f..bcd2f89 100644 --- a/dev_opsgpt/codebase_handler/parser/__init__.py +++ b/dev_opsgpt/codechat/codebase_handler/__init__.py @@ -2,6 +2,6 @@ ''' @author: 温进 @file: __init__.py.py -@time: 2023/10/23 下午5:00 +@time: 2023/11/21 下午2:07 @desc: ''' \ No newline at end of file diff --git a/dev_opsgpt/codechat/codebase_handler/code_importer.py b/dev_opsgpt/codechat/codebase_handler/code_importer.py new file mode 100644 index 0000000..bf50b28 --- /dev/null +++ b/dev_opsgpt/codechat/codebase_handler/code_importer.py @@ -0,0 +1,175 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: codebase_handler.py +@time: 2023/11/21 下午2:07 +@desc: +''' +import time +from loguru import logger + +from configs.server_config import NEBULA_HOST, NEBULA_PORT, NEBULA_USER, NEBULA_PASSWORD, NEBULA_STORAGED_PORT +from configs.server_config import CHROMA_PERSISTENT_PATH +from dev_opsgpt.db_handler.graph_db_handler.nebula_handler import NebulaHandler +from dev_opsgpt.db_handler.vector_db_handler.chroma_handler import ChromaHandler +from dev_opsgpt.embeddings.get_embedding import get_embedding + + +class CodeImporter: + def __init__(self, codebase_name: str, engine: str, nh: NebulaHandler, ch: ChromaHandler): + self.codebase_name = codebase_name + self.engine = engine + self.nh = nh + self.ch = ch + + def import_code(self, static_analysis_res: dict, interpretation: dict, do_interpret: bool = True): + ''' + import code to nebula and chroma + @return: + ''' + self.analysis_res_to_graph(static_analysis_res) + self.interpretation_to_db(static_analysis_res, interpretation, do_interpret) + + def analysis_res_to_graph(self, static_analysis_res): + ''' + transform static_analysis_res to tuple + @param static_analysis_res: + @return: + ''' + vertex_value_dict = { + 'package':{ + 'properties_name': [], + 'values': {} + }, + 'class': { + 'properties_name': [], + 'values': {} + }, + 'method': { + 'properties_name': [], + 'values': {} + }, + } + + edge_value_dict = { + 'contain': { + 'properties_name': [], + 'values': {} + }, + 'depend': { + 'properties_name': [], + 'values': {} + } + } + + for _, structure in static_analysis_res.items(): + pac_name = structure['pac_name'] + vertex_value_dict['package']['values'][pac_name] = [] + + for class_name in structure['class_name_list']: + vertex_value_dict['class']['values'][class_name] = [] + + edge_value_dict['contain']['values'][(pac_name, class_name)] = [] + + for func_name in structure['func_name_dict'].get(class_name, []): + vertex_value_dict['method']['values'][func_name] = [] + + edge_value_dict['contain']['values'][(class_name, func_name)] = [] + + for depend_pac_name in structure['import_pac_name_list']: + vertex_value_dict['package']['values'][depend_pac_name] = [] + + edge_value_dict['depend']['values'][(pac_name, depend_pac_name)] = [] + + # create vertex + for tag_name, value_dict in vertex_value_dict.items(): + res = self.nh.insert_vertex(tag_name, value_dict) + logger.debug(res.error_msg()) + + # create edge + for tag_name, value_dict in edge_value_dict.items(): + res = self.nh.insert_edge(tag_name, value_dict) + logger.debug(res.error_msg()) + + return + + def interpretation_to_db(self, static_analysis_res, interpretation, do_interpret): + ''' + vectorize interpretation and save to db + @return: + ''' + # if not do_interpret, fake some vector + if do_interpret: + logger.info('start get embedding for interpretion') + interp_list = list(interpretation.values()) + emb = get_embedding(engine=self.engine, text_list=interp_list) + logger.info('get embedding done') + else: + emb = {i: [0] for i in list(interpretation.values())} + + ids = [] + embeddings = [] + documents = [] + metadatas = [] + + for code_text, interp in interpretation.items(): + pac_name = static_analysis_res[code_text]['pac_name'] + if pac_name in ids: + continue + + ids.append(pac_name) + documents.append(interp) + + metadatas.append({ + 'code_text': code_text + }) + + embeddings.append(emb[interp]) + + # add documents to chroma + res = self.ch.add_data(ids=ids, embeddings=embeddings, documents=documents, metadatas=metadatas) + logger.debug(res) + + def init_graph(self): + ''' + init graph + @return: + ''' + res = self.nh.create_space(space_name=self.codebase_name, vid_type='FIXED_STRING(1024)') + logger.debug(res.error_msg()) + time.sleep(5) + + self.nh.set_space_name(self.codebase_name) + + logger.info(f'space_name={self.nh.space_name}') + # create tag + tag_name = 'package' + prop_dict = {} + res = self.nh.create_tag(tag_name, prop_dict) + logger.debug(res.error_msg()) + + tag_name = 'class' + prop_dict = {} + res = self.nh.create_tag(tag_name, prop_dict) + logger.debug(res.error_msg()) + + tag_name = 'method' + prop_dict = {} + res = self.nh.create_tag(tag_name, prop_dict) + logger.debug(res.error_msg()) + + # create edge type + edge_type_name = 'contain' + prop_dict = {} + res = self.nh.create_edge_type(edge_type_name, prop_dict) + logger.debug(res.error_msg()) + + # create edge type + edge_type_name = 'depend' + prop_dict = {} + res = self.nh.create_edge_type(edge_type_name, prop_dict) + logger.debug(res.error_msg()) + + +if __name__ == '__main__': + static_res = {'package com.theokanning.openai.client;\nimport com.theokanning.openai.DeleteResult;\nimport com.theokanning.openai.OpenAiResponse;\nimport com.theokanning.openai.audio.TranscriptionResult;\nimport com.theokanning.openai.audio.TranslationResult;\nimport com.theokanning.openai.billing.BillingUsage;\nimport com.theokanning.openai.billing.Subscription;\nimport com.theokanning.openai.completion.CompletionRequest;\nimport com.theokanning.openai.completion.CompletionResult;\nimport com.theokanning.openai.completion.chat.ChatCompletionRequest;\nimport com.theokanning.openai.completion.chat.ChatCompletionResult;\nimport com.theokanning.openai.edit.EditRequest;\nimport com.theokanning.openai.edit.EditResult;\nimport com.theokanning.openai.embedding.EmbeddingRequest;\nimport com.theokanning.openai.embedding.EmbeddingResult;\nimport com.theokanning.openai.engine.Engine;\nimport com.theokanning.openai.file.File;\nimport com.theokanning.openai.fine_tuning.FineTuningEvent;\nimport com.theokanning.openai.fine_tuning.FineTuningJob;\nimport com.theokanning.openai.fine_tuning.FineTuningJobRequest;\nimport com.theokanning.openai.finetune.FineTuneEvent;\nimport com.theokanning.openai.finetune.FineTuneRequest;\nimport com.theokanning.openai.finetune.FineTuneResult;\nimport com.theokanning.openai.image.CreateImageRequest;\nimport com.theokanning.openai.image.ImageResult;\nimport com.theokanning.openai.model.Model;\nimport com.theokanning.openai.moderation.ModerationRequest;\nimport com.theokanning.openai.moderation.ModerationResult;\nimport io.reactivex.Single;\nimport okhttp3.MultipartBody;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport retrofit2.Call;\nimport retrofit2.http.*;\nimport java.time.LocalDate;\npublic interface OpenAiApi {\n @GET("v1/models")\n Single> listModels();\n @GET("/v1/models/{model_id}")\n Single getModel(@Path("model_id") String modelId);\n @POST("/v1/completions")\n Single createCompletion(@Body CompletionRequest request);\n @Streaming\n @POST("/v1/completions")\n Call createCompletionStream(@Body CompletionRequest request);\n @POST("/v1/chat/completions")\n Single createChatCompletion(@Body ChatCompletionRequest request);\n @Streaming\n @POST("/v1/chat/completions")\n Call createChatCompletionStream(@Body ChatCompletionRequest request);\n @Deprecated\n @POST("/v1/engines/{engine_id}/completions")\n Single createCompletion(@Path("engine_id") String engineId, @Body CompletionRequest request);\n @POST("/v1/edits")\n Single createEdit(@Body EditRequest request);\n @Deprecated\n @POST("/v1/engines/{engine_id}/edits")\n Single createEdit(@Path("engine_id") String engineId, @Body EditRequest request);\n @POST("/v1/embeddings")\n Single createEmbeddings(@Body EmbeddingRequest request);\n @Deprecated\n @POST("/v1/engines/{engine_id}/embeddings")\n Single createEmbeddings(@Path("engine_id") String engineId, @Body EmbeddingRequest request);\n @GET("/v1/files")\n Single> listFiles();\n @Multipart\n @POST("/v1/files")\n Single uploadFile(@Part("purpose") RequestBody purpose, @Part MultipartBody.Part file);\n @DELETE("/v1/files/{file_id}")\n Single deleteFile(@Path("file_id") String fileId);\n @GET("/v1/files/{file_id}")\n Single retrieveFile(@Path("file_id") String fileId);\n @Streaming\n @GET("/v1/files/{file_id}/content")\n Single retrieveFileContent(@Path("file_id") String fileId);\n @POST("/v1/fine_tuning/jobs")\n Single createFineTuningJob(@Body FineTuningJobRequest request);\n @GET("/v1/fine_tuning/jobs")\n Single> listFineTuningJobs();\n @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}")\n Single retrieveFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId);\n @POST("/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel")\n Single cancelFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId);\n @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}/events")\n Single> listFineTuningJobEvents(@Path("fine_tuning_job_id") String fineTuningJobId);\n @Deprecated\n @POST("/v1/fine-tunes")\n Single createFineTune(@Body FineTuneRequest request);\n @POST("/v1/completions")\n Single createFineTuneCompletion(@Body CompletionRequest request);\n @Deprecated\n @GET("/v1/fine-tunes")\n Single> listFineTunes();\n @Deprecated\n @GET("/v1/fine-tunes/{fine_tune_id}")\n Single retrieveFineTune(@Path("fine_tune_id") String fineTuneId);\n @Deprecated\n @POST("/v1/fine-tunes/{fine_tune_id}/cancel")\n Single cancelFineTune(@Path("fine_tune_id") String fineTuneId);\n @Deprecated\n @GET("/v1/fine-tunes/{fine_tune_id}/events")\n Single> listFineTuneEvents(@Path("fine_tune_id") String fineTuneId);\n @DELETE("/v1/models/{fine_tune_id}")\n Single deleteFineTune(@Path("fine_tune_id") String fineTuneId);\n @POST("/v1/images/generations")\n Single createImage(@Body CreateImageRequest request);\n @POST("/v1/images/edits")\n Single createImageEdit(@Body RequestBody requestBody);\n @POST("/v1/images/variations")\n Single createImageVariation(@Body RequestBody requestBody);\n @POST("/v1/audio/transcriptions")\n Single createTranscription(@Body RequestBody requestBody);\n @POST("/v1/audio/translations")\n Single createTranslation(@Body RequestBody requestBody);\n @POST("/v1/moderations")\n Single createModeration(@Body ModerationRequest request);\n @Deprecated\n @GET("v1/engines")\n Single> getEngines();\n @Deprecated\n @GET("/v1/engines/{engine_id}")\n Single getEngine(@Path("engine_id") String engineId);\n /**\n * Account information inquiry: It contains total amount (in US dollars) and other information.\n *\n * @return\n */\n @Deprecated\n @GET("v1/dashboard/billing/subscription")\n Single subscription();\n /**\n * Account call interface consumption amount inquiry.\n * totalUsage = Total amount used by the account (in US cents).\n *\n * @param starDate\n * @param endDate\n * @return Consumption amount information.\n */\n @Deprecated\n @GET("v1/dashboard/billing/usage")\n Single billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate);\n}': {'pac_name': 'com.theokanning.openai.client', 'class_name_list': ['com.theokanning.openai.client.OpenAiApi'], 'func_name_dict': {'com.theokanning.openai.client.OpenAiApi': ['com.theokanning.openai.client.OpenAiApi.listModels', 'com.theokanning.openai.client.OpenAiApi.getModel_String', 'com.theokanning.openai.client.OpenAiApi.createCompletion_CompletionRequest', 'com.theokanning.openai.client.OpenAiApi.createCompletionStream_CompletionRequest', 'com.theokanning.openai.client.OpenAiApi.createChatCompletion_ChatCompletionRequest', 'com.theokanning.openai.client.OpenAiApi.createChatCompletionStream_ChatCompletionRequest', 'com.theokanning.openai.client.OpenAiApi.createCompletion_String_CompletionRequest', 'com.theokanning.openai.client.OpenAiApi.createEdit_EditRequest', 'com.theokanning.openai.client.OpenAiApi.createEdit_String_EditRequest', 'com.theokanning.openai.client.OpenAiApi.createEmbeddings_EmbeddingRequest', 'com.theokanning.openai.client.OpenAiApi.createEmbeddings_String_EmbeddingRequest', 'com.theokanning.openai.client.OpenAiApi.listFiles', 'com.theokanning.openai.client.OpenAiApi.uploadFile_RequestBody_MultipartBody', 'com.theokanning.openai.client.OpenAiApi.deleteFile_String', 'com.theokanning.openai.client.OpenAiApi.retrieveFile_String', 'com.theokanning.openai.client.OpenAiApi.retrieveFileContent_String', 'com.theokanning.openai.client.OpenAiApi.createFineTuningJob_FineTuningJobRequest', 'com.theokanning.openai.client.OpenAiApi.listFineTuningJobs', 'com.theokanning.openai.client.OpenAiApi.retrieveFineTuningJob_String', 'com.theokanning.openai.client.OpenAiApi.cancelFineTuningJob_String', 'com.theokanning.openai.client.OpenAiApi.listFineTuningJobEvents_String', 'com.theokanning.openai.client.OpenAiApi.createFineTune_FineTuneRequest', 'com.theokanning.openai.client.OpenAiApi.createFineTuneCompletion_CompletionRequest', 'com.theokanning.openai.client.OpenAiApi.listFineTunes', 'com.theokanning.openai.client.OpenAiApi.retrieveFineTune_String', 'com.theokanning.openai.client.OpenAiApi.cancelFineTune_String', 'com.theokanning.openai.client.OpenAiApi.listFineTuneEvents_String', 'com.theokanning.openai.client.OpenAiApi.deleteFineTune_String', 'com.theokanning.openai.client.OpenAiApi.createImage_CreateImageRequest', 'com.theokanning.openai.client.OpenAiApi.createImageEdit_RequestBody', 'com.theokanning.openai.client.OpenAiApi.createImageVariation_RequestBody', 'com.theokanning.openai.client.OpenAiApi.createTranscription_RequestBody', 'com.theokanning.openai.client.OpenAiApi.createTranslation_RequestBody', 'com.theokanning.openai.client.OpenAiApi.createModeration_ModerationRequest', 'com.theokanning.openai.client.OpenAiApi.getEngines', 'com.theokanning.openai.client.OpenAiApi.getEngine_String', 'com.theokanning.openai.client.OpenAiApi.subscription', 'com.theokanning.openai.client.OpenAiApi.billingUsage_LocalDate_LocalDate']}, 'import_pac_name_list': ['com.theokanning.openai.DeleteResult', 'com.theokanning.openai.OpenAiResponse', 'com.theokanning.openai.audio.TranscriptionResult', 'com.theokanning.openai.audio.TranslationResult', 'com.theokanning.openai.billing.BillingUsage', 'com.theokanning.openai.billing.Subscription', 'com.theokanning.openai.completion.CompletionRequest', 'com.theokanning.openai.completion.CompletionResult', 'com.theokanning.openai.completion.chat.ChatCompletionRequest', 'com.theokanning.openai.completion.chat.ChatCompletionResult', 'com.theokanning.openai.edit.EditRequest', 'com.theokanning.openai.edit.EditResult', 'com.theokanning.openai.embedding.EmbeddingRequest', 'com.theokanning.openai.embedding.EmbeddingResult', 'com.theokanning.openai.engine.Engine', 'com.theokanning.openai.file.File', 'com.theokanning.openai.fine_tuning.FineTuningEvent', 'com.theokanning.openai.fine_tuning.FineTuningJob', 'com.theokanning.openai.fine_tuning.FineTuningJobRequest', 'com.theokanning.openai.finetune.FineTuneEvent', 'com.theokanning.openai.finetune.FineTuneRequest', 'com.theokanning.openai.finetune.FineTuneResult', 'com.theokanning.openai.image.CreateImageRequest', 'com.theokanning.openai.image.ImageResult', 'com.theokanning.openai.model.Model', 'com.theokanning.openai.moderation.ModerationRequest', 'com.theokanning.openai.moderation.ModerationResult', 'io.reactivex.Single', 'okhttp3.MultipartBody', 'okhttp3.RequestBody', 'okhttp3.ResponseBody', 'retrofit2.Call', 'retrofit2.http', 'java.time.LocalDate']}, '\npackage com.theokanning.openai;\n\n/**\n * OkHttp Interceptor that adds an authorization token header\n * \n * @deprecated Use {@link com.theokanning.openai.client.AuthenticationInterceptor}\n */\n@Deprecated\npublic class AuthenticationInterceptor extends com.theokanning.openai.client.AuthenticationInterceptor {\n\n AuthenticationInterceptor(String token) {\n super(token);\n }\n\n}\n': {'pac_name': 'com.theokanning.openai', 'class_name_list': ['com.theokanning.openai.AuthenticationInterceptor'], 'func_name_dict': {}, 'import_pac_name_list': []}} diff --git a/dev_opsgpt/codechat/codebase_handler/codebase_handler.py b/dev_opsgpt/codechat/codebase_handler/codebase_handler.py new file mode 100644 index 0000000..66970c5 --- /dev/null +++ b/dev_opsgpt/codechat/codebase_handler/codebase_handler.py @@ -0,0 +1,169 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: codebase_handler.py +@time: 2023/11/21 下午2:25 +@desc: +''' +import time +from loguru import logger + +from configs.server_config import NEBULA_HOST, NEBULA_PORT, NEBULA_USER, NEBULA_PASSWORD, NEBULA_STORAGED_PORT +from configs.server_config import CHROMA_PERSISTENT_PATH + +from configs.model_config import EMBEDDING_ENGINE + +from dev_opsgpt.db_handler.graph_db_handler.nebula_handler import NebulaHandler +from dev_opsgpt.db_handler.vector_db_handler.chroma_handler import ChromaHandler +from dev_opsgpt.codechat.code_crawler.zip_crawler import * +from dev_opsgpt.codechat.code_analyzer.code_analyzer import CodeAnalyzer +from dev_opsgpt.codechat.codebase_handler.code_importer import CodeImporter +from dev_opsgpt.codechat.code_search.code_search import CodeSearch + + +class CodeBaseHandler: + def __init__(self, codebase_name: str, code_path: str = '', + language: str = 'java', crawl_type: str = 'ZIP'): + self.codebase_name = codebase_name + self.code_path = code_path + self.language = language + self.crawl_type = crawl_type + + self.nh = NebulaHandler(host=NEBULA_HOST, port=NEBULA_PORT, username=NEBULA_USER, + password=NEBULA_PASSWORD, space_name=codebase_name) + self.nh.add_host(NEBULA_HOST, NEBULA_STORAGED_PORT) + time.sleep(1) + + self.ch = ChromaHandler(path=CHROMA_PERSISTENT_PATH, collection_name=codebase_name) + + def import_code(self, zip_file='', do_interpret=True): + ''' + analyze code and save it to codekg and codedb + @return: + ''' + # init graph to init tag and edge + code_importer = CodeImporter(engine=EMBEDDING_ENGINE, codebase_name=self.codebase_name, + nh=self.nh, ch=self.ch) + code_importer.init_graph() + time.sleep(5) + + # crawl code + st0 = time.time() + logger.info('start crawl') + code_dict = self.crawl_code(zip_file) + logger.debug('crawl done, rt={}'.format(time.time() - st0)) + + # analyze code + logger.info('start analyze') + st1 = time.time() + code_analyzer = CodeAnalyzer(language=self.language) + static_analysis_res, interpretation = code_analyzer.analyze(code_dict, do_interpret=do_interpret) + logger.debug('analyze done, rt={}'.format(time.time() - st1)) + + # add info to nebula and chroma + st2 = time.time() + code_importer.import_code(static_analysis_res, interpretation, do_interpret=do_interpret) + logger.debug('update codebase done, rt={}'.format(time.time() - st2)) + + # get KG info + stat = self.nh.get_stat() + vertices_num, edges_num = stat['vertices'], stat['edges'] + + # get chroma info + file_num = self.ch.count()['result'] + + return vertices_num, edges_num, file_num + + def delete_codebase(self, codebase_name: str): + ''' + delete codebase + @param codebase_name: name of codebase + @return: + ''' + self.nh.drop_space(space_name=codebase_name) + self.ch.delete_collection(collection_name=codebase_name) + + def crawl_code(self, zip_file=''): + ''' + @return: + ''' + if self.language == 'java': + suffix = 'java' + + logger.info(f'crawl_type={self.crawl_type}') + + code_dict = {} + if self.crawl_type.lower() == 'zip': + code_dict = ZipCrawler.crawl(zip_file, output_path=self.code_path, suffix=suffix) + elif self.crawl_type.lower() == 'dir': + code_dict = DirCrawler.crawl(self.code_path, suffix) + + return code_dict + + def search_code(self, query: str, search_type: str, limit: int = 3): + ''' + search code from codebase + @param limit: + @param engine: + @param query: query from user + @param search_type: ['cypher', 'graph', 'vector'] + @return: + ''' + assert search_type in ['cypher', 'tag', 'description'] + + code_search = CodeSearch(nh=self.nh, ch=self.ch, limit=limit) + + if search_type == 'cypher': + search_res = code_search.search_by_cypher(query=query) + elif search_type == 'tag': + search_res = code_search.search_by_tag(query=query) + elif search_type == 'description': + search_res = code_search.search_by_desciption(query=query, engine=EMBEDDING_ENGINE) + + context, related_vertice = self.format_search_res(search_res, search_type) + return context, related_vertice + + def format_search_res(self, search_res: str, search_type: str): + ''' + format search_res + @param search_res: + @param search_type: + @return: + ''' + CYPHER_QA_PROMPT = ''' + 执行的 Cypher 是: {cypher} + Cypher 的结果是: {result} + ''' + + if search_type == 'cypher': + context = CYPHER_QA_PROMPT.format(cypher=search_res['cypher'], result=search_res['cypher_res']) + related_vertice = [] + elif search_type == 'tag': + context = '' + related_vertice = [] + for code in search_res: + context = context + code['code_text'] + '\n' + related_vertice.append(code['vertex']) + elif search_type == 'description': + context = '' + related_vertice = [] + for code in search_res: + context = context + code['code_text'] + '\n' + related_vertice.append(code['vertex']) + + return context, related_vertice + + +if __name__ == '__main__': + codebase_name = 'testing' + code_path = '/Users/bingxu/Desktop/工作/大模型/chatbot/test_code_repo/client' + cbh = CodeBaseHandler(codebase_name, code_path, crawl_type='dir') + + # query = '使用不同的HTTP请求类型(GET、POST、DELETE等)来执行不同的操作' + # query = '代码中一共有多少个类' + + query = 'intercept 函数作用是什么' + search_type = 'graph' + limit = 2 + res = cbh.search_code(query, search_type, limit) + logger.debug(res) diff --git a/dev_opsgpt/connector/agents/__init__.py b/dev_opsgpt/connector/agents/__init__.py index b4aed3f..e0ee881 100644 --- a/dev_opsgpt/connector/agents/__init__.py +++ b/dev_opsgpt/connector/agents/__init__.py @@ -1,6 +1,9 @@ from .base_agent import BaseAgent from .react_agent import ReactAgent +from .check_agent import CheckAgent +from .executor_agent import ExecutorAgent +from .selector_agent import SelectorAgent __all__ = [ - "BaseAgent", "ReactAgent" + "BaseAgent", "ReactAgent", "CheckAgent", "ExecutorAgent", "SelectorAgent" ] \ No newline at end of file diff --git a/dev_opsgpt/connector/agents/base_agent.py b/dev_opsgpt/connector/agents/base_agent.py index 1def69c..f98724b 100644 --- a/dev_opsgpt/connector/agents/base_agent.py +++ b/dev_opsgpt/connector/agents/base_agent.py @@ -7,16 +7,19 @@ import traceback import uuid from loguru import logger -from dev_opsgpt.connector.shcema.memory import Memory -from dev_opsgpt.connector.connector_schema import ( - Task, Role, Message, ActionStatus, Doc, CodeDoc - ) +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, ActionStatus, CodeDoc, Doc +) from configs.server_config import SANDBOX_SERVER from dev_opsgpt.sandbox import PyCodeBox, CodeBoxResponse from dev_opsgpt.tools import DDGSTool, DocRetrieval, CodeRetrieval -from dev_opsgpt.connector.configs.agent_config import REACT_PROMPT_INPUT +from dev_opsgpt.connector.configs.prompts import BASE_PROMPT_INPUT, QUERY_CONTEXT_DOC_PROMPT_INPUT, BEGIN_PROMPT_INPUT +from dev_opsgpt.connector.message_process import MessageUtils +from dev_opsgpt.connector.configs.agent_config import REACT_PROMPT_INPUT, QUERY_CONTEXT_PROMPT_INPUT, PLAN_PROMPT_INPUT from dev_opsgpt.llm_models import getChatModel +from dev_opsgpt.connector.utils import parse_section + class BaseAgent: @@ -34,67 +37,74 @@ class BaseAgent: stop: Union[List[str], str] = None, do_filter: bool = True, do_use_self_memory: bool = True, - # docs_prompt: str, + focus_agents: List[str] = [], + focus_message_keys: List[str] = [], # prompt_mamnger: PromptManager ): self.task = task self.role = role + self.message_utils = MessageUtils() self.llm = self.create_llm_engine(temperature, stop) self.memory = self.init_history(memory) self.chat_turn = chat_turn self.do_search = do_search self.do_doc_retrieval = do_doc_retrieval self.do_tool_retrieval = do_tool_retrieval - self.codebox = PyCodeBox( - remote_url=SANDBOX_SERVER["url"], - remote_ip=SANDBOX_SERVER["host"], - remote_port=SANDBOX_SERVER["port"], - token="mytoken", - do_code_exe=True, - do_remote=SANDBOX_SERVER["do_remote"], - do_check_net=False - ) + self.focus_agents = focus_agents + self.focus_message_keys = focus_message_keys self.do_filter = do_filter self.do_use_self_memory = do_use_self_memory - # self.docs_prompt = docs_prompt # self.prompt_manager = None - def run(self, query: Message, history: Memory = None, background: Memory = None) -> Message: - '''llm inference''' + def run(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory=None) -> Message: + '''agent reponse from multi-message''' + message = None + for message in self.arun(query, history, background, memory_pool): + pass + return message + + def arun(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory=None) -> Message: + '''agent reponse from multi-message''' # insert query into memory query_c = copy.deepcopy(query) self_memory = self.memory if self.do_use_self_memory else None - prompt = self.create_prompt(query_c, self_memory, history, background) + # create your llm prompt + prompt = self.create_prompt(query_c, self_memory, history, background, memory_pool=memory_pool) content = self.llm.predict(prompt) logger.debug(f"{self.role.role_name} prompt: {prompt}") - # logger.debug(f"{self.role.role_name} content: {content}") + logger.debug(f"{self.role.role_name} content: {content}") output_message = Message( role_name=self.role.role_name, role_type="ai", #self.role.role_type, role_content=content, role_contents=[content], + step_content=content, input_query=query_c.input_query, - tools=query_c.tools + tools=query_c.tools, + parsed_output_list=[query.parsed_output] ) - - output_message = self.parser(output_message) + # common parse llm' content to message + output_message = self.message_utils.parser(output_message) if self.do_filter: - output_message = self.filter(output_message) + output_message = self.message_utils.filter(output_message) - - # 更新自身的回答 + # update self_memory self.append_history(query_c) self.append_history(output_message) - logger.info(f"{self.role.role_name} step_run: {output_message.role_content}") - return output_message + # logger.info(f"{self.role.role_name} currenct question: {output_message.input_query}\nllm_step_run: {output_message.role_content}") + output_message.input_query = output_message.role_content + output_message.parsed_output_list.append(output_message.parsed_output) + # update memory pool + memory_pool.append(output_message) + yield output_message def create_prompt( - self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, prompt_mamnger=None) -> str: + self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, memory_pool: Memory=None, prompt_mamnger=None) -> str: ''' - role\task\tools\docs\memory + prompt engineer, contains role\task\tools\docs\memory ''' # doc_infos = self.create_doc_prompt(query) @@ -105,50 +115,105 @@ class BaseAgent: background_prompt = self.create_background_prompt(background, control_key="step_content") history_prompt = self.create_history_prompt(history) selfmemory_prompt = self.create_selfmemory_prompt(memory, control_key="step_content") - # + # extra_system_prompt = self.role.role_prompt + prompt = self.role.role_prompt.format(**{"formatted_tools": formatted_tools, "tool_names": tool_names}) + # + memory_pool_select_by_agent_key = self.select_memory_by_agent_key(memory_pool) + memory_pool_select_by_agent_key_context = '\n\n'.join([f"*{k}*\n{v}" for parsed_output in memory_pool_select_by_agent_key.get_parserd_output_list() for k, v in parsed_output.items() if k not in ['Action Status']]) + + # input_query = query.input_query + + # # logger.debug(f"{self.role.role_name} extra_system_prompt: {self.role.role_prompt}") + # # logger.debug(f"{self.role.role_name} input_query: {input_query}") + # # logger.debug(f"{self.role.role_name} doc_infos: {doc_infos}") + # # logger.debug(f"{self.role.role_name} tool_names: {tool_names}") + # if "**Context:**" in self.role.role_prompt: + # # logger.debug(f"parsed_output_list: {query.parsed_output_list}") + # # input_query = "'''" + "\n".join([f"###{k}###\n{v}" for i in query.parsed_output_list for k,v in i.items() if "Action Status" !=k]) + "'''" + # context = "\n".join([f"*{k}*\n{v}" for i in query.parsed_output_list for k,v in i.items() if "Action Status" !=k]) + # # context = history_prompt or '""' + # # logger.debug(f"parsed_output_list: {t}") + # prompt += "\n" + QUERY_CONTEXT_PROMPT_INPUT.format(**{"context": context, "query": query.origin_query}) + # else: + # prompt += "\n" + PLAN_PROMPT_INPUT.format(**{"query": input_query}) task = query.task or self.task if task_prompt is not None: prompt += "\n" + task.task_prompt + DocInfos = "" if doc_infos is not None and doc_infos!="" and doc_infos!="不存在知识库辅助信息": - prompt += f"\n知识库信息: {doc_infos}" + DocInfos += f"\nDocument Information: {doc_infos}" if code_infos is not None and code_infos!="" and code_infos!="不存在代码库辅助信息": - prompt += f"\n代码库信息: {code_infos}" + DocInfos += f"\nCodeBase Infomation: {code_infos}" + + # if selfmemory_prompt: + # prompt += "\n" + selfmemory_prompt - if background_prompt: - prompt += "\n" + background_prompt + # if background_prompt: + # prompt += "\n" + background_prompt - if history_prompt: - prompt += "\n" + history_prompt + # if history_prompt: + # prompt += "\n" + history_prompt - if selfmemory_prompt: - prompt += "\n" + selfmemory_prompt - - # input_query = memory.to_tuple_messages(content_key="step_content") - # input_query = "\n".join([f"{k}: {v}" for k, v in input_query if v]) - - input_query = query.role_content + input_query = query.input_query # logger.debug(f"{self.role.role_name} extra_system_prompt: {self.role.role_prompt}") - logger.debug(f"{self.role.role_name} input_query: {input_query}") + # logger.debug(f"{self.role.role_name} input_query: {input_query}") # logger.debug(f"{self.role.role_name} doc_infos: {doc_infos}") - logger.debug(f"{self.role.role_name} tool_names: {tool_names}") - prompt += "\n" + REACT_PROMPT_INPUT.format(**{"query": input_query}) + # logger.debug(f"{self.role.role_name} tool_names: {tool_names}") + + # extra_system_prompt = self.role.role_prompt + input_keys = parse_section(self.role.role_prompt, 'Input Format') + prompt = self.role.role_prompt.format(**{"formatted_tools": formatted_tools, "tool_names": tool_names}) + prompt += "\n" + BEGIN_PROMPT_INPUT + for input_key in input_keys: + if input_key == "Origin Query": + prompt += "\n**Origin Query:**\n" + query.origin_query + elif input_key == "Context": + context = "\n".join([f"*{k}*\n{v}" for i in query.parsed_output_list for k,v in i.items() if "Action Status" !=k]) + if history: + context = history_prompt + "\n" + context + if not context: + context = "there is no context" + + if self.focus_agents and memory_pool_select_by_agent_key_context: + context = memory_pool_select_by_agent_key_context + prompt += "\n**Context:**\n" + context + "\n" + input_query + elif input_key == "DocInfos": + prompt += "\n**DocInfos:**\n" + DocInfos + elif input_key == "Question": + prompt += "\n**Question:**\n" + input_query + + # if "**Context:**" in self.role.role_prompt: + # # logger.debug(f"parsed_output_list: {query.parsed_output_list}") + # # input_query = "'''" + "\n".join([f"###{k}###\n{v}" for i in query.parsed_output_list for k,v in i.items() if "Action Status" !=k]) + "'''" + # context = "\n".join([f"*{k}*\n{v}" for i in query.parsed_output_list for k,v in i.items() if "Action Status" !=k]) + # if history: + # context = history_prompt + "\n" + context + + # if not context: + # context = "there is no context" + + # # logger.debug(f"parsed_output_list: {t}") + # if "DocInfos" in prompt: + # prompt += "\n" + QUERY_CONTEXT_DOC_PROMPT_INPUT.format(**{"context": context, "query": query.origin_query, "DocInfos": DocInfos}) + # else: + # prompt += "\n" + QUERY_CONTEXT_PROMPT_INPUT.format(**{"context": context, "query": query.origin_query, "DocInfos": DocInfos}) + # else: + # prompt += "\n" + BASE_PROMPT_INPUT.format(**{"query": input_query}) + # prompt = extra_system_prompt.format(**{"query": input_query, "doc_infos": doc_infos, "formatted_tools": formatted_tools, "tool_names": tool_names}) while "{{" in prompt or "}}" in prompt: prompt = prompt.replace("{{", "{") prompt = prompt.replace("}}", "}") + + # logger.debug(f"{self.role.role_name} prompt: {prompt}") return prompt - - # prompt_comp = [("system", extra_system_prompt)] + memory.to_tuple_messages() - # prompt = ChatPromptTemplate.from_messages(prompt_comp) - # prompt = prompt.format(**{"query": query.role_content, "doc_infos": doc_infos, "formatted_tools": formatted_tools, "tool_names": tool_names}) - # return prompt def create_doc_prompt(self, message: Message) -> str: '''''' @@ -197,7 +262,7 @@ class BaseAgent: return "\n补充自身对话信息: " + selfmemory_message if selfmemory_message else None def init_history(self, memory: Memory = None) -> Memory: - return Memory([]) + return Memory(messages=[]) def update_history(self, message: Message): self.memory.append(message) @@ -212,216 +277,389 @@ class BaseAgent: def create_llm_engine(self, temperature=0.2, stop=None): return getChatModel(temperature=temperature, stop=stop) - def filter(self, message: Message, stop=None) -> Message: + # def filter(self, message: Message, stop=None) -> Message: - tool_params = self.parser_spec_key(message.role_content, "tool_params") - code_content = self.parser_spec_key(message.role_content, "code_content") - plan = self.parser_spec_key(message.role_content, "plan") - plans = self.parser_spec_key(message.role_content, "plans", do_search=False) - content = self.parser_spec_key(message.role_content, "content", do_search=False) + # tool_params = self.parser_spec_key(message.role_content, "tool_params") + # code_content = self.parser_spec_key(message.role_content, "code_content") + # plan = self.parser_spec_key(message.role_content, "plan") + # plans = self.parser_spec_key(message.role_content, "plans", do_search=False) + # content = self.parser_spec_key(message.role_content, "content", do_search=False) - # logger.debug(f"tool_params: {tool_params}, code_content: {code_content}, plan: {plan}, plans: {plans}, content: {content}") - role_content = tool_params or code_content or plan or plans or content - message.role_content = role_content or message.role_content - return message + # # logger.debug(f"tool_params: {tool_params}, code_content: {code_content}, plan: {plan}, plans: {plans}, content: {content}") + # role_content = tool_params or code_content or plan or plans or content + # message.role_content = role_content or message.role_content + # return message def token_usage(self, ): + '''calculate the usage of token''' pass - def get_extra_infos(self, message: Message) -> Message: - '''''' - if self.do_search: - message = self.get_search_retrieval(message) + def select_memory_by_key(self, memory: Memory) -> Memory: + return Memory( + messages=[self.select_message_by_key(message) for message in memory.messages + if self.select_message_by_key(message) is not None] + ) + + def select_memory_by_agent_key(self, memory: Memory) -> Memory: + return Memory( + messages=[self.select_message_by_agent_key(message) for message in memory.messages + if self.select_message_by_agent_key(message) is not None] + ) + + def select_message_by_agent_key(self, message: Message) -> Message: + # assume we focus all agents + if self.focus_agents == []: + return message + return None if message is None or message.role_name not in self.focus_agents else self.select_message_by_key(message) + + def select_message_by_key(self, message: Message) -> Message: + # assume we focus all key contents + if message is None: + return message - if self.do_doc_retrieval: - message = self.get_doc_retrieval(message) - - if self.do_tool_retrieval: - message = self.get_tool_retrieval(message) + if self.focus_message_keys == []: + return message - return message - - def get_search_retrieval(self, message: Message,) -> Message: - SEARCH_ENGINES = {"duckduckgo": DDGSTool} - search_docs = [] - for idx, doc in enumerate(SEARCH_ENGINES["duckduckgo"].run(message.role_content, 3)): - doc.update({"index": idx}) - search_docs.append(Doc(**doc)) - message.search_docs = search_docs - return message - - def get_doc_retrieval(self, message: Message) -> Message: - query = message.role_content - knowledge_basename = message.doc_engine_name - top_k = message.top_k - score_threshold = message.score_threshold - if knowledge_basename: - docs = DocRetrieval.run(query, knowledge_basename, top_k, score_threshold) - message.db_docs = [Doc(**doc) for doc in docs] - return message - - def get_code_retrieval(self, message: Message) -> Message: - # DocRetrieval.run("langchain是什么", "DSADSAD") - query = message.input_query - code_engine_name = message.code_engine_name - history_node_list = message.history_node_list - code_docs = CodeRetrieval.run(code_engine_name, query, code_limit=message.top_k, history_node_list=history_node_list) - message.code_docs = [CodeDoc(**doc) for doc in code_docs] - return message - - def get_tool_retrieval(self, message: Message) -> Message: - return message - - def step_router(self, message: Message) -> Message: - '''''' - # message = self.parser(message) - # logger.debug(f"message.action_status: {message.action_status}") - if message.action_status == ActionStatus.CODING: - message = self.code_step(message) - elif message.action_status == ActionStatus.TOOL_USING: - message = self.tool_step(message) + message_c = copy.deepcopy(message) + message_c.parsed_output = {k: v for k,v in message_c.parsed_output.items() if k in self.focus_message_keys} + message_c.parsed_output_list = [{k: v for k,v in parsed_output.items() if k in self.focus_message_keys} for parsed_output in message_c.parsed_output_list] + return message_c - return message - - def code_step(self, message: Message) -> Message: - '''execute code''' - # logger.debug(f"message.role_content: {message.role_content}, message.code_content: {message.code_content}") - code_answer = self.codebox.chat('```python\n{}```'.format(message.code_content)) - code_prompt = f"执行上述代码后存在报错信息为 {code_answer.code_exe_response},需要进行修复" \ - if code_answer.code_exe_type == "error" else f"执行上述代码后返回信息为 {code_answer.code_exe_response}" - uid = str(uuid.uuid1()) - if code_answer.code_exe_type == "image/png": - message.figures[uid] = code_answer.code_exe_response - message.code_answer = f"\n观察: 执行上述代码后生成一张图片, 图片名为{uid}\n" - message.observation = f"\n观察: 执行上述代码后生成一张图片, 图片名为{uid}\n" - message.step_content += f"\n观察: 执行上述代码后生成一张图片, 图片名为{uid}\n" - message.step_contents += [f"\n观察: 执行上述代码后生成一张图片, 图片名为{uid}\n"] - message.role_content += f"\n观察:执行上述代码后生成一张图片, 图片名为{uid}\n" - else: - message.code_answer = code_answer.code_exe_response - message.observation = code_answer.code_exe_response - message.step_content += f"\n观察: {code_prompt}\n" - message.step_contents += [f"\n观察: {code_prompt}\n"] - message.role_content += f"\n观察: {code_prompt}\n" - # logger.info(f"观察: {message.action_status}, {message.observation}") - return message - - def tool_step(self, message: Message) -> Message: - '''execute tool''' - # logger.debug(f"message: {message.action_status}, {message.tool_name}, {message.tool_params}") - tool_names = [tool.name for tool in message.tools] - if message.tool_name not in tool_names: - message.tool_answer = "不存在可以执行的tool" - message.observation = "不存在可以执行的tool" - message.role_content += f"\n观察: 不存在可以执行的tool\n" - message.step_content += f"\n观察: 不存在可以执行的tool\n" - message.step_contents += [f"\n观察: 不存在可以执行的tool\n"] - for tool in message.tools: - if tool.name == message.tool_name: - tool_res = tool.func(**message.tool_params) - message.tool_answer = tool_res - message.observation = tool_res - message.role_content += f"\n观察: {tool_res}\n" - message.step_content += f"\n观察: {tool_res}\n" - message.step_contents += [f"\n观察: {tool_res}\n"] - - # logger.info(f"观察: {message.action_status}, {message.observation}") - return message - - def parser(self, message: Message) -> Message: - '''''' - content = message.role_content - parser_keys = ["action", "code_content", "code_filename", "tool_params", "plans"] - try: - s_json = self._parse_json(content) - message.action_status = s_json.get("action") - message.code_content = s_json.get("code_content") - message.tool_params = s_json.get("tool_params") - message.tool_name = s_json.get("tool_name") - message.code_filename = s_json.get("code_filename") - message.plans = s_json.get("plans") - # for parser_key in parser_keys: - # message.action_status = content.get(parser_key) - except Exception as e: - # logger.warning(f"{traceback.format_exc()}") - action_value = self._match(r"'action':\s*'([^']*)'", content) if "'action'" in content else self._match(r'"action":\s*"([^"]*)"', content) - code_content_value = self._match(r"'code_content':\s*'([^']*)'", content) if "'code_content'" in content else self._match(r'"code_content":\s*"([^"]*)"', content) - filename_value = self._match(r"'code_filename':\s*'([^']*)'", content) if "'code_filename'" in content else self._match(r'"code_filename":\s*"([^"]*)"', content) - tool_params_value = self._match(r"'tool_params':\s*(\{[^{}]*\})", content, do_json=True) if "'tool_params'" in content \ - else self._match(r'"tool_params":\s*(\{[^{}]*\})', content, do_json=True) - tool_name_value = self._match(r"'tool_name':\s*'([^']*)'", content) if "'tool_name'" in content else self._match(r'"tool_name":\s*"([^"]*)"', content) - plans_value = self._match(r"'plans':\s*(\[.*?\])", content, do_search=False) if "'plans'" in content else self._match(r'"plans":\s*(\[.*?\])', content, do_search=False, ) - # re解析 - message.action_status = action_value or "default" - message.code_content = code_content_value - message.code_filename = filename_value - message.tool_params = tool_params_value - message.tool_name = tool_name_value - message.plans = plans_value - - # logger.debug(f"确认当前的action: {message.action_status}") - - return message - - def parser_spec_key(self, content, key, do_search=True, do_json=False) -> str: - '''''' - key2pattern = { - "'action'": r"'action':\s*'([^']*)'", '"action"': r'"action":\s*"([^"]*)"', - "'code_content'": r"'code_content':\s*'([^']*)'", '"code_content"': r'"code_content":\s*"([^"]*)"', - "'code_filename'": r"'code_filename':\s*'([^']*)'", '"code_filename"': r'"code_filename":\s*"([^"]*)"', - "'tool_params'": r"'tool_params':\s*(\{[^{}]*\})", '"tool_params"': r'"tool_params":\s*(\{[^{}]*\})', - "'tool_name'": r"'tool_name':\s*'([^']*)'", '"tool_name"': r'"tool_name":\s*"([^"]*)"', - "'plans'": r"'plans':\s*(\[.*?\])", '"plans"': r'"plans":\s*(\[.*?\])', - "'content'": r"'content':\s*'([^']*)'", '"content"': r'"content":\s*"([^"]*)"', - } + # def get_extra_infos(self, message: Message) -> Message: + # '''''' + # if self.do_search: + # message = self.get_search_retrieval(message) - s_json = self._parse_json(content) - try: - if s_json and key in s_json: - return str(s_json[key]) - except: - pass + # if self.do_doc_retrieval: + # message = self.get_doc_retrieval(message) - keystr = f"'{key}'" if f"'{key}'" in content else f'"{key}"' - return self._match(key2pattern.get(keystr, fr"'{key}':\s*'([^']*)'"), content, do_search=do_search, do_json=do_json) + # if self.do_tool_retrieval: + # message = self.get_tool_retrieval(message) + + # return message - def _match(self, pattern, s, do_search=True, do_json=False): - try: - if do_search: - match = re.search(pattern, s) - if match: - value = match.group(1).replace("\\n", "\n") - if do_json: - value = json.loads(value) - else: - value = None - else: - match = re.findall(pattern, s, re.DOTALL) - if match: - value = match[0] - if do_json: - value = json.loads(value) - else: - value = None - except Exception as e: - logger.warning(f"{traceback.format_exc()}") + # def get_search_retrieval(self, message: Message,) -> Message: + # SEARCH_ENGINES = {"duckduckgo": DDGSTool} + # search_docs = [] + # for idx, doc in enumerate(SEARCH_ENGINES["duckduckgo"].run(message.role_content, 3)): + # doc.update({"index": idx}) + # search_docs.append(Doc(**doc)) + # message.search_docs = search_docs + # return message + + # def get_doc_retrieval(self, message: Message) -> Message: + # query = message.role_content + # knowledge_basename = message.doc_engine_name + # top_k = message.top_k + # score_threshold = message.score_threshold + # if knowledge_basename: + # docs = DocRetrieval.run(query, knowledge_basename, top_k, score_threshold) + # message.db_docs = [Doc(**doc) for doc in docs] + # return message + + # def get_code_retrieval(self, message: Message) -> Message: + # # DocRetrieval.run("langchain是什么", "DSADSAD") + # query = message.input_query + # code_engine_name = message.code_engine_name + # history_node_list = message.history_node_list + # code_docs = CodeRetrieval.run(code_engine_name, query, code_limit=message.top_k, history_node_list=history_node_list) + # message.code_docs = [CodeDoc(**doc) for doc in code_docs] + # return message + + # def get_tool_retrieval(self, message: Message) -> Message: + # return message + + # def step_router(self, message: Message) -> tuple[Message, ...]: + # '''''' + # # message = self.parser(message) + # # logger.debug(f"message.action_status: {message.action_status}") + # observation_message = None + # if message.action_status == ActionStatus.CODING: + # message, observation_message = self.code_step(message) + # elif message.action_status == ActionStatus.TOOL_USING: + # message, observation_message = self.tool_step(message) - # logger.debug(f"pattern: {pattern}, s: {s}, match: {match}") - return value + # return message, observation_message + + # def code_step(self, message: Message) -> Message: + # '''execute code''' + # # logger.debug(f"message.role_content: {message.role_content}, message.code_content: {message.code_content}") + # code_answer = self.codebox.chat('```python\n{}```'.format(message.code_content)) + # code_prompt = f"执行上述代码后存在报错信息为 {code_answer.code_exe_response},需要进行修复" \ + # if code_answer.code_exe_type == "error" else f"执行上述代码后返回信息为 {code_answer.code_exe_response}" + + # observation_message = Message( + # role_name="observation", + # role_type="func", #self.role.role_type, + # role_content="", + # step_content="", + # input_query=message.code_content, + # ) + # uid = str(uuid.uuid1()) + # if code_answer.code_exe_type == "image/png": + # message.figures[uid] = code_answer.code_exe_response + # message.code_answer = f"\n**Observation:**: 执行上述代码后生成一张图片, 图片名为{uid}\n" + # message.observation = f"\n**Observation:**: 执行上述代码后生成一张图片, 图片名为{uid}\n" + # message.step_content += f"\n**Observation:**: 执行上述代码后生成一张图片, 图片名为{uid}\n" + # message.step_contents += [f"\n**Observation:**: 执行上述代码后生成一张图片, 图片名为{uid}\n"] + # # message.role_content += f"\n**Observation:**:执行上述代码后生成一张图片, 图片名为{uid}\n" + # observation_message.role_content = f"\n**Observation:**: 执行上述代码后生成一张图片, 图片名为{uid}\n" + # observation_message.parsed_output = {"Observation": f"执行上述代码后生成一张图片, 图片名为{uid}"} + # else: + # message.code_answer = code_answer.code_exe_response + # message.observation = code_answer.code_exe_response + # message.step_content += f"\n**Observation:**: {code_prompt}\n" + # message.step_contents += [f"\n**Observation:**: {code_prompt}\n"] + # # message.role_content += f"\n**Observation:**: {code_prompt}\n" + # observation_message.role_content = f"\n**Observation:**: {code_prompt}\n" + # observation_message.parsed_output = {"Observation": code_prompt} + # # logger.info(f"**Observation:** {message.action_status}, {message.observation}") + # return message, observation_message + + # def tool_step(self, message: Message) -> Message: + # '''execute tool''' + # # logger.debug(f"{message}") + # observation_message = Message( + # role_name="observation", + # role_type="function", #self.role.role_type, + # role_content="\n**Observation:** there is no tool can execute\n" , + # step_content="", + # input_query=str(message.tool_params), + # tools=message.tools, + # ) + # # logger.debug(f"message: {message.action_status}, {message.tool_name}, {message.tool_params}") + # tool_names = [tool.name for tool in message.tools] + # if message.tool_name not in tool_names: + # message.tool_answer = "\n**Observation:** there is no tool can execute\n" + # message.observation = "\n**Observation:** there is no tool can execute\n" + # # message.role_content += f"\n**Observation:**: 不存在可以执行的tool\n" + # message.step_content += f"\n**Observation:** there is no tool can execute\n" + # message.step_contents += [f"\n**Observation:** there is no tool can execute\n"] + # observation_message.role_content = f"\n**Observation:** there is no tool can execute\n" + # observation_message.parsed_output = {"Observation": "there is no tool can execute\n"} + # for tool in message.tools: + # if tool.name == message.tool_name: + # tool_res = tool.func(**message.tool_params.get("tool_params", {})) + # logger.debug(f"tool_res {tool_res}") + # message.tool_answer = tool_res + # message.observation = tool_res + # # message.role_content += f"\n**Observation:**: {tool_res}\n" + # message.step_content += f"\n**Observation:** {tool_res}\n" + # message.step_contents += [f"\n**Observation:** {tool_res}\n"] + # observation_message.role_content = f"\n**Observation:** {tool_res}\n" + # observation_message.parsed_output = {"Observation": tool_res} + # break + + # # logger.info(f"**Observation:** {message.action_status}, {message.observation}") + # return message, observation_message - def _parse_json(self, s): - try: - pattern = r"```([^`]+)```" - match = re.findall(pattern, s) - if match: - return eval(match[0]) - except: - pass - return None + # def parser(self, message: Message) -> Message: + # '''''' + # content = message.role_content + # parser_keys = ["action", "code_content", "code_filename", "tool_params", "plans"] + # try: + # s_json = self._parse_json(content) + # message.action_status = s_json.get("action") + # message.code_content = s_json.get("code_content") + # message.tool_params = s_json.get("tool_params") + # message.tool_name = s_json.get("tool_name") + # message.code_filename = s_json.get("code_filename") + # message.plans = s_json.get("plans") + # # for parser_key in parser_keys: + # # message.action_status = content.get(parser_key) + # except Exception as e: + # # logger.warning(f"{traceback.format_exc()}") + # def parse_text_to_dict(text): + # # Define a regular expression pattern to capture the key and value + # main_pattern = r"\*\*(.+?):\*\*\s*(.*?)\s*(?=\*\*|$)" + # list_pattern = r'```python\n(.*?)```' + + # # Use re.findall to find all main matches in the text + # main_matches = re.findall(main_pattern, text, re.DOTALL) + + # # Convert main matches to a dictionary + # parsed_dict = {key.strip(): value.strip() for key, value in main_matches} + + # for k, v in parsed_dict.items(): + # for pattern in [list_pattern]: + # if "PLAN" != k: continue + # match_value = re.search(pattern, v, re.DOTALL) + # if match_value: + # # Add the code block to the dictionary + # parsed_dict[k] = eval(match_value.group(1).strip()) + # break + + # return parsed_dict + + # def extract_content_from_backticks(text): + # code_blocks = [] + # lines = text.split('\n') + # is_code_block = False + # code_block = '' + # language = '' + # for line in lines: + # if line.startswith('```') and not is_code_block: + # is_code_block = True + # language = line[3:] + # code_block = '' + # elif line.startswith('```') and is_code_block: + # is_code_block = False + # code_blocks.append({language.strip(): code_block.strip()}) + # elif is_code_block: + # code_block += line + '\n' + # return code_blocks + + # def parse_dict_to_dict(parsed_dict): + # code_pattern = r'```python\n(.*?)```' + # tool_pattern = r'```tool_params\n(.*?)```' + + # pattern_dict = {"code": code_pattern, "tool_params": tool_pattern} + # spec_parsed_dict = copy.deepcopy(parsed_dict) + # for key, pattern in pattern_dict.items(): + # for k, text in parsed_dict.items(): + # # Search for the code block + # if not isinstance(text, str): continue + # _match = re.search(pattern, text, re.DOTALL) + # if _match: + # # Add the code block to the dictionary + # try: + # spec_parsed_dict[key] = json.loads(_match.group(1).strip()) + # except: + # spec_parsed_dict[key] = _match.group(1).strip() + # break + # return spec_parsed_dict + # def parse_dict_to_dict(parsed_dict): + # code_pattern = r'```python\n(.*?)```' + # tool_pattern = r'```json\n(.*?)```' + + # pattern_dict = {"code": code_pattern, "json": tool_pattern} + # spec_parsed_dict = copy.deepcopy(parsed_dict) + # for key, pattern in pattern_dict.items(): + # for k, text in parsed_dict.items(): + # # Search for the code block + # if not isinstance(text, str): continue + # _match = re.search(pattern, text, re.DOTALL) + # if _match: + # # Add the code block to the dictionary + # logger.debug(f"dsadsa {text}") + # try: + # spec_parsed_dict[key] = json.loads(_match.group(1).strip()) + # except: + # spec_parsed_dict[key] = _match.group(1).strip() + # break + # return spec_parsed_dict + + # parsed_dict = parse_text_to_dict(content) + # spec_parsed_dict = parse_dict_to_dict(parsed_dict) + # action_value = parsed_dict.get('Action Status') + # if action_value: + # action_value = action_value.lower() + # logger.info(f'{self.role.role_name}: action_value: {action_value}') + # # action_value = self._match(r"'action':\s*'([^']*)'", content) if "'action'" in content else self._match(r'"action":\s*"([^"]*)"', content) + + # code_content_value = spec_parsed_dict.get('code') + # # code_content_value = self._match(r"'code_content':\s*'([^']*)'", content) if "'code_content'" in content else self._match(r'"code_content":\s*"([^"]*)"', content) + # filename_value = self._match(r"'code_filename':\s*'([^']*)'", content) if "'code_filename'" in content else self._match(r'"code_filename":\s*"([^"]*)"', content) + # tool_params_value = spec_parsed_dict.get('tool_params') + # # tool_params_value = self._match(r"'tool_params':\s*(\{[^{}]*\})", content, do_json=True) if "'tool_params'" in content \ + # # else self._match(r'"tool_params":\s*(\{[^{}]*\})', content, do_json=True) + # tool_name_value = self._match(r"'tool_name':\s*'([^']*)'", content) if "'tool_name'" in content else self._match(r'"tool_name":\s*"([^"]*)"', content) + # plans_value = self._match(r"'plans':\s*(\[.*?\])", content, do_search=False) if "'plans'" in content else self._match(r'"plans":\s*(\[.*?\])', content, do_search=False, ) + # # re解析 + # message.action_status = action_value or "default" + # message.code_content = code_content_value + # message.code_filename = filename_value + # message.tool_params = tool_params_value + # message.tool_name = tool_name_value + # message.plans = plans_value + # message.parsed_output = parsed_dict + # message.spec_parsed_output = spec_parsed_dict + # code_content_value = spec_parsed_dict.get('code') + # # code_content_value = self._match(r"'code_content':\s*'([^']*)'", content) if "'code_content'" in content else self._match(r'"code_content":\s*"([^"]*)"', content) + # filename_value = self._match(r"'code_filename':\s*'([^']*)'", content) if "'code_filename'" in content else self._match(r'"code_filename":\s*"([^"]*)"', content) + # logger.debug(spec_parsed_dict) + + # if action_value == 'tool_using': + # tool_params_value = spec_parsed_dict.get('json') + # else: + # tool_params_value = None + # # tool_params_value = self._match(r"'tool_params':\s*(\{[^{}]*\})", content, do_json=True) if "'tool_params'" in content \ + # # else self._match(r'"tool_params":\s*(\{[^{}]*\})', content, do_json=True) + # tool_name_value = self._match(r"'tool_name':\s*'([^']*)'", content) if "'tool_name'" in content else self._match(r'"tool_name":\s*"([^"]*)"', content) + # plans_value = self._match(r"'plans':\s*(\[.*?\])", content, do_search=False) if "'plans'" in content else self._match(r'"plans":\s*(\[.*?\])', content, do_search=False, ) + # # re解析 + # message.action_status = action_value or "default" + # message.code_content = code_content_value + # message.code_filename = filename_value + # message.tool_params = tool_params_value + # message.tool_name = tool_name_value + # message.plans = plans_value + # message.parsed_output = parsed_dict + # message.spec_parsed_output = spec_parsed_dict + + # # logger.debug(f"确认当前的action: {message.action_status}") + + # return message + + # def parser_spec_key(self, content, key, do_search=True, do_json=False) -> str: + # '''''' + # key2pattern = { + # "'action'": r"'action':\s*'([^']*)'", '"action"': r'"action":\s*"([^"]*)"', + # "'code_content'": r"'code_content':\s*'([^']*)'", '"code_content"': r'"code_content":\s*"([^"]*)"', + # "'code_filename'": r"'code_filename':\s*'([^']*)'", '"code_filename"': r'"code_filename":\s*"([^"]*)"', + # "'tool_params'": r"'tool_params':\s*(\{[^{}]*\})", '"tool_params"': r'"tool_params":\s*(\{[^{}]*\})', + # "'tool_name'": r"'tool_name':\s*'([^']*)'", '"tool_name"': r'"tool_name":\s*"([^"]*)"', + # "'plans'": r"'plans':\s*(\[.*?\])", '"plans"': r'"plans":\s*(\[.*?\])', + # "'content'": r"'content':\s*'([^']*)'", '"content"': r'"content":\s*"([^"]*)"', + # } + + # s_json = self._parse_json(content) + # try: + # if s_json and key in s_json: + # return str(s_json[key]) + # except: + # pass + + # keystr = f"'{key}'" if f"'{key}'" in content else f'"{key}"' + # return self._match(key2pattern.get(keystr, fr"'{key}':\s*'([^']*)'"), content, do_search=do_search, do_json=do_json) + + # def _match(self, pattern, s, do_search=True, do_json=False): + # try: + # if do_search: + # match = re.search(pattern, s) + # if match: + # value = match.group(1).replace("\\n", "\n") + # if do_json: + # value = json.loads(value) + # else: + # value = None + # else: + # match = re.findall(pattern, s, re.DOTALL) + # if match: + # value = match[0] + # if do_json: + # value = json.loads(value) + # else: + # value = None + # except Exception as e: + # logger.warning(f"{traceback.format_exc()}") + + # # logger.debug(f"pattern: {pattern}, s: {s}, match: {match}") + # return value + + # def _parse_json(self, s): + # try: + # pattern = r"```([^`]+)```" + # match = re.findall(pattern, s) + # if match: + # return eval(match[0]) + # except: + # pass + # return None - def get_memory(self, ): + def get_memory(self, content_key="role_content"): return self.memory.to_tuple_messages(content_key="step_content") - def get_memory_str(self, ): + def get_memory_str(self, content_key="role_content"): return "\n".join([": ".join(i) for i in self.memory.to_tuple_messages(content_key="step_content")]) \ No newline at end of file diff --git a/dev_opsgpt/connector/agents/check_agent.py b/dev_opsgpt/connector/agents/check_agent.py new file mode 100644 index 0000000..7e2ea63 --- /dev/null +++ b/dev_opsgpt/connector/agents/check_agent.py @@ -0,0 +1,110 @@ +from pydantic import BaseModel +from typing import List, Union +import re +import json +import traceback +import copy +from loguru import logger + +from langchain.prompts.chat import ChatPromptTemplate + +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, ActionStatus +) +from dev_opsgpt.llm_models import getChatModel +from dev_opsgpt.connector.configs.agent_config import REACT_PROMPT_INPUT, CONTEXT_PROMPT_INPUT, QUERY_CONTEXT_PROMPT_INPUT + +from .base_agent import BaseAgent + + +class CheckAgent(BaseAgent): + def __init__( + self, + role: Role, + task: Task = None, + memory: Memory = None, + chat_turn: int = 1, + do_search: bool = False, + do_doc_retrieval: bool = False, + do_tool_retrieval: bool = False, + temperature: float = 0.2, + stop: Union[List[str], str] = None, + do_filter: bool = True, + do_use_self_memory: bool = True, + focus_agents: List[str] = [], + focus_message_keys: List[str] = [], + # prompt_mamnger: PromptManager + ): + + super().__init__(role, task, memory, chat_turn, do_search, do_doc_retrieval, + do_tool_retrieval, temperature, stop, do_filter,do_use_self_memory, + focus_agents, focus_message_keys + ) + + def create_prompt( + self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, memory_pool: Memory=None, prompt_mamnger=None) -> str: + ''' + role\task\tools\docs\memory + ''' + # + doc_infos = self.create_doc_prompt(query) + code_infos = self.create_codedoc_prompt(query) + # + formatted_tools, tool_names = self.create_tools_prompt(query) + task_prompt = self.create_task_prompt(query) + background_prompt = self.create_background_prompt(background) + history_prompt = self.create_history_prompt(history) + selfmemory_prompt = self.create_selfmemory_prompt(memory, control_key="step_content") + + # react 流程是自身迭代过程,另外二次触发的是需要作为历史对话信息 + # input_query = react_memory.to_tuple_messages(content_key="step_content") + input_query = query.input_query + + # logger.debug(f"{self.role.role_name} extra_system_prompt: {self.role.role_prompt}") + # logger.debug(f"{self.role.role_name} input_query: {input_query}") + # logger.debug(f"{self.role.role_name} doc_infos: {doc_infos}") + # logger.debug(f"{self.role.role_name} tool_names: {tool_names}") + # prompt += "\n" + CHECK_PROMPT_INPUT.format(**{"query": input_query}) + # prompt.format(**{"query": input_query}) + + # extra_system_prompt = self.role.role_prompt + prompt = self.role.role_prompt.format(**{"query": input_query, "formatted_tools": formatted_tools, "tool_names": tool_names}) + + if "**Context:**" in self.role.role_prompt: + # logger.debug(f"parsed_output_list: {query.parsed_output_list}") + # input_query = "'''" + "\n".join([f"*{k}*\n{v}" for i in background.get_parserd_output_list() for k,v in i.items() if "Action Status" !=k]) + "'''" + context = "\n".join([f"*{k}*\n{v}" for i in background.get_parserd_output_list() for k,v in i.items() if "Action Status" !=k]) + # logger.debug(context) + # logger.debug(f"parsed_output_list: {t}") + prompt += "\n" + QUERY_CONTEXT_PROMPT_INPUT.format(**{"query": query.origin_query, "context": context}) + else: + prompt += "\n" + REACT_PROMPT_INPUT.format(**{"query": input_query}) + + + task = query.task or self.task + if task_prompt is not None: + prompt += "\n" + task.task_prompt + + # if doc_infos is not None and doc_infos!="" and doc_infos!="不存在知识库辅助信息": + # prompt += f"\n知识库信息: {doc_infos}" + + # if code_infos is not None and code_infos!="" and code_infos!="不存在代码库辅助信息": + # prompt += f"\n代码库信息: {code_infos}" + + # if background_prompt: + # prompt += "\n" + background_prompt + + # if history_prompt: + # prompt += "\n" + history_prompt + + # if selfmemory_prompt: + # prompt += "\n" + selfmemory_prompt + + # prompt = extra_system_prompt.format(**{"query": input_query, "doc_infos": doc_infos, "formatted_tools": formatted_tools, "tool_names": tool_names}) + while "{{" in prompt or "}}" in prompt: + prompt = prompt.replace("{{", "{") + prompt = prompt.replace("}}", "}") + + # logger.debug(f"{self.role.role_name} prompt: {prompt}") + return prompt + diff --git a/dev_opsgpt/connector/agents/executor_agent.py b/dev_opsgpt/connector/agents/executor_agent.py new file mode 100644 index 0000000..d92222e --- /dev/null +++ b/dev_opsgpt/connector/agents/executor_agent.py @@ -0,0 +1,214 @@ +from pydantic import BaseModel +from typing import List, Union, Tuple, Any +import re +import json +import traceback +import copy +from loguru import logger + +from langchain.prompts.chat import ChatPromptTemplate + +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, ActionStatus +) +from dev_opsgpt.llm_models import getChatModel +from dev_opsgpt.connector.configs.prompts import EXECUTOR_PROMPT_INPUT, BEGIN_PROMPT_INPUT +from dev_opsgpt.connector.utils import parse_section + +from .base_agent import BaseAgent + + +class ExecutorAgent(BaseAgent): + def __init__( + self, + role: Role, + task: Task = None, + memory: Memory = None, + chat_turn: int = 1, + do_search: bool = False, + do_doc_retrieval: bool = False, + do_tool_retrieval: bool = False, + temperature: float = 0.2, + stop: Union[List[str], str] = None, + do_filter: bool = True, + do_use_self_memory: bool = True, + focus_agents: List[str] = [], + focus_message_keys: List[str] = [], + # prompt_mamnger: PromptManager + ): + + super().__init__(role, task, memory, chat_turn, do_search, do_doc_retrieval, + do_tool_retrieval, temperature, stop, do_filter,do_use_self_memory, + focus_agents, focus_message_keys + ) + self.do_all_task = True # run all tasks + + def arun(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory=None) -> Message: + '''agent reponse from multi-message''' + # insert query into memory + task_executor_memory = Memory(messages=[]) + # insert query + output_message = Message( + role_name=self.role.role_name, + role_type="ai", #self.role.role_type, + role_content=query.input_query, + step_content="", + input_query=query.input_query, + tools=query.tools, + parsed_output_list=[query.parsed_output] + ) + + self_memory = self.memory if self.do_use_self_memory else None + + plan_step = int(query.parsed_output.get("PLAN_STEP", 0)) + # 如果存在plan字段且plan字段为str的时候 + if "PLAN" not in query.parsed_output or isinstance(query.parsed_output.get("PLAN", []), str) or plan_step >= len(query.parsed_output.get("PLAN", [])): + query_c = copy.deepcopy(query) + query_c.parsed_output = {"Question": query_c.input_query} + task_executor_memory.append(query_c) + for output_message, task_executor_memory in self._arun_step(output_message, query_c, self_memory, history, background, memory_pool, task_executor_memory): + pass + # task_executor_memory.append(query_c) + # content = "the execution step of the plan is exceed the planned scope." + # output_message.parsed_dict = {"Thought": content, "Action Status": "finished", "Action": content} + # task_executor_memory.append(output_message) + + elif "PLAN" in query.parsed_output: + logger.debug(f"{query.parsed_output['PLAN']}") + if self.do_all_task: + # run all tasks step by step + for task_content in query.parsed_output["PLAN"][plan_step:]: + # create your llm prompt + query_c = copy.deepcopy(query) + query_c.parsed_output = {"Question": task_content} + task_executor_memory.append(query_c) + for output_message, task_executor_memory in self._arun_step(output_message, query_c, self_memory, history, background, memory_pool, task_executor_memory): + pass + yield output_message + else: + query_c = copy.deepcopy(query) + task_content = query_c.parsed_output["PLAN"][plan_step] + query_c.parsed_output = {"Question": task_content} + task_executor_memory.append(query_c) + for output_message, task_executor_memory in self._arun_step(output_message, query_c, self_memory, history, background, memory_pool, task_executor_memory): + pass + output_message.parsed_output.update({"CURRENT_STEP": plan_step}) + # update self_memory + self.append_history(query) + self.append_history(output_message) + # logger.info(f"{self.role.role_name} currenct question: {output_message.input_query}\nllm_executor_run: {output_message.step_content}") + # logger.info(f"{self.role.role_name} currenct parserd_output_list: {output_message.parserd_output_list}") + output_message.input_query = output_message.role_content + # update memory pool + memory_pool.append(output_message) + yield output_message + + def _arun_step(self, output_message: Message, query: Message, self_memory: Memory, + history: Memory, background: Memory, memory_pool: Memory, + react_memory: Memory) -> Union[Message, Memory]: + '''execute the llm predict by created prompt''' + prompt = self.create_prompt(query, self_memory, history, background, memory_pool=memory_pool, react_memory=react_memory) + content = self.llm.predict(prompt) + # logger.debug(f"{self.role.role_name} prompt: {prompt}") + logger.debug(f"{self.role.role_name} content: {content}") + + output_message.role_content = content + output_message.role_contents += [content] + output_message.step_content += "\n"+output_message.role_content + output_message.step_contents + [output_message.role_content] + + output_message = self.message_utils.parser(output_message) + # according the output to choose one action for code_content or tool_content + output_message, observation_message = self.message_utils.step_router(output_message) + # logger.debug(f"{self.role.role_name} content: {content}") + # update parserd_output_list + output_message.parsed_output_list.append(output_message.parsed_output) + + react_message = copy.deepcopy(output_message) + react_memory.append(react_message) + if observation_message: + react_memory.append(observation_message) + output_message.parsed_output_list.append(observation_message.parsed_output) + logger.debug(f"{observation_message.role_name} content: {observation_message.role_content}") + yield output_message, react_memory + + def create_prompt( + self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, memory_pool: Memory=None, react_memory: Memory = None, prompt_mamnger=None) -> str: + ''' + role\task\tools\docs\memory + ''' + # + doc_infos = self.create_doc_prompt(query) + code_infos = self.create_codedoc_prompt(query) + # + formatted_tools, tool_names = self.create_tools_prompt(query) + task_prompt = self.create_task_prompt(query) + background_prompt = self.create_background_prompt(background, control_key="step_content") + history_prompt = self.create_history_prompt(history) + selfmemory_prompt = self.create_selfmemory_prompt(memory, control_key="step_content") + + # + memory_pool_select_by_agent_key = self.select_memory_by_agent_key(memory_pool) + memory_pool_select_by_agent_key_context = '\n\n'.join([ + f"*{k}*\n{v}" for parsed_output in memory_pool_select_by_agent_key.get_parserd_output_list() for k, v in parsed_output.items() if k not in ['Action Status'] + ]) + + DocInfos = "" + if doc_infos is not None and doc_infos!="" and doc_infos!="不存在知识库辅助信息": + DocInfos += f"\nDocument Information: {doc_infos}" + + if code_infos is not None and code_infos!="" and code_infos!="不存在代码库辅助信息": + DocInfos += f"\nCodeBase Infomation: {code_infos}" + + # extra_system_prompt = self.role.role_prompt + prompt = self.role.role_prompt.format(**{"formatted_tools": formatted_tools, "tool_names": tool_names}) + + # input_query = react_memory.to_tuple_messages(content_key="role_content") + # logger.debug(f"get_parserd_dict {react_memory.get_parserd_output()}") + input_query = "\n".join(["\n".join([f"**{k}:**\n{v}" for k,v in _dict.items()]) for _dict in react_memory.get_parserd_output()]) + # input_query = query.input_query + "\n".join([f"{v}" for k, v in input_query if v]) + last_agent_parsed_output = "\n".join(["\n".join([f"*{k}*\n{v}" for k,v in _dict.items()]) for _dict in query.parsed_output_list]) + react_parsed_output = "\n".join(["\n".join([f"*{k}_context*\n{v}" for k,v in _dict.items()]) for _dict in react_memory.get_parserd_output()[:-1]]) + # + prompt += "\n" + BEGIN_PROMPT_INPUT + + input_keys = parse_section(self.role.role_prompt, 'Input Format') + if input_keys: + for input_key in input_keys: + if input_key == "Origin Query": + prompt += "\n**Origin Query:**\n" + query.origin_query + elif input_key == "DocInfos": + prompt += "\n**DocInfos:**\n" + DocInfos + elif input_key == "Context": + if self.focus_agents and memory_pool_select_by_agent_key_context: + context = memory_pool_select_by_agent_key_context + else: + context = last_agent_parsed_output + prompt += "\n**Context:**\n" + context + f"\n{react_parsed_output}" + elif input_key == "Question": + prompt += "\n**Question:**\n" + query.parsed_output.get("Question") + else: + prompt += "\n" + input_query + + task = query.task or self.task + # if task_prompt is not None: + # prompt += "\n" + task.task_prompt + + # if selfmemory_prompt: + # prompt += "\n" + selfmemory_prompt + + # if background_prompt: + # prompt += "\n" + background_prompt + + # if history_prompt: + # prompt += "\n" + history_prompt + + # prompt = extra_system_prompt.format(**{"query": input_query, "doc_infos": doc_infos, "formatted_tools": formatted_tools, "tool_names": tool_names}) + while "{{" in prompt or "}}" in prompt: + prompt = prompt.replace("{{", "{") + prompt = prompt.replace("}}", "}") + return prompt + + def set_task(self, do_all_task): + '''set task exec type''' + self.do_all_task = do_all_task \ No newline at end of file diff --git a/dev_opsgpt/connector/agents/react_agent.py b/dev_opsgpt/connector/agents/react_agent.py index 4625f74..e20b049 100644 --- a/dev_opsgpt/connector/agents/react_agent.py +++ b/dev_opsgpt/connector/agents/react_agent.py @@ -1,15 +1,16 @@ from pydantic import BaseModel from typing import List, Union import re +import json import traceback import copy from loguru import logger from langchain.prompts.chat import ChatPromptTemplate -from dev_opsgpt.connector.connector_schema import Message -from dev_opsgpt.connector.shcema.memory import Memory -from dev_opsgpt.connector.connector_schema import Task, Env, Role, Message, ActionStatus +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, ActionStatus +) from dev_opsgpt.llm_models import getChatModel from dev_opsgpt.connector.configs.agent_config import REACT_PROMPT_INPUT @@ -27,63 +28,93 @@ class ReactAgent(BaseAgent): do_doc_retrieval: bool = False, do_tool_retrieval: bool = False, temperature: float = 0.2, - stop: Union[List[str], str] = "观察", + stop: Union[List[str], str] = None, do_filter: bool = True, do_use_self_memory: bool = True, - # docs_prompt: str, + focus_agents: List[str] = [], + focus_message_keys: List[str] = [], # prompt_mamnger: PromptManager ): + super().__init__(role, task, memory, chat_turn, do_search, do_doc_retrieval, - do_tool_retrieval, temperature, stop, do_filter,do_use_self_memory + do_tool_retrieval, temperature, stop, do_filter,do_use_self_memory, + focus_agents, focus_message_keys ) - def run(self, query: Message, history: Memory = None, background: Memory = None) -> Message: + def run(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory = None) -> Message: + '''agent reponse from multi-message''' + for message in self.arun(query, history, background, memory_pool): + pass + return message + + def arun(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory = None) -> Message: + '''agent reponse from multi-message''' step_nums = copy.deepcopy(self.chat_turn) - react_memory = Memory([]) - # 问题插入 + react_memory = Memory(messages=[]) + # insert query output_message = Message( role_name=self.role.role_name, role_type="ai", #self.role.role_type, role_content=query.input_query, - step_content=query.input_query, + step_content="", input_query=query.input_query, - tools=query.tools + tools=query.tools, + parsed_output_list=[query.parsed_output] ) - react_memory.append(output_message) + query_c = copy.deepcopy(query) + query_c.parsed_output = {"Question": "\n".join([f"{v}" for k, v in query.parsed_output.items() if k not in ["Action Status"]])} + react_memory.append(query_c) + self_memory = self.memory if self.do_use_self_memory else None idx = 0 + # start to react while step_nums > 0: output_message.role_content = output_message.step_content - self_memory = self.memory if self.do_use_self_memory else None - prompt = self.create_prompt(query, self_memory, history, background, react_memory) + prompt = self.create_prompt(query, self_memory, history, background, react_memory, memory_pool) try: content = self.llm.predict(prompt) except Exception as e: logger.warning(f"error prompt: {prompt}") raise Exception(traceback.format_exc()) - output_message.role_content = content + output_message.role_content = "\n"+content output_message.role_contents += [content] - output_message.step_content += output_message.role_content + output_message.step_content += "\n"+output_message.role_content output_message.step_contents + [output_message.role_content] + yield output_message # logger.debug(f"{self.role.role_name}, {idx} iteration prompt: {prompt}") - # logger.info(f"{self.role.role_name}, {idx} iteration step_run: {output_message.role_content}") + logger.info(f"{self.role.role_name}, {idx} iteration step_run: {output_message.role_content}") - output_message = self.parser(output_message) + output_message = self.message_utils.parser(output_message) # when get finished signal can stop early if output_message.action_status == ActionStatus.FINISHED: break # according the output to choose one action for code_content or tool_content - output_message = self.step_router(output_message) - logger.info(f"{self.role.role_name} react_run: {output_message.role_content}") + output_message, observation_message = self.message_utils.step_router(output_message) + output_message.parsed_output_list.append(output_message.parsed_output) + react_message = copy.deepcopy(output_message) + react_memory.append(react_message) + if observation_message: + react_memory.append(observation_message) + output_message.parsed_output_list.append(observation_message.parsed_output) + # logger.debug(f"{observation_message.role_name} content: {observation_message.role_content}") + # logger.info(f"{self.role.role_name} currenct question: {output_message.input_query}\nllm_react_run: {output_message.role_content}") + idx += 1 step_nums -= 1 + yield output_message # react' self_memory saved at last self.append_history(output_message) - return output_message - + # update memory pool + # memory_pool.append(output_message) + output_message.input_query = query.input_query + # update memory pool + memory_pool.append(output_message) + yield output_message + def create_prompt( - self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, react_memory: Memory = None, prompt_mamnger=None) -> str: + self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, react_memory: Memory = None, memory_pool: Memory= None, + prompt_mamnger=None) -> str: ''' role\task\tools\docs\memory ''' @@ -100,35 +131,39 @@ class ReactAgent(BaseAgent): # extra_system_prompt = self.role.role_prompt prompt = self.role.role_prompt.format(**{"formatted_tools": formatted_tools, "tool_names": tool_names}) + # react 流程是自身迭代过程,另外二次触发的是需要作为历史对话信息 + # input_query = react_memory.to_tuple_messages(content_key="step_content") + # # input_query = query.input_query + "\n" + "\n".join([f"{v}" for k, v in input_query if v]) + # input_query = "\n".join([f"{v}" for k, v in input_query if v]) + input_query = "\n".join(["\n".join([f"**{k}:**\n{v}" for k,v in _dict.items()]) for _dict in react_memory.get_parserd_output()]) + logger.debug(f"input_query: {input_query}") + + prompt += "\n" + REACT_PROMPT_INPUT.format(**{"query": input_query}) task = query.task or self.task - if task_prompt is not None: - prompt += "\n" + task.task_prompt + # if task_prompt is not None: + # prompt += "\n" + task.task_prompt - if doc_infos is not None and doc_infos!="" and doc_infos!="不存在知识库辅助信息": - prompt += f"\n知识库信息: {doc_infos}" + # if doc_infos is not None and doc_infos!="" and doc_infos!="不存在知识库辅助信息": + # prompt += f"\n知识库信息: {doc_infos}" - if code_infos is not None and code_infos!="" and code_infos!="不存在代码库辅助信息": - prompt += f"\n代码库信息: {code_infos}" + # if code_infos is not None and code_infos!="" and code_infos!="不存在代码库辅助信息": + # prompt += f"\n代码库信息: {code_infos}" - if background_prompt: - prompt += "\n" + background_prompt + # if background_prompt: + # prompt += "\n" + background_prompt - if history_prompt: - prompt += "\n" + history_prompt + # if history_prompt: + # prompt += "\n" + history_prompt - if selfmemory_prompt: - prompt += "\n" + selfmemory_prompt - - # react 流程是自身迭代过程,另外二次触发的是需要作为历史对话信息 - input_query = react_memory.to_tuple_messages(content_key="step_content") - input_query = "\n".join([f"{v}" for k, v in input_query if v]) + # if selfmemory_prompt: + # prompt += "\n" + selfmemory_prompt # logger.debug(f"{self.role.role_name} extra_system_prompt: {self.role.role_prompt}") # logger.debug(f"{self.role.role_name} input_query: {input_query}") # logger.debug(f"{self.role.role_name} doc_infos: {doc_infos}") # logger.debug(f"{self.role.role_name} tool_names: {tool_names}") - prompt += "\n" + REACT_PROMPT_INPUT.format(**{"query": input_query}) + # prompt += "\n" + REACT_PROMPT_INPUT.format(**{"query": input_query}) # prompt = extra_system_prompt.format(**{"query": input_query, "doc_infos": doc_infos, "formatted_tools": formatted_tools, "tool_names": tool_names}) while "{{" in prompt or "}}" in prompt: diff --git a/dev_opsgpt/connector/agents/selector_agent.py b/dev_opsgpt/connector/agents/selector_agent.py new file mode 100644 index 0000000..951ec87 --- /dev/null +++ b/dev_opsgpt/connector/agents/selector_agent.py @@ -0,0 +1,109 @@ +from pydantic import BaseModel +from typing import List, Union +import re +import json +import traceback +import copy +from loguru import logger + +from langchain.prompts.chat import ChatPromptTemplate + +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, ActionStatus +) +from dev_opsgpt.llm_models import getChatModel +from dev_opsgpt.connector.configs.agent_config import REACT_PROMPT_INPUT, CONTEXT_PROMPT_INPUT, QUERY_CONTEXT_PROMPT_INPUT + +from .base_agent import BaseAgent + + +class SelectorAgent(BaseAgent): + def __init__( + self, + role: Role, + task: Task = None, + memory: Memory = None, + chat_turn: int = 1, + do_search: bool = False, + do_doc_retrieval: bool = False, + do_tool_retrieval: bool = False, + temperature: float = 0.2, + stop: Union[List[str], str] = None, + do_filter: bool = True, + do_use_self_memory: bool = True, + focus_agents: List[str] = [], + focus_message_keys: List[str] = [], + # prompt_mamnger: PromptManager + ): + + super().__init__(role, task, memory, chat_turn, do_search, do_doc_retrieval, + do_tool_retrieval, temperature, stop, do_filter,do_use_self_memory, + focus_agents, focus_message_keys + ) + + def create_prompt( + self, query: Message, memory: Memory =None, history: Memory = None, background: Memory = None, memory_pool: Memory=None, prompt_mamnger=None) -> str: + ''' + role\task\tools\docs\memory + ''' + # + doc_infos = self.create_doc_prompt(query) + code_infos = self.create_codedoc_prompt(query) + # + formatted_tools, tool_names = self.create_tools_prompt(query) + task_prompt = self.create_task_prompt(query) + background_prompt = self.create_background_prompt(background) + history_prompt = self.create_history_prompt(history) + selfmemory_prompt = self.create_selfmemory_prompt(memory, control_key="step_content") + + # react 流程是自身迭代过程,另外二次触发的是需要作为历史对话信息 + # input_query = react_memory.to_tuple_messages(content_key="step_content") + input_query = query.input_query + + # logger.debug(f"{self.role.role_name} extra_system_prompt: {self.role.role_prompt}") + logger.debug(f"{self.role.role_name} input_query: {input_query}") + # logger.debug(f"{self.role.role_name} doc_infos: {doc_infos}") + # logger.debug(f"{self.role.role_name} tool_names: {tool_names}") + # prompt += "\n" + CHECK_PROMPT_INPUT.format(**{"query": input_query}) + # prompt.format(**{"query": input_query}) + + # extra_system_prompt = self.role.role_prompt + prompt = self.role.role_prompt.format(**{"query": input_query, "formatted_tools": formatted_tools, "tool_names": tool_names}) + + if "**Context:**" in self.role.role_prompt: + # logger.debug(f"parsed_output_list: {query.parsed_output_list}") + # input_query = "'''" + "\n".join([f"*{k}*\n{v}" for i in background.get_parserd_output_list() for k,v in i.items() if "Action Status" !=k]) + "'''" + context = "\n".join([f"*{k}*\n{v}" for i in background.get_parserd_output_list() for k,v in i.items() if "Action Status" !=k]) + # logger.debug(f"parsed_output_list: {t}") + prompt += "\n" + QUERY_CONTEXT_PROMPT_INPUT.format(**{"query": query.origin_query, "context": context}) + else: + prompt += "\n" + REACT_PROMPT_INPUT.format(**{"query": input_query}) + + + task = query.task or self.task + if task_prompt is not None: + prompt += "\n" + task.task_prompt + + # if doc_infos is not None and doc_infos!="" and doc_infos!="不存在知识库辅助信息": + # prompt += f"\n知识库信息: {doc_infos}" + + # if code_infos is not None and code_infos!="" and code_infos!="不存在代码库辅助信息": + # prompt += f"\n代码库信息: {code_infos}" + + # if background_prompt: + # prompt += "\n" + background_prompt + + # if history_prompt: + # prompt += "\n" + history_prompt + + # if selfmemory_prompt: + # prompt += "\n" + selfmemory_prompt + + # prompt = extra_system_prompt.format(**{"query": input_query, "doc_infos": doc_infos, "formatted_tools": formatted_tools, "tool_names": tool_names}) + while "{{" in prompt or "}}" in prompt: + prompt = prompt.replace("{{", "{") + prompt = prompt.replace("}}", "}") + + # logger.debug(f"{self.role.role_name} prompt: {prompt}") + return prompt + diff --git a/dev_opsgpt/connector/chains/base_chain.py b/dev_opsgpt/connector/chains/base_chain.py index dc49755..59d6e9e 100644 --- a/dev_opsgpt/connector/chains/base_chain.py +++ b/dev_opsgpt/connector/chains/base_chain.py @@ -7,13 +7,14 @@ import traceback import uuid import copy -from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.agents import BaseAgent, CheckAgent from dev_opsgpt.tools.base_tool import BaseTools, Tool -from dev_opsgpt.connector.shcema.memory import Memory -from dev_opsgpt.connector.connector_schema import ( - Role, Message, ActionStatus, ChainConfig, + +from dev_opsgpt.connector.schema import ( + Memory, Role, Message, ActionStatus, ChainConfig, load_role_configs ) +from dev_opsgpt.connector.message_process import MessageUtils from dev_opsgpt.sandbox import PyCodeBox, CodeBoxResponse @@ -37,7 +38,7 @@ class BaseChain: self.agents = agents self.chat_turn = chat_turn self.do_checker = do_checker - self.checker = BaseAgent(role=role_configs["checker"].role, + self.checker = CheckAgent(role=role_configs["checker"].role, task = None, memory = None, do_search = role_configs["checker"].do_search, @@ -45,37 +46,64 @@ class BaseChain: do_tool_retrieval = role_configs["checker"].do_tool_retrieval, do_filter=False, do_use_self_memory=False) - self.global_memory = Memory([]) - self.local_memory = Memory([]) - self.do_code_exec = do_code_exec - self.codebox = PyCodeBox( - remote_url=SANDBOX_SERVER["url"], - remote_ip=SANDBOX_SERVER["host"], - remote_port=SANDBOX_SERVER["port"], - token="mytoken", - do_code_exe=True, - do_remote=SANDBOX_SERVER["do_remote"], - do_check_net=False - ) + self.do_agent_selector = False + self.agent_selector = CheckAgent(role=role_configs["checker"].role, + task = None, + memory = None, + do_search = role_configs["checker"].do_search, + do_doc_retrieval = role_configs["checker"].do_doc_retrieval, + do_tool_retrieval = role_configs["checker"].do_tool_retrieval, + do_filter=False, do_use_self_memory=False) + + self.messageUtils = MessageUtils() + # all memory created by agent until instance deleted + self.global_memory = Memory(messages=[]) + # self.do_code_exec = do_code_exec + # self.codebox = PyCodeBox( + # remote_url=SANDBOX_SERVER["url"], + # remote_ip=SANDBOX_SERVER["host"], + # remote_port=SANDBOX_SERVER["port"], + # token="mytoken", + # do_code_exe=True, + # do_remote=SANDBOX_SERVER["do_remote"], + # do_check_net=False + # ) - def step(self, query: Message, history: Memory = None, background: Memory = None) -> Message: + def step(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory = None) -> Message: '''execute chain''' - local_memory = Memory([]) + for output_message, local_memory in self.astep(query, history, background, memory_pool): + pass + return output_message, local_memory + + def astep(self, query: Message, history: Memory = None, background: Memory = None, memory_pool: Memory = None) -> Message: + '''execute chain''' + local_memory = Memory(messages=[]) input_message = copy.deepcopy(query) step_nums = copy.deepcopy(self.chat_turn) check_message = None self.global_memory.append(input_message) - local_memory.append(input_message) + # local_memory.append(input_message) while step_nums > 0: - for agent in self.agents: - output_message = agent.run(input_message, history, background=background) - output_message = self.inherit_extrainfo(input_message, output_message) + if self.do_agent_selector: + agent_message = copy.deepcopy(query) + agent_message.agents = self.agents + for selectory_message in self.agent_selector.arun(query, background=self.global_memory, memory_pool=memory_pool): + pass + selectory_message = self.messageUtils.parser(selectory_message) + selectory_message = self.messageUtils.filter(selectory_message) + agent = self.agents[selectory_message.agent_index] + # selector agent to execure next task + for output_message in agent.arun(input_message, history, background=background, memory_pool=memory_pool): + # logger.debug(f"local_memory {local_memory + output_message}") + yield output_message, local_memory + output_message + + output_message = self.messageUtils.inherit_extrainfo(input_message, output_message) # according the output to choose one action for code_content or tool_content - logger.info(f"{agent.role.role_name} message: {output_message.role_content}") - output_message = self.parser(output_message) - # output_message = self.step_router(output_message) + # logger.info(f"{agent.role.role_name}\nmessage: {output_message.step_content}\nquery: {output_message.input_query}") + output_message = self.messageUtils.parser(output_message) + yield output_message, local_memory + output_message input_message = output_message self.global_memory.append(output_message) @@ -84,192 +112,243 @@ class BaseChain: # when get finished signal can stop early if output_message.action_status == ActionStatus.FINISHED: break + else: + for agent in self.agents: + for output_message in agent.arun(input_message, history, background=background, memory_pool=memory_pool): + # logger.debug(f"local_memory {local_memory + output_message}") + yield output_message, local_memory + output_message + + output_message = self.messageUtils.inherit_extrainfo(input_message, output_message) + # according the output to choose one action for code_content or tool_content + # logger.info(f"{agent.role.role_name} currenct message: {output_message.step_content}\n next llm question: {output_message.input_query}") + output_message = self.messageUtils.parser(output_message) + yield output_message, local_memory + output_message + # output_message = self.step_router(output_message) - if self.do_checker: - logger.debug(f"{self.checker.role.role_name} input global memory: {self.global_memory.to_str_messages(content_key='step_content')}") - check_message = self.checker.run(query, background=self.global_memory) - check_message = self.parser(check_message) - check_message = self.filter(check_message) - check_message = self.inherit_extrainfo(output_message, check_message) - logger.debug(f"{self.checker.role.role_name}: {check_message.role_content}") + input_message = output_message + self.global_memory.append(output_message) - if check_message.action_status == ActionStatus.FINISHED: - self.global_memory.append(check_message) - break + local_memory.append(output_message) + # when get finished signal can stop early + if output_message.action_status == ActionStatus.FINISHED: + action_status = False + break + + if self.do_checker and self.chat_turn > 1: + # logger.debug(f"{self.checker.role.role_name} input global memory: {self.global_memory.to_str_messages(content_key='step_content', return_all=False)}") + for check_message in self.checker.arun(query, background=local_memory, memory_pool=memory_pool): + pass + check_message = self.messageUtils.parser(check_message) + check_message = self.messageUtils.filter(check_message) + check_message = self.messageUtils.inherit_extrainfo(output_message, check_message) + logger.debug(f"{self.checker.role.role_name}: {check_message.role_content}") + + if check_message.action_status == ActionStatus.FINISHED: + self.global_memory.append(check_message) + break step_nums -= 1 - - return check_message or output_message, local_memory - - def step_router(self, message: Message) -> Message: - '''''' - # message = self.parser(message) - # logger.debug(f"message.action_status: {message.action_status}") - if message.action_status == ActionStatus.CODING: - message = self.code_step(message) - elif message.action_status == ActionStatus.TOOL_USING: - message = self.tool_step(message) - - return message - - def code_step(self, message: Message) -> Message: - '''execute code''' - # logger.debug(f"message.role_content: {message.role_content}, message.code_content: {message.code_content}") - code_answer = self.codebox.chat('```python\n{}```'.format(message.code_content)) - uid = str(uuid.uuid1()) - if code_answer.code_exe_type == "image/png": - message.figures[uid] = code_answer.code_exe_response - message.code_answer = f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n" - message.observation = f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n" - message.step_content += f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n" - message.step_contents += [f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n"] - message.role_content += f"\n执行代码后获得输出一张图片, 文件名为{uid}\n" - else: - message.code_answer = code_answer.code_exe_response - message.observation = code_answer.code_exe_response - message.step_content += f"\n观察: 执行代码后获得输出是 {code_answer.code_exe_response}\n" - message.step_contents += [f"\n观察: 执行代码后获得输出是 {code_answer.code_exe_response}\n"] - message.role_content += f"\n观察: 执行代码后获得输出是 {code_answer.code_exe_response}\n" - logger.info(f"观察: {message.action_status}, {message.observation}") - return message - - def tool_step(self, message: Message) -> Message: - '''execute tool''' - # logger.debug(f"message: {message.action_status}, {message.tool_name}, {message.tool_params}") - tool_names = [tool.name for tool in message.tools] - if message.tool_name not in tool_names: - message.tool_answer = "不存在可以执行的tool" - message.observation = "不存在可以执行的tool" - message.role_content += f"\n观察: 不存在可以执行的tool\n" - message.step_content += f"\n观察: 不存在可以执行的tool\n" - message.step_contents += [f"\n观察: 不存在可以执行的tool\n"] - for tool in message.tools: - if tool.name == message.tool_name: - tool_res = tool.func(**message.tool_params) - message.tool_answer = tool_res - message.observation = tool_res - message.role_content += f"\n观察: {tool_res}\n" - message.step_content += f"\n观察: {tool_res}\n" - message.step_contents += [f"\n观察: {tool_res}\n"] - return message - - def filter(self, message: Message, stop=None) -> Message: - - tool_params = self.parser_spec_key(message.role_content, "tool_params") - code_content = self.parser_spec_key(message.role_content, "code_content") - plan = self.parser_spec_key(message.role_content, "plan") - plans = self.parser_spec_key(message.role_content, "plans", do_search=False) - content = self.parser_spec_key(message.role_content, "content", do_search=False) - - # logger.debug(f"tool_params: {tool_params}, code_content: {code_content}, plan: {plan}, plans: {plans}, content: {content}") - role_content = tool_params or code_content or plan or plans or content - message.role_content = role_content or message.role_content - return message - - def parser(self, message: Message) -> Message: - '''''' - content = message.role_content - parser_keys = ["action", "code_content", "code_filename", "tool_params", "plans"] - try: - s_json = self._parse_json(content) - message.action_status = s_json.get("action") - message.code_content = s_json.get("code_content") - message.tool_params = s_json.get("tool_params") - message.tool_name = s_json.get("tool_name") - message.code_filename = s_json.get("code_filename") - message.plans = s_json.get("plans") - # for parser_key in parser_keys: - # message.action_status = content.get(parser_key) - except Exception as e: - # logger.warning(f"{traceback.format_exc()}") - action_value = self._match(r"'action':\s*'([^']*)'", content) if "'action'" in content else self._match(r'"action":\s*"([^"]*)"', content) - code_content_value = self._match(r"'code_content':\s*'([^']*)'", content) if "'code_content'" in content else self._match(r'"code_content":\s*"([^"]*)"', content) - filename_value = self._match(r"'code_filename':\s*'([^']*)'", content) if "'code_filename'" in content else self._match(r'"code_filename":\s*"([^"]*)"', content) - tool_params_value = self._match(r"'tool_params':\s*(\{[^{}]*\})", content, do_json=True) if "'tool_params'" in content \ - else self._match(r'"tool_params":\s*(\{[^{}]*\})', content, do_json=True) - tool_name_value = self._match(r"'tool_name':\s*'([^']*)'", content) if "'tool_name'" in content else self._match(r'"tool_name":\s*"([^"]*)"', content) - plans_value = self._match(r"'plans':\s*(\[.*?\])", content, do_search=False) if "'plans'" in content else self._match(r'"plans":\s*(\[.*?\])', content, do_search=False, ) - # re解析 - message.action_status = action_value or "default" - message.code_content = code_content_value - message.code_filename = filename_value - message.tool_params = tool_params_value - message.tool_name = tool_name_value - message.plans = plans_value - - logger.debug(f"确认当前的action: {message.action_status}") - - return message - - def parser_spec_key(self, content, key, do_search=True, do_json=False) -> str: - '''''' - key2pattern = { - "'action'": r"'action':\s*'([^']*)'", '"action"': r'"action":\s*"([^"]*)"', - "'code_content'": r"'code_content':\s*'([^']*)'", '"code_content"': r'"code_content":\s*"([^"]*)"', - "'code_filename'": r"'code_filename':\s*'([^']*)'", '"code_filename"': r'"code_filename":\s*"([^"]*)"', - "'tool_params'": r"'tool_params':\s*(\{[^{}]*\})", '"tool_params"': r'"tool_params":\s*(\{[^{}]*\})', - "'tool_name'": r"'tool_name':\s*'([^']*)'", '"tool_name"': r'"tool_name":\s*"([^"]*)"', - "'plans'": r"'plans':\s*(\[.*?\])", '"plans"': r'"plans":\s*(\[.*?\])', - "'content'": r"'content':\s*'([^']*)'", '"content"': r'"content":\s*"([^"]*)"', - } + # + output_message = check_message or output_message # 返回chain和checker的结果 + output_message.input_query = query.input_query # chain和chain之间消息通信不改变问题 + yield output_message, local_memory - s_json = self._parse_json(content) - try: - if s_json and key in s_json: - return str(s_json[key]) - except: - pass + # def step_router(self, message: Message) -> Message: + # '''''' + # # message = self.parser(message) + # # logger.debug(f"message.action_status: {message.action_status}") + # if message.action_status == ActionStatus.CODING: + # message = self.code_step(message) + # elif message.action_status == ActionStatus.TOOL_USING: + # message = self.tool_step(message) - keystr = f"'{key}'" if f"'{key}'" in content else f'"{key}"' - return self._match(key2pattern.get(keystr, fr"'{key}':\s*'([^']*)'"), content, do_search=do_search, do_json=do_json) - - def _match(self, pattern, s, do_search=True, do_json=False): - try: - if do_search: - match = re.search(pattern, s) - if match: - value = match.group(1).replace("\\n", "\n") - if do_json: - value = json.loads(value) - else: - value = None - else: - match = re.findall(pattern, s, re.DOTALL) - if match: - value = match[0] - if do_json: - value = json.loads(value) - else: - value = None - except Exception as e: - logger.warning(f"{traceback.format_exc()}") + # return message - # logger.debug(f"pattern: {pattern}, s: {s}, match: {match}") - return value - - def _parse_json(self, s): - try: - pattern = r"```([^`]+)```" - match = re.findall(pattern, s) - if match: - return eval(match[0]) - except: - pass - return None + # def code_step(self, message: Message) -> Message: + # '''execute code''' + # # logger.debug(f"message.role_content: {message.role_content}, message.code_content: {message.code_content}") + # code_answer = self.codebox.chat('```python\n{}```'.format(message.code_content)) + # uid = str(uuid.uuid1()) + # if code_answer.code_exe_type == "image/png": + # message.figures[uid] = code_answer.code_exe_response + # message.code_answer = f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n" + # message.observation = f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n" + # message.step_content += f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n" + # message.step_contents += [f"\n观察: 执行代码后获得输出一张图片, 文件名为{uid}\n"] + # message.role_content += f"\n执行代码后获得输出一张图片, 文件名为{uid}\n" + # else: + # message.code_answer = code_answer.code_exe_response + # message.observation = code_answer.code_exe_response + # message.step_content += f"\n观察: 执行代码后获得输出是 {code_answer.code_exe_response}\n" + # message.step_contents += [f"\n观察: 执行代码后获得输出是 {code_answer.code_exe_response}\n"] + # message.role_content += f"\n观察: 执行代码后获得输出是 {code_answer.code_exe_response}\n" + # logger.info(f"观察: {message.action_status}, {message.observation}") + # return message - def inherit_extrainfo(self, input_message: Message, output_message: Message): - output_message.db_docs = input_message.db_docs - output_message.search_docs = input_message.search_docs - output_message.code_docs = input_message.code_docs - output_message.figures.update(input_message.figures) - return output_message + # def tool_step(self, message: Message) -> Message: + # '''execute tool''' + # # logger.debug(f"message: {message.action_status}, {message.tool_name}, {message.tool_params}") + # tool_names = [tool.name for tool in message.tools] + # if message.tool_name not in tool_names: + # message.tool_answer = "不存在可以执行的tool" + # message.observation = "不存在可以执行的tool" + # message.role_content += f"\n观察: 不存在可以执行的tool\n" + # message.step_content += f"\n观察: 不存在可以执行的tool\n" + # message.step_contents += [f"\n观察: 不存在可以执行的tool\n"] + # for tool in message.tools: + # if tool.name == message.tool_name: + # tool_res = tool.func(**message.tool_params) + # message.tool_answer = tool_res + # message.observation = tool_res + # message.role_content += f"\n观察: {tool_res}\n" + # message.step_content += f"\n观察: {tool_res}\n" + # message.step_contents += [f"\n观察: {tool_res}\n"] + # return message + + # def filter(self, message: Message, stop=None) -> Message: + + # tool_params = self.parser_spec_key(message.role_content, "tool_params") + # code_content = self.parser_spec_key(message.role_content, "code_content") + # plan = self.parser_spec_key(message.role_content, "plan") + # plans = self.parser_spec_key(message.role_content, "plans", do_search=False) + # content = self.parser_spec_key(message.role_content, "content", do_search=False) + + # # logger.debug(f"tool_params: {tool_params}, code_content: {code_content}, plan: {plan}, plans: {plans}, content: {content}") + # role_content = tool_params or code_content or plan or plans or content + # message.role_content = role_content or message.role_content + # return message - def get_memory(self, do_all_memory=True, content_key="role_content") -> Memory: - memory = self.global_memory if do_all_memory else self.local_memory + # def parser(self, message: Message) -> Message: + # '''''' + # content = message.role_content + # parser_keys = ["action", "code_content", "code_filename", "tool_params", "plans"] + # try: + # s_json = self._parse_json(content) + # message.action_status = s_json.get("action") + # message.code_content = s_json.get("code_content") + # message.tool_params = s_json.get("tool_params") + # message.tool_name = s_json.get("tool_name") + # message.code_filename = s_json.get("code_filename") + # message.plans = s_json.get("plans") + # # for parser_key in parser_keys: + # # message.action_status = content.get(parser_key) + # except Exception as e: + # # logger.warning(f"{traceback.format_exc()}") + # def parse_text_to_dict(text): + # # Define a regular expression pattern to capture the key and value + # main_pattern = r"\*\*(.+?):\*\*\s*(.*?)\s*(?=\*\*|$)" + # code_pattern = r'```python\n(.*?)```' + + # # Use re.findall to find all main matches in the text + # main_matches = re.findall(main_pattern, text, re.DOTALL) + + # # Convert main matches to a dictionary + # parsed_dict = {key.strip(): value.strip() for key, value in main_matches} + + # # Search for the code block + # code_match = re.search(code_pattern, text, re.DOTALL) + # if code_match: + # # Add the code block to the dictionary + # parsed_dict['code'] = code_match.group(1).strip() + + # return parsed_dict + + # parsed_dict = parse_text_to_dict(content) + # action_value = parsed_dict.get('Action Status') + # if action_value: + # action_value = action_value.lower() + # logger.debug(f'action_value: {action_value}') + # # action_value = self._match(r"'action':\s*'([^']*)'", content) if "'action'" in content else self._match(r'"action":\s*"([^"]*)"', content) + + # code_content_value = parsed_dict.get('code') + # # code_content_value = self._match(r"'code_content':\s*'([^']*)'", content) if "'code_content'" in content else self._match(r'"code_content":\s*"([^"]*)"', content) + # filename_value = self._match(r"'code_filename':\s*'([^']*)'", content) if "'code_filename'" in content else self._match(r'"code_filename":\s*"([^"]*)"', content) + # tool_params_value = self._match(r"'tool_params':\s*(\{[^{}]*\})", content, do_json=True) if "'tool_params'" in content \ + # else self._match(r'"tool_params":\s*(\{[^{}]*\})', content, do_json=True) + # tool_name_value = self._match(r"'tool_name':\s*'([^']*)'", content) if "'tool_name'" in content else self._match(r'"tool_name":\s*"([^"]*)"', content) + # plans_value = self._match(r"'plans':\s*(\[.*?\])", content, do_search=False) if "'plans'" in content else self._match(r'"plans":\s*(\[.*?\])', content, do_search=False, ) + # # re解析 + # message.action_status = action_value or "default" + # message.code_content = code_content_value + # message.code_filename = filename_value + # message.tool_params = tool_params_value + # message.tool_name = tool_name_value + # message.plans = plans_value + + # # logger.debug(f"确认当前的action: {message.action_status}") + + # return message + + # def parser_spec_key(self, content, key, do_search=True, do_json=False) -> str: + # '''''' + # key2pattern = { + # "'action'": r"'action':\s*'([^']*)'", '"action"': r'"action":\s*"([^"]*)"', + # "'code_content'": r"'code_content':\s*'([^']*)'", '"code_content"': r'"code_content":\s*"([^"]*)"', + # "'code_filename'": r"'code_filename':\s*'([^']*)'", '"code_filename"': r'"code_filename":\s*"([^"]*)"', + # "'tool_params'": r"'tool_params':\s*(\{[^{}]*\})", '"tool_params"': r'"tool_params":\s*(\{[^{}]*\})', + # "'tool_name'": r"'tool_name':\s*'([^']*)'", '"tool_name"': r'"tool_name":\s*"([^"]*)"', + # "'plans'": r"'plans':\s*(\[.*?\])", '"plans"': r'"plans":\s*(\[.*?\])', + # "'content'": r"'content':\s*'([^']*)'", '"content"': r'"content":\s*"([^"]*)"', + # } + + # s_json = self._parse_json(content) + # try: + # if s_json and key in s_json: + # return str(s_json[key]) + # except: + # pass + + # keystr = f"'{key}'" if f"'{key}'" in content else f'"{key}"' + # return self._match(key2pattern.get(keystr, fr"'{key}':\s*'([^']*)'"), content, do_search=do_search, do_json=do_json) + + # def _match(self, pattern, s, do_search=True, do_json=False): + # try: + # if do_search: + # match = re.search(pattern, s) + # if match: + # value = match.group(1).replace("\\n", "\n") + # if do_json: + # value = json.loads(value) + # else: + # value = None + # else: + # match = re.findall(pattern, s, re.DOTALL) + # if match: + # value = match[0] + # if do_json: + # value = json.loads(value) + # else: + # value = None + # except Exception as e: + # logger.warning(f"{traceback.format_exc()}") + + # # logger.debug(f"pattern: {pattern}, s: {s}, match: {match}") + # return value + + # def _parse_json(self, s): + # try: + # pattern = r"```([^`]+)```" + # match = re.findall(pattern, s) + # if match: + # return eval(match[0]) + # except: + # pass + # return None + + # def inherit_extrainfo(self, input_message: Message, output_message: Message): + # output_message.db_docs = input_message.db_docs + # output_message.search_docs = input_message.search_docs + # output_message.code_docs = input_message.code_docs + # output_message.origin_query = input_message.origin_query + # output_message.figures.update(input_message.figures) + # return output_message + + def get_memory(self, content_key="role_content") -> Memory: + memory = self.global_memory return memory.to_tuple_messages(content_key=content_key) - def get_memory_str(self, do_all_memory=True, content_key="role_content") -> Memory: - memory = self.global_memory if do_all_memory else self.local_memory + def get_memory_str(self, content_key="role_content") -> Memory: + memory = self.global_memory # for i in memory.to_tuple_messages(content_key=content_key): # logger.debug(f"{i}") return "\n".join([": ".join(i) for i in memory.to_tuple_messages(content_key=content_key)]) diff --git a/dev_opsgpt/connector/chains/chains.py b/dev_opsgpt/connector/chains/chains.py index fd490b0..a8536e6 100644 --- a/dev_opsgpt/connector/chains/chains.py +++ b/dev_opsgpt/connector/chains/chains.py @@ -1,28 +1,18 @@ from typing import List +from loguru import logger +import copy from dev_opsgpt.connector.agents import BaseAgent from .base_chain import BaseChain +from dev_opsgpt.connector.agents import BaseAgent, CheckAgent +from dev_opsgpt.connector.schema import ( + Memory, Role, Message, ActionStatus, ChainConfig, + load_role_configs +) -class simpleChatChain(BaseChain): - - def __init__(self, agents: List[BaseAgent], do_code_exec: bool = False) -> None: - super().__init__(agents, do_code_exec) - - -class toolChatChain(BaseChain): - - def __init__(self, agents: List[BaseAgent], do_code_exec: bool = False) -> None: - super().__init__(agents, do_code_exec) - - -class dataAnalystChain(BaseChain): - - def __init__(self, agents: List[BaseAgent], do_code_exec: bool = False) -> None: - super().__init__(agents, do_code_exec) - - -class plannerChain(BaseChain): + +class ExecutorRefineChain(BaseChain): def __init__(self, agents: List[BaseAgent], do_code_exec: bool = False) -> None: super().__init__(agents, do_code_exec) diff --git a/dev_opsgpt/connector/configs/agent_config.py b/dev_opsgpt/connector/configs/agent_config.py index dc52852..66bf623 100644 --- a/dev_opsgpt/connector/configs/agent_config.py +++ b/dev_opsgpt/connector/configs/agent_config.py @@ -1,213 +1,33 @@ from enum import Enum +from .prompts import ( + REACT_PROMPT_INPUT, CHECK_PROMPT_INPUT, EXECUTOR_PROMPT_INPUT, CONTEXT_PROMPT_INPUT, QUERY_CONTEXT_PROMPT_INPUT,PLAN_PROMPT_INPUT, + RECOGNIZE_INTENTION_PROMPT, + CHECKER_TEMPLATE_PROMPT, + CONV_SUMMARY_PROMPT, + QA_PROMPT, CODE_QA_PROMPT, QA_TEMPLATE_PROMPT, + EXECUTOR_TEMPLATE_PROMPT, + REFINE_TEMPLATE_PROMPT, + PLANNER_TEMPLATE_PROMPT, GENERAL_PLANNER_PROMPT, DATA_PLANNER_PROMPT, TOOL_PLANNER_PROMPT, + PRD_WRITER_METAGPT_PROMPT, DESIGN_WRITER_METAGPT_PROMPT, TASK_WRITER_METAGPT_PROMPT, CODE_WRITER_METAGPT_PROMPT, + REACT_TEMPLATE_PROMPT, + REACT_TOOL_PROMPT, REACT_CODE_PROMPT, REACT_TOOL_AND_CODE_PLANNER_PROMPT, REACT_TOOL_AND_CODE_PROMPT +) + class AgentType: REACT = "ReactAgent" + EXECUTOR = "ExecutorAgent" ONE_STEP = "BaseAgent" DEFAULT = "BaseAgent" -REACT_TOOL_PROMPT = """尽可能地以有帮助和准确的方式回应人类。您可以使用以下工具: -{formatted_tools} -使用json blob来指定一个工具,提供一个action关键字(工具名称)和一个tool_params关键字(工具输入)。 -有效的"action"值为:"finished" 或 "tool_using" (使用工具来回答问题) -有效的"tool_name"值为:{tool_names} -请仅在每个$JSON_BLOB中提供一个action,如下所示: -``` -{{{{ -"action": $ACTION, -"tool_name": $TOOL_NAME -"tool_params": $INPUT -}}}} -``` - -按照以下格式进行回应: -问题:输入问题以回答 -思考:考虑之前和之后的步骤 -行动: -``` -$JSON_BLOB -``` -观察:行动结果 -...(重复思考/行动/观察N次) -思考:我知道该如何回应 -行动: -``` -{{{{ -"action": "finished", -"tool_name": "notool" -"tool_params": "最终返回答案给到用户" -}}}} -``` -""" - -REACT_PROMPT_INPUT = '''下面开始!记住根据问题进行返回需要生成的答案 -问题: {query}''' - - -REACT_CODE_PROMPT = """尽可能地以有帮助和准确的方式回应人类,能够逐步编写可执行并打印变量的代码来解决问题 -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 code (生成代码)。 -有效的 'action' 值为:'coding'(结合总结下述思维链过程编写下一步的可执行代码) or 'finished' (总结下述思维链过程可回答问题)。 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{{{'action': $ACTION,'code_content': $CODE}}}} -``` - -按照以下思维链格式进行回应: -问题:输入问题以回答 -思考:考虑之前和之后的步骤 -行动: -``` -$JSON_BLOB -``` -观察:行动结果 -...(重复思考/行动/观察N次) -思考:我知道该如何回应 -行动: -``` -{{{{ -"action": "finished", -"code_content": "总结上述思维链过程回答问题" -}}}} -``` -""" - -GENERAL_PLANNER_PROMPT = """你是一个通用计划拆解助手,将问题拆解问题成各个详细明确的步骤计划或直接回答问题,尽可能地以有帮助和准确的方式回应人类, -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 -有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 -有效的 'plans' 值为: 一个任务列表,按顺序写出需要执行的计划 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{'action': 'planning', 'plans': [$PLAN1, $PLAN2, $PLAN3, ..., $PLANN], }} -或者 -{{'action': 'only_answer', 'plans': "直接回答问题", }} -``` - -按照以下格式进行回应: -问题:输入问题以回答 -行动: -``` -$JSON_BLOB -``` -""" - -DATA_PLANNER_PROMPT = """你是一个数据分析助手,能够根据问题来制定一个详细明确的数据分析计划,尽可能地以有帮助和准确的方式回应人类, -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 -有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 -有效的 'plans' 值为: 一份数据分析计划清单,按顺序排列,用文本表示 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{'action': 'planning', 'plans': '$PLAN1, $PLAN2, ..., $PLAN3' }} -``` - -按照以下格式进行回应: -问题:输入问题以回答 -行动: -``` -$JSON_BLOB -``` -""" - -TOOL_PLANNER_PROMPT = """你是一个工具使用过程的计划拆解助手,将问题拆解为一系列的工具使用计划,若没有可用工具则直接回答问题,尽可能地以有帮助和准确的方式回应人类,你可以使用以下工具: -{formatted_tools} -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 -有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 -有效的 'plans' 值为: 一个任务列表,按顺序写出需要使用的工具和使用该工具的理由 -在每个 $JSON_BLOB 中仅提供一个 action,如下两个示例所示: -``` -{{'action': 'planning', 'plans': [$PLAN1, $PLAN2, $PLAN3, ..., $PLANN], }} -``` -或者 若无法通过以上工具解决问题,则直接回答问题 -``` -{{'action': 'only_answer', 'plans': "直接回答问题", }} -``` - -按照以下格式进行回应: -问题:输入问题以回答 -行动: -``` -$JSON_BLOB -``` -""" - - -RECOGNIZE_INTENTION_PROMPT = """你是一个任务决策助手,能够将理解用户意图并决策采取最合适的行动,尽可能地以有帮助和准确的方式回应人类, -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 -有效的 'action' 值为:'planning'(需要先进行拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)or "tool_using" (使用工具来回答问题) or 'coding'(生成可执行的代码)。 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{'action': $ACTION}} -``` -按照以下格式进行回应: -问题:输入问题以回答 -行动:$ACTION -``` -$JSON_BLOB -``` -""" - - -CHECKER_PROMPT = """尽可能地以有帮助和准确的方式回应人类,判断问题是否得到解答,同时展现解答的过程和内容 -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 -有效的 'action' 值为:'finished'(任务已经可以通过“背景信息”和“对话信息”回答问题) or 'continue' (“背景信息”和“对话信息”不足以回答问题)。 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{'action': $ACTION, 'content': '提取“背景信息”和“对话信息”中信息来回答问题'}} -``` -按照以下格式进行回应: -问题:输入问题以回答 -行动:$ACTION -``` -$JSON_BLOB -``` -""" - -CONV_SUMMARY_PROMPT = """尽可能地以有帮助和准确的方式回应人类,根据“背景信息”中的有效信息回答问题, -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 -有效的 'action' 值为:'finished'(任务已经可以通过上下文信息可以回答) or 'continue' (根据背景信息回答问题)。 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{'action': $ACTION, 'content': '根据背景信息回答问题'}} -``` -按照以下格式进行回应: -问题:输入问题以回答 -行动: -``` -$JSON_BLOB -``` -""" - -CONV_SUMMARY_PROMPT = """尽可能地以有帮助和准确的方式回应人类 -根据“背景信息”中的有效信息回答问题,同时展现解答的过程和内容 -若能根“背景信息”回答问题,则直接回答 -否则,总结“背景信息”的内容 -""" - - - -QA_PROMPT = """根据已知信息,简洁和专业的来回答问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题”,不允许在答案中添加编造成分,答案请使用中文。 -使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 -有效的 'action' 值为:'finished'(任务已经可以通过上下文信息可以回答) or 'continue' (上下文信息不足以回答问题)。 -在每个 $JSON_BLOB 中仅提供一个 action,如下所示: -``` -{{'action': $ACTION, 'content': '总结对话内容'}} -``` -按照以下格式进行回应: -问题:输入问题以回答 -行动:$ACTION -``` -$JSON_BLOB -``` -""" - -CODE_QA_PROMPT = """【指令】根据已知信息来回答问题""" - AGETN_CONFIGS = { "checker": { "role": { - "role_prompt": CHECKER_PROMPT, - "role_type": "ai", + "role_prompt": CHECKER_TEMPLATE_PROMPT, + "role_type": "assistant", "role_name": "checker", "role_desc": "", "agent_type": "BaseAgent" @@ -220,7 +40,7 @@ AGETN_CONFIGS = { "conv_summary": { "role": { "role_prompt": CONV_SUMMARY_PROMPT, - "role_type": "ai", + "role_type": "assistant", "role_name": "conv_summary", "role_desc": "", "agent_type": "BaseAgent" @@ -232,8 +52,8 @@ AGETN_CONFIGS = { }, "general_planner": { "role": { - "role_prompt": GENERAL_PLANNER_PROMPT, - "role_type": "ai", + "role_prompt": PLANNER_TEMPLATE_PROMPT, + "role_type": "assistant", "role_name": "general_planner", "role_desc": "", "agent_type": "BaseAgent" @@ -243,10 +63,37 @@ AGETN_CONFIGS = { "do_doc_retrieval": False, "do_tool_retrieval": False }, + "executor": { + "role": { + "role_prompt": EXECUTOR_TEMPLATE_PROMPT, + "role_type": "assistant", + "role_name": "executor", + "role_desc": "", + "agent_type": "ExecutorAgent", + }, + "stop": "\n**Observation:**", + "chat_turn": 1, + "do_search": False, + "do_doc_retrieval": False, + "do_tool_retrieval": False + }, + "base_refiner": { + "role": { + "role_prompt": REFINE_TEMPLATE_PROMPT, + "role_type": "assistant", + "role_name": "base_refiner", + "role_desc": "", + "agent_type": "BaseAgent" + }, + "chat_turn": 1, + "do_search": False, + "do_doc_retrieval": False, + "do_tool_retrieval": False + }, "planner": { "role": { "role_prompt": DATA_PLANNER_PROMPT, - "role_type": "ai", + "role_type": "assistant", "role_name": "planner", "role_desc": "", "agent_type": "BaseAgent" @@ -259,7 +106,7 @@ AGETN_CONFIGS = { "intention_recognizer": { "role": { "role_prompt": RECOGNIZE_INTENTION_PROMPT, - "role_type": "ai", + "role_type": "assistant", "role_name": "intention_recognizer", "role_desc": "", "agent_type": "BaseAgent" @@ -272,7 +119,7 @@ AGETN_CONFIGS = { "tool_planner": { "role": { "role_prompt": TOOL_PLANNER_PROMPT, - "role_type": "ai", + "role_type": "assistant", "role_name": "tool_planner", "role_desc": "", "agent_type": "BaseAgent" @@ -282,10 +129,37 @@ AGETN_CONFIGS = { "do_doc_retrieval": False, "do_tool_retrieval": False }, + "tool_and_code_react": { + "role": { + "role_prompt": REACT_TOOL_AND_CODE_PROMPT, + "role_type": "assistant", + "role_name": "tool_and_code_react", + "role_desc": "", + "agent_type": "ReactAgent", + }, + "stop": "\n**Observation:**", + "chat_turn": 7, + "do_search": False, + "do_doc_retrieval": False, + "do_tool_retrieval": False + }, + "tool_and_code_planner": { + "role": { + "role_prompt": REACT_TOOL_AND_CODE_PLANNER_PROMPT, + "role_type": "assistant", + "role_name": "tool_and_code_planner", + "role_desc": "", + "agent_type": "BaseAgent" + }, + "chat_turn": 1, + "do_search": False, + "do_doc_retrieval": False, + "do_tool_retrieval": False + }, "tool_react": { "role": { "role_prompt": REACT_TOOL_PROMPT, - "role_type": "ai", + "role_type": "assistant", "role_name": "tool_react", "role_desc": "", "agent_type": "ReactAgent" @@ -294,12 +168,12 @@ AGETN_CONFIGS = { "do_search": False, "do_doc_retrieval": False, "do_tool_retrieval": False, - "stop": "观察" + "stop": "\n**Observation:**" }, "code_react": { "role": { "role_prompt": REACT_CODE_PROMPT, - "role_type": "ai", + "role_type": "assistant", "role_name": "code_react", "role_desc": "", "agent_type": "ReactAgent" @@ -308,25 +182,25 @@ AGETN_CONFIGS = { "do_search": False, "do_doc_retrieval": False, "do_tool_retrieval": False, - "stop": "观察" + "stop": "\n**Observation:**" }, "qaer": { "role": { - "role_prompt": QA_PROMPT, - "role_type": "ai", + "role_prompt": QA_TEMPLATE_PROMPT, + "role_type": "assistant", "role_name": "qaer", "role_desc": "", "agent_type": "BaseAgent" }, "chat_turn": 1, "do_search": False, - "do_doc_retrieval": True, + "do_doc_retrieval": False, "do_tool_retrieval": False }, "code_qaer": { "role": { "role_prompt": CODE_QA_PROMPT , - "role_type": "ai", + "role_type": "assistant", "role_name": "code_qaer", "role_desc": "", "agent_type": "BaseAgent" @@ -338,8 +212,8 @@ AGETN_CONFIGS = { }, "searcher": { "role": { - "role_prompt": QA_PROMPT, - "role_type": "ai", + "role_prompt": QA_TEMPLATE_PROMPT, + "role_type": "assistant", "role_name": "searcher", "role_desc": "", "agent_type": "BaseAgent" @@ -349,62 +223,65 @@ AGETN_CONFIGS = { "do_doc_retrieval": False, "do_tool_retrieval": False }, - "answer": { + "metaGPT_PRD": { "role": { - "role_prompt": "", - "role_type": "ai", - "role_name": "answer", + "role_prompt": PRD_WRITER_METAGPT_PROMPT, + "role_type": "assistant", + "role_name": "metaGPT_PRD", "role_desc": "", "agent_type": "BaseAgent" }, "chat_turn": 1, "do_search": False, "do_doc_retrieval": False, - "do_tool_retrieval": False + "do_tool_retrieval": False, + "focus_agents": [], + "focus_message_keys": [], }, - "data_analyst": { + + "metaGPT_DESIGN": { "role": { - "role_prompt": """你是一个数据分析的代码开发助手,能够编写可执行的代码来完成相关的数据分析问题,使用 JSON Blob 来指定一个返回的内容,通过提供一个 action(行动)和一个 code (生成代码)和 一个 file_name (指定保存文件)。\ - 有效的 'action' 值为:'coding'(生成可执行的代码) or 'finished' (不生成代码并直接返回答案)。在每个 $JSON_BLOB 中仅提供一个 action,如下所示:\ - ```\n{{'action': $ACTION,'code_content': $CODE, 'code_filename': $FILE_NAME}}```\ - 下面开始!记住根据问题进行返回需要生成的答案,格式为 ```JSON_BLOB```""", - "role_type": "ai", - "role_name": "data_analyst", + "role_prompt": DESIGN_WRITER_METAGPT_PROMPT, + "role_type": "assistant", + "role_name": "metaGPT_DESIGN", "role_desc": "", "agent_type": "BaseAgent" }, "chat_turn": 1, "do_search": False, "do_doc_retrieval": False, - "do_tool_retrieval": False + "do_tool_retrieval": False, + "focus_agents": ["metaGPT_PRD"], + "focus_message_keys": [], }, - "deveploer": { + "metaGPT_TASK": { "role": { - "role_prompt": """你是一个代码开发助手,能够编写可执行的代码来完成问题,使用 JSON Blob 来指定一个返回的内容,通过提供一个 action(行动)和一个 code (生成代码)和 一个 file_name (指定保存文件)。\ - 有效的 'action' 值为:'coding'(生成可执行的代码) or 'finished' (不生成代码并直接返回答案)。在每个 $JSON_BLOB 中仅提供一个 action,如下所示:\ - ```\n{{'action': $ACTION,'code_content': $CODE, 'code_filename': $FILE_NAME}}```\ - 下面开始!记住根据问题进行返回需要生成的答案,格式为 ```JSON_BLOB```""", - "role_type": "ai", - "role_name": "deveploer", + "role_prompt": TASK_WRITER_METAGPT_PROMPT, + "role_type": "assistant", + "role_name": "metaGPT_TASK", "role_desc": "", "agent_type": "BaseAgent" }, "chat_turn": 1, "do_search": False, "do_doc_retrieval": False, - "do_tool_retrieval": False + "do_tool_retrieval": False, + "focus_agents": ["metaGPT_DESIGN"], + "focus_message_keys": [], }, - "tester": { + "metaGPT_CODER": { "role": { - "role_prompt": "你是一个QA问答的助手,能够尽可能准确地回答问题,下面请逐步思考问题并回答", - "role_type": "ai", - "role_name": "tester", + "role_prompt": CODE_WRITER_METAGPT_PROMPT, + "role_type": "assistant", + "role_name": "metaGPT_CODER", "role_desc": "", - "agent_type": "BaseAgent" + "agent_type": "ExecutorAgent" }, "chat_turn": 1, "do_search": False, "do_doc_retrieval": False, - "do_tool_retrieval": False - } + "do_tool_retrieval": False, + "focus_agents": ["metaGPT_DESIGN", "metaGPT_TASK"], + "focus_message_keys": [], + }, } \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/agent_prompt/design_writer.yaml b/dev_opsgpt/connector/configs/agent_prompt/design_writer.yaml new file mode 100644 index 0000000..e1135ef --- /dev/null +++ b/dev_opsgpt/connector/configs/agent_prompt/design_writer.yaml @@ -0,0 +1,99 @@ +You are a Architect, named Bob, your goal is Design a concise, usable, complete python system, and the constraint is Try to specify good open source tools as much as possible. + +# Context +## Original Requirements: +Create a snake game. + +## Product Goals: +Develop a highly addictive and engaging snake game. +Provide a user-friendly and intuitive user interface. +Implement various levels and challenges to keep the players entertained. +## User Stories: +As a user, I want to be able to control the snake's movement using arrow keys or touch gestures. +As a user, I want to see my score and progress displayed on the screen. +As a user, I want to be able to pause and resume the game at any time. +As a user, I want to be challenged with different obstacles and levels as I progress. +As a user, I want to have the option to compete with other players and compare my scores. +## Competitive Analysis: +Python Snake Game: A simple snake game implemented in Python with basic features and limited levels. +Snake.io: A multiplayer online snake game with competitive gameplay and high engagement. +Slither.io: Another multiplayer online snake game with a larger player base and addictive gameplay. +Snake Zone: A mobile snake game with various power-ups and challenges. +Snake Mania: A classic snake game with modern graphics and smooth controls. +Snake Rush: A fast-paced snake game with time-limited challenges. +Snake Master: A snake game with unique themes and customizable snakes. + +## Requirement Analysis: +The product should be a highly addictive and engaging snake game with a user-friendly interface. It should provide various levels and challenges to keep the players entertained. The game should have smooth controls and allow the users to compete with each other. + +## Requirement Pool: +``` +[ + ["Implement different levels with increasing difficulty", "P0"], + ["Allow users to control the snake using arrow keys or touch gestures", "P0"], + ["Display the score and progress on the screen", "P1"], + ["Provide an option to pause and resume the game", "P1"], + ["Integrate leaderboards to enable competition among players", "P2"] +] +``` +## UI Design draft: +The game will have a simple and clean interface. The main screen will display the snake, obstacles, and the score. The snake's movement can be controlled using arrow keys or touch gestures. There will be buttons to pause and resume the game. The level and difficulty will be indicated on the screen. The design will have a modern and visually appealing style with smooth animations. + +## Anything UNCLEAR: +There are no unclear points. + +## Format example +--- +## Implementation approach +We will ... + +## Python package name +```python +"snake_game" +``` + +## File list +```python +[ + "main.py", +] +``` + +## Data structures and interface definitions +```mermaid +classDiagram + class Game{ + +int score + } + ... + Game "1" -- "1" Food: has +``` + +## Program call flow +```mermaid +sequenceDiagram + participant M as Main + ... + G->>M: end game +``` + +## Anything UNCLEAR +The requirement is clear to me. +--- +----- +Role: You are an architect; the goal is to design a SOTA PEP8-compliant python system; make the best use of good open source tools +Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately +Max Output: 8192 chars or 2048 tokens. Try to use them up. +Attention: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the code and triple quote. + +## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework. + +## Python package name: Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores + +## File list: Provided as Python list[str], the list of ONLY REQUIRED files needed to write the program(LESS IS MORE!). Only need relative paths, comply with PEP8 standards. ALWAYS write a main.py or app.py here + +## Data structures and interface definitions: Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design. + +## Program call flow: Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT. + +## Anything UNCLEAR: Provide as Plain text. Make clear here. \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/agent_prompt/prd_writer.yaml b/dev_opsgpt/connector/configs/agent_prompt/prd_writer.yaml new file mode 100644 index 0000000..0aec402 --- /dev/null +++ b/dev_opsgpt/connector/configs/agent_prompt/prd_writer.yaml @@ -0,0 +1,101 @@ +You are a Product Manager, named Alice, your goal is Efficiently create a successful product, and the constraint is . + +# Context +## Original Requirements +Create a snake game. + +## Search Information +### Search Results + +### Search Summary + +## mermaid quadrantChart code syntax example. DONT USE QUOTO IN CODE DUE TO INVALID SYNTAX. Replace the with REAL COMPETITOR NAME +```mermaid +quadrantChart + title Reach and engagement of campaigns + x-axis Low Reach --> High Reach + y-axis Low Engagement --> High Engagement + quadrant-1 We should expand + quadrant-2 Need to promote + quadrant-3 Re-evaluate + quadrant-4 May be improved + "Campaign: A": [0.3, 0.6] + "Campaign B": [0.45, 0.23] + "Campaign C": [0.57, 0.69] + "Campaign D": [0.78, 0.34] + "Campaign E": [0.40, 0.34] + "Campaign F": [0.35, 0.78] + "Our Target Product": [0.5, 0.6] +``` + +## Format example +--- +## Original Requirements +The boss ... + +## Product Goals +```python +[ + "Create a ...", +] +``` + +## User Stories +```python +[ + "As a user, ...", +] +``` + +## Competitive Analysis +```python +[ + "Python Snake Game: ...", +] +``` + +## Competitive Quadrant Chart +```mermaid +quadrantChart + title Reach and engagement of campaigns + ... + "Our Target Product": [0.6, 0.7] +``` + +## Requirement Analysis +The product should be a ... + +## Requirement Pool +```python +[ + ["End game ...", "P0"] +] +``` + +## UI Design draft +Give a basic function description, and a draft + +## Anything UNCLEAR +There are no unclear points. +--- +----- +Role: You are a professional product manager; the goal is to design a concise, usable, efficient product +Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly. If the requirements are unclear, ensure minimum viability and avoid excessive design +ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## ' SHOULD WRITE BEFORE the code and triple quote. Output carefully referenced "Format example" in format. + +## Original Requirements: Provide as Plain text, place the polished complete original requirements here + +## Product Goals: Provided as Python list[str], up to 3 clear, orthogonal product goals. If the requirement itself is simple, the goal should also be simple + +## User Stories: Provided as Python list[str], up to 5 scenario-based user stories, If the requirement itself is simple, the user stories should also be less + +## Competitive Analysis: Provided as Python list[str], up to 7 competitive product analyses, consider as similar competitors as possible + +## Competitive Quadrant Chart: Use mermaid quadrantChart code syntax. up to 14 competitive products. Translation: Distribute these competitor scores evenly between 0 and 1, trying to conform to a normal distribution centered around 0.5 as much as possible. + +## Requirement Analysis: Provide as Plain text. Be simple. LESS IS MORE. Make your requirements less dumb. Delete the parts unnessasery. + +## Requirement Pool: Provided as Python list[list[str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards; no more than 5 requirements and consider to make its difficulty lower + +## UI Design draft: Provide as Plain text. Be simple. Describe the elements and functions, also provide a simple style description and layout description. +## Anything UNCLEAR: Provide as Plain text. Make clear here. \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/agent_prompt/review_code.yaml b/dev_opsgpt/connector/configs/agent_prompt/review_code.yaml new file mode 100644 index 0000000..32567d8 --- /dev/null +++ b/dev_opsgpt/connector/configs/agent_prompt/review_code.yaml @@ -0,0 +1,177 @@ + +NOTICE +Role: You are a professional software engineer, and your main task is to review the code. You need to ensure that the code conforms to the PEP8 standards, is elegantly designed and modularized, easy to read and maintain, and is written in Python 3.9 (or in another programming language). +ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example". + +## Code Review: Based on the following context and code, and following the check list, Provide key, clear, concise, and specific code modification suggestions, up to 5. +``` +1. Check 0: Is the code implemented as per the requirements? +2. Check 1: Are there any issues with the code logic? +3. Check 2: Does the existing code follow the "Data structures and interface definitions"? +4. Check 3: Is there a function in the code that is omitted or not fully implemented that needs to be implemented? +5. Check 4: Does the code have unnecessary or lack dependencies? +``` + +## Rewrite Code: point.py Base on "Code Review" and the source code, rewrite code with triple quotes. Do your utmost to optimize THIS SINGLE FILE. +----- +# Context +## Implementation approach +For the snake game, we can use the Pygame library, which is an open-source and easy-to-use library for game development in Python. Pygame provides a simple and efficient way to handle graphics, sound, and user input, making it suitable for developing a snake game. + +## Python package name +``` +"snake_game" +``` +## File list +```` +[ + "main.py", +] +``` +## Data structures and interface definitions +``` +classDiagram + class Game: + -int score + -bool paused + +__init__() + +start_game() + +handle_input(key: int) + +update_game() + +draw_game() + +game_over() + + class Snake: + -list[Point] body + -Point dir + -bool alive + +__init__(start_pos: Point) + +move() + +change_direction(dir: Point) + +grow() + +get_head() -> Point + +get_body() -> list[Point] + +is_alive() -> bool + + class Point: + -int x + -int y + +__init__(x: int, y: int) + +set_coordinate(x: int, y: int) + +get_coordinate() -> tuple[int, int] + + class Food: + -Point pos + -bool active + +__init__() + +generate_new_food() + +get_position() -> Point + +is_active() -> bool + + Game "1" -- "1" Snake: contains + Game "1" -- "1" Food: has +``` + +## Program call flow +``` +sequenceDiagram + participant M as Main + participant G as Game + participant S as Snake + participant F as Food + + M->>G: Start game + G->>G: Initialize game + loop + M->>G: Handle user input + G->>S: Handle input + G->>F: Check if snake eats food + G->>S: Update snake movement + G->>G: Check game over condition + G->>G: Update score + G->>G: Draw game + M->>G: Update display + end + G->>G: Game over +``` +## Required Python third-party packages +``` +""" +pygame==2.0.1 +""" +``` +## Required Other language third-party packages +``` +""" +No third-party packages required for other languages. +""" +``` + +## Logic Analysis +``` +[ + ["main.py", "Main"], + ["game.py", "Game"], + ["snake.py", "Snake"], + ["point.py", "Point"], + ["food.py", "Food"] +] +``` +## Task list +``` +[ + "point.py", + "food.py", + "snake.py", + "game.py", + "main.py" +] +``` +## Shared Knowledge +``` +""" +The 'point.py' module contains the implementation of the Point class, which represents a point in a 2D coordinate system. + +The 'food.py' module contains the implementation of the Food class, which represents the food in the game. + +The 'snake.py' module contains the implementation of the Snake class, which represents the snake in the game. + +The 'game.py' module contains the implementation of the Game class, which manages the game logic. + +The 'main.py' module is the entry point of the application and starts the game. +""" +``` +## Anything UNCLEAR +We need to clarify the main entry point of the application and ensure that all required third-party libraries are properly initialized. + +## Code: point.py +``` +class Point: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + def set_coordinate(self, x: int, y: int): + self.x = x + self.y = y + + def get_coordinate(self) -> tuple[int, int]: + return self.x, self.y +``` +----- + +## Format example +----- +## Code Review +1. The code ... +2. ... +3. ... +4. ... +5. ... + +## Rewrite Code: point.py +```python +## point.py +... +``` +----- diff --git a/dev_opsgpt/connector/configs/agent_prompt/task_write.yaml b/dev_opsgpt/connector/configs/agent_prompt/task_write.yaml new file mode 100644 index 0000000..faf2c2c --- /dev/null +++ b/dev_opsgpt/connector/configs/agent_prompt/task_write.yaml @@ -0,0 +1,148 @@ +You are a Project Manager, named Eve, your goal isImprove team efficiency and deliver with quality and quantity, and the constraint is . + +# Context +## Implementation approach +For the snake game, we can use the Pygame library, which is an open-source and easy-to-use library for game development in Python. Pygame provides a simple and efficient way to handle graphics, sound, and user input, making it suitable for developing a snake game. + +## Python package name +``` +"snake_game" +``` +## File list +```` +[ + "main.py", + "game.py", + "snake.py", + "food.py" +] +``` +## Data structures and interface definitions +``` +classDiagram + class Game{ + -int score + -bool game_over + +start_game() : void + +end_game() : void + +update() : void + +draw() : void + +handle_events() : void + } + class Snake{ + -list[Tuple[int, int]] body + -Tuple[int, int] direction + +move() : void + +change_direction(direction: Tuple[int, int]) : void + +is_collision() : bool + +grow() : void + +draw() : void + } + class Food{ + -Tuple[int, int] position + +generate() : void + +draw() : void + } + class Main{ + -Game game + +run() : void + } + Game "1" -- "1" Snake: contains + Game "1" -- "1" Food: has + Main "1" -- "1" Game: has +``` +## Program call flow +``` +sequenceDiagram + participant M as Main + participant G as Game + participant S as Snake + participant F as Food + + M->G: run() + G->G: start_game() + G->G: handle_events() + G->G: update() + G->G: draw() + G->G: end_game() + + G->S: move() + S->S: change_direction() + S->S: is_collision() + S->S: grow() + S->S: draw() + + G->F: generate() + F->F: draw() +``` +## Anything UNCLEAR +The design and implementation of the snake game are clear based on the given requirements. + +## Format example +--- +## Required Python third-party packages +```python +""" +flask==1.1.2 +bcrypt==3.2.0 +""" +``` + +## Required Other language third-party packages +```python +""" +No third-party ... +""" +``` + +## Full API spec +```python +""" +openapi: 3.0.0 +... +description: A JSON object ... +""" +``` + +## Logic Analysis +```python +[ + ["game.py", "Contains ..."], +] +``` + +## Task list +```python +[ + "game.py", +] +``` + +## Shared Knowledge +```python +""" +'game.py' contains ... +""" +``` + +## Anything UNCLEAR +We need ... how to start. +--- +----- +Role: You are a project manager; the goal is to break down tasks according to PRD/technical design, give a task list, and analyze task dependencies to start with the prerequisite modules +Requirements: Based on the context, fill in the following missing information, note that all sections are returned in Python code triple quote form seperatedly. Here the granularity of the task is a file, if there are any missing files, you can supplement them +Attention: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the code and triple quote. + +## Required Python third-party packages: Provided in requirements.txt format + +## Required Other language third-party packages: Provided in requirements.txt format + +## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend. + +## Logic Analysis: Provided as a Python list[list[str]. the first is filename, the second is class/method/function should be implemented in this file. Analyze the dependencies between the files, which work should be done first + +## Task list: Provided as Python list[str]. Each str is a filename, the more at the beginning, the more it is a prerequisite dependency, should be done first + +## Shared Knowledge: Anything that should be public like utils' functions, config's variables details that should make clear first. + +## Anything UNCLEAR: Provide as Plain text. Make clear here. For example, don't forget a main entry. don't forget to init 3rd party libs. \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/agent_prompt/write_code.yaml b/dev_opsgpt/connector/configs/agent_prompt/write_code.yaml new file mode 100644 index 0000000..4193b8b --- /dev/null +++ b/dev_opsgpt/connector/configs/agent_prompt/write_code.yaml @@ -0,0 +1,147 @@ +NOTICE +Role: You are a professional engineer; the main goal is to write PEP8 compliant, elegant, modular, easy to read and maintain Python 3.9 code (but you can also use other programming language) +ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example". + +## Code: snake.py Write code with triple quoto, based on the following list and context. +1. Do your best to implement THIS ONLY ONE FILE. ONLY USE EXISTING API. IF NO API, IMPLEMENT IT. +2. Requirement: Based on the context, implement one following code file, note to return only in code form, your code will be part of the entire project, so please implement complete, reliable, reusable code snippets +3. Attention1: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. +4. Attention2: YOU MUST FOLLOW "Data structures and interface definitions". DONT CHANGE ANY DESIGN. +5. Think before writing: What should be implemented and provided in this document? +6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE. +7. Do not use public member functions that do not exist in your design. + +----- +# Context +## Implementation approach +For the snake game, we can use the Pygame library, which is an open-source and easy-to-use library for game development in Python. Pygame provides a simple and efficient way to handle graphics, sound, and user input, making it suitable for developing a snake game. + +## Python package name +``` +"snake_game" +``` +## File list +```` +[ + "main.py", + "game.py", + "snake.py", + "food.py" +] +``` +## Data structures and interface definitions +``` +classDiagram + class Game{ + -int score + -bool game_over + +start_game() : void + +end_game() : void + +update() : void + +draw() : void + +handle_events() : void + } + class Snake{ + -list[Tuple[int, int]] body + -Tuple[int, int] direction + +move() : void + +change_direction(direction: Tuple[int, int]) : void + +is_collision() : bool + +grow() : void + +draw() : void + } + class Food{ + -Tuple[int, int] position + +generate() : void + +draw() : void + } + class Main{ + -Game game + +run() : void + } + Game "1" -- "1" Snake: contains + Game "1" -- "1" Food: has + Main "1" -- "1" Game: has +``` +## Program call flow +``` +sequenceDiagram + participant M as Main + participant G as Game + participant S as Snake + participant F as Food + + M->G: run() + G->G: start_game() + G->G: handle_events() + G->G: update() + G->G: draw() + G->G: end_game() + + G->S: move() + S->S: change_direction() + S->S: is_collision() + S->S: grow() + S->S: draw() + + G->F: generate() + F->F: draw() +``` +## Anything UNCLEAR +The design and implementation of the snake game are clear based on the given requirements. + +## Required Python third-party packages +``` +""" +pygame==2.0.1 +""" +``` +## Required Other language third-party packages +``` +""" +No third-party packages required for other languages. +""" +``` + +## Logic Analysis +``` +[ + ["main.py", "Main"], + ["game.py", "Game"], + ["snake.py", "Snake"], + ["food.py", "Food"] +] +``` +## Task list +``` +[ + "snake.py", + "food.py", + "game.py", + "main.py" +] +``` +## Shared Knowledge +``` +""" +'game.py' contains the main logic for the snake game, including starting the game, handling user input, updating the game state, and drawing the game state. + +'snake.py' contains the logic for the snake, including moving the snake, changing its direction, checking for collisions, growing the snake, and drawing the snake. + +'food.py' contains the logic for the food, including generating a new food position and drawing the food. + +'main.py' initializes the game and runs the game loop. +""" +``` +## Anything UNCLEAR +We need to clarify the main entry point of the application and ensure that all required third-party libraries are properly initialized. + +----- +## Format example +----- +## Code: snake.py +```python +## snake.py +... +``` +----- \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/chain_config.py b/dev_opsgpt/connector/configs/chain_config.py index 5ed2605..bc43b8d 100644 --- a/dev_opsgpt/connector/configs/chain_config.py +++ b/dev_opsgpt/connector/configs/chain_config.py @@ -1,17 +1,16 @@ +from enum import Enum +# from .prompts import PLANNER_TEMPLATE_PROMPT + CHAIN_CONFIGS = { "chatChain": { "chain_name": "chatChain", "chain_type": "BaseChain", - "agents": ["answer"], + "agents": ["qaer"], "chat_turn": 1, "do_checker": False, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" }, "docChatChain": { "chain_name": "docChatChain", @@ -19,11 +18,7 @@ CHAIN_CONFIGS = { "agents": ["qaer"], "chat_turn": 1, "do_checker": False, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" }, "searchChatChain": { "chain_name": "searchChatChain", @@ -31,11 +26,7 @@ CHAIN_CONFIGS = { "agents": ["searcher"], "chat_turn": 1, "do_checker": False, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" }, "codeChatChain": { "chain_name": "codehChatChain", @@ -43,11 +34,7 @@ CHAIN_CONFIGS = { "agents": ["code_qaer"], "chat_turn": 1, "do_checker": False, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" }, "toolReactChain": { "chain_name": "toolReactChain", @@ -55,34 +42,70 @@ CHAIN_CONFIGS = { "agents": ["tool_planner", "tool_react"], "chat_turn": 2, "do_checker": True, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" + }, + "codePlannerChain": { + "chain_name": "codePlannerChain", + "chain_type": "BaseChain", + "agents": ["planner"], + "chat_turn": 1, + "do_checker": True, + "chain_prompt": "" }, "codeReactChain": { "chain_name": "codeReactChain", "chain_type": "BaseChain", - "agents": ["planner", "code_react"], - "chat_turn": 2, + "agents": ["code_react"], + "chat_turn": 6, "do_checker": True, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" }, - "dataAnalystChain": { - "chain_name": "dataAnalystChain", + "codeToolPlanChain": { + "chain_name": "codeToolPlanChain", "chain_type": "BaseChain", - "agents": ["planner", "code_react"], - "chat_turn": 2, + "agents": ["tool_and_code_planner"], + "chat_turn": 1, + "do_checker": False, + "chain_prompt": "" + }, + "codeToolReactChain": { + "chain_name": "codeToolReactChain", + "chain_type": "BaseChain", + "agents": ["tool_and_code_react"], + "chat_turn": 3, "do_checker": True, - "clear_structure": "True", - "brainstorming": "False", - "gui_design": "True", - "git_management": "False", - "self_improve": "False" + "chain_prompt": "" + }, + "planChain": { + "chain_name": "planChain", + "chain_type": "BaseChain", + "agents": ["general_planner"], + "chat_turn": 1, + "do_checker": False, + "chain_prompt": "" + }, + "executorChain": { + "chain_name": "executorChain", + "chain_type": "BaseChain", + "agents": ["executor"], + "chat_turn": 1, + "do_checker": True, + "chain_prompt": "" + }, + "executorRefineChain": { + "chain_name": "executorRefineChain", + "chain_type": "BaseChain", + "agents": ["executor", "base_refiner"], + "chat_turn": 3, + "do_checker": True, + "chain_prompt": "" + }, + "metagptChain": { + "chain_name": "metagptChain", + "chain_type": "BaseChain", + "agents": ["metaGPT_PRD", "metaGPT_DESIGN", "metaGPT_TASK", "metaGPT_CODER"], + "chat_turn": 1, + "do_checker": False, + "chain_prompt": "" }, } diff --git a/dev_opsgpt/connector/configs/phase_config.py b/dev_opsgpt/connector/configs/phase_config.py index 30b9de3..b8bf29d 100644 --- a/dev_opsgpt/connector/configs/phase_config.py +++ b/dev_opsgpt/connector/configs/phase_config.py @@ -55,9 +55,10 @@ PHASE_CONFIGS = { "do_using_tool": True }, "codeReactPhase": { - "phase_name": "codeReacttPhase", + "phase_name": "codeReactPhase", "phase_type": "BasePhase", - "chains": ["codeReactChain"], + # "chains": ["codePlannerChain", "codeReactChain"], + "chains": ["planChain", "codeReactChain"], "do_summary": False, "do_search": False, "do_doc_retrieval": False, @@ -65,15 +66,37 @@ PHASE_CONFIGS = { "do_tool_retrieval": False, "do_using_tool": False }, - "dataReactPhase": { - "phase_name": "dataReactPhase", + "codeToolReactPhase": { + "phase_name": "codeToolReactPhase", "phase_type": "BasePhase", - "chains": ["dataAnalystChain"], - "do_summary": True, + "chains": ["codeToolPlanChain", "codeToolReactChain"], + "do_summary": False, + "do_search": False, + "do_doc_retrieval": False, + "do_code_retrieval": False, + "do_tool_retrieval": False, + "do_using_tool": True + }, + "baseTaskPhase": { + "phase_name": "baseTaskPhase", + "phase_type": "BasePhase", + "chains": ["planChain", "executorChain"], + "do_summary": False, "do_search": False, "do_doc_retrieval": False, "do_code_retrieval": False, "do_tool_retrieval": False, "do_using_tool": False - } + }, + "metagpt_code_devlop": { + "phase_name": "metagpt_code_devlop", + "phase_type": "BasePhase", + "chains": ["metagptChain",], + "do_summary": False, + "do_search": False, + "do_doc_retrieval": False, + "do_code_retrieval": False, + "do_tool_retrieval": False, + "do_using_tool": False + }, } diff --git a/dev_opsgpt/connector/configs/prompts/__init__.py b/dev_opsgpt/connector/configs/prompts/__init__.py new file mode 100644 index 0000000..929ab77 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/__init__.py @@ -0,0 +1,38 @@ +from .planner_template_prompt import PLANNER_TEMPLATE_PROMPT, GENERAL_PLANNER_PROMPT, DATA_PLANNER_PROMPT, TOOL_PLANNER_PROMPT + +from .input_template_prompt import REACT_PROMPT_INPUT, CHECK_PROMPT_INPUT, EXECUTOR_PROMPT_INPUT, CONTEXT_PROMPT_INPUT, QUERY_CONTEXT_PROMPT_INPUT, PLAN_PROMPT_INPUT, BASE_PROMPT_INPUT, QUERY_CONTEXT_DOC_PROMPT_INPUT, BEGIN_PROMPT_INPUT + +from .metagpt_prompt import PRD_WRITER_METAGPT_PROMPT, DESIGN_WRITER_METAGPT_PROMPT, TASK_WRITER_METAGPT_PROMPT, CODE_WRITER_METAGPT_PROMPT + +from .intention_template_prompt import RECOGNIZE_INTENTION_PROMPT + +from .checker_template_prompt import CHECKER_PROMPT, CHECKER_TEMPLATE_PROMPT + +from .summary_template_prompt import CONV_SUMMARY_PROMPT + +from .qa_template_prompt import QA_PROMPT, CODE_QA_PROMPT, QA_TEMPLATE_PROMPT + +from .executor_template_prompt import EXECUTOR_TEMPLATE_PROMPT +from .refine_template_prompt import REFINE_TEMPLATE_PROMPT + +from .react_template_prompt import REACT_TEMPLATE_PROMPT +from .react_code_prompt import REACT_CODE_PROMPT +from .react_tool_prompt import REACT_TOOL_PROMPT +from .react_tool_code_prompt import REACT_TOOL_AND_CODE_PROMPT +from .react_tool_code_planner_prompt import REACT_TOOL_AND_CODE_PLANNER_PROMPT + + + +__all__ = [ + "REACT_PROMPT_INPUT", "CHECK_PROMPT_INPUT", "EXECUTOR_PROMPT_INPUT", "CONTEXT_PROMPT_INPUT", "QUERY_CONTEXT_PROMPT_INPUT", "PLAN_PROMPT_INPUT", "BASE_PROMPT_INPUT", "QUERY_CONTEXT_DOC_PROMPT_INPUT", "BEGIN_PROMPT_INPUT", + "RECOGNIZE_INTENTION_PROMPT", + "PRD_WRITER_METAGPT_PROMPT", "DESIGN_WRITER_METAGPT_PROMPT", "TASK_WRITER_METAGPT_PROMPT", "CODE_WRITER_METAGPT_PROMPT", + "CHECKER_PROMPT", "CHECKER_TEMPLATE_PROMPT", + "CONV_SUMMARY_PROMPT", + "QA_PROMPT", "CODE_QA_PROMPT", "QA_TEMPLATE_PROMPT", + "EXECUTOR_TEMPLATE_PROMPT", + "REFINE_TEMPLATE_PROMPT", + "PLANNER_TEMPLATE_PROMPT", "GENERAL_PLANNER_PROMPT", "DATA_PLANNER_PROMPT", "TOOL_PLANNER_PROMPT", + "REACT_TEMPLATE_PROMPT", + "REACT_CODE_PROMPT", "REACT_TOOL_PROMPT", "REACT_TOOL_AND_CODE_PROMPT", "REACT_TOOL_AND_CODE_PLANNER_PROMPT" +] \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/checker_template_prompt.py b/dev_opsgpt/connector/configs/prompts/checker_template_prompt.py new file mode 100644 index 0000000..102ada1 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/checker_template_prompt.py @@ -0,0 +1,37 @@ + +CHECKER_TEMPLATE_PROMPT = """#### Checker Assistance Guidance + +When users have completed a sequence of tasks or if there is clear evidence that no further actions are required, your role is to confirm the completion. +Your task is to assess the current situation based on the context and determine whether all objectives have been met. +Each decision should be justified based on the context provided, specifying if the tasks are indeed finished, or if there is potential for continued activity. + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +#### Response Output Format +**REASON:** Justify the decision of choosing 'finished' and 'continued' by evaluating the progress step by step. +Consider all relevant information. If the tasks were aimed at an ongoing process, assess whether it has reached a satisfactory conclusion. + +**Action Status:** Set to 'finished' or 'continued'. +If it's 'finished', the context can answer the origin query. +If it's 'continued', the context cant answer the origin query. +""" + +CHECKER_PROMPT = """尽可能地以有帮助和准确的方式回应人类,判断问题是否得到解答,同时展现解答的过程和内容。 +用户的问题:{query} +使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 +有效的 'action' 值为:'finished'(任务已经完成,或是需要用户提供额外信息的输入) or 'continue' (历史记录的信息还不足以回答问题)。 +在每个 $JSON_BLOB 中仅提供一个 action,如下所示: +``` +{{'content': '提取“背景信息”和“对话信息”中信息来回答问题', 'reason': '解释$ACTION的原因', 'action': $ACTION}} +``` +按照以下格式进行回应: +问题:输入问题以回答 +行动: +``` +$JSON_BLOB +``` +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/executor_template_prompt.py b/dev_opsgpt/connector/configs/prompts/executor_template_prompt.py new file mode 100644 index 0000000..82b2b8a --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/executor_template_prompt.py @@ -0,0 +1,33 @@ +EXECUTOR_TEMPLATE_PROMPT = """#### Writing Code Assistance Guidance + +When users need help with coding, your role is to provide precise and effective guidance. + +Write the code step by step, showing only the part necessary to solve the current problem. + +Each reply should contain only the code required for the current step. + +#### Response Process + +**Question:** First, clarify the problem to be solved. + +**Thoughts:** Based on the question and observations above, provide the plan for executing this step. + +**Action Status:** Set to 'finished' or 'coding'. If it's 'finished', the next action is to provide the final answer to the original question. If it's 'coding', the next step is to write the code. + +**Action:** Code according to your thoughts. Use this format for code: + +```python +# Write your code here +``` + +**Observation:** Check the results and effects of the executed code. + +... (Repeat this Question/Thoughts/Action/Observation cycle as needed) + +**Thoughts:** I now know the final answer + +**Action Status:** Set to 'finished' + +**Action:** The final answer to the original input question + +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/input_template_prompt.py b/dev_opsgpt/connector/configs/prompts/input_template_prompt.py new file mode 100644 index 0000000..97ef84f --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/input_template_prompt.py @@ -0,0 +1,40 @@ + + +BASE_PROMPT_INPUT = '''#### Begin!!! +''' + +PLAN_PROMPT_INPUT = '''#### Begin!!! +**Question:** {query} +''' + +REACT_PROMPT_INPUT = '''#### Begin!!! +{query} +''' + + +CONTEXT_PROMPT_INPUT = '''#### Begin!!! +**Context:** {context} +''' + +QUERY_CONTEXT_DOC_PROMPT_INPUT = '''#### Begin!!! +**Origin Query:** {query} + +**Context:** {context} + +**DocInfos:** {DocInfos} +''' + +QUERY_CONTEXT_PROMPT_INPUT = '''#### Begin!!! +**Origin Query:** {query} + +**Context:** {context} +''' + +EXECUTOR_PROMPT_INPUT = '''#### Begin!!! +{query} +''' + +BEGIN_PROMPT_INPUT = '''#### Begin!!! +''' + +CHECK_PROMPT_INPUT = '''下面是用户的原始问题:{query}''' diff --git a/dev_opsgpt/connector/configs/prompts/intention_template_prompt.py b/dev_opsgpt/connector/configs/prompts/intention_template_prompt.py new file mode 100644 index 0000000..5f641ac --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/intention_template_prompt.py @@ -0,0 +1,14 @@ +RECOGNIZE_INTENTION_PROMPT = """你是一个任务决策助手,能够将理解用户意图并决策采取最合适的行动,尽可能地以有帮助和准确的方式回应人类, +使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 +有效的 'action' 值为:'planning'(需要先进行拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)or "tool_using" (使用工具来回答问题) or 'coding'(生成可执行的代码)。 +在每个 $JSON_BLOB 中仅提供一个 action,如下所示: +``` +{{'action': $ACTION}} +``` +按照以下格式进行回应: +问题:输入问题以回答 +行动:$ACTION +``` +$JSON_BLOB +``` +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/metagpt_prompt.py b/dev_opsgpt/connector/configs/prompts/metagpt_prompt.py new file mode 100644 index 0000000..39b92c4 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/metagpt_prompt.py @@ -0,0 +1,218 @@ +PRD_WRITER_METAGPT_PROMPT = """#### PRD Writer Assistance Guidance + +You are a professional Product Manager, your goal is to design a concise, usable, efficient product. +According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly. +If the Origin Query are unclear, ensure minimum viability and avoid excessive design. +ATTENTION: response carefully referenced "Response Output Format" in format. + + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +#### Response Output Format +**Original Requirements:** +The boss ... + +**Product Goals:** +```python +[ + "Create a ...", +] +``` + +**User Stories:** +```python +[ + "As a user, ...", +] +``` + +**Competitive Analysis:** +```python +[ + "Python Snake Game: ...", +] +``` + +**Requirement Analysis:** +The product should be a ... + +**Requirement Pool:** +```python +[ + ["End game ...", "P0"] +] +``` + +**UI Design draft:** +Give a basic function description, and a draft + +**Anything UNCLEAR:** +There are no unclear points.''' +""" + + + +DESIGN_WRITER_METAGPT_PROMPT = """#### PRD Writer Assistance Guidance + +You are an architect; the goal is to design a SOTA PEP8-compliant python system; make the best use of good open source tools. +Fill in the following missing information based on the context, note that all sections are response with code form separately. +8192 chars or 2048 tokens. Try to use them up. +ATTENTION: response carefully referenced "Response Format" in format. + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +#### Response Format +**Implementation approach:** +Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework. + +**Python package name:** +Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores +```python +"snake_game" +``` + +**File list:** +Provided as Python list[str], the list of ONLY REQUIRED files needed to write the program(LESS IS MORE!). Only need relative paths, comply with PEP8 standards. ALWAYS write a main.py or app.py here + +```python +[ + "main.py", + ... +] +``` + +**Data structures and interface definitions:** +Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), +CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design. + +```mermaid +classDiagram + class Game {{ + +int score + }} + ... + Game "1" -- "1" Food: has +``` + +**Program call flow:** +Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT. +```mermaid +sequenceDiagram + participant M as Main + ... + G->>M: end game +``` + +**Anything UNCLEAR:** +Provide as Plain text. Make clear here. +""" + + + +TASK_WRITER_METAGPT_PROMPT = """#### Task Plan Assistance Guidance + +You are a project manager, the goal is to break down tasks according to PRD/technical design, give a task list, and analyze task dependencies to start with the prerequisite modules +Based on the context, fill in the following missing information, note that all sections are returned in Python code triple quote form seperatedly. +Here the granularity of the task is a file, if there are any missing files, you can supplement them +8192 chars or 2048 tokens. Try to use them up. +ATTENTION: response carefully referenced "Response Output Format" in format. + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +#### Response Output Format + +**Required Python third-party packages:** Provided in requirements.txt format +```python +flask==1.1.2 +bcrypt==3.2.0 +... +``` + +**Required Other language third-party packages:** Provided in requirements.txt format +```python +No third-party ... +``` + +**Full API spec:** Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend. +```python +openapi: 3.0.0 +... +description: A JSON object ... +``` + +**Logic Analysis:** Provided as a Python list[list[str]. the first is filename, the second is class/method/function should be implemented in this file. Analyze the dependencies between the files, which work should be done first +```python +[ + ["game.py", "Contains ..."], +] +``` + +**PLAN:** Provided as Python list[str]. Each str is a filename, the more at the beginning, the more it is a prerequisite dependency, should be done first +```python +[ + "game.py", +] +``` + +**Shared Knowledge:** Anything that should be public like utils' functions, config's variables details that should make clear first. +```python +'game.py' contains ... +``` + +**Anything UNCLEAR:** +Provide as Plain text. Make clear here. For example, don't forget a main entry. don't forget to init 3rd party libs. +""" + + +CODE_WRITER_METAGPT_PROMPT = """#### Code Writer Assistance Guidance + +You are a professional engineer; the main goal is to write PEP8 compliant, elegant, modular, easy to read and maintain Python 3.9 code (but you can also use other programming language) + +Code: Write code with triple quoto, based on the following list and context. +1. Do your best to implement THIS ONLY ONE FILE. ONLY USE EXISTING API. IF NO API, IMPLEMENT IT. +2. Requirement: Based on the context, implement one following code file, note to return only in code form, your code will be part of the entire project, so please implement complete, reliable, reusable code snippets +3. Attention1: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. +4. Attention2: YOU MUST FOLLOW "Data structures and interface definitions". DONT CHANGE ANY DESIGN. +5. Think before writing: What should be implemented and provided in this document? +6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE. +7. Do not use public member functions that do not exist in your design. +8. **$key:** is Input format or Output format, *$key* is the context infomation, they are different. + +8192 chars or 2048 tokens. Try to use them up. +ATTENTION: response carefully referenced "Response Output Format" in format **$key:**. + + +#### Input Format +**Origin Query:** the user's origin query you should to be solved + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +**Question:** clarify the current question to be solved + +#### Response Output Format +**Action Status:** Coding2File + +**SaveFileName** construct a local file name based on Question and Context, such as + +```python +$projectname/$filename.py +``` + +**Code:** Write your code here +```python +# Write your code here +``` + +""" diff --git a/dev_opsgpt/connector/configs/prompts/planner_template_prompt.py b/dev_opsgpt/connector/configs/prompts/planner_template_prompt.py new file mode 100644 index 0000000..fa9e1bb --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/planner_template_prompt.py @@ -0,0 +1,114 @@ + + +PLANNER_TEMPLATE_PROMPT = """#### Planner Assistance Guidance + +When users need assistance with generating a sequence of achievable tasks, your role is to provide a coherent and continuous plan. +Design the plan step by step, ensuring each task builds on the completion of the previous one. +Each instruction should be actionable and directly follow from the outcome of the preceding step. +ATTENTION: response carefully referenced "Response Output Format" in format. + +#### Input Format + +**Question:** First, clarify the problem to be solved. + +#### Response Output Format + +**Action Status:** Set to 'finished' or 'planning'. +If it's 'finished', the PLAN is to provide the final answer to the original question. +If it's 'planning', the PLAN is to provide a Python list[str] of achievable tasks. + +**PLAN:** +```list +[ + "First, we should ...", +] +``` + +""" + + +TOOL_PLANNER_PROMPT = """#### Tool Planner Assistance Guidance + +Helps user to break down a process of tool usage into a series of plans. +If there are no available tools, can directly answer the question. +Rrespond to humans in the most helpful and accurate way possible. +You can use the following tool: {formatted_tools} + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +#### Response Output Format + +**Action Status:** Set to 'finished' or 'planning'. If it's 'finished', the PLAN is to provide the final answer to the original question. If it's 'planning', the PLAN is to provide a sequence of achievable tasks. + +**PLAN:** +```python +[ + "First, we should ...", +] +``` +""" + + +GENERAL_PLANNER_PROMPT = """你是一个通用计划拆解助手,将问题拆解问题成各个详细明确的步骤计划或直接回答问题,尽可能地以有帮助和准确的方式回应人类, +使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 +有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 +有效的 'plans' 值为: 一个任务列表,按顺序写出需要执行的计划 +在每个 $JSON_BLOB 中仅提供一个 action,如下所示: +``` +{{'action': 'planning', 'plans': [$PLAN1, $PLAN2, $PLAN3, ..., $PLANN], }} +或者 +{{'action': 'only_answer', 'plans': "直接回答问题", }} +``` + +按照以下格式进行回应: +问题:输入问题以回答 +行动: +``` +$JSON_BLOB +``` +""" + + +DATA_PLANNER_PROMPT = """你是一个数据分析助手,能够根据问题来制定一个详细明确的数据分析计划,尽可能地以有帮助和准确的方式回应人类, +使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 +有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 +有效的 'plans' 值为: 一份数据分析计划清单,按顺序排列,用文本表示 +在每个 $JSON_BLOB 中仅提供一个 action,如下所示: +``` +{{'action': 'planning', 'plans': '$PLAN1, $PLAN2, ..., $PLAN3' }} +``` + +按照以下格式进行回应: +问题:输入问题以回答 +行动: +``` +$JSON_BLOB +``` +""" + + +# TOOL_PLANNER_PROMPT = """你是一个工具使用过程的计划拆解助手,将问题拆解为一系列的工具使用计划,若没有可用工具则直接回答问题,尽可能地以有帮助和准确的方式回应人类,你可以使用以下工具: +# {formatted_tools} +# 使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 +# 有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 +# 有效的 'plans' 值为: 一个任务列表,按顺序写出需要使用的工具和使用该工具的理由 +# 在每个 $JSON_BLOB 中仅提供一个 action,如下两个示例所示: +# ``` +# {{'action': 'planning', 'plans': [$PLAN1, $PLAN2, $PLAN3, ..., $PLANN], }} +# ``` +# 或者 若无法通过以上工具解决问题,则直接回答问题 +# ``` +# {{'action': 'only_answer', 'plans': "直接回答问题", }} +# ``` + +# 按照以下格式进行回应: +# 问题:输入问题以回答 +# 行动: +# ``` +# $JSON_BLOB +# ``` +# """ \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/qa_template_prompt.py b/dev_opsgpt/connector/configs/prompts/qa_template_prompt.py new file mode 100644 index 0000000..91e55ca --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/qa_template_prompt.py @@ -0,0 +1,52 @@ +QA_TEMPLATE_PROMPT = """#### Question Answer Assistance Guidance + +Based on the information provided, please answer the origin query concisely and professionally. +If the answer cannot be derived from the given Context and DocInfos, please say 'The question cannot be answered based on the information provided' and do not add any fabricated elements to the answer. +Attention: Follow the input format and response output format + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**Context:** the current status and history of the tasks to determine if Origin Query has been achieved. + +**DocInfos:**: the relevant doc information or code information, if this is empty, don't refer to this. + +#### Response Output Format +**Answer:** Response to the user's origin query based on Context and DocInfos. If DocInfos is empty, you can ignore it. +""" + + +CODE_QA_PROMPT = """#### Code Answer Assistance Guidance + +Based on the information provided, please answer the origin query concisely and professionally. +If the answer cannot be derived from the given Context and DocInfos, please say 'The question cannot be answered based on the information provided' and do not add any fabricated elements to the answer. +Attention: Follow the input format and response output format + +#### Input Format + +**Origin Query:** the initial question or objective that the user wanted to achieve + +**DocInfos:**: the relevant doc information or code information, if this is empty, don't refer to this. + +#### Response Output Format +**Answer:** Response to the user's origin query based on DocInfos. If DocInfos is empty, you can ignore it. +""" + + +QA_PROMPT = """根据已知信息,简洁和专业的来回答问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题”,不允许在答案中添加编造成分,答案请使用中文。 +使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 +有效的 'action' 值为:'finished'(任务已经可以通过上下文信息可以回答) or 'continue' (上下文信息不足以回答问题)。 +在每个 $JSON_BLOB 中仅提供一个 action,如下所示: +``` +{{'action': $ACTION, 'content': '总结对话内容'}} +``` +按照以下格式进行回应: +问题:输入问题以回答 +行动:$ACTION +``` +$JSON_BLOB +``` +""" + +# CODE_QA_PROMPT = """【指令】根据已知信息来回答问""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/react_code_prompt.py b/dev_opsgpt/connector/configs/prompts/react_code_prompt.py new file mode 100644 index 0000000..596d747 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/react_code_prompt.py @@ -0,0 +1,31 @@ + + +REACT_CODE_PROMPT = """#### Writing Code Assistance Guidance + +When users need help with coding, your role is to provide precise and effective guidance. Write the code step by step, showing only the part necessary to solve the current problem. Each reply should contain only the code required for the current step. + +#### Response Process + +**Question:** First, clarify the problem to be solved. + +**Thoughts:** Based on the question and observations above, provide the plan for executing this step. + +**Action Status:** Set to 'finished' or 'coding'. If it's 'finished', the next action is to provide the final answer to the original question. If it's 'coding', the next step is to write the code. + +**Action:** Code according to your thoughts. (Please note that only the content printed out by the executed code can be observed in the subsequent observation.) Use this format for code: + +```python +# Write your code here +``` + +**Observation:** Check the results and effects of the executed code. + +... (Repeat this Thoughts/Action/Observation cycle as needed) + +**Thoughts:** I now know the final answer + +**Action Status:** Set to 'finished' + +**Action:** The final answer to the original input question + +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/react_template_prompt.py b/dev_opsgpt/connector/configs/prompts/react_template_prompt.py new file mode 100644 index 0000000..6939f01 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/react_template_prompt.py @@ -0,0 +1,31 @@ + + +REACT_TEMPLATE_PROMPT = """#### Writing Code Assistance Guidance + +When users need help with coding, your role is to provide precise and effective guidance. Write the code step by step, showing only the part necessary to solve the current problem. Each reply should contain only the code required for the current step. + +#### Response Process + +**Question:** First, clarify the problem to be solved. + +**Thoughts:** Based on the question and observations above, provide the plan for executing this step. + +**Action Status:** Set to 'finished' or 'coding'. If it's 'finished', the next action is to provide the final answer to the original question. If it's 'coding', the next step is to write the code. + +**Action:** Code according to your thoughts. Use this format for code: + +```python +# Write your code here +``` + +**Observation:** Check the results and effects of the executed code. + +... (Repeat this Thoughts/Action/Observation cycle as needed) + +**Thoughts:** I now know the final answer + +**Action Status:** Set to 'finished' + +**Action:** The final answer to the original input question + +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/react_tool_code_planner_prompt.py b/dev_opsgpt/connector/configs/prompts/react_tool_code_planner_prompt.py new file mode 100644 index 0000000..63af9b1 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/react_tool_code_planner_prompt.py @@ -0,0 +1,49 @@ +REACT_TOOL_AND_CODE_PLANNER_PROMPT = """#### Tool and Code Sequence Breakdown Assistant +When users need assistance with deconstructing problems into a series of actionable plans using tools or code, your role is to provide a structured plan or a direct solution. +You may use the following tools: +{formatted_tools} +Depending on the user's query, the response will either be a plan detailing the use of tools and reasoning, or a direct answer if the problem does not require breaking down. + +#### Input Format + +**Origin Query:** user's query + +#### Follow this Response Format + +**Action Status:** Set to 'planning' to provide a sequence of tasks, or 'only_answer' to provide a direct response without a plan. + +**Action:** + +For planning: +```list +[ + "First step of the plan using a specified tool or a outline plan for code...", + "Next step in the plan...", + // Continue with additional steps as necessary +] +``` + +Or, provide the direct answer. +""" + +# REACT_TOOL_AND_CODE_PLANNER_PROMPT = """你是一个工具和代码使用过程的计划拆解助手,将问题拆解为一系列的工具使用计划,若没有可用工具则使用代码,尽可能地以有帮助和准确的方式回应人类,你可以使用以下工具: +# {formatted_tools} +# 使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)和一个 plans (生成的计划)。 +# 有效的 'action' 值为:'planning'(拆解计划) or 'only_answer' (不需要拆解问题即可直接回答问题)。 +# 有效的 'plans' 值为: 一个任务列表,按顺序写出需要使用的工具和使用该工具的理由 +# 在每个 $JSON_BLOB 中仅提供一个 action,如下两个示例所示: +# ``` +# {{'action': 'planning', 'plans': [$PLAN1, $PLAN2, $PLAN3, ..., $PLANN], }} +# ``` +# 或者 若无法通过以上工具或者代码解决问题,则直接回答问题 +# ``` +# {{'action': 'only_answer', 'plans': "直接回答问题", }} +# ``` + +# 按照以下格式进行回应($JSON_BLOB要求符合上述规定): +# 问题:输入问题以回答 +# 行动: +# ``` +# $JSON_BLOB +# ``` +# """ \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/react_tool_code_prompt.py b/dev_opsgpt/connector/configs/prompts/react_tool_code_prompt.py new file mode 100644 index 0000000..6e0efbb --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/react_tool_code_prompt.py @@ -0,0 +1,81 @@ +REACT_TOOL_AND_CODE_PROMPT = """#### Code and Tool Agent Assistance Guidance + +When users need help with coding or using tools, your role is to provide precise and effective guidance. Use the tools provided if they can solve the problem, otherwise, write the code step by step, showing only the part necessary to solve the current problem. Each reply should contain only the guidance required for the current step either by tool usage or code. + +#### Tool Infomation + +You can use these tools:\n{formatted_tools} + +Valid "tool_name" value:\n{tool_names} + +#### Response Process + +**Question:** Start by understanding the input question to be answered. + +**Thoughts:** Considering the user's question, previously executed steps, and the plan, decide whether the current step requires the use of a tool or coding. Solve the problem step by step, only displaying the thought process necessary for the current step of solving the problem. If a tool can be used, provide its name and parameters. If coding is required, outline the plan for executing this step. + +**Action Status:** finished, tool_using, or coding. (Choose one from these three statuses. If the task is done, set it to 'finished'. If using a tool, set it to 'tool_using'. If writing code, set it to 'coding'.) + +**Action:** + +If using a tool, output the following format to call the tool: + +```json +{{ + "tool_name": "$TOOL_NAME", + "tool_params": "$INPUT" +}} +``` + +If the problem cannot be solved with a tool at the moment, then proceed to solve the issue using code. Output the following format to execute the code: + +```python +# Write your code here +``` + +**Observation:** Check the results and effects of the executed action. + +... (Repeat this Thoughts/Action/Observation cycle as needed) + +**Thoughts:** Conclude the final response to the input question. + +**Action Status:** finished + +**Action:** The final answer or guidance to the original input question. +""" + +# REACT_TOOL_AND_CODE_PROMPT = """你是一个使用工具与代码的助手。 +# 如果现有工具不足以完成整个任务,请不要添加不存在的工具,只使用现有工具完成可能的部分。 +# 如果当前步骤不能使用工具完成,将由代码来完成。 +# 有效的"action"值为:"finished"(已经完成用户的任务) 、 "tool_using" (使用工具来回答问题) 或 'coding'(结合总结下述思维链过程编写下一步的可执行代码)。 +# 尽可能地以有帮助和准确的方式回应人类,你可以使用以下工具: +# {formatted_tools} +# 如果现在的步骤可以用工具解决问题,请仅在每个$JSON_BLOB中提供一个action,如下所示: +# ``` +# {{{{ +# "action": $ACTION, +# "tool_name": $TOOL_NAME +# "tool_params": $INPUT +# }}}} +# ``` +# 若当前无法通过工具解决问题,则使用代码解决问题 +# 请仅在每个$JSON_BLOB中提供一个action,如下所示: +# ``` +# {{{{'action': $ACTION,'code_content': $CODE}}}} +# ``` + +# 按照以下思维链格式进行回应($JSON_BLOB要求符合上述规定): +# 问题:输入问题以回答 +# 思考:考虑之前和之后的步骤 +# 行动: +# ``` +# $JSON_BLOB +# ``` +# 观察:行动结果 +# ...(重复思考/行动/观察N次) +# 思考:我知道该如何回应 +# 行动: +# ``` +# $JSON_BLOB +# ``` +# """ \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/react_tool_prompt.py b/dev_opsgpt/connector/configs/prompts/react_tool_prompt.py new file mode 100644 index 0000000..416c36d --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/react_tool_prompt.py @@ -0,0 +1,81 @@ +REACT_TOOL_PROMPT = """#### Tool Agent Assistance Guidance + +When interacting with users, your role is to respond in a helpful and accurate manner using the tools available. Follow the steps below to ensure efficient and effective use of the tools. + +Please note that all the tools you can use are listed below. You can only choose from these tools for use. If there are no suitable tools, please do not invent any tools. Just let the user know that you do not have suitable tools to use. + +#### Tool List + +you can use these tools:\n{formatted_tools} + +valid "tool_name" value is:\n{tool_names} + +#### Response Process + +**Question:** Start by understanding the input question to be answered. + +**Thoughts:** Based on the question and previous observations, plan the approach for using the tool effectively. + +**Action Status:** Set to either 'finished' or 'tool_using'. If 'finished', provide the final response to the original question. If 'tool_using', proceed with using the specified tool. + +**Action:** Use the tools by formatting the tool action in JSON. The format should be: + +```json +{{ + "tool_name": "$TOOL_NAME", + "tool_params": "$INPUT" +}} +``` + +**Observation:** Evaluate the outcome of the tool's usage. + +... (Repeat this Thoughts/Action/Observation cycle as needed) + +**Thoughts:** Determine the final response based on the results. + +**Action Status:** Set to 'finished' + +**Action:** Conclude with the final response to the original question in this format: + +```json +{{ + "tool_params": "Final response to be provided to the user", + "tool_name": "notool", +}} +``` +""" + + +# REACT_TOOL_PROMPT = """尽可能地以有帮助和准确的方式回应人类。您可以使用以下工具: +# {formatted_tools} +# 使用json blob来指定一个工具,提供一个action关键字(工具名称)和一个tool_params关键字(工具输入)。 +# 有效的"action"值为:"finished" 或 "tool_using" (使用工具来回答问题) +# 有效的"tool_name"值为:{tool_names} +# 请仅在每个$JSON_BLOB中提供一个action,如下所示: +# ``` +# {{{{ +# "action": $ACTION, +# "tool_name": $TOOL_NAME, +# "tool_params": $INPUT +# }}}} +# ``` + +# 按照以下格式进行回应: +# 问题:输入问题以回答 +# 思考:考虑之前和之后的步骤 +# 行动: +# ``` +# $JSON_BLOB +# ``` +# 观察:行动结果 +# ...(重复思考/行动/观察N次) +# 思考:我知道该如何回应 +# 行动: +# ``` +# {{{{ +# "action": "finished", +# "tool_name": "notool", +# "tool_params": "最终返回答案给到用户" +# }}}} +# ``` +# """ diff --git a/dev_opsgpt/connector/configs/prompts/refine_template_prompt.py b/dev_opsgpt/connector/configs/prompts/refine_template_prompt.py new file mode 100644 index 0000000..76cd466 --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/refine_template_prompt.py @@ -0,0 +1,30 @@ +REFINE_TEMPLATE_PROMPT = """#### Refiner Assistance Guidance + +When users have a sequence of tasks that require optimization or adjustment based on feedback from the context, your role is to refine the existing plan. +Your task is to identify where improvements can be made and provide a revised plan that is more efficient or effective. +Each instruction should be an enhancement of the existing plan and should specify the step from which the changes should be implemented. + +#### Input Format + +**Context:** Review the history of the plan and feedback to identify areas for improvement. +Take into consideration all feedback information from the current step. If there is no existing plan, generate a new one. + +#### Response Output Format + +**REASON:** think the reason of why choose 'finished', 'unchanged' or 'adjusted' step by step. + +**Action Status:** Set to 'finished', 'unchanged' or 'adjusted'. +If it's 'finished', all tasks are accomplished, and no adjustments are needed, so PLAN_STEP is set to -1. +If it's 'unchanged', this PLAN has no problem, just set PLAN_STEP to CURRENT_STEP+1. +If it's 'adjusted', the PLAN is to provide an optimized version of the original plan. + +**PLAN:** +```list +[ + "First, we should ...", +] +``` + +**PLAN_STEP:** Set to the plan index from which the changes should start. Index range from 0 to n-1 or -1 +If it's 'finished', the PLAN_STEP is -1. If it's 'adjusted', the PLAN_STEP is the index of the first revised task in the sequence. +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/configs/prompts/summary_template_prompt.py b/dev_opsgpt/connector/configs/prompts/summary_template_prompt.py new file mode 100644 index 0000000..0659cde --- /dev/null +++ b/dev_opsgpt/connector/configs/prompts/summary_template_prompt.py @@ -0,0 +1,20 @@ +CONV_SUMMARY_PROMPT = """尽可能地以有帮助和准确的方式回应人类,根据“背景信息”中的有效信息回答问题, +使用 JSON Blob 来指定一个返回的内容,提供一个 action(行动)。 +有效的 'action' 值为:'finished'(任务已经可以通过上下文信息可以回答) or 'continue' (根据背景信息回答问题)。 +在每个 $JSON_BLOB 中仅提供一个 action,如下所示: +``` +{{'action': $ACTION, 'content': '根据背景信息回答问题'}} +``` +按照以下格式进行回应: +问题:输入问题以回答 +行动: +``` +$JSON_BLOB +``` +""" + +CONV_SUMMARY_PROMPT = """尽可能地以有帮助和准确的方式回应人类 +根据“背景信息”中的有效信息回答问题,同时展现解答的过程和内容 +若能根“背景信息”回答问题,则直接回答 +否则,总结“背景信息”的内容 +""" \ No newline at end of file diff --git a/dev_opsgpt/connector/message_process.py b/dev_opsgpt/connector/message_process.py new file mode 100644 index 0000000..694d570 --- /dev/null +++ b/dev_opsgpt/connector/message_process.py @@ -0,0 +1,364 @@ +import re, traceback, uuid, copy, json, os +from loguru import logger + + +from configs.server_config import SANDBOX_SERVER +from configs.model_config import JUPYTER_WORK_PATH +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, ActionStatus, CodeDoc, Doc +) +from dev_opsgpt.tools import DDGSTool, DocRetrieval, CodeRetrieval +from dev_opsgpt.sandbox import PyCodeBox, CodeBoxResponse + + +class MessageUtils: + def __init__(self, role: Role = None) -> None: + self.role = role + self.codebox = PyCodeBox( + remote_url=SANDBOX_SERVER["url"], + remote_ip=SANDBOX_SERVER["host"], + remote_port=SANDBOX_SERVER["port"], + token="mytoken", + do_code_exe=True, + do_remote=SANDBOX_SERVER["do_remote"], + do_check_net=False + ) + + def filter(self, message: Message, stop=None) -> Message: + + tool_params = self.parser_spec_key(message.role_content, "tool_params") + code_content = self.parser_spec_key(message.role_content, "code_content") + plan = self.parser_spec_key(message.role_content, "plan") + plans = self.parser_spec_key(message.role_content, "plans", do_search=False) + content = self.parser_spec_key(message.role_content, "content", do_search=False) + + # logger.debug(f"tool_params: {tool_params}, code_content: {code_content}, plan: {plan}, plans: {plans}, content: {content}") + role_content = tool_params or code_content or plan or plans or content + message.role_content = role_content or message.role_content + return message + + def inherit_extrainfo(self, input_message: Message, output_message: Message): + output_message.db_docs = input_message.db_docs + output_message.search_docs = input_message.search_docs + output_message.code_docs = input_message.code_docs + output_message.figures.update(input_message.figures) + output_message.origin_query = input_message.origin_query + return output_message + + def get_extrainfo_step(self, message: Message, do_search, do_doc_retrieval, do_code_retrieval, do_tool_retrieval) -> Message: + '''''' + if do_search: + message = self.get_search_retrieval(message) + + if do_doc_retrieval: + message = self.get_doc_retrieval(message) + + if do_code_retrieval: + input_message = self.get_code_retrieval(message) + + if do_tool_retrieval: + message = self.get_tool_retrieval(message) + + return message + + def get_search_retrieval(self, message: Message,) -> Message: + SEARCH_ENGINES = {"duckduckgo": DDGSTool} + search_docs = [] + for idx, doc in enumerate(SEARCH_ENGINES["duckduckgo"].run(message.role_content, 3)): + doc.update({"index": idx}) + search_docs.append(Doc(**doc)) + message.search_docs = search_docs + return message + + def get_doc_retrieval(self, message: Message) -> Message: + query = message.role_content + knowledge_basename = message.doc_engine_name + top_k = message.top_k + score_threshold = message.score_threshold + if knowledge_basename: + docs = DocRetrieval.run(query, knowledge_basename, top_k, score_threshold) + message.db_docs = [Doc(**doc) for doc in docs] + return message + + def get_code_retrieval(self, message: Message) -> Message: + # DocRetrieval.run("langchain是什么", "DSADSAD") + query = message.input_query + code_engine_name = message.code_engine_name + history_node_list = message.history_node_list + code_docs = CodeRetrieval.run(code_engine_name, query, code_limit=message.top_k, history_node_list=history_node_list, search_type=message.cb_search_type) + message.code_docs = [CodeDoc(**doc) for doc in code_docs] + return message + + def get_tool_retrieval(self, message: Message) -> Message: + return message + + def step_router(self, message: Message) -> tuple[Message, ...]: + '''''' + # message = self.parser(message) + # logger.debug(f"message.action_status: {message.action_status}") + observation_message = None + if message.action_status == ActionStatus.CODING: + message, observation_message = self.code_step(message) + elif message.action_status == ActionStatus.TOOL_USING: + message, observation_message = self.tool_step(message) + elif message.action_status == ActionStatus.CODING2FILE: + self.save_code2file(message) + + return message, observation_message + + def code_step(self, message: Message) -> Message: + '''execute code''' + # logger.debug(f"message.role_content: {message.role_content}, message.code_content: {message.code_content}") + code_answer = self.codebox.chat('```python\n{}```'.format(message.code_content)) + code_prompt = f"The return error after executing the above code is {code_answer.code_exe_response},need to recover" \ + if code_answer.code_exe_type == "error" else f"The return information after executing the above code is {code_answer.code_exe_response}" + + observation_message = Message( + role_name="observation", + role_type="func", #self.role.role_type, + role_content="", + step_content="", + input_query=message.code_content, + ) + uid = str(uuid.uuid1()) + if code_answer.code_exe_type == "image/png": + message.figures[uid] = code_answer.code_exe_response + message.code_answer = f"\n**Observation:**: The return figure name is {uid} after executing the above code.\n" + message.observation = f"\n**Observation:**: The return figure name is {uid} after executing the above code.\n" + message.step_content += f"\n**Observation:**: The return figure name is {uid} after executing the above code.\n" + message.step_contents += [f"\n**Observation:**:The return figure name is {uid} after executing the above code.\n"] + # message.role_content += f"\n**Observation:**:执行上述代码后生成一张图片, 图片名为{uid}\n" + observation_message.role_content = f"\n**Observation:**: The return figure name is {uid} after executing the above code.\n" + observation_message.parsed_output = {"Observation": f"The return figure name is {uid} after executing the above code."} + else: + message.code_answer = code_answer.code_exe_response + message.observation = code_answer.code_exe_response + message.step_content += f"\n**Observation:**: {code_prompt}\n" + message.step_contents += [f"\n**Observation:**: {code_prompt}\n"] + # message.role_content += f"\n**Observation:**: {code_prompt}\n" + observation_message.role_content = f"\n**Observation:**: {code_prompt}\n" + observation_message.parsed_output = {"Observation": code_prompt} + # logger.info(f"**Observation:** {message.action_status}, {message.observation}") + return message, observation_message + + def tool_step(self, message: Message) -> Message: + '''execute tool''' + # logger.debug(f"{message}") + observation_message = Message( + role_name="observation", + role_type="function", #self.role.role_type, + role_content="\n**Observation:** there is no tool can execute\n" , + step_content="", + input_query=str(message.tool_params), + tools=message.tools, + ) + # logger.debug(f"message: {message.action_status}, {message.tool_name}, {message.tool_params}") + tool_names = [tool.name for tool in message.tools] + if message.tool_name not in tool_names: + message.tool_answer = "\n**Observation:** there is no tool can execute\n" + message.observation = "\n**Observation:** there is no tool can execute\n" + # message.role_content += f"\n**Observation:**: 不存在可以执行的tool\n" + message.step_content += f"\n**Observation:** there is no tool can execute\n" + message.step_contents += [f"\n**Observation:** there is no tool can execute\n"] + observation_message.role_content = f"\n**Observation:** there is no tool can execute\n" + observation_message.parsed_output = {"Observation": "there is no tool can execute\n"} + for tool in message.tools: + if tool.name == message.tool_name: + tool_res = tool.func(**message.tool_params.get("tool_params", {})) + logger.debug(f"tool_res {tool_res}") + message.tool_answer = tool_res + message.observation = tool_res + # message.role_content += f"\n**Observation:**: {tool_res}\n" + message.step_content += f"\n**Observation:** {tool_res}\n" + message.step_contents += [f"\n**Observation:** {tool_res}\n"] + observation_message.role_content = f"\n**Observation:** {tool_res}\n" + observation_message.parsed_output = {"Observation": tool_res} + break + + # logger.info(f"**Observation:** {message.action_status}, {message.observation}") + return message, observation_message + + def parser(self, message: Message) -> Message: + '''''' + content = message.role_content + parser_keys = ["action", "code_content", "code_filename", "tool_params", "plans"] + try: + s_json = self._parse_json(content) + message.action_status = s_json.get("action") + message.code_content = s_json.get("code_content") + message.tool_params = s_json.get("tool_params") + message.tool_name = s_json.get("tool_name") + message.code_filename = s_json.get("code_filename") + message.plans = s_json.get("plans") + # for parser_key in parser_keys: + # message.action_status = content.get(parser_key) + except Exception as e: + # logger.warning(f"{traceback.format_exc()}") + def parse_text_to_dict(text): + # Define a regular expression pattern to capture the key and value + main_pattern = r"\*\*(.+?):\*\*\s*(.*?)\s*(?=\*\*|$)" + list_pattern = r'```python\n(.*?)```' + + # Use re.findall to find all main matches in the text + main_matches = re.findall(main_pattern, text, re.DOTALL) + + # Convert main matches to a dictionary + parsed_dict = {key.strip(): value.strip() for key, value in main_matches} + + for k, v in parsed_dict.items(): + for pattern in [list_pattern]: + if "PLAN" != k: continue + v = v.replace("```list", "```python") + match_value = re.search(pattern, v, re.DOTALL) + if match_value: + # Add the code block to the dictionary + parsed_dict[k] = eval(match_value.group(1).strip()) + break + + return parsed_dict + + def extract_content_from_backticks(text): + code_blocks = [] + lines = text.split('\n') + is_code_block = False + code_block = '' + language = '' + for line in lines: + if line.startswith('```') and not is_code_block: + is_code_block = True + language = line[3:] + code_block = '' + elif line.startswith('```') and is_code_block: + is_code_block = False + code_blocks.append({language.strip(): code_block.strip()}) + elif is_code_block: + code_block += line + '\n' + return code_blocks + + def parse_dict_to_dict(parsed_dict) -> dict: + code_pattern = r'```python\n(.*?)```' + tool_pattern = r'```json\n(.*?)```' + + pattern_dict = {"code": code_pattern, "json": tool_pattern} + spec_parsed_dict = copy.deepcopy(parsed_dict) + for key, pattern in pattern_dict.items(): + for k, text in parsed_dict.items(): + # Search for the code block + if not isinstance(text, str): continue + _match = re.search(pattern, text, re.DOTALL) + if _match: + # Add the code block to the dictionary + try: + spec_parsed_dict[key] = json.loads(_match.group(1).strip()) + except: + spec_parsed_dict[key] = _match.group(1).strip() + break + return spec_parsed_dict + + parsed_dict = parse_text_to_dict(content) + spec_parsed_dict = parse_dict_to_dict(parsed_dict) + action_value = parsed_dict.get('Action Status') + if action_value: + action_value = action_value.lower() + logger.info(f'{message.role_name}: action_value: {action_value}') + # action_value = self._match(r"'action':\s*'([^']*)'", content) if "'action'" in content else self._match(r'"action":\s*"([^"]*)"', content) + + code_content_value = spec_parsed_dict.get('code') + # code_content_value = self._match(r"'code_content':\s*'([^']*)'", content) if "'code_content'" in content else self._match(r'"code_content":\s*"([^"]*)"', content) + filename_value = self._match(r"'code_filename':\s*'([^']*)'", content) if "'code_filename'" in content else self._match(r'"code_filename":\s*"([^"]*)"', content) + + if action_value == 'tool_using': + tool_params_value = spec_parsed_dict.get('json') + else: + tool_params_value = None + # tool_params_value = spec_parsed_dict.get('tool_params') + # tool_params_value = self._match(r"'tool_params':\s*(\{[^{}]*\})", content, do_json=True) if "'tool_params'" in content \ + # else self._match(r'"tool_params":\s*(\{[^{}]*\})', content, do_json=True) + tool_name_value = self._match(r"'tool_name':\s*'([^']*)'", content) if "'tool_name'" in content else self._match(r'"tool_name":\s*"([^"]*)"', content) + plans_value = self._match(r"'plans':\s*(\[.*?\])", content, do_search=False) if "'plans'" in content else self._match(r'"plans":\s*(\[.*?\])', content, do_search=False, ) + # re解析 + message.action_status = action_value or "default" + message.code_content = code_content_value + message.code_filename = filename_value + message.tool_params = tool_params_value + message.tool_name = tool_name_value + message.plans = plans_value + message.parsed_output = parsed_dict + message.spec_parsed_output = spec_parsed_dict + + # logger.debug(f"确认当前的action: {message.action_status}") + + return message + + def parser_spec_key(self, content, key, do_search=True, do_json=False) -> str: + '''''' + key2pattern = { + "'action'": r"'action':\s*'([^']*)'", '"action"': r'"action":\s*"([^"]*)"', + "'code_content'": r"'code_content':\s*'([^']*)'", '"code_content"': r'"code_content":\s*"([^"]*)"', + "'code_filename'": r"'code_filename':\s*'([^']*)'", '"code_filename"': r'"code_filename":\s*"([^"]*)"', + "'tool_params'": r"'tool_params':\s*(\{[^{}]*\})", '"tool_params"': r'"tool_params":\s*(\{[^{}]*\})', + "'tool_name'": r"'tool_name':\s*'([^']*)'", '"tool_name"': r'"tool_name":\s*"([^"]*)"', + "'plans'": r"'plans':\s*(\[.*?\])", '"plans"': r'"plans":\s*(\[.*?\])', + "'content'": r"'content':\s*'([^']*)'", '"content"': r'"content":\s*"([^"]*)"', + } + + s_json = self._parse_json(content) + try: + if s_json and key in s_json: + return str(s_json[key]) + except: + pass + + keystr = f"'{key}'" if f"'{key}'" in content else f'"{key}"' + return self._match(key2pattern.get(keystr, fr"'{key}':\s*'([^']*)'"), content, do_search=do_search, do_json=do_json) + + def _match(self, pattern, s, do_search=True, do_json=False): + try: + if do_search: + match = re.search(pattern, s) + if match: + value = match.group(1).replace("\\n", "\n") + if do_json: + value = json.loads(value) + else: + value = None + else: + match = re.findall(pattern, s, re.DOTALL) + if match: + value = match[0] + if do_json: + value = json.loads(value) + else: + value = None + except Exception as e: + logger.warning(f"{traceback.format_exc()}") + + # logger.debug(f"pattern: {pattern}, s: {s}, match: {match}") + return value + + def _parse_json(self, s): + try: + pattern = r"```([^`]+)```" + match = re.findall(pattern, s) + if match: + return eval(match[0]) + except: + pass + return None + + + def save_code2file(self, message: Message, project_dir=JUPYTER_WORK_PATH): + filename = message.parsed_output.get("SaveFileName") + code = message.spec_parsed_output.get("code") + + for k, v in {">": ">", "≥": ">=", "<": "<", "≤": "<="}.items(): + code = code.replace(k, v) + + file_path = os.path.join(project_dir, filename) + + if not os.path.exists(file_path): + os.makedirs(os.path.dirname(file_path), exist_ok=True) + + with open(file_path, "w") as f: + f.write(code) + \ No newline at end of file diff --git a/dev_opsgpt/connector/phase/base_phase.py b/dev_opsgpt/connector/phase/base_phase.py index cea23f4..c979778 100644 --- a/dev_opsgpt/connector/phase/base_phase.py +++ b/dev_opsgpt/connector/phase/base_phase.py @@ -8,14 +8,13 @@ from loguru import logger from dev_opsgpt.connector.agents import BaseAgent from dev_opsgpt.connector.chains import BaseChain from dev_opsgpt.tools.base_tool import BaseTools, Tool -from dev_opsgpt.connector.shcema.memory import Memory -from dev_opsgpt.connector.connector_schema import ( - Task, Env, Role, Message, Doc, Docs, AgentConfig, ChainConfig, PhaseConfig, CodeDoc, + +from dev_opsgpt.connector.schema import ( + Memory, Task, Env, Role, Message, Doc, Docs, AgentConfig, ChainConfig, PhaseConfig, CodeDoc, load_chain_configs, load_phase_configs, load_role_configs ) from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS -from dev_opsgpt.tools import DDGSTool, DocRetrieval, CodeRetrieval - +from dev_opsgpt.connector.message_process import MessageUtils role_configs = load_role_configs(AGETN_CONFIGS) chain_configs = load_chain_configs(CHAIN_CONFIGS) @@ -56,57 +55,71 @@ class BasePhase: chain_config = chain_config, role_config = role_config, ) + + self.message_utils = MessageUtils() self.phase_name = phase_name self.do_summary = do_summary self.do_search = do_search self.do_code_retrieval = do_code_retrieval self.do_doc_retrieval = do_doc_retrieval self.do_tool_retrieval = do_tool_retrieval - - self.global_message = Memory([]) + # + self.global_memory = Memory(messages=[]) # self.chain_message = Memory([]) self.phase_memory: List[Memory] = [] + # memory_pool dont have specific order + self.memory_pool = Memory(messages=[]) - def step(self, query: Message, history: Memory = None) -> Tuple[Message, Memory]: + def astep(self, query: Message, history: Memory = None) -> Tuple[Message, Memory]: summary_message = None - chain_message = Memory([]) - local_memory = Memory([]) + chain_message = Memory(messages=[]) + local_phase_memory = Memory(messages=[]) # do_search、do_doc_search、do_code_search - query = self.get_extrainfo_step(query) + query = self.message_utils.get_extrainfo_step(query, self.do_search, self.do_doc_retrieval, self.do_code_retrieval, self.do_tool_retrieval) input_message = copy.deepcopy(query) - self.global_message.append(input_message) + self.global_memory.append(input_message) + local_phase_memory.append(input_message) for chain in self.chains: # chain can supply background and query to next chain - output_message, chain_memory = chain.step(input_message, history, background=chain_message) - output_message = self.inherit_extrainfo(input_message, output_message) + for output_message, local_chain_memory in chain.astep(input_message, history, background=chain_message, memory_pool=self.memory_pool): + # logger.debug(f"local_memory: {local_memory + chain_memory}") + yield output_message, local_phase_memory + local_chain_memory + + output_message = self.message_utils.inherit_extrainfo(input_message, output_message) input_message = output_message logger.info(f"{chain.chainConfig.chain_name} phase_step: {output_message.role_content}") + # 这一段也有问题 + self.global_memory.extend(local_chain_memory) + local_phase_memory.extend(local_chain_memory) - self.global_message.append(output_message) - local_memory.extend(chain_memory) - - # whether use summary_llm + # whether to use summary_llm if self.do_summary: - logger.info(f"{self.conv_summary_agent.role.role_name} input global memory: {self.global_message.to_str_messages(content_key='step_content')}") - logger.info(f"{self.conv_summary_agent.role.role_name} input global memory: {self.global_message.to_str_messages(content_key='role_content')}") - summary_message = self.conv_summary_agent.run(query, background=self.global_message) + logger.info(f"{self.conv_summary_agent.role.role_name} input global memory: {local_phase_memory.to_str_messages(content_key='step_content')}") + for summary_message in self.conv_summary_agent.arun(query, background=local_phase_memory, memory_pool=self.memory_pool): + pass + # summary_message = Message(**summary_message) summary_message.role_name = chain.chainConfig.chain_name - summary_message = self.conv_summary_agent.parser(summary_message) - summary_message = self.conv_summary_agent.filter(summary_message) - summary_message = self.inherit_extrainfo(output_message, summary_message) + summary_message = self.conv_summary_agent.message_utils.parser(summary_message) + summary_message = self.conv_summary_agent.message_utils.filter(summary_message) + summary_message = self.message_utils.inherit_extrainfo(output_message, summary_message) chain_message.append(summary_message) - + + message = summary_message or output_message + yield message, local_phase_memory + # 由于不会存在多轮chain执行,所以直接保留memory即可 for chain in self.chains: self.phase_memory.append(chain.global_memory) - + # TODO:local_memory缺少添加summary的过程 message = summary_message or output_message message.role_name = self.phase_name - # message.db_docs = query.db_docs - # message.code_docs = query.code_docs - # message.search_docs = query.search_docs - return summary_message or output_message, local_memory + yield message, local_phase_memory + + def step(self, query: Message, history: Memory = None) -> Tuple[Message, Memory]: + for message, local_phase_memory in self.astep(query, history=history): + pass + return message, local_phase_memory def init_chains(self, phase_name, phase_config, chain_config, role_config, task=None, memory=None) -> List[BaseChain]: @@ -118,25 +131,33 @@ class BasePhase: chains = [] self.chain_module = importlib.import_module("dev_opsgpt.connector.chains") self.agent_module = importlib.import_module("dev_opsgpt.connector.agents") - phase = phase_configs.get(phase_name) - for chain_name in phase.chains: - logger.info(f"chain_name: {chain_name}") - # chain_class = getattr(self.chain_module, chain_name) - logger.debug(f"{chain_configs.keys()}") - chain_config = chain_configs[chain_name] - agents = [ - getattr(self.agent_module, role_configs[agent_name].role.agent_type)( - role_configs[agent_name].role, + phase = phase_configs.get(phase_name) + logger.info(f"start to init the phase, the phase_name is {phase_name}, it contains these chains such as {phase.chains}") + + for chain_name in phase.chains: + # logger.debug(f"{chain_configs.keys()}") + chain_config = chain_configs[chain_name] + logger.info(f"start to init the chain, the chain_name is {chain_name}, it contains these agents such as {chain_config.agents}") + + agents = [] + for agent_name in chain_config.agents: + agent_config = role_configs[agent_name] + baseAgent: BaseAgent = getattr(self.agent_module, agent_config.role.agent_type) + base_agent = baseAgent( + agent_config.role, task = task, memory = memory, - chat_turn=role_configs[agent_name].chat_turn, - do_search = role_configs[agent_name].do_search, - do_doc_retrieval = role_configs[agent_name].do_doc_retrieval, - do_tool_retrieval = role_configs[agent_name].do_tool_retrieval, + chat_turn=agent_config.chat_turn, + do_search = agent_config.do_search, + do_doc_retrieval = agent_config.do_doc_retrieval, + do_tool_retrieval = agent_config.do_tool_retrieval, + stop= agent_config.stop, + focus_agents=agent_config.focus_agents, + focus_message_keys=agent_config.focus_message_keys, ) - for agent_name in chain_config.agents - ] + agents.append(base_agent) + chain_instance = BaseChain( chain_config, agents, chain_config.chat_turn, do_checker=chain_configs[chain_name].do_checker, @@ -145,56 +166,57 @@ class BasePhase: return chains - def get_extrainfo_step(self, input_message): - if self.do_doc_retrieval: - input_message = self.get_doc_retrieval(input_message) + # def get_extrainfo_step(self, input_message): + # if self.do_doc_retrieval: + # input_message = self.get_doc_retrieval(input_message) - logger.debug(F"self.do_code_retrieval: {self.do_code_retrieval}") - if self.do_code_retrieval: - input_message = self.get_code_retrieval(input_message) + # # logger.debug(F"self.do_code_retrieval: {self.do_code_retrieval}") + # if self.do_code_retrieval: + # input_message = self.get_code_retrieval(input_message) - if self.do_search: - input_message = self.get_search_retrieval(input_message) + # if self.do_search: + # input_message = self.get_search_retrieval(input_message) - return input_message + # return input_message - def inherit_extrainfo(self, input_message: Message, output_message: Message): - output_message.db_docs = input_message.db_docs - output_message.search_docs = input_message.search_docs - output_message.code_docs = input_message.code_docs - output_message.figures.update(input_message.figures) - return output_message + # def inherit_extrainfo(self, input_message: Message, output_message: Message): + # output_message.db_docs = input_message.db_docs + # output_message.search_docs = input_message.search_docs + # output_message.code_docs = input_message.code_docs + # output_message.figures.update(input_message.figures) + # output_message.origin_query = input_message.origin_query + # return output_message - def get_search_retrieval(self, message: Message,) -> Message: - SEARCH_ENGINES = {"duckduckgo": DDGSTool} - search_docs = [] - for idx, doc in enumerate(SEARCH_ENGINES["duckduckgo"].run(message.role_content, 3)): - doc.update({"index": idx}) - search_docs.append(Doc(**doc)) - message.search_docs = search_docs - return message + # def get_search_retrieval(self, message: Message,) -> Message: + # SEARCH_ENGINES = {"duckduckgo": DDGSTool} + # search_docs = [] + # for idx, doc in enumerate(SEARCH_ENGINES["duckduckgo"].run(message.role_content, 3)): + # doc.update({"index": idx}) + # search_docs.append(Doc(**doc)) + # message.search_docs = search_docs + # return message - def get_doc_retrieval(self, message: Message) -> Message: - query = message.role_content - knowledge_basename = message.doc_engine_name - top_k = message.top_k - score_threshold = message.score_threshold - if knowledge_basename: - docs = DocRetrieval.run(query, knowledge_basename, top_k, score_threshold) - message.db_docs = [Doc(**doc) for doc in docs] - return message + # def get_doc_retrieval(self, message: Message) -> Message: + # query = message.role_content + # knowledge_basename = message.doc_engine_name + # top_k = message.top_k + # score_threshold = message.score_threshold + # if knowledge_basename: + # docs = DocRetrieval.run(query, knowledge_basename, top_k, score_threshold) + # message.db_docs = [Doc(**doc) for doc in docs] + # return message - def get_code_retrieval(self, message: Message) -> Message: - # DocRetrieval.run("langchain是什么", "DSADSAD") - query = message.input_query - code_engine_name = message.code_engine_name - history_node_list = message.history_node_list - code_docs = CodeRetrieval.run(code_engine_name, query, code_limit=message.top_k, history_node_list=history_node_list) - message.code_docs = [CodeDoc(**doc) for doc in code_docs] - return message + # def get_code_retrieval(self, message: Message) -> Message: + # # DocRetrieval.run("langchain是什么", "DSADSAD") + # query = message.input_query + # code_engine_name = message.code_engine_name + # history_node_list = message.history_node_list + # code_docs = CodeRetrieval.run(code_engine_name, query, code_limit=message.top_k, history_node_list=history_node_list) + # message.code_docs = [CodeDoc(**doc) for doc in code_docs] + # return message - def get_tool_retrieval(self, message: Message) -> Message: - return message + # def get_tool_retrieval(self, message: Message) -> Message: + # return message def update(self) -> Memory: pass @@ -205,7 +227,7 @@ class BasePhase: ) def get_memory_str(self, do_all_memory=True, content_key="role_content") -> str: - memory = self.global_message if do_all_memory else self.phase_memory + memory = self.global_memory if do_all_memory else self.phase_memory return "\n".join([": ".join(i) for i in memory.to_tuple_messages(content_key=content_key)]) def get_chains_memory(self, content_key="role_content") -> List[Tuple]: diff --git a/dev_opsgpt/connector/schema/__init__.py b/dev_opsgpt/connector/schema/__init__.py new file mode 100644 index 0000000..f858055 --- /dev/null +++ b/dev_opsgpt/connector/schema/__init__.py @@ -0,0 +1,9 @@ +from .memory import Memory +from .general_schema import * +from .message import Message + +__all__ = [ + "Memory", "ActionStatus", "Doc", "CodeDoc", "Task", + "Env", "Role", "ChainConfig", "AgentConfig", "PhaseConfig", "Message", + "load_role_configs", "load_chain_configs", "load_phase_configs" +] \ No newline at end of file diff --git a/dev_opsgpt/connector/connector_schema.py b/dev_opsgpt/connector/schema/general_schema.py similarity index 58% rename from dev_opsgpt/connector/connector_schema.py rename to dev_opsgpt/connector/schema/general_schema.py index 75c5387..2281762 100644 --- a/dev_opsgpt/connector/connector_schema.py +++ b/dev_opsgpt/connector/schema/general_schema.py @@ -16,11 +16,50 @@ class ActionStatus(Enum): EXECUTING_CODE = "executing_code" EXECUTING_TOOL = "executing_tool" DEFAUILT = "default" + CODING2FILE = "coding2file" def __eq__(self, other): if isinstance(other, str): return self.value == other return super().__eq__(other) + + +class RoleTypeEnums(Enum): + SYSTEM = "system" + USER = "user" + ASSISTANT = "assistant" + FUNCTION = "function" + OBSERVATION = "observation" + + def __eq__(self, other): + if isinstance(other, str): + return self.value == other + return super().__eq__(other) + + +class InputKeyEnums(Enum): + # Origin Query is ui's user question + ORIGIN_QUERY = "origin_query" + # agent's input from last agent + CURRENT_QUESTION = "current_question" + # ui memory contaisn (user and assistants) + UI_MEMORY = "ui_memory" + # agent's memory + SELF_MEMORY = "self_memory" + # chain memory + CHAIN_MEMORY = "chain_memory" + # agent's memory + SELF_ONE_MEMORY = "self_one_memory" + # chain memory + CHAIN_ONE_MEMORY = "chain_one_memory" + # Doc Infomations contains (Doc\Code\Search) + DOC_INFOS = "doc_infos" + + def __eq__(self, other): + if isinstance(other, str): + return self.value == other + return super().__eq__(other) + class Doc(BaseModel): title: str @@ -117,10 +156,13 @@ class ChainConfig(BaseModel): class AgentConfig(BaseModel): role: Role + stop: str = None chat_turn: int = 1 do_search: bool = False do_doc_retrieval: bool = False do_tool_retrieval: bool = False + focus_agents: List = [] + focus_message_keys: List = [] class PhaseConfig(BaseModel): @@ -133,92 +175,6 @@ class PhaseConfig(BaseModel): do_code_retrieval: bool = False do_tool_retrieval: bool = False -class Message(BaseModel): - role_name: str - role_type: str - role_prompt: str = None - input_query: str = None - - # 模型最终返回 - role_content: str = None - role_contents: List[str] = [] - step_content: str = None - step_contents: List[str] = [] - chain_content: str = None - chain_contents: List[str] = [] - - # 模型结果解析 - plans: List[str] = None - code_content: str = None - code_filename: str = None - tool_params: str = None - tool_name: str = None - - # 执行结果 - action_status: str = ActionStatus.DEFAUILT - code_answer: str = None - tool_answer: str = None - observation: str = None - figures: Dict[str, str] = {} - - # 辅助信息 - tools: List[BaseTool] = [] - task: Task = None - db_docs: List['Doc'] = [] - code_docs: List['CodeDoc'] = [] - search_docs: List['Doc'] = [] - - # 执行输入 - phase_name: str = None - chain_name: str = None - do_search: bool = False - doc_engine_name: str = None - code_engine_name: str = None - search_engine_name: str = None - top_k: int = 3 - score_threshold: float = 1.0 - do_doc_retrieval: bool = False - do_code_retrieval: bool = False - do_tool_retrieval: bool = False - history_node_list: List[str] = [] - - - def to_tuple_message(self, return_all: bool = False, content_key="role_content"): - if content_key == "role_content": - role_content = self.role_content - elif content_key == "step_content": - role_content = self.step_content or self.role_content - else: - role_content =self.role_content - - if return_all: - return (self.role_name, self.role_type, role_content) - else: - return (self.role_name, role_content) - return (self.role_type, re.sub("}", "}}", re.sub("{", "{{", str(self.role_content)))) - - def to_dict_message(self, return_all: bool = False, content_key="role_content"): - if content_key == "role_content": - role_content =self.role_content - elif content_key == "step_content": - role_content = self.step_content or self.role_content - else: - role_content =self.role_content - - if return_all: - return vars(self) - else: - return {"role": self.role_name, "content": role_content} - - def is_system_role(self,): - return self.role_type == "system" - - def __str__(self) -> str: - # key_str = '\n'.join([k for k, v in vars(self).items()]) - # logger.debug(f"{key_str}") - return "\n".join([": ".join([k, str(v)]) for k, v in vars(self).items()]) - - def load_role_configs(config) -> Dict[str, AgentConfig]: if isinstance(config, str): diff --git a/dev_opsgpt/connector/shcema/memory.py b/dev_opsgpt/connector/schema/memory.py similarity index 51% rename from dev_opsgpt/connector/shcema/memory.py rename to dev_opsgpt/connector/schema/memory.py index f5e6a5f..70d2809 100644 --- a/dev_opsgpt/connector/shcema/memory.py +++ b/dev_opsgpt/connector/schema/memory.py @@ -1,17 +1,18 @@ from pydantic import BaseModel -from typing import List +from typing import List, Union from loguru import logger -from dev_opsgpt.connector.connector_schema import Message +from .message import Message from dev_opsgpt.utils.common_utils import ( save_to_jsonl_file, save_to_json_file, read_json_file, read_jsonl_file ) -class Memory: - - def __init__(self, messages: List[Message] = []): - self.messages = messages +class Memory(BaseModel): + messages: List[Message] = [] + + # def __init__(self, messages: List[Message] = []): + # self.messages = messages def append(self, message: Message): self.messages.append(message) @@ -28,8 +29,9 @@ class Memory: def delete(self, ): pass - def get_messages(self, ) -> List[Message]: - return self.messages + def get_messages(self, k=0) -> List[Message]: + """Return the most recent k memories, return all when k=0""" + return self.messages[-k:] def save(self, file_type="jsonl", return_all=True): try: @@ -57,32 +59,56 @@ class Memory: return False - def to_tuple_messages(self, return_system: bool = False, return_all: bool = False, content_key="role_content"): + def to_tuple_messages(self, return_all: bool = True, content_key="role_content", filter_roles=[]): # logger.debug(f"{[message.to_tuple_message(return_all, content_key) for message in self.messages ]}") return [ message.to_tuple_message(return_all, content_key) for message in self.messages - if not message.is_system_role() | return_system + if message.role_name not in filter_roles ] - def to_dict_messages(self, return_system: bool = False, return_all: bool = False, content_key="role_content"): + def to_dict_messages(self, return_all: bool = True, content_key="role_content", filter_roles=[]): return [ message.to_dict_message(return_all, content_key) for message in self.messages - if not message.is_system_role() | return_system + if message.role_name not in filter_roles ] - def to_str_messages(self, return_system: bool = False, return_all: bool = False, content_key="role_content"): + def to_str_messages(self, return_all: bool = True, content_key="role_content", filter_roles=[]): + # for message in self.messages: + # logger.debug(f"{message.to_tuple_message(return_all, content_key)}") # logger.debug(f"{[message.to_tuple_message(return_all, content_key) for message in self.messages ]}") - return "\n".join([ - ": ".join(message.to_tuple_message(return_all, content_key)) for message in self.messages - if not message.is_system_role() | return_system + return "\n\n".join([message.to_str_content(return_all, content_key) for message in self.messages + if message.role_name not in filter_roles ]) + def get_parserd_output(self, ): + return [message.parsed_output for message in self.messages] + + def get_parserd_output_list(self, ): + # for message in self.messages: + # logger.debug(f"{message.role_name}: {message.parsed_output_list}") + return [parsed_output for message in self.messages for parsed_output in message.parsed_output_list[1:]] + + def get_rolenames(self, ): + '''''' + return [message.role_name for message in self.messages] + @classmethod def from_memory_list(cls, memorys: List['Memory']) -> 'Memory': - return cls([message for memory in memorys for message in memory.get_messages()]) + return cls(messages=[message for memory in memorys for message in memory.get_messages()]) def __len__(self, ): return len(self.messages) def __str__(self) -> str: - return "\n".join([":".join(i) for i in self.to_tuple_messages()]) \ No newline at end of file + return "\n".join([":".join(i) for i in self.to_tuple_messages()]) + + def __add__(self, other: Union[Message, 'Memory']) -> 'Memory': + if isinstance(other, Message): + return Memory(messages=self.messages + [other]) + elif isinstance(other, Memory): + return Memory(messages=self.messages + other.messages) + else: + raise ValueError(f"cant add unspecified type like as {type(other)}") + + + \ No newline at end of file diff --git a/dev_opsgpt/connector/schema/message.py b/dev_opsgpt/connector/schema/message.py new file mode 100644 index 0000000..862d7a3 --- /dev/null +++ b/dev_opsgpt/connector/schema/message.py @@ -0,0 +1,98 @@ +from pydantic import BaseModel +from loguru import logger + +from .general_schema import * + + +class Message(BaseModel): + chat_index: str = None + role_name: str + role_type: str + role_prompt: str = None + input_query: str = None + origin_query: str = None + + # 模型最终返回 + role_content: str = None + role_contents: List[str] = [] + step_content: str = None + step_contents: List[str] = [] + chain_content: str = None + chain_contents: List[str] = [] + + # 模型结果解析 + plans: List[str] = None + code_content: str = None + code_filename: str = None + tool_params: str = None + tool_name: str = None + parsed_output: dict = {} + spec_parsed_output: dict = {} + parsed_output_list: List[Dict] = [] + + # 执行结果 + action_status: str = ActionStatus.DEFAUILT + agent_index: int = None + code_answer: str = None + tool_answer: str = None + observation: str = None + figures: Dict[str, str] = {} + + # 辅助信息 + tools: List[BaseTool] = [] + task: Task = None + db_docs: List['Doc'] = [] + code_docs: List['CodeDoc'] = [] + search_docs: List['Doc'] = [] + agents: List = [] + + # 执行输入 + phase_name: str = None + chain_name: str = None + do_search: bool = False + doc_engine_name: str = None + code_engine_name: str = None + cb_search_type: str = None + search_engine_name: str = None + top_k: int = 3 + score_threshold: float = 1.0 + do_doc_retrieval: bool = False + do_code_retrieval: bool = False + do_tool_retrieval: bool = False + history_node_list: List[str] = [] + + def to_tuple_message(self, return_all: bool = True, content_key="role_content"): + role_content = self.to_str_content(False, content_key) + if return_all: + return (self.role_name, role_content) + else: + return (role_content) + + def to_dict_message(self, return_all: bool = True, content_key="role_content"): + role_content = self.to_str_content(False, content_key) + if return_all: + return {"role": self.role_name, "content": role_content} + else: + return vars(self) + + def to_str_content(self, return_all: bool = True, content_key="role_content"): + if content_key == "role_content": + role_content = self.role_content or self.input_query + elif content_key == "step_content": + role_content = self.step_content or self.role_content or self.input_query + else: + role_content = self.role_content or self.input_query + + if return_all: + return f"{self.role_name}: {role_content}" + else: + return role_content + + def is_system_role(self,): + return self.role_type == "system" + + def __str__(self) -> str: + # key_str = '\n'.join([k for k, v in vars(self).items()]) + # logger.debug(f"{key_str}") + return "\n".join([": ".join([k, str(v)]) for k, v in vars(self).items()]) + \ No newline at end of file diff --git a/dev_opsgpt/connector/shcema/__init__.py b/dev_opsgpt/connector/shcema/__init__.py deleted file mode 100644 index d523fc9..0000000 --- a/dev_opsgpt/connector/shcema/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .memory import Memory - - -__all__ = [ - "Memory" -] \ No newline at end of file diff --git a/dev_opsgpt/connector/utils.py b/dev_opsgpt/connector/utils.py index 9684505..e6a95de 100644 --- a/dev_opsgpt/connector/utils.py +++ b/dev_opsgpt/connector/utils.py @@ -1,5 +1,30 @@ +import re + +def parse_section(text, section_name): + # Define a pattern to extract the named section along with its content + section_pattern = rf'#### {section_name}\n(.*?)(?=####|$)' + + # Find the specific section content + section_content = re.search(section_pattern, text, re.DOTALL) + + if section_content: + # If the section is found, extract the content + content = section_content.group(1) + + # Define a pattern to find segments that follow the format **xx:** + segments_pattern = r'\*\*([^*]+):\*\*' + + # Use findall method to extract all matches in the section content + segments = re.findall(segments_pattern, content) + + return segments + else: + # If the section is not found, return an empty list + return [] + + def prompt_cost(model_type: str, num_prompt_tokens: float, num_completion_tokens: float): input_cost_map = { "gpt-3.5-turbo": 0.0015, diff --git a/dev_opsgpt/db_handler/__init__.py b/dev_opsgpt/db_handler/__init__.py new file mode 100644 index 0000000..83c9d92 --- /dev/null +++ b/dev_opsgpt/db_handler/__init__.py @@ -0,0 +1,7 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: __init__.py.py +@time: 2023/11/16 下午3:15 +@desc: +''' \ No newline at end of file diff --git a/dev_opsgpt/db_handler/graph_db_handler/__init__.py b/dev_opsgpt/db_handler/graph_db_handler/__init__.py new file mode 100644 index 0000000..6d3396a --- /dev/null +++ b/dev_opsgpt/db_handler/graph_db_handler/__init__.py @@ -0,0 +1,7 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: __init__.py.py +@time: 2023/11/20 下午3:07 +@desc: +''' \ No newline at end of file diff --git a/dev_opsgpt/db_handler/graph_db_handler/nebula_handler.py b/dev_opsgpt/db_handler/graph_db_handler/nebula_handler.py new file mode 100644 index 0000000..8629b86 --- /dev/null +++ b/dev_opsgpt/db_handler/graph_db_handler/nebula_handler.py @@ -0,0 +1,263 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: nebula_handler.py +@time: 2023/11/16 下午3:15 +@desc: +''' +import time +from loguru import logger + +from nebula3.gclient.net import ConnectionPool +from nebula3.Config import Config + + +class NebulaHandler: + def __init__(self, host: str, port: int, username: str, password: str = '', space_name: str = ''): + ''' + init nebula connection_pool + @param host: host + @param port: port + @param username: username + @param password: password + ''' + config = Config() + + self.connection_pool = ConnectionPool() + self.connection_pool.init([(host, port)], config) + self.username = username + self.password = password + self.space_name = space_name + + def execute_cypher(self, cypher: str, space_name: str = ''): + ''' + + @param space_name: space_name, if provided, will execute use space_name first + @param cypher: + @return: + ''' + with self.connection_pool.session_context(self.username, self.password) as session: + if space_name: + cypher = f'USE {space_name};{cypher}' + logger.debug(cypher) + resp = session.execute(cypher) + + return resp + + def close_connection(self): + self.connection_pool.close() + + def create_space(self, space_name: str, vid_type: str, comment: str = ''): + ''' + create space + @param space_name: cannot startwith number + @return: + ''' + cypher = f'CREATE SPACE IF NOT EXISTS {space_name} (vid_type={vid_type}) comment="{comment}";' + resp = self.execute_cypher(cypher) + return resp + + def show_space(self): + cypher = 'SHOW SPACES' + resp = self.execute_cypher(cypher) + return resp + + def drop_space(self, space_name): + cypher = f'DROP SPACE {space_name}' + return self.execute_cypher(cypher) + + def create_tag(self, tag_name: str, prop_dict: dict = {}): + ''' + 创建 tag + @param tag_name: tag 名称 + @param prop_dict: 属性字典 {'prop 名字': 'prop 类型'} + @return: + ''' + cypher = f'CREATE TAG IF NOT EXISTS {tag_name}' + cypher += '(' + for k, v in prop_dict.items(): + cypher += f'{k} {v},' + cypher = cypher.rstrip(',') + cypher += ')' + cypher += ';' + + res = self.execute_cypher(cypher, self.space_name) + return res + + def show_tags(self): + ''' + 查看 tag + @return: + ''' + cypher = 'SHOW TAGS' + resp = self.execute_cypher(cypher, self.space_name) + return resp + + def insert_vertex(self, tag_name: str, value_dict: dict): + ''' + insert vertex + @param tag_name: + @param value_dict: {'properties_name': [], values: {'vid':[]}} order should be the same in properties_name and values + @return: + ''' + cypher = f'INSERT VERTEX {tag_name} (' + + properties_name = value_dict['properties_name'] + + for property_name in properties_name: + cypher += f'{property_name},' + cypher = cypher.rstrip(',') + + cypher += ') VALUES ' + + for vid, properties in value_dict['values'].items(): + cypher += f'"{vid}":(' + for property in properties: + if type(property) == str: + cypher += f'"{property}",' + else: + cypher += f'{property}' + cypher = cypher.rstrip(',') + cypher += '),' + cypher = cypher.rstrip(',') + cypher += ';' + + res = self.execute_cypher(cypher, self.space_name) + return res + + def create_edge_type(self, edge_type_name: str, prop_dict: dict = {}): + ''' + 创建 tag + @param edge_type_name: tag 名称 + @param prop_dict: 属性字典 {'prop 名字': 'prop 类型'} + @return: + ''' + cypher = f'CREATE EDGE IF NOT EXISTS {edge_type_name}' + + cypher += '(' + for k, v in prop_dict.items(): + cypher += f'{k} {v},' + cypher = cypher.rstrip(',') + cypher += ')' + cypher += ';' + + res = self.execute_cypher(cypher, self.space_name) + return res + + def show_edge_type(self): + ''' + 查看 tag + @return: + ''' + cypher = 'SHOW EDGES' + resp = self.execute_cypher(cypher, self.space_name) + return resp + + def drop_edge_type(self, edge_type_name: str): + cypher = f'DROP EDGE {edge_type_name}' + return self.execute_cypher(cypher, self.space_name) + + def insert_edge(self, edge_type_name: str, value_dict: dict): + ''' + insert edge + @param edge_type_name: + @param value_dict: value_dict: {'properties_name': [], values: {(src_vid, dst_vid):[]}} order should be the + same in properties_name and values + @return: + ''' + cypher = f'INSERT EDGE {edge_type_name} (' + + properties_name = value_dict['properties_name'] + + for property_name in properties_name: + cypher += f'{property_name},' + cypher = cypher.rstrip(',') + + cypher += ') VALUES ' + + for (src_vid, dst_vid), properties in value_dict['values'].items(): + cypher += f'"{src_vid}"->"{dst_vid}":(' + for property in properties: + if type(property) == str: + cypher += f'"{property}",' + else: + cypher += f'{property}' + cypher = cypher.rstrip(',') + cypher += '),' + cypher = cypher.rstrip(',') + cypher += ';' + + res = self.execute_cypher(cypher, self.space_name) + return res + + def set_space_name(self, space_name): + self.space_name = space_name + + def add_host(self, host: str, port: str): + ''' + add host + @return: + ''' + cypher = f'ADD HOSTS {host}:{port}' + res = self.execute_cypher(cypher) + return res + + def get_stat(self): + ''' + + @return: + ''' + submit_cypher = 'SUBMIT JOB STATS;' + self.execute_cypher(cypher=submit_cypher, space_name=self.space_name) + time.sleep(2) + + stats_cypher = 'SHOW STATS;' + stats_res = self.execute_cypher(cypher=stats_cypher, space_name=self.space_name) + + res = {'vertices': -1, 'edges': -1} + + stats_res_dict = self.result_to_dict(stats_res) + for idx in range(len(stats_res_dict['Type'])): + t = stats_res_dict['Type'][idx].as_string() + name = stats_res_dict['Name'][idx].as_string() + count = stats_res_dict['Count'][idx].as_int() + + if t == 'Space' and name in res: + res[name] = count + return res + + def get_vertices(self, tag_name: str = '', limit: int = 10000): + ''' + get all vertices + @return: + ''' + if tag_name: + cypher = f'''MATCH (v:{tag_name}) RETURN v LIMIT {limit};''' + else: + cypher = f'MATCH (v) RETURN v LIMIT {limit};' + + res = self.execute_cypher(cypher, self.space_name) + return self.result_to_dict(res) + + def result_to_dict(self, result) -> dict: + """ + build list for each column, and transform to dataframe + """ + logger.info(result.error_msg()) + assert result.is_succeeded() + columns = result.keys() + d = {} + for col_num in range(result.col_size()): + col_name = columns[col_num] + col_list = result.column_values(col_name) + d[col_name] = [x for x in col_list] + return d + + + + + + + + + diff --git a/dev_opsgpt/db_handler/vector_db_handler/__init__.py b/dev_opsgpt/db_handler/vector_db_handler/__init__.py new file mode 100644 index 0000000..2b67ecf --- /dev/null +++ b/dev_opsgpt/db_handler/vector_db_handler/__init__.py @@ -0,0 +1,7 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: __init__.py.py +@time: 2023/11/20 下午3:08 +@desc: +''' \ No newline at end of file diff --git a/dev_opsgpt/db_handler/vector_db_handler/chroma_handler.py b/dev_opsgpt/db_handler/vector_db_handler/chroma_handler.py new file mode 100644 index 0000000..07822e1 --- /dev/null +++ b/dev_opsgpt/db_handler/vector_db_handler/chroma_handler.py @@ -0,0 +1,140 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: chroma_handler.py +@time: 2023/11/21 下午12:21 +@desc: +''' +from loguru import logger +import chromadb + + +class ChromaHandler: + def __init__(self, path: str, collection_name: str = ''): + ''' + init client + @param path: path of data + @collection_name: name of collection + ''' + self.client = chromadb.PersistentClient(path) + self.client.heartbeat() + + if collection_name: + self.collection = self.client.get_or_create_collection(name=collection_name) + + def create_collection(self, collection_name: str): + ''' + create collection, if exists, will override + @return: + ''' + try: + collection = self.client.create_collection(name=collection_name) + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + return {'result_code': 0, 'msg': 'success'} + + def delete_collection(self, collection_name: str): + ''' + + @param collection_name: + @return: + ''' + try: + self.client.delete_collection(name=collection_name) + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + return {'result_code': 0, 'msg': 'success'} + + def set_collection(self, collection_name: str): + ''' + + @param collection_name: + @return: + ''' + try: + self.collection = self.client.get_collection(collection_name) + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + return {'result_code': 0, 'msg': 'success'} + + def add_data(self, ids: list, documents: list = None, embeddings: list = None, metadatas: list = None): + ''' + add data to chroma + @param documents: list of doc string + @param embeddings: list of vector + @param metadatas: list of metadata + @param ids: list of id + @return: + ''' + try: + self.collection.add( + ids=ids, + embeddings=embeddings, + metadatas=metadatas, + documents=documents + ) + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + return {'result_code': 0, 'msg': 'success'} + + def query(self, query_embeddings=None, query_texts=None, n_results=10, where=None, where_document=None, + include=["metadatas", "documents", "distances"]): + ''' + + @param query_embeddings: + @param query_texts: + @param n_results: + @param where: + @param where_document: + @param include: + @return: + ''' + try: + query_result = self.collection.query(query_embeddings=query_embeddings, query_texts=query_texts, + n_results=n_results, where=where, where_document=where_document, + include=include) + return {'result_code': 0, 'msg': 'success', 'result': query_result} + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + + def get(self, ids=None, where=None, limit=None, offset=None, where_document=None, include=["metadatas", "documents"]): + ''' + get by condition + @param ids: + @param where: + @param limit: + @param offset: + @param where_document: + @param include: + @return: + ''' + try: + query_result = self.collection.get(ids=ids, where=where, where_document=where_document, + limit=limit, + offset=offset, include=include) + return {'result_code': 0, 'msg': 'success', 'result': query_result} + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + + def peek(self, limit: int=10): + ''' + peek + @param limit: + @return: + ''' + try: + query_result = self.collection.peek(limit) + return {'result_code': 0, 'msg': 'success', 'result': query_result} + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} + + def count(self): + ''' + count + @return: + ''' + try: + query_result = self.collection.count() + return {'result_code': 0, 'msg': 'success', 'result': query_result} + except Exception as e: + return {'result_code': -1, 'msg': f'fail, error={e}'} diff --git a/dev_opsgpt/embeddings/get_embedding.py b/dev_opsgpt/embeddings/get_embedding.py new file mode 100644 index 0000000..b7b8cdc --- /dev/null +++ b/dev_opsgpt/embeddings/get_embedding.py @@ -0,0 +1,39 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: get_embedding.py +@time: 2023/11/22 上午11:30 +@desc: +''' +from loguru import logger + +from configs.model_config import EMBEDDING_MODEL +from dev_opsgpt.embeddings.openai_embedding import OpenAIEmbedding +from dev_opsgpt.embeddings.huggingface_embedding import HFEmbedding + + +def get_embedding(engine: str, text_list: list): + ''' + get embedding + @param engine: openai / hf + @param text_list: + @return: + ''' + emb_res = {} + + if engine == 'openai': + oae = OpenAIEmbedding() + emb_res = oae.get_emb(text_list) + elif engine == 'model': + hfe = HFEmbedding(EMBEDDING_MODEL) + emb_res = hfe.get_emb(text_list) + + return emb_res + + +if __name__ == '__main__': + engine = 'model' + text_list = ['这段代码是一个OkHttp拦截器,用于在请求头中添加授权令牌。它继承自`com.theokanning.openai.client.AuthenticationInterceptor`类,并且被标记为`@Deprecated`,意味着它已经过时了。\n\n这个拦截器的作用是在每个请求的头部添加一个名为"Authorization"的字段,值为传入的授权令牌。这样,当请求被发送到服务器时,服务器可以使用这个令牌来验证请求的合法性。\n\n这段代码的构造函数接受一个令牌作为参数,并将其传递给父类的构造函数。这个令牌应该是一个有效的授权令牌,用于访问受保护的资源。', '这段代码定义了一个接口`OpenAiApi`,并使用`@Deprecated`注解将其标记为已过时。它还扩展了`com.theokanning.openai.client.OpenAiApi`接口。\n\n`@Deprecated`注解表示该接口已经过时,不推荐使用。开发者应该使用`com.theokanning.openai.client.OpenAiApi`接口代替。\n\n注释中提到这个接口只是为了保持向后兼容性。这意味着它可能是为了与旧版本的代码兼容而保留的,但不推荐在新代码中使用。', '这段代码是一个OkHttp的拦截器,用于在请求头中添加授权令牌(authorization token)。\n\n在这个拦截器中,首先获取到传入的授权令牌(token),然后在每个请求的构建过程中,使用`newBuilder()`方法创建一个新的请求构建器,并在该构建器中添加一个名为"Authorization"的请求头,值为"Bearer " + token。最后,使用该构建器构建一个新的请求,并通过`chain.proceed(request)`方法继续处理该请求。\n\n这样,当使用OkHttp发送请求时,该拦截器会自动在请求头中添加授权令牌,以实现身份验证的功能。', '这段代码是一个Java接口,用于定义与OpenAI API进行通信的方法。它包含了各种不同类型的请求和响应方法,用于与OpenAI API的不同端点进行交互。\n\n接口中的方法包括:\n- `listModels()`:获取可用的模型列表。\n- `getModel(String modelId)`:获取指定模型的详细信息。\n- `createCompletion(CompletionRequest request)`:创建文本生成的请求。\n- `createChatCompletion(ChatCompletionRequest request)`:创建聊天式文本生成的请求。\n- `createEdit(EditRequest request)`:创建文本编辑的请求。\n- `createEmbeddings(EmbeddingRequest request)`:创建文本嵌入的请求。\n- `listFiles()`:获取已上传文件的列表。\n- `uploadFile(RequestBody purpose, MultipartBody.Part file)`:上传文件。\n- `deleteFile(String fileId)`:删除文件。\n- `retrieveFile(String fileId)`:获取文件的详细信息。\n- `retrieveFileContent(String fileId)`:获取文件的内容。\n- `createFineTuningJob(FineTuningJobRequest request)`:创建Fine-Tuning任务。\n- `listFineTuningJobs()`:获取Fine-Tuning任务的列表。\n- `retrieveFineTuningJob(String fineTuningJobId)`:获取指定Fine-Tuning任务的详细信息。\n- `cancelFineTuningJob(String fineTuningJobId)`:取消Fine-Tuning任务。\n- `listFineTuningJobEvents(String fineTuningJobId)`:获取Fine-Tuning任务的事件列表。\n- `createFineTuneCompletion(CompletionRequest request)`:创建Fine-Tuning模型的文本生成请求。\n- `createImage(CreateImageRequest request)`:创建图像生成的请求。\n- `createImageEdit(RequestBody requestBody)`:创建图像编辑的请求。\n- `createImageVariation(RequestBody requestBody)`:创建图像变体的请求。\n- `createTranscription(RequestBody requestBody)`:创建音频转录的请求。\n- `createTranslation(RequestBody requestBody)`:创建音频翻译的请求。\n- `createModeration(ModerationRequest request)`:创建内容审核的请求。\n- `getEngines()`:获取可用的引擎列表。\n- `getEngine(String engineId)`:获取指定引擎的详细信息。\n- `subscription()`:获取账户订阅信息。\n- `billingUsage(LocalDate starDate, LocalDate endDate)`:获取账户消费信息。\n\n这些方法使用不同的HTTP请求类型(GET、POST、DELETE)和路径来与OpenAI API进行交互,并返回相应的响应数据。'] + + res = get_embedding(engine, text_list) + logger.debug(res) \ No newline at end of file diff --git a/dev_opsgpt/embeddings/huggingface_embedding.py b/dev_opsgpt/embeddings/huggingface_embedding.py new file mode 100644 index 0000000..1175d83 --- /dev/null +++ b/dev_opsgpt/embeddings/huggingface_embedding.py @@ -0,0 +1,49 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: huggingface_embedding.py +@time: 2023/11/30 上午11:41 +@desc: +''' +from loguru import logger +from configs.model_config import EMBEDDING_DEVICE +from dev_opsgpt.embeddings.utils import load_embeddings + + +class HFEmbedding: + _instance = {} + + def __new__(cls, *args, **kwargs): + instance_key = f'{args},{kwargs}' + + if cls._instance.get(instance_key, None): + return cls._instance[instance_key] + else: + cls._instance[instance_key] = super().__new__(cls) + return cls._instance[instance_key] + + def __init__(self, model_name): + self.model = load_embeddings(model=model_name, device=EMBEDDING_DEVICE) + logger.debug('load success') + + def get_emb(self, text_list): + ''' + get embedding + @param text_list: + @return: + ''' + logger.info('st') + emb_res = self.model.embed_documents(text_list) + logger.info('ed') + res = { + text_list[idx]: emb_res[idx] for idx in range(len(text_list)) + } + return res + + +if __name__ == '__main__': + model_name = 'text2vec-base' + hfe = HFEmbedding(model_name) + text_list = ['这段代码是一个OkHttp拦截器,用于在请求头中添加授权令牌。它继承自`com.theokanning.openai.client.AuthenticationInterceptor`类,并且被标记为`@Deprecated`,意味着它已经过时了。\n\n这个拦截器的作用是在每个请求的头部添加一个名为"Authorization"的字段,值为传入的授权令牌。这样,当请求被发送到服务器时,服务器可以使用这个令牌来验证请求的合法性。\n\n这段代码的构造函数接受一个令牌作为参数,并将其传递给父类的构造函数。这个令牌应该是一个有效的授权令牌,用于访问受保护的资源。', '这段代码定义了一个接口`OpenAiApi`,并使用`@Deprecated`注解将其标记为已过时。它还扩展了`com.theokanning.openai.client.OpenAiApi`接口。\n\n`@Deprecated`注解表示该接口已经过时,不推荐使用。开发者应该使用`com.theokanning.openai.client.OpenAiApi`接口代替。\n\n注释中提到这个接口只是为了保持向后兼容性。这意味着它可能是为了与旧版本的代码兼容而保留的,但不推荐在新代码中使用。', '这段代码是一个OkHttp的拦截器,用于在请求头中添加授权令牌(authorization token)。\n\n在这个拦截器中,首先获取到传入的授权令牌(token),然后在每个请求的构建过程中,使用`newBuilder()`方法创建一个新的请求构建器,并在该构建器中添加一个名为"Authorization"的请求头,值为"Bearer " + token。最后,使用该构建器构建一个新的请求,并通过`chain.proceed(request)`方法继续处理该请求。\n\n这样,当使用OkHttp发送请求时,该拦截器会自动在请求头中添加授权令牌,以实现身份验证的功能。', '这段代码是一个Java接口,用于定义与OpenAI API进行通信的方法。它包含了各种不同类型的请求和响应方法,用于与OpenAI API的不同端点进行交互。\n\n接口中的方法包括:\n- `listModels()`:获取可用的模型列表。\n- `getModel(String modelId)`:获取指定模型的详细信息。\n- `createCompletion(CompletionRequest request)`:创建文本生成的请求。\n- `createChatCompletion(ChatCompletionRequest request)`:创建聊天式文本生成的请求。\n- `createEdit(EditRequest request)`:创建文本编辑的请求。\n- `createEmbeddings(EmbeddingRequest request)`:创建文本嵌入的请求。\n- `listFiles()`:获取已上传文件的列表。\n- `uploadFile(RequestBody purpose, MultipartBody.Part file)`:上传文件。\n- `deleteFile(String fileId)`:删除文件。\n- `retrieveFile(String fileId)`:获取文件的详细信息。\n- `retrieveFileContent(String fileId)`:获取文件的内容。\n- `createFineTuningJob(FineTuningJobRequest request)`:创建Fine-Tuning任务。\n- `listFineTuningJobs()`:获取Fine-Tuning任务的列表。\n- `retrieveFineTuningJob(String fineTuningJobId)`:获取指定Fine-Tuning任务的详细信息。\n- `cancelFineTuningJob(String fineTuningJobId)`:取消Fine-Tuning任务。\n- `listFineTuningJobEvents(String fineTuningJobId)`:获取Fine-Tuning任务的事件列表。\n- `createFineTuneCompletion(CompletionRequest request)`:创建Fine-Tuning模型的文本生成请求。\n- `createImage(CreateImageRequest request)`:创建图像生成的请求。\n- `createImageEdit(RequestBody requestBody)`:创建图像编辑的请求。\n- `createImageVariation(RequestBody requestBody)`:创建图像变体的请求。\n- `createTranscription(RequestBody requestBody)`:创建音频转录的请求。\n- `createTranslation(RequestBody requestBody)`:创建音频翻译的请求。\n- `createModeration(ModerationRequest request)`:创建内容审核的请求。\n- `getEngines()`:获取可用的引擎列表。\n- `getEngine(String engineId)`:获取指定引擎的详细信息。\n- `subscription()`:获取账户订阅信息。\n- `billingUsage(LocalDate starDate, LocalDate endDate)`:获取账户消费信息。\n\n这些方法使用不同的HTTP请求类型(GET、POST、DELETE)和路径来与OpenAI API进行交互,并返回相应的响应数据。'] + + hfe.get_emb(text_list) \ No newline at end of file diff --git a/dev_opsgpt/embeddings/openai_embedding.py b/dev_opsgpt/embeddings/openai_embedding.py new file mode 100644 index 0000000..02c2d92 --- /dev/null +++ b/dev_opsgpt/embeddings/openai_embedding.py @@ -0,0 +1,50 @@ +# encoding: utf-8 +''' +@author: 温进 +@file: openai_embedding.py +@time: 2023/11/22 上午10:45 +@desc: +''' +import openai +import base64 +import json +import os +from loguru import logger + +from configs.model_config import OPENAI_API_BASE + + +class OpenAIEmbedding: + def __init__(self): + pass + + def get_emb(self, text_list): + openai.api_key = os.environ["OPENAI_API_KEY"] + openai.api_base = os.environ["API_BASE_URL"] + + # change , to ,to avoid bug + modified_text_list = [i.replace(',', ',') for i in text_list] + + emb_all_result = openai.Embedding.create( + model="text-embedding-ada-002", + input=modified_text_list + ) + + res = {} + # logger.debug(emb_all_result) + logger.debug(f'len of result={len(emb_all_result["data"])}') + for emb_result in emb_all_result['data']: + index = emb_result['index'] + # logger.debug(index) + text = text_list[index] + emb = emb_result['embedding'] + res[text] = emb + + return res + + +if __name__ == '__main__': + oae = OpenAIEmbedding() + res = oae.get_emb(text_list=['这段代码是一个OkHttp拦截器,用于在请求头中添加授权令牌。它继承自`com.theokanning.openai.client.AuthenticationInterceptor`类,并且被标记为`@Deprecated`,意味着它已经过时了。\n\n这个拦截器的作用是在每个请求的头部添加一个名为"Authorization"的字段,值为传入的授权令牌。这样,当请求被发送到服务器时,服务器可以使用这个令牌来验证请求的合法性。\n\n这段代码的构造函数接受一个令牌作为参数,并将其传递给父类的构造函数。这个令牌应该是一个有效的授权令牌,用于访问受保护的资源。', '这段代码定义了一个接口`OpenAiApi`,并使用`@Deprecated`注解将其标记为已过时。它还扩展了`com.theokanning.openai.client.OpenAiApi`接口。\n\n`@Deprecated`注解表示该接口已经过时,不推荐使用。开发者应该使用`com.theokanning.openai.client.OpenAiApi`接口代替。\n\n注释中提到这个接口只是为了保持向后兼容性。这意味着它可能是为了与旧版本的代码兼容而保留的,但不推荐在新代码中使用。', '这段代码是一个OkHttp的拦截器,用于在请求头中添加授权令牌(authorization token)。\n\n在这个拦截器中,首先获取到传入的授权令牌(token),然后在每个请求的构建过程中,使用`newBuilder()`方法创建一个新的请求构建器,并在该构建器中添加一个名为"Authorization"的请求头,值为"Bearer " + token。最后,使用该构建器构建一个新的请求,并通过`chain.proceed(request)`方法继续处理该请求。\n\n这样,当使用OkHttp发送请求时,该拦截器会自动在请求头中添加授权令牌,以实现身份验证的功能。', '这段代码是一个Java接口,用于定义与OpenAI API进行通信的方法。它包含了各种不同类型的请求和响应方法,用于与OpenAI API的不同端点进行交互。\n\n接口中的方法包括:\n- `listModels()`:获取可用的模型列表。\n- `getModel(String modelId)`:获取指定模型的详细信息。\n- `createCompletion(CompletionRequest request)`:创建文本生成的请求。\n- `createChatCompletion(ChatCompletionRequest request)`:创建聊天式文本生成的请求。\n- `createEdit(EditRequest request)`:创建文本编辑的请求。\n- `createEmbeddings(EmbeddingRequest request)`:创建文本嵌入的请求。\n- `listFiles()`:获取已上传文件的列表。\n- `uploadFile(RequestBody purpose, MultipartBody.Part file)`:上传文件。\n- `deleteFile(String fileId)`:删除文件。\n- `retrieveFile(String fileId)`:获取文件的详细信息。\n- `retrieveFileContent(String fileId)`:获取文件的内容。\n- `createFineTuningJob(FineTuningJobRequest request)`:创建Fine-Tuning任务。\n- `listFineTuningJobs()`:获取Fine-Tuning任务的列表。\n- `retrieveFineTuningJob(String fineTuningJobId)`:获取指定Fine-Tuning任务的详细信息。\n- `cancelFineTuningJob(String fineTuningJobId)`:取消Fine-Tuning任务。\n- `listFineTuningJobEvents(String fineTuningJobId)`:获取Fine-Tuning任务的事件列表。\n- `createFineTuneCompletion(CompletionRequest request)`:创建Fine-Tuning模型的文本生成请求。\n- `createImage(CreateImageRequest request)`:创建图像生成的请求。\n- `createImageEdit(RequestBody requestBody)`:创建图像编辑的请求。\n- `createImageVariation(RequestBody requestBody)`:创建图像变体的请求。\n- `createTranscription(RequestBody requestBody)`:创建音频转录的请求。\n- `createTranslation(RequestBody requestBody)`:创建音频翻译的请求。\n- `createModeration(ModerationRequest request)`:创建内容审核的请求。\n- `getEngines()`:获取可用的引擎列表。\n- `getEngine(String engineId)`:获取指定引擎的详细信息。\n- `subscription()`:获取账户订阅信息。\n- `billingUsage(LocalDate starDate, LocalDate endDate)`:获取账户消费信息。\n\n这些方法使用不同的HTTP请求类型(GET、POST、DELETE)和路径来与OpenAI API进行交互,并返回相应的响应数据。']) + # res = oae.get_emb(text_list=['''test1"test2test3''', '''test4test5test6''']) + print(res) diff --git a/dev_opsgpt/embeddings/utils.py b/dev_opsgpt/embeddings/utils.py index 5b19371..c762c04 100644 --- a/dev_opsgpt/embeddings/utils.py +++ b/dev_opsgpt/embeddings/utils.py @@ -9,4 +9,6 @@ def load_embeddings(model: str, device: str): logger.info("load embedding model: {}, {}".format(model, embedding_model_dict[model])) embeddings = HuggingFaceEmbeddings(model_name=embedding_model_dict[model], model_kwargs={'device': device}) - return embeddings \ No newline at end of file + return embeddings + + diff --git a/dev_opsgpt/orm/commands/code_base_cds.py b/dev_opsgpt/orm/commands/code_base_cds.py index 939d1d5..258a039 100644 --- a/dev_opsgpt/orm/commands/code_base_cds.py +++ b/dev_opsgpt/orm/commands/code_base_cds.py @@ -11,12 +11,14 @@ from dev_opsgpt.orm.schemas.base_schema import CodeBaseSchema @with_session -def add_cb_to_db(session, code_name, code_path, code_graph_node_num, code_file_num): +def add_cb_to_db(session, code_name, code_path, code_graph_node_num, code_file_num, do_interpret): + do_interpret = 'YES' if do_interpret else 'NO' + # 增:创建知识库实例 cb = session.query(CodeBaseSchema).filter_by(code_name=code_name).first() if not cb: cb = CodeBaseSchema(code_name=code_name, code_path=code_path, code_graph_node_num=code_graph_node_num, - code_file_num=code_file_num) + code_file_num=code_file_num, do_interpret=do_interpret) session.add(cb) else: cb.code_path = code_path @@ -47,10 +49,10 @@ def cb_exists(session, code_name): def load_cb_from_db(session, code_name): cb = session.query(CodeBaseSchema).filter_by(code_name=code_name).first() if cb: - code_name, code_path, code_graph_node_num = cb.code_name, cb.code_path, cb.code_graph_node_num + code_name, code_path, code_graph_node_num, do_interpret = cb.code_name, cb.code_path, cb.code_graph_node_num, cb.do_interpret else: - code_name, code_path, code_graph_node_num = None, None, None - return code_name, code_path, code_graph_node_num + code_name, code_path, code_graph_node_num = None, None, None, None + return code_name, code_path, code_graph_node_num, do_interpret @with_session @@ -71,7 +73,8 @@ def get_cb_detail(session, code_name: str) -> dict: "code_name": cb.code_name, "code_path": cb.code_path, "code_graph_node_num": cb.code_graph_node_num, - 'code_file_num': cb.code_file_num + 'code_file_num': cb.code_file_num, + 'do_interpret': cb.do_interpret } else: return { diff --git a/dev_opsgpt/orm/schemas/base_schema.py b/dev_opsgpt/orm/schemas/base_schema.py index 6c497d3..2657884 100644 --- a/dev_opsgpt/orm/schemas/base_schema.py +++ b/dev_opsgpt/orm/schemas/base_schema.py @@ -58,6 +58,7 @@ class CodeBaseSchema(Base): code_path = Column(String, comment='代码本地路径') code_graph_node_num = Column(String, comment='代码图谱节点数') code_file_num = Column(String, comment='代码解析文件数') + do_interpret = Column(String, comment='是否代码解读,Y or N') create_time = Column(DateTime, default=func.now(), comment='创建时间') def __repr__(self): @@ -65,5 +66,6 @@ class CodeBaseSchema(Base): code_name='{self.code_name}', code_path='{self.code_path}', code_graph_node_num='{self.code_graph_node_num}', - code_file_num='{self.code_file_num}' + code_file_num='{self.code_file_num}', + do_interpret='{self.do_interpret}', create_time='{self.create_time}')>""" diff --git a/dev_opsgpt/sandbox/pycodebox.py b/dev_opsgpt/sandbox/pycodebox.py index 2012752..5914ef2 100644 --- a/dev_opsgpt/sandbox/pycodebox.py +++ b/dev_opsgpt/sandbox/pycodebox.py @@ -92,11 +92,11 @@ class PyCodeBox(BaseBox): "metadata": {}, "content": { "code": code_text, - "silent": True, - "store_history": True, + "silent": False, # True,则内核会执行代码,但不会发送执行结果(如输出) + "store_history": True, # True,则执行的代码会被记录在交互式环境的历史记录中 "user_expressions": {}, - "allow_stdin": False, - "stop_on_error": True, + "allow_stdin": False, # True,允许代码执行时接受用户输入 + "stop_on_error": True, # True,当执行中遇到错误时,后续代码将不会继续执行。 }, "channel": "shell", "buffers": [], @@ -163,7 +163,7 @@ class PyCodeBox(BaseBox): return CodeBoxResponse( code_exe_type="text", code_text=code_text, - code_exe_response=result or "Code run successfully (no output),可能没有打印需要确认的变量", + code_exe_response=result or "Code run successfully (no output)", code_exe_status=200, do_code_exe=self.do_code_exe ) diff --git a/dev_opsgpt/service/cb_api.py b/dev_opsgpt/service/cb_api.py index 937f29d..f4e9ef0 100644 --- a/dev_opsgpt/service/cb_api.py +++ b/dev_opsgpt/service/cb_api.py @@ -23,7 +23,9 @@ from configs.model_config import ( CB_ROOT_PATH ) -from dev_opsgpt.codebase_handler.codebase_handler import CodeBaseHandler +# from dev_opsgpt.codebase_handler.codebase_handler import CodeBaseHandler + +from dev_opsgpt.codechat.codebase_handler.codebase_handler import CodeBaseHandler from loguru import logger @@ -33,10 +35,12 @@ async def list_cbs(): return ListResponse(data=list_cbs_from_db()) -async def create_cb(cb_name: str = Body(..., examples=["samples"]), - code_path: str = Body(..., examples=["samples"]) +async def create_cb(zip_file, + cb_name: str = Body(..., examples=["samples"]), + code_path: str = Body(..., examples=["samples"]), + do_interpret: bool = Body(..., examples=["samples"]) ) -> BaseResponse: - logger.info('cb_name={}, zip_path={}'.format(cb_name, code_path)) + logger.info('cb_name={}, zip_path={}, do_interpret={}'.format(cb_name, code_path, do_interpret)) # Create selected knowledge base if not validate_kb_name(cb_name): @@ -50,17 +54,16 @@ async def create_cb(cb_name: str = Body(..., examples=["samples"]), try: logger.info('start build code base') - cbh = CodeBaseHandler(cb_name, code_path, cb_root_path=CB_ROOT_PATH) - cbh.import_code(do_save=True) - code_graph_node_num = len(cbh.nh) - code_file_num = len(cbh.lcdh) + cbh = CodeBaseHandler(cb_name, code_path) + vertices_num, edge_num, file_num = cbh.import_code(zip_file=zip_file, do_interpret=do_interpret) logger.info('build code base done') # create cb to table - add_cb_to_db(cb_name, cbh.code_path, code_graph_node_num, code_file_num) + add_cb_to_db(cb_name, cbh.code_path, vertices_num, file_num, do_interpret) logger.info('add cb to mysql table success') except Exception as e: print(e) + logger.exception(e) return BaseResponse(code=500, msg=f"创建代码知识库出错: {e}") return BaseResponse(code=200, msg=f"已新增代码知识库 {cb_name}") @@ -81,6 +84,11 @@ async def delete_cb(cb_name: str = Body(..., examples=["samples"])) -> BaseRespo # delete local file shutil.rmtree(CB_ROOT_PATH + os.sep + cb_name) + + # delete from codebase + cbh = CodeBaseHandler(cb_name) + cbh.delete_codebase(codebase_name=cb_name) + except Exception as e: print(e) return BaseResponse(code=500, msg=f"删除代码知识库出错: {e}") @@ -91,24 +99,25 @@ async def delete_cb(cb_name: str = Body(..., examples=["samples"])) -> BaseRespo def search_code(cb_name: str = Body(..., examples=["sofaboot"]), query: str = Body(..., examples=['你好']), code_limit: int = Body(..., examples=['1']), + search_type: str = Body(..., examples=['你好']), history_node_list: list = Body(...)) -> dict: logger.info('cb_name={}'.format(cb_name)) logger.info('query={}'.format(query)) logger.info('code_limit={}'.format(code_limit)) + logger.info('search_type={}'.format(search_type)) logger.info('history_node_list={}'.format(history_node_list)) try: # load codebase - cbh = CodeBaseHandler(code_name=cb_name, cb_root_path=CB_ROOT_PATH) - cbh.import_code(do_load=True) + cbh = CodeBaseHandler(codebase_name=cb_name) # search code - related_code, related_node = cbh.search_code(query, code_limit=code_limit, history_node_list=history_node_list) + context, related_vertices = cbh.search_code(query, search_type=search_type, limit=code_limit) res = { - 'related_code': related_code, - 'related_node': related_node + 'context': context, + 'related_vertices': related_vertices } return res except Exception as e: diff --git a/dev_opsgpt/service/sdfile_api.py b/dev_opsgpt/service/sdfile_api.py index 17336b5..2d92915 100644 --- a/dev_opsgpt/service/sdfile_api.py +++ b/dev_opsgpt/service/sdfile_api.py @@ -12,7 +12,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse, FileResponse from fastapi import File, UploadFile -from dev_opsgpt.utils.server_utils import BaseResponse, ListResponse +from dev_opsgpt.utils.server_utils import BaseResponse, ListResponse, DataResponse from configs.server_config import OPEN_CROSS_DOMAIN, SDFILE_API_SERVER from configs.model_config import ( JUPYTER_WORK_PATH @@ -35,7 +35,8 @@ async def sd_upload_file(file: UploadFile = File(...), work_dir: str = JUPYTER_W async def sd_download_file(filename: str, save_filename: str = "filename_to_download.ext", work_dir: str = JUPYTER_WORK_PATH): # 从服务器下载文件 logger.debug(f"{os.path.join(work_dir, filename)}") - return {"data": FileResponse(os.path.join(work_dir, filename), filename=save_filename)} + return {"data": os.path.join(work_dir, filename), "filename": save_filename} + # return {"data": FileResponse(os.path.join(work_dir, filename), filename=save_filename)} async def sd_list_files(work_dir: str = JUPYTER_WORK_PATH): @@ -78,7 +79,7 @@ def create_app(): app.get("/sdfiles/download", tags=["files upload and download"], - response_model=BaseResponse, + response_model=DataResponse, summary="从沙盒下载文件" )(sd_download_file) diff --git a/dev_opsgpt/tools/__init__.py b/dev_opsgpt/tools/__init__.py index 130dc71..5412f03 100644 --- a/dev_opsgpt/tools/__init__.py +++ b/dev_opsgpt/tools/__init__.py @@ -1,3 +1,5 @@ +import importlib + from .base_tool import toLangchainTools, get_tool_schema, BaseToolModel from .weather import WeatherInfo, DistrictInfo from .multiplier import Multiplier @@ -7,27 +9,22 @@ from .metrics_query import MetricsQuery from .duckduckgo_search import DDGSTool from .docs_retrieval import DocRetrieval from .cb_query_tool import CodeRetrieval +from .ocr_tool import BaiduOcrTool +from .stock_tool import StockInfo, StockName -TOOL_SETS = [ - "WeatherInfo", "WorldTimeGetTimezoneByArea", "Multiplier", "DistrictInfo", "KSigmaDetector", "MetricsQuery", "DDGSTool", - "DocRetrieval", "CodeRetrieval" - ] -TOOL_DICT = { - "WeatherInfo": WeatherInfo, - "WorldTimeGetTimezoneByArea": WorldTimeGetTimezoneByArea, - "Multiplier": Multiplier, - "DistrictInfo": DistrictInfo, - "KSigmaDetector": KSigmaDetector, - "MetricsQuery": MetricsQuery, - "DDGSTool": DDGSTool, - "DocRetrieval": DocRetrieval, - "CodeRetrieval": CodeRetrieval -} - -__all__ = [ - "WeatherInfo", "WorldTimeGetTimezoneByArea", "Multiplier", "DistrictInfo", "KSigmaDetector", "MetricsQuery", "DDGSTool", - "DocRetrieval", "CodeRetrieval", - "toLangchainTools", "get_tool_schema", "tool_sets", "BaseToolModel" +IMPORT_TOOL = [ + WeatherInfo, DistrictInfo, Multiplier, WorldTimeGetTimezoneByArea, + KSigmaDetector, MetricsQuery, DDGSTool, DocRetrieval, CodeRetrieval, + BaiduOcrTool, StockInfo, StockName ] +TOOL_SETS = [tool.__name__ for tool in IMPORT_TOOL] + +TOOL_DICT = {tool.__name__: tool for tool in IMPORT_TOOL} + + +__all__ = [ + "toLangchainTools", "get_tool_schema", "tool_sets", "BaseToolModel" +] + TOOL_SETS + diff --git a/dev_opsgpt/tools/cb_query_tool.py b/dev_opsgpt/tools/cb_query_tool.py index c162252..4295829 100644 --- a/dev_opsgpt/tools/cb_query_tool.py +++ b/dev_opsgpt/tools/cb_query_tool.py @@ -35,13 +35,26 @@ class CodeRetrieval(BaseToolModel): code: str = Field(..., description="检索代码") @classmethod - def run(cls, code_base_name, query, code_limit=CODE_SEARCH_TOP_K, history_node_list=[]): + def run(cls, code_base_name, query, code_limit=CODE_SEARCH_TOP_K, history_node_list=[], search_type="tag"): """excute your tool!""" - codes = search_code(code_base_name, query, code_limit, history_node_list=history_node_list) - return_codes = [] - related_code = codes['related_code'] - related_nodes = codes['related_node'] + + search_type = { + '基于 cypher': 'cypher', + '基于标签': 'tag', + '基于描述': 'description', + 'tag': 'tag', + 'description': 'description', + 'cypher': 'cypher' + }.get(search_type, 'tag') + + # default + codes = search_code(code_base_name, query, code_limit, search_type=search_type, history_node_list=history_node_list) + return_codes = [] + context = codes['context'] + related_nodes = codes['related_vertices'] + logger.debug(f"{code_base_name}, {query}, {code_limit}, {search_type}") + logger.debug(f"context: {context}, related_nodes: {related_nodes}") + + return_codes.append({'index': 0, 'code': context, "related_nodes": related_nodes}) - for idx, code in enumerate(related_code): - return_codes.append({'index': idx, 'code': code, "related_nodes": related_nodes}) return return_codes diff --git a/dev_opsgpt/tools/multiplier.py b/dev_opsgpt/tools/multiplier.py index e40d382..fee4971 100644 --- a/dev_opsgpt/tools/multiplier.py +++ b/dev_opsgpt/tools/multiplier.py @@ -32,7 +32,4 @@ class Multiplier(BaseToolModel): @staticmethod def run(a, b): - return a * b - -def multi_run(a, b): - return a * b \ No newline at end of file + return a * b \ No newline at end of file diff --git a/dev_opsgpt/tools/ocr_tool.py b/dev_opsgpt/tools/ocr_tool.py new file mode 100644 index 0000000..f19d1e6 --- /dev/null +++ b/dev_opsgpt/tools/ocr_tool.py @@ -0,0 +1,96 @@ +from pydantic import BaseModel, Field +from typing import List, Dict +import requests +import base64 +import urllib +import os +from loguru import logger +from .base_tool import BaseToolModel + +from configs.model_config import JUPYTER_WORK_PATH + + +class BaiduOcrTool(BaseToolModel): + """ + Tips: + 百度ocr tool + + example: + API_KEY = "" + SECRET_KEY = "" + image_path = '' + ocr_result = BaiduOcrTool.run(API_KEY=API_KEY , SECRET_KEY=SECRET_KEY, image_path=image_path) + """ + + name: str = "Baidu_orc_tool" + description: str = """ 百度OCR手写字符识别调用器。 输入一张图片位置,返回图片中的文本""" + + class ToolInputArgs(BaseModel): + """Input for Multiplier.""" + image_name : str = Field(..., description="待提取文本信息的图片名称") + + class ToolOutputArgs(BaseModel): + """Output for Multiplier.""" + + ocr_result: str = Field(..., description="OCR分析提取的自然语言文本") + + @classmethod + def ocr_baidu_main(cls, API_KEY, SECRET_KEY, image_path): + ''' + 根据图片地址,返回OCR识别结果 + OCR的结果不仅包含了文字,也包含了文字的位置。但可以根据简单的提取方法,只将文字提前取出来 + 下面是ocr的返回结果 + '{"words_result":[{"location":{"top":17,"left":33,"width":227,"height":24},"words":"手写识别测试图片样例:"}, + {"location":{"top":91,"left":190,"width":713,"height":70},"words":"每一个人的生命中,都应该有一次,"}, + {"location":{"top":177,"left":87,"width":831,"height":65},"words":"为了某个人而忘了自己,不求有结果."}, + {"location":{"top":263,"left":80,"width":842,"height":76},"words":"不求同行,不求曾经拥有,甚至不求"}], + "words_result_num":4,"log_id":1722502064951792680}' + ''' + url = "https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token=" + BaiduOcrTool.get_access_token(API_KEY, SECRET_KEY) + + # image 可以通过 get_file_content_as_base64("C:\fakepath\ocr_input_example.png",True) 方法获取 + image = BaiduOcrTool.get_file_content_as_base64(image_path, True) + payload = 'image=' + image + '&detect_direction=false&probability=false' + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + s = "" + try: + for word_result in response.json()["words_result"]: + s += "\n" + word_result["words"] + except Exception as e: + logger.exception(e) + s = "无法识别图片内容" + return s + + @classmethod + def get_file_content_as_base64(cls, image_path, urlencoded=False): + """ + 获取文件base64编码 + :param path: 文件路径 + :param urlencoded: 是否对结果进行urlencoded + :return: base64编码信息 + """ + with open(image_path, "rb") as f: + content = base64.b64encode(f.read()).decode("utf8") + if urlencoded: + content = urllib.parse.quote_plus(content) + return content + + @classmethod + def get_access_token(cls, API_KEY, SECRET_KEY): + """ + 使用 AK,SK 生成鉴权签名(Access Token) + :return: access_token,或是None(如果错误) + """ + url = "https://aip.baidubce.com/oauth/2.0/token" + params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY} + return str(requests.post(url, params=params).json().get("access_token")) + + @classmethod + def run(cls, image_name, image_path=JUPYTER_WORK_PATH, API_KEY=os.environ.get("BAIDU_OCR_API_KEY"), SECRET_KEY=os.environ.get("BAIDU_OCR_SECRET_KEY")): + image_file = os.path.join(image_path, image_name) + return cls.ocr_baidu_main(API_KEY, SECRET_KEY, image_file) \ No newline at end of file diff --git a/dev_opsgpt/tools/stock_tool.py b/dev_opsgpt/tools/stock_tool.py new file mode 100644 index 0000000..2f4e946 --- /dev/null +++ b/dev_opsgpt/tools/stock_tool.py @@ -0,0 +1,189 @@ + +import json +import os +import re +from pydantic import BaseModel, Field +from typing import List, Dict, Optional +import requests +import numpy as np +from loguru import logger + +from .base_tool import BaseToolModel +from dev_opsgpt.utils.common_utils import read_json_file + + +cur_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +stock_infos = read_json_file(os.path.join(cur_dir, "../sources/tool_datas/stock.json")) +stock_dict = {i["mc"]: i["jys"]+i["dm"] for i in stock_infos} + + +class StockName(BaseToolModel): + """ + Tips + """ + name: str = "StockName" + description: str = "通过股票名称查询股票代码" + + class ToolInputArgs(BaseModel): + """Input for StockName""" + stock_name: int = Field(..., description="股票名称") + + class ToolOutputArgs(BaseModel): + """Output for StockName""" + stock_code: str = Field(..., description="股票代码") + + @staticmethod + def run(stock_name: str): + return stock_dict.get(stock_name, "no stock_code") + + + +class StockInfo(BaseToolModel): + """ + 用于查询股票市场数据的StockInfo工具。 + """ + + name: str = "StockInfo" + description: str = "根据提供的股票代码、日期范围和数据频率提供股票市场数据。" + + class ToolInputArgs(BaseModel): + """StockInfo的输入参数。""" + code: str = Field(..., description="要查询的股票代码,格式为'marketcode'") + end_date: Optional[str] = Field(default="", description="数据查询的结束日期。留空则为当前日期。如果没有提供结束日期,就留空") + count: int = Field(default=10, description="返回数据点的数量。") + frequency: str = Field(default='1d', description="数据点的频率,例如,'1d'表示每日,'1w'表示每周,'1M'表示每月,'1m'表示每分钟等。") + + class ToolOutputArgs(BaseModel): + """StockInfo的输出参数。""" + data: dict = Field(default=None, description="查询到的股票市场数据。") + + @staticmethod + def run(code: str, count: int, frequency: str, end_date: Optional[str]="") -> "ToolOutputArgs": + """执行股票数据查询工具。""" + # 该方法封装了调用底层股票数据API的逻辑,并将结果格式化为pandas DataFrame。 + try: + df = get_price(code, end_date=end_date, count=count, frequency=frequency) + # 将DataFrame转换为输出的字典格式 + data = df.reset_index().to_dict(orient='list') # 将dataframe转换为字典列表 + return StockInfo.ToolOutputArgs(data=data) + except Exception as e: + logger.exception("获取股票数据时发生错误。") + return e + +#-*- coding:utf-8 -*- --------------Ashare 股票行情数据双核心版( https://github.com/mpquant/Ashare ) + +import json,requests,datetime +import pandas as pd # + +#腾讯日线 + +def get_price_day_tx(code, end_date='', count=10, frequency='1d'): #日线获取 + + unit='week' if frequency in '1w' else 'month' if frequency in '1M' else 'day' #判断日线,周线,月线 + + if end_date: end_date=end_date.strftime('%Y-%m-%d') if isinstance(end_date,datetime.date) else end_date.split(' ')[0] + + end_date='' if end_date==datetime.datetime.now().strftime('%Y-%m-%d') else end_date #如果日期今天就变成空 + + URL=f'http://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param={code},{unit},,{end_date},{count},qfq' + + st= json.loads(requests.get(URL).content); ms='qfq'+unit; stk=st['data'][code] + + buf=stk[ms] if ms in stk else stk[unit] #指数返回不是qfqday,是day + + df=pd.DataFrame(buf,columns=['time','open','close','high','low','volume'],dtype='float') + + df.time=pd.to_datetime(df.time); df.set_index(['time'], inplace=True); df.index.name='' #处理索引 + + return df + + +#腾讯分钟线 + +def get_price_min_tx(code, end_date=None, count=10, frequency='1d'): #分钟线获取 + + ts=int(frequency[:-1]) if frequency[:-1].isdigit() else 1 #解析K线周期数 + + if end_date: end_date=end_date.strftime('%Y-%m-%d') if isinstance(end_date,datetime.date) else end_date.split(' ')[0] + + URL=f'http://ifzq.gtimg.cn/appstock/app/kline/mkline?param={code},m{ts},,{count}' + + st= json.loads(requests.get(URL).content); buf=st[ 'data'][code]['m'+str(ts)] + + df=pd.DataFrame(buf,columns=['time','open','close','high','low','volume','n1','n2']) + + df=df[['time','open','close','high','low','volume']] + + df[['open','close','high','low','volume']]=df[['open','close','high','low','volume']].astype('float') + + df.time=pd.to_datetime(df.time); df.set_index(['time'], inplace=True); df.index.name='' #处理索引 + + df['close'][-1]=float(st['data'][code]['qt'][code][3]) #最新基金数据是3位的 + + return df + + +#sina新浪全周期获取函数,分钟线 5m,15m,30m,60m 日线1d=240m 周线1w=1200m 1月=7200m + +def get_price_sina(code, end_date='', count=10, frequency='60m'): #新浪全周期获取函数 + + frequency=frequency.replace('1d','240m').replace('1w','1200m').replace('1M','7200m'); mcount=count + + ts=int(frequency[:-1]) if frequency[:-1].isdigit() else 1 #解析K线周期数 + + if (end_date!='') & (frequency in ['240m','1200m','7200m']): + + end_date=pd.to_datetime(end_date) if not isinstance(end_date,datetime.date) else end_date #转换成datetime + unit=4 if frequency=='1200m' else 29 if frequency=='7200m' else 1 #4,29多几个数据不影响速度 + + count=count+(datetime.datetime.now()-end_date).days//unit #结束时间到今天有多少天自然日(肯定 >交易日) + + #print(code,end_date,count) + + URL=f'http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol={code}&scale={ts}&ma=5&datalen={count}' + + dstr= json.loads(requests.get(URL).content); + + #df=pd.DataFrame(dstr,columns=['day','open','high','low','close','volume'],dtype='float') + + df= pd.DataFrame(dstr,columns=['day','open','high','low','close','volume']) + + df['open'] = df['open'].astype(float); df['high'] = df['high'].astype(float); #转换数据类型 + df['low'] = df['low'].astype(float); df['close'] = df['close'].astype(float); df['volume'] = df['volume'].astype(float) + + df.day=pd.to_datetime(df.day); + + df.set_index(['day'], inplace=True); + + df.index.name='' #处理索引 + + if (end_date!='') & (frequency in ['240m','1200m','7200m']): + return df[df.index<=end_date][-mcount:] #日线带结束时间先返回 + + return df + + +def get_price(code, end_date='',count=10, frequency='1d', fields=[]): #对外暴露只有唯一函数,这样对用户才是最友好的 + + xcode= code.replace('.XSHG','').replace('.XSHE','') #证券代码编码兼容处理 + xcode='sh'+xcode if ('XSHG' in code) else 'sz'+xcode if ('XSHE' in code) else code + + if frequency in ['1d','1w','1M']: #1d日线 1w周线 1M月线 + try: + return get_price_sina( xcode, end_date=end_date,count=count,frequency=frequency) #主力 + except: + return get_price_day_tx(xcode,end_date=end_date,count=count,frequency=frequency) #备用 + + if frequency in ['1m','5m','15m','30m','60m']: #分钟线 ,1m只有腾讯接口 5分钟5m 60分钟60m + if frequency in '1m': + return get_price_min_tx(xcode,end_date=end_date,count=count,frequency=frequency) + try: + return get_price_sina(xcode,end_date=end_date,count=count,frequency=frequency) #主力 + except: + return get_price_min_tx(xcode,end_date=end_date,count=count,frequency=frequency) #备用 + + +if __name__ == "__main__": + tool = StockInfo() + output = tool.run(code='sh600519', end_date='', count=10, frequency='15m') + print(output.json(indent=2)) \ No newline at end of file diff --git a/dev_opsgpt/utils/common_utils.py b/dev_opsgpt/utils/common_utils.py index 64d2fa6..047c831 100644 --- a/dev_opsgpt/utils/common_utils.py +++ b/dev_opsgpt/utils/common_utils.py @@ -73,14 +73,14 @@ def save_to_json_file(data, filename): def file_normalize(file: Union[str, Path, bytes], filename=None): - logger.debug(f"{file}") + # logger.debug(f"{file}") if isinstance(file, bytes): # raw bytes file = BytesIO(file) elif hasattr(file, "read"): # a file io like object filename = filename or file.name else: # a local path file = Path(file).absolute().open("rb") - logger.debug(file) + # logger.debug(file) filename = filename or file.name return file, filename diff --git a/dev_opsgpt/utils/nebula_cp.sh b/dev_opsgpt/utils/nebula_cp.sh new file mode 100644 index 0000000..b98de0c --- /dev/null +++ b/dev_opsgpt/utils/nebula_cp.sh @@ -0,0 +1,3 @@ +if [ -d "/usr/local/nebula/data/meta" ]; then + cp -r /usr/local/nebula/data /home/user/chatbot/data/nebula_data +fi \ No newline at end of file diff --git a/dev_opsgpt/utils/postprocess.py b/dev_opsgpt/utils/postprocess.py index 9929aaa..fa65bc3 100644 --- a/dev_opsgpt/utils/postprocess.py +++ b/dev_opsgpt/utils/postprocess.py @@ -5,8 +5,8 @@ @time: 2023/11/9 下午4:01 @desc: ''' +import html def replace_lt_gt(text: str): - text = text.replace('<', '<') - text = text.replace('>', '>') + text = html.unescape(text) return text \ No newline at end of file diff --git a/dev_opsgpt/utils/server_utils.py b/dev_opsgpt/utils/server_utils.py index 27a133f..926590d 100644 --- a/dev_opsgpt/utils/server_utils.py +++ b/dev_opsgpt/utils/server_utils.py @@ -1,6 +1,6 @@ import pydantic from pydantic import BaseModel -from typing import List +from typing import List, Union import torch from fastapi import FastAPI from pathlib import Path @@ -21,6 +21,18 @@ class BaseResponse(BaseModel): } } +class DataResponse(BaseResponse): + data: Union[str, bytes] = pydantic.Field(..., description="data") + + class Config: + schema_extra = { + "example": { + "code": 200, + "msg": "success", + "data": "data" + } + } + class ListResponse(BaseResponse): data: List[str] = pydantic.Field(..., description="List of names") diff --git a/dev_opsgpt/webui/code.py b/dev_opsgpt/webui/code.py index 0e6a6c4..5b6bffd 100644 --- a/dev_opsgpt/webui/code.py +++ b/dev_opsgpt/webui/code.py @@ -84,6 +84,10 @@ def code_page(api: ApiRequest): accept_multiple_files=False, ) + do_interpret = st.checkbox('**代码解读**', value=True, help='代码解读会针对每个代码文件通过 LLM 获取解释并且向量化存储。当代码文件较多时,\ + 导入速度会变慢,且如果使用收费 API 的话可能会造成较大花费。如果要使用基于描述的代码问答模式,此项必须勾选', key='do_interpret') + + logger.info(f'do_interpret={do_interpret}') submit_create_kb = st.form_submit_button( "新建", use_container_width=True, @@ -104,6 +108,7 @@ def code_page(api: ApiRequest): ret = api.create_code_base( cb_name, file, + do_interpret, no_remote_api=True ) st.toast(ret.get("msg", " ")) @@ -124,6 +129,7 @@ def code_page(api: ApiRequest): cb_details.get('code_file_num', 'unknown'))) st.write('代码知识库 `{}` 知识图谱节点数=`{}`'.format(cb_details['code_name'], cb_details['code_graph_node_num'])) + st.write('代码知识库 `{}` 是否进行代码解读=`{}`'.format(cb_details['code_name'], cb_details['do_interpret'])) st.divider() diff --git a/dev_opsgpt/webui/dialogue.py b/dev_opsgpt/webui/dialogue.py index 03f6d93..95c2413 100644 --- a/dev_opsgpt/webui/dialogue.py +++ b/dev_opsgpt/webui/dialogue.py @@ -9,23 +9,34 @@ from dev_opsgpt.utils import * from dev_opsgpt.tools import TOOL_SETS from dev_opsgpt.chat.search_chat import SEARCH_ENGINES from dev_opsgpt.connector import PHASE_LIST, PHASE_CONFIGS +from dev_opsgpt.service.service_factory import get_cb_details_by_cb_name chat_box = ChatBox( assistant_avatar="../sources/imgs/devops-chatbot2.png" ) GLOBAL_EXE_CODE_TEXT = "" +GLOBAL_MESSAGE = {"figures": {}, "final_contents": {}} - -def get_messages_history(history_len: int) -> List[Dict]: +def get_messages_history(history_len: int, isDetailed=False) -> List[Dict]: def filter(msg): ''' 针对当前简单文本对话,只返回每条消息的第一个element的内容 ''' content = [x._content for x in msg["elements"] if x._output_method in ["markdown", "text"]] + content = content[0] if content else "" + if isDetailed: + for k, v in GLOBAL_MESSAGE["final_contents"].items(): + if k == content: + content = v[-1] + break + + for k, v in GLOBAL_MESSAGE["figures"].items(): + content = content.replace(v, k) + return { "role": msg["role"], - "content": content[0] if content else "", + "content": content, } history = chat_box.filter_history(100000, filter) # workaround before upgrading streamlit-chatbox. @@ -39,6 +50,18 @@ def get_messages_history(history_len: int) -> List[Dict]: return history[-i:] +def upload2sandbox(upload_file, api: ApiRequest): + if upload_file is None: + res = {"msg": False} + else: + res = api.web_sd_upload(upload_file) + # logger.debug(res) + # if res["msg"]: + # st.success("上文件传成功") + # else: + # st.toast("文件上传失败") + + def dialogue_page(api: ApiRequest): global GLOBAL_EXE_CODE_TEXT chat_box.init_session() @@ -60,8 +83,6 @@ def dialogue_page(api: ApiRequest): "知识库问答", "代码知识库问答", "搜索引擎问答", - "工具问答", - "数据分析", "Agent问答" ], on_change=on_mode_change, @@ -76,8 +97,16 @@ def dialogue_page(api: ApiRequest): def on_cb_change(): st.toast(f"已加载代码知识库: {st.session_state.selected_cb}") + cb_details = get_cb_details_by_cb_name(st.session_state.selected_cb) + st.session_state['do_interpret'] = cb_details['do_interpret'] + + # + if "interpreter_file_key" not in st.session_state: + st.session_state["interpreter_file_key"] = 0 not_agent_qa = True + interpreter_file = "" + is_detailed = False if dialogue_mode == "知识库问答": with st.expander("知识库配置", True): kb_list = api.list_knowledge_bases(no_remote_api=True) @@ -101,56 +130,33 @@ def dialogue_page(api: ApiRequest): on_change=on_cb_change, key="selected_cb", ) + + # change do_interpret st.toast(f"已加载代码知识库: {st.session_state.selected_cb}") + cb_details = get_cb_details_by_cb_name(st.session_state.selected_cb) + st.session_state['do_interpret'] = cb_details['do_interpret'] + cb_code_limit = st.number_input("匹配代码条数:", 1, 20, 1) + + search_type_list = ['基于 cypher', '基于标签', '基于描述'] if st.session_state['do_interpret'] == 'YES' \ + else ['基于 cypher', '基于标签'] + + cb_search_type = st.selectbox( + '请选择查询模式:', + search_type_list, + key='cb_search_type' + ) elif dialogue_mode == "搜索引擎问答": with st.expander("搜索引擎配置", True): search_engine = st.selectbox("请选择搜索引擎", SEARCH_ENGINES.keys(), 0) se_top_k = st.number_input("匹配搜索结果条数:", 1, 20, 3) - elif dialogue_mode == "工具问答": - with st.expander("工具军火库", True): - tool_selects = st.multiselect( - '请选择待使用的工具', TOOL_SETS, ["WeatherInfo"]) - - elif dialogue_mode == "数据分析": - with st.expander("沙盒文件管理", False): - def _upload(upload_file): - res = api.web_sd_upload(upload_file) - logger.debug(res) - if res["msg"]: - st.success("上文件传成功") - else: - st.toast("文件上传失败") - - interpreter_file = st.file_uploader( - "上传沙盒文件", - [i for ls in LOADER2EXT_DICT.values() for i in ls], - accept_multiple_files=False, - key="interpreter_file", - ) - - if interpreter_file: - _upload(interpreter_file) - interpreter_file = None - # - files = api.web_sd_list_files() - files = files["data"] - download_file = st.selectbox("选择要处理文件", files, - key="download_file",) - - cols = st.columns(2) - file_url, file_name = api.web_sd_download(download_file) - cols[0].download_button("点击下载", file_url, file_name) - if cols[1].button("点击删除", ): - api.web_sd_delete(download_file) - elif dialogue_mode == "Agent问答": not_agent_qa = False with st.expander("Phase管理", True): choose_phase = st.selectbox( '请选择待使用的执行链路', PHASE_LIST, 0) - is_detailed = st.toggle("返回明细的Agent交互", False) + is_detailed = st.toggle("是否使用明细信息进行agent交互", False) tool_using_on = st.toggle("开启工具使用", PHASE_CONFIGS[choose_phase]["do_using_tool"]) tool_selects = [] if tool_using_on: @@ -181,6 +187,7 @@ def dialogue_page(api: ApiRequest): code_retrieval_on = st.toggle("开启代码检索增强", PHASE_CONFIGS[choose_phase]["do_code_retrieval"]) selected_cb, top_k = None, 1 + cb_search_type = "tag" if code_retrieval_on: with st.expander('代码知识库配置', True): cb_list = api.list_cb(no_remote_api=True) @@ -194,36 +201,41 @@ def dialogue_page(api: ApiRequest): st.toast(f"已加载代码知识库: {st.session_state.selected_cb}") top_k = st.number_input("匹配代码条数:", 1, 20, 1) - with st.expander("沙盒文件管理", False): - def _upload(upload_file): - res = api.web_sd_upload(upload_file) - logger.debug(res) - if res["msg"]: - st.success("上文件传成功") - else: - st.toast("文件上传失败") + cb_details = get_cb_details_by_cb_name(st.session_state.selected_cb) + st.session_state['do_interpret'] = cb_details['do_interpret'] + search_type_list = ['基于 cypher', '基于标签', '基于描述'] if st.session_state['do_interpret'] == 'YES' \ + else ['基于 cypher', '基于标签'] + cb_search_type = st.selectbox( + '请选择查询模式:', + search_type_list, + key='cb_search_type' + ) - interpreter_file = st.file_uploader( - "上传沙盒文件", - [i for ls in LOADER2EXT_DICT.values() for i in ls], - accept_multiple_files=False, - key="interpreter_file", - ) + with st.expander("沙盒文件管理", False): - if interpreter_file: - _upload(interpreter_file) - interpreter_file = None - # - files = api.web_sd_list_files() - files = files["data"] - download_file = st.selectbox("选择要处理文件", files, - key="download_file",) + interpreter_file = st.file_uploader( + "上传沙盒文件", + [i for ls in LOADER2EXT_DICT.values() for i in ls] + ["jpg", "png"], + accept_multiple_files=False, + key=st.session_state.interpreter_file_key, + ) - cols = st.columns(2) - file_url, file_name = api.web_sd_download(download_file) - cols[0].download_button("点击下载", file_url, file_name) - if cols[1].button("点击删除", ): - api.web_sd_delete(download_file) + files = api.web_sd_list_files() + files = files["data"] + download_file = st.selectbox("选择要处理文件", files, + key="download_file",) + + cols = st.columns(3) + file_url, file_name = api.web_sd_download(download_file) + if cols[0].button("点击上传"): + upload2sandbox(interpreter_file, api) + st.session_state["interpreter_file_key"] += 1 + interpreter_file = "" + st.experimental_rerun() + + cols[1].download_button("点击下载", file_url, file_name) + if cols[2].button("点击删除", ): + api.web_sd_delete(download_file) code_interpreter_on = st.toggle("开启代码解释器") and not_agent_qa code_exec_on = st.toggle("自动执行代码") and not_agent_qa @@ -237,7 +249,10 @@ def dialogue_page(api: ApiRequest): codebox_res = None if prompt := st.chat_input(chat_input_placeholder, key="prompt"): - history = get_messages_history(history_len) + upload2sandbox(interpreter_file, api) + logger.debug(f"prompt: {prompt}") + + history = get_messages_history(history_len, is_detailed) chat_box.user_say(prompt) if dialogue_mode == "LLM 对话": chat_box.ai_say("正在思考...") @@ -281,6 +296,7 @@ def dialogue_page(api: ApiRequest): "doc_engine_name": selected_kb, "search_engine_name": search_engine, "code_engine_name": selected_cb, + "cb_search_type": cb_search_type, "top_k": top_k, "score_threshold": score_threshold, "do_search": search_on, @@ -293,21 +309,26 @@ def dialogue_page(api: ApiRequest): "choose_tools": tool_selects, "history_node_list": history_node_list, "isDetailed": is_detailed, + "upload_file": interpreter_file, } text = "" d = {"docs": []} - for idx_count, d in enumerate(api.agent_chat(**input_kargs)): + for idx_count, d in enumerate(api.agent_achat(**input_kargs)): if error_msg := check_error_msg(d): # check whether error occured st.error(error_msg) - text += d["answer"] - if idx_count%20 == 0: - chat_box.update_msg(text, element_index=0) + # logger.debug(f"d: {d['answer']}") + text = d["answer"] + for text_length in range(0, len(text)+1, 10): + chat_box.update_msg(text[:text_length+10], element_index=0, streaming=True) + + GLOBAL_MESSAGE.setdefault("final_contents", {}).setdefault(d.get("answer", ""), []).append(d.get("final_content", "")) for k, v in d["figures"].items(): - logger.debug(f"figure: {k}") if k in text: img_html = "\n\n".format(v) text = text.replace(k, img_html).replace(".png", "") + GLOBAL_MESSAGE.setdefault("figures", {}).setdefault(k, v) + chat_box.update_msg(text, element_index=0, streaming=False, state="complete") # 更新最终的字符串,去除光标 if search_on: chat_box.update_msg("搜索匹配结果:\n\n" + "\n\n".join(d["search_docs"]), element_index=search_on, streaming=False, state="complete") @@ -317,41 +338,6 @@ def dialogue_page(api: ApiRequest): history_node_list.extend([node[0] for node in d.get("related_nodes", [])]) history_node_list = list(set(history_node_list)) st.session_state['history_node_list'] = history_node_list - - elif dialogue_mode == "工具问答": - chat_box.ai_say("正在思考...") - text = "" - r = api.tool_chat(prompt, history, tool_sets=tool_selects) - for t in r: - if error_msg := check_error_msg(t): # check whether error occured - st.error(error_msg) - break - text += t["answer"] - chat_box.update_msg(text) - logger.debug(f"text: {text}") - chat_box.update_msg(text, streaming=False) # 更新最终的字符串,去除光标 - # 判断是否存在代码, 并提高编辑功能,执行功能 - code_text = api.codebox.decode_code_from_text(text) - GLOBAL_EXE_CODE_TEXT = code_text - if code_text and code_exec_on: - codebox_res = api.codebox_chat("```"+code_text+"```", do_code_exe=True) - elif dialogue_mode == "数据分析": - chat_box.ai_say("正在思考...") - text = "" - r = api.data_chat(prompt, history) - for t in r: - if error_msg := check_error_msg(t): # check whether error occured - st.error(error_msg) - break - text += t["answer"] - chat_box.update_msg(text) - logger.debug(f"text: {text}") - chat_box.update_msg(text, streaming=False) # 更新最终的字符串,去除光标 - # 判断是否存在代码, 并提高编辑功能,执行功能 - code_text = api.codebox.decode_code_from_text(text) - GLOBAL_EXE_CODE_TEXT = code_text - if code_text and code_exec_on: - codebox_res = api.codebox_chat("```"+code_text+"```", do_code_exe=True) elif dialogue_mode == "知识库问答": history = get_messages_history(history_len) chat_box.ai_say([ @@ -382,13 +368,14 @@ def dialogue_page(api: ApiRequest): chat_box.ai_say([ f"正在查询代码知识库 `{selected_cb}` ...", - Markdown("...", in_expander=True, title="代码库匹配结果"), + Markdown("...", in_expander=True, title="代码库匹配节点"), ]) text = "" d = {"codes": []} for idx_count, d in enumerate(api.code_base_chat(query=prompt, code_base_name=selected_cb, code_limit=cb_code_limit, history=history, + cb_search_type=cb_search_type, no_remote_api=True)): if error_msg := check_error_msg(d): st.error(error_msg) @@ -396,14 +383,15 @@ def dialogue_page(api: ApiRequest): if idx_count % 10 == 0: # text = replace_lt_gt(text) chat_box.update_msg(text, element_index=0) + # postprocess - # text = replace_lt_gt(text) + text = replace_lt_gt(text) chat_box.update_msg(text, element_index=0, streaming=False) # 更新最终的字符串,去除光标 logger.debug('text={}'.format(text)) chat_box.update_msg("\n".join(d["codes"]), element_index=1, streaming=False, state="complete") # session state update - st.session_state['history_node_list'] = api.codeChat.history_node_list + # st.session_state['history_node_list'] = api.codeChat.history_node_list elif dialogue_mode == "搜索引擎问答": chat_box.ai_say([ @@ -427,6 +415,10 @@ def dialogue_page(api: ApiRequest): if code_text and code_exec_on: codebox_res = api.codebox_chat("```"+code_text+"```", do_code_exe=True) + # 将上传文件清空 + st.session_state["interpreter_file_key"] += 1 + st.experimental_rerun() + if code_interpreter_on: with st.expander("代码编辑执行器", False): code_part = st.text_area("代码片段", code_text, key="code_text") diff --git a/dev_opsgpt/webui/utils.py b/dev_opsgpt/webui/utils.py index 5868fd3..ac17d46 100644 --- a/dev_opsgpt/webui/utils.py +++ b/dev_opsgpt/webui/utils.py @@ -10,7 +10,7 @@ import json import nltk import traceback from loguru import logger -import zipfile + from configs.model_config import ( EMBEDDING_MODEL, @@ -30,10 +30,12 @@ from configs.server_config import SANDBOX_SERVER from dev_opsgpt.utils.server_utils import run_async, iter_over_async from dev_opsgpt.service.kb_api import * from dev_opsgpt.service.cb_api import * -from dev_opsgpt.chat import LLMChat, SearchChat, KnowledgeChat, ToolChat, DataChat, CodeChat, AgentChat +from dev_opsgpt.chat import LLMChat, SearchChat, KnowledgeChat, CodeChat, AgentChat from dev_opsgpt.sandbox import PyCodeBox, CodeBoxResponse from dev_opsgpt.utils.common_utils import file_normalize, get_uploadfile +from dev_opsgpt.codechat.code_crawler.zip_crawler import ZipCrawler + from web_crawler.utils.WebCrawler import WebCrawler nltk.data.path = [NLTK_DATA_PATH] + nltk.data.path @@ -73,11 +75,9 @@ class ApiRequest: self.llmChat = LLMChat() self.searchChat = SearchChat() self.knowledgeChat = KnowledgeChat() - self.toolChat = ToolChat() - self.dataChat = DataChat() self.codeChat = CodeChat() - self.agentChat = AgentChat() + self.codebox = PyCodeBox( remote_url=SANDBOX_SERVER["url"], remote_ip=SANDBOX_SERVER["host"], # "http://localhost", @@ -247,6 +247,18 @@ class ApiRequest: except Exception as e: logger.error(traceback.format_exc()) + def _stream2generator(self, response: str, as_json: bool =False): + ''' + 将api.py中视图函数返回的StreamingResponse转化为同步生成器 + ''' + try: + if as_json and response: + return json.loads(response) + elif response.strip(): + return response + except Exception as e: + logger.error(traceback.format_exc()) + def _httpx_stream2generator( self, response: contextlib._GeneratorContextManager, @@ -372,66 +384,13 @@ class ApiRequest: ) return self._httpx_stream2generator(response, as_json=True) - def tool_chat( - self, - query: str, - history: List[Dict] = [], - tool_sets: List[str] = [], - stream: bool = True, - no_remote_api: bool = None, - ): - ''' - 对应api.py/chat/chat接口 - ''' - if no_remote_api is None: - no_remote_api = self.no_remote_api - - data = { - "query": query, - "history": history, - "tool_sets": tool_sets, - "stream": stream, - } - - if no_remote_api: - response = self.toolChat.chat(**data) - return self._fastapi_stream2generator(response, as_json=True) - else: - response = self.post("/chat/tool_chat", json=data, stream=True) - return self._httpx_stream2generator(response) - - def data_chat( - self, - query: str, - history: List[Dict] = [], - stream: bool = True, - no_remote_api: bool = None, - ): - ''' - 对应api.py/chat/chat接口 - ''' - if no_remote_api is None: - no_remote_api = self.no_remote_api - - data = { - "query": query, - "history": history, - "stream": stream, - } - - if no_remote_api: - response = self.dataChat.chat(**data) - return self._fastapi_stream2generator(response, as_json=True) - else: - response = self.post("/chat/data_chat", json=data, stream=True) - return self._httpx_stream2generator(response) - def code_base_chat( self, query: str, code_base_name: str, code_limit: int = 1, history: List[Dict] = [], + cb_search_type: str = 'tag', stream: bool = True, no_remote_api: bool = None, ): @@ -441,20 +400,31 @@ class ApiRequest: if no_remote_api is None: no_remote_api = self.no_remote_api + cb_search_type = { + '基于 cypher': 'cypher', + '基于标签': 'tag', + '基于描述': 'description', + 'tag': 'tag', + 'description': 'description', + 'cypher': 'cypher' + }.get(cb_search_type, 'tag') + + data = { "query": query, "history": history, "engine_name": code_base_name, "code_limit": code_limit, + "cb_search_type": cb_search_type, "stream": stream, "local_doc_url": no_remote_api, } logger.info('data={}'.format(data)) if no_remote_api: - logger.info('history_node_list before={}'.format(self.codeChat.history_node_list)) + # logger.info('history_node_list before={}'.format(self.codeChat.history_node_list)) response = self.codeChat.chat(**data) - logger.info('history_node_list after={}'.format(self.codeChat.history_node_list)) + # logger.info('history_node_list after={}'.format(self.codeChat.history_node_list)) return self._fastapi_stream2generator(response, as_json=True) else: response = self.post( @@ -486,7 +456,8 @@ class ApiRequest: custom_role_configs = {}, no_remote_api: bool = None, history_node_list: List[str] = [], - isDetailed: bool = False + isDetailed: bool = False, + upload_file: Union[str, Path, bytes] = "", ): ''' 对应api.py/chat/chat接口 @@ -515,7 +486,8 @@ class ApiRequest: "custom_role_configs": custom_role_configs, "choose_tools": choose_tools, "history_node_list": history_node_list, - "isDetailed": isDetailed + "isDetailed": isDetailed, + "upload_file": upload_file } if no_remote_api: response = self.agentChat.chat(**data) @@ -524,6 +496,71 @@ class ApiRequest: response = self.post("/chat/data_chat", json=data, stream=True) return self._httpx_stream2generator(response) + def agent_achat( + self, + query: str, + phase_name: str, + doc_engine_name: str, + code_engine_name: str, + cb_search_type: str, + search_engine_name: str, + top_k: int = 3, + score_threshold: float = 1.0, + history: List[Dict] = [], + stream: bool = True, + local_doc_url: bool = False, + do_search: bool = False, + do_doc_retrieval: bool = False, + do_code_retrieval: bool = False, + do_tool_retrieval: bool = False, + choose_tools: List[str] = [], + custom_phase_configs = {}, + custom_chain_configs = {}, + custom_role_configs = {}, + no_remote_api: bool = None, + history_node_list: List[str] = [], + isDetailed: bool = False, + upload_file: Union[str, Path, bytes] = "", + ): + ''' + 对应api.py/chat/chat接口 + ''' + if no_remote_api is None: + no_remote_api = self.no_remote_api + + data = { + "query": query, + "phase_name": phase_name, + "chain_name": "", + "history": history, + "doc_engine_name": doc_engine_name, + "code_engine_name": code_engine_name, + "cb_search_type": cb_search_type, + "search_engine_name": search_engine_name, + "top_k": top_k, + "score_threshold": score_threshold, + "stream": stream, + "local_doc_url": local_doc_url, + "do_search": do_search, + "do_doc_retrieval": do_doc_retrieval, + "do_code_retrieval": do_code_retrieval, + "do_tool_retrieval": do_tool_retrieval, + "custom_phase_configs": custom_phase_configs, + "custom_chain_configs": custom_chain_configs, + "custom_role_configs": custom_role_configs, + "choose_tools": choose_tools, + "history_node_list": history_node_list, + "isDetailed": isDetailed, + "upload_file": upload_file + } + + if no_remote_api: + for response in self.agentChat.achat(**data): + yield self._stream2generator(response, as_json=True) + else: + response = self.post("/chat/data_chat", json=data, stream=True) + yield self._httpx_stream2generator(response) + def _check_httpx_json_response( self, response: httpx.Response, @@ -851,12 +888,15 @@ class ApiRequest: def web_sd_download(self, filename: str, save_filename: str = None): '''对应file_service/sd_download_file''' save_filename = save_filename or filename - # response = self.get( - # f"/sdfiles/download", - # params={"filename": filename, "save_filename": save_filename} - # ) - key_value_str = f"filename={filename}&save_filename={save_filename}" - return self._parse_url(f"/sdfiles/download?{key_value_str}"), save_filename + response = self.get( + f"/sdfiles/download", + params={"filename": filename, "save_filename": save_filename} + ) + # logger.debug(f"response: {response.json()}") + if filename: + file_content, _ = file_normalize(response.json()["data"]) + return file_content, save_filename + return "", save_filename def web_sd_delete(self, filename: str): '''对应file_service/sd_delete_file''' @@ -872,7 +912,7 @@ class ApiRequest: return self._check_httpx_json_response(response) # code base 相关操作 - def create_code_base(self, cb_name, zip_file, no_remote_api: bool = None,): + def create_code_base(self, cb_name, zip_file, do_interpret: bool, no_remote_api: bool = None,): ''' 创建 code_base @param cb_name: @@ -893,13 +933,11 @@ class ApiRequest: if not os.path.exists(dir): os.makedirs(dir) - # unzip - with zipfile.ZipFile(zip_file, 'r') as z: - z.extractall(raw_code_path) - data = { + "zip_file": zip_file, "cb_name": cb_name, - "code_path": raw_code_path + "code_path": raw_code_path, + "do_interpret": do_interpret } logger.info('create cb data={}'.format(data)) diff --git a/examples/agent_examples/baseTaskPhase_example.py b/examples/agent_examples/baseTaskPhase_example.py new file mode 100644 index 0000000..78f1b4f --- /dev/null +++ b/examples/agent_examples/baseTaskPhase_example.py @@ -0,0 +1,52 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, Memory, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + + +phase_name = "baseTaskPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=True, + do_search=False, + ) + +# round-1 +query_content = "确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型;然后画柱状图" +query = Message( + role_name="human", role_type="user", + role_content=query_content, input_query=query_content, origin_query=query_content, + ) + +output_message, _ = phase.step(query) diff --git a/examples/agent_examples/codeChatPhase_example.py b/examples/agent_examples/codeChatPhase_example.py new file mode 100644 index 0000000..1fa2a1c --- /dev/null +++ b/examples/agent_examples/codeChatPhase_example.py @@ -0,0 +1,129 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, Memory, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + + +phase_name = "codeChatPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=True, + do_doc_retrieval=False, + do_search=False, + ) + +# 代码一共有多少类 => 基于cypher +# 代码库里有哪些函数,返回5个就行 => 基于cypher +# remove 这个函数是做什么的 => 基于标签 +# 有没有函数已经实现了从字符串删除指定字符串的功能,使用的话可以怎么使用,写个java代码 => 基于描述 +# 有根据我以下的需求用 java 开发一个方法:输入为字符串,将输入中的 .java 字符串给删除掉,然后返回新的字符串 => 基于描述 + +# round-1 +query_content = "代码一共有多少类" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + code_engine_name="client", score_threshold=1.0, top_k=3, cb_search_type="cypher" + ) + +output_message1, _ = phase.step(query) + +# round-2 +history = Memory(messages=[ + Message(role_name="user", role_type="human", role_content="代码一共有多少类"), + Message(role_name="ai", role_type="assistant", role_content=output_message1.step_content), + ]) + +query_content = "代码库里有哪些函数,返回5个就行" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + code_engine_name="client", score_threshold=1.0, top_k=3, cb_search_type="cypher" + ) +output_message2, _ = phase.step(query) + +# round-3 +history = Memory(messages=[ + Message(role_name="user", role_type="human", role_content="代码一共有多少类"), + Message(role_name="ai", role_type="assistant", role_content=output_message1.step_content), + Message(role_name="user", role_type="human", role_content="代码库里有哪些函数,返回5个就行"), + Message(role_name="ai", role_type="assistant", role_content=output_message2.step_content), + ]) + +query_content = "remove 这个函数是做什么的" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + code_engine_name="client", score_threshold=1.0, top_k=3, cb_search_type="tag" + ) +output_message3, _ = phase.step(query) + +# round-4 +history = Memory(messages=[ + Message(role_name="user", role_type="human", role_content="代码一共有多少类"), + Message(role_name="ai", role_type="assistant", role_content=output_message1.step_content), + Message(role_name="user", role_type="human", role_content="代码库里有哪些函数,返回5个就行"), + Message(role_name="ai", role_type="assistant", role_content=output_message2.step_content), + Message(role_name="user", role_type="human", role_content="remove 这个函数是做什么的"), + Message(role_name="ai", role_type="assistant", role_content=output_message3.step_content), + ]) + + +query_content = "有没有函数已经实现了从字符串删除指定字符串的功能,使用的话可以怎么使用,写个java代码" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + code_engine_name="client", score_threshold=1.0, top_k=3, cb_search_type="description" + ) +output_message4, _ = phase.step(query) + + +# round-5 +history = Memory(messages=[ + Message(role_name="user", role_type="human", role_content="代码一共有多少类"), + Message(role_name="ai", role_type="assistant", role_content=output_message1.step_content), + Message(role_name="user", role_type="human", role_content="代码库里有哪些函数,返回5个就行"), + Message(role_name="ai", role_type="assistant", role_content=output_message2.step_content), + Message(role_name="user", role_type="human", role_content="remove 这个函数是做什么的"), + Message(role_name="ai", role_type="assistant", role_content=output_message3.step_content), + Message(role_name="user", role_type="human", role_content="有没有函数已经实现了从字符串删除指定字符串的功能,使用的话可以怎么使用,写个java代码"), + Message(role_name="ai", role_type="assistant", role_content=output_message4.step_content), + ]) + +query_content = "有根据我以下的需求用 java 开发一个方法:输入为字符串,将输入中的 .java 字符串给删除掉,然后返回新的字符串" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + code_engine_name="client", score_threshold=1.0, top_k=3, cb_search_type="description" + ) +output_message5, _ = phase.step(query) \ No newline at end of file diff --git a/examples/agent_examples/codeReactPhase_example.py b/examples/agent_examples/codeReactPhase_example.py new file mode 100644 index 0000000..22cf560 --- /dev/null +++ b/examples/agent_examples/codeReactPhase_example.py @@ -0,0 +1,52 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, Memory, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + + +phase_name = "codeReactPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=True, + do_search=False, + ) + +# round-1 +query_content = "确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型;然后画柱状图" +query = Message( + role_name="human", role_type="user", + role_content=query_content, input_query=query_content, origin_query=query_content, + ) + +output_message, _ = phase.step(query) diff --git a/examples/agent_examples/coedToolReactPhase_example.py b/examples/agent_examples/coedToolReactPhase_example.py new file mode 100644 index 0000000..e39386c --- /dev/null +++ b/examples/agent_examples/coedToolReactPhase_example.py @@ -0,0 +1,59 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +# src_dir = os.path.join( +# os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# ) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + +print(src_dir) + +# tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + +TOOL_SETS = [ + "StockInfo", "StockName" + ] + +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + +phase_name = "codeToolReactPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=True, + do_search=False, + ) + +query_content = "查询贵州茅台的股票代码,并查询截止到当前日期(2023年11月8日)的最近10天的每日时序数据,然后对时序数据画出折线图并分析" + +query = Message(role_name="human", role_type="user", input_query=query_content, role_content=query_content, origin_query=query_content, tools=tools) + +output_message = phase.step(query) \ No newline at end of file diff --git a/examples/agent_examples/docChatPhase_example.py b/examples/agent_examples/docChatPhase_example.py new file mode 100644 index 0000000..58839b8 --- /dev/null +++ b/examples/agent_examples/docChatPhase_example.py @@ -0,0 +1,67 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, Memory, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + + +phase_name = "docChatPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=True, + do_search=False, + ) + +# round-1 +query_content = "langchain有哪些模块" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + doc_engine_name="DSADSAD", score_threshold=1.0, top_k=3 + ) + +output_message, _ = phase.step(query) + +# round-2 +history = Memory(messages=[ + Message(role_name="user", role_type="human", role_content="langchain有哪些模块"), + Message(role_name="ai", role_type="assistant", role_content=output_message.step_content), + ]) + +query_content = "提示(prompts)有什么用?" +query = Message( + role_name="human", role_type="user", + role_content=query_content, input_query=query_content, origin_query=query_content, + doc_engine_name="DSADSAD", score_threshold=1.0, top_k=3 + ) +output_message, _ = phase.step(query) \ No newline at end of file diff --git a/examples/agent_examples/metagpt_phase_example.py b/examples/agent_examples/metagpt_phase_example.py new file mode 100644 index 0000000..5c3f72b --- /dev/null +++ b/examples/agent_examples/metagpt_phase_example.py @@ -0,0 +1,41 @@ +import os, sys + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + + +phase_name = "metagpt_code_devlop" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=True, + do_search=False, + ) + +query_content = "create a snake game" +query = Message(role_name="human", role_type="user", input_query=query_content, role_content=query_content, origin_query=query_content) + +output_message = phase.step(query) diff --git a/examples/agent_examples/searchChatPhase_example.py b/examples/agent_examples/searchChatPhase_example.py new file mode 100644 index 0000000..6d64e6f --- /dev/null +++ b/examples/agent_examples/searchChatPhase_example.py @@ -0,0 +1,67 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, Memory, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + + +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + + +phase_name = "searchChatPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=False, + do_search=True, + ) + +# round-1 +query_content = "美国当前总统是谁?" +query = Message( + role_name="user", role_type="human", + role_content=query_content, input_query=query_content, origin_query=query_content, + search_engine_name="duckduckgo", score_threshold=1.0, top_k=3 + ) + +output_message, _ = phase.step(query) + +# round-2 +history = Memory(messages=[ + Message(role_name="user", role_type="human", role_content="美国当前总统是谁?"), + Message(role_name="ai", role_type="assistant", role_content=output_message.step_content), + ]) + +query_content = "美国上一任总统是谁,两个人有什么关系没?" +query = Message( + role_name="human", role_type="user", + role_content=query_content, input_query=query_content, origin_query=query_content, + search_engine_name="duckduckgo", score_threshold=1.0, top_k=3 + ) +output_message, _ = phase.step(query) \ No newline at end of file diff --git a/examples/agent_examples/toolReactPhase_example.py b/examples/agent_examples/toolReactPhase_example.py new file mode 100644 index 0000000..00136d2 --- /dev/null +++ b/examples/agent_examples/toolReactPhase_example.py @@ -0,0 +1,53 @@ +import os, sys, requests + +src_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +sys.path.append(src_dir) + +from dev_opsgpt.tools import ( + toLangchainTools, get_tool_schema, DDGSTool, DocRetrieval, + TOOL_DICT, TOOL_SETS + ) + +from configs.model_config import * +from dev_opsgpt.connector.phase import BasePhase +from dev_opsgpt.connector.agents import BaseAgent +from dev_opsgpt.connector.chains import BaseChain +from dev_opsgpt.connector.schema import ( + Message, Memory, load_role_configs, load_phase_configs, load_chain_configs + ) +from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS +import importlib + + +# init configs +role_configs = load_role_configs(AGETN_CONFIGS) +chain_configs = load_chain_configs(CHAIN_CONFIGS) +phase_configs = load_phase_configs(PHASE_CONFIGS) + +agent_module = importlib.import_module("dev_opsgpt.connector.agents") + +# +phase_name = "toolReactPhase" +phase = BasePhase(phase_name, + task = None, + phase_config = PHASE_CONFIGS, + chain_config = CHAIN_CONFIGS, + role_config = AGETN_CONFIGS, + do_summary=False, + do_code_retrieval=False, + do_doc_retrieval=True, + do_search=False, + ) + +# round-1 +tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + +query_content = "帮我确认下127.0.0.1这个服务器的在10点是否存在异常,请帮我判断一下" +query = Message( + role_name="huma", role_type="user", tools=tools, + role_content=query_content, input_query=query_content, origin_query=query_content, + ) + +output_message, _ = phase.step(query) diff --git a/examples/docker_start.sh b/examples/docker_start.sh deleted file mode 100644 index 8526460..0000000 --- a/examples/docker_start.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -x - -CONTAINER_NAME=devopsgpt_default -IMAGES=devopsgpt:pypy3 -WORK_DIR=$PWD - -docker stop $CONTAINER_NAME -docker rm $CONTAINER_NAME -EXTERNAL_PORT=5050 - -# linux start -# docker run -it -p 5050:5050 --name $CONTAINER_NAME $IMAGES bash - -# windows start -winpty docker run -it -d -p $EXTERNAL_PORT:5050 --name $CONTAINER_NAME $IMAGES bash diff --git a/examples/start.py b/examples/start.py index cd6f52f..888dcca 100644 --- a/examples/start.py +++ b/examples/start.py @@ -12,7 +12,7 @@ from configs.model_config import USE_FASTCHAT, JUPYTER_WORK_PATH from configs.server_config import ( NO_REMOTE_API, SANDBOX_SERVER, SANDBOX_IMAGE_NAME, SANDBOX_CONTRAINER_NAME, WEBUI_SERVER, API_SERVER, SDFILE_API_SERVER, CONTRAINER_NAME, IMAGE_NAME, DOCKER_SERVICE, - DEFAULT_BIND_HOST, + DEFAULT_BIND_HOST, NEBULA_GRAPH_SERVER ) @@ -44,6 +44,18 @@ def check_docker(client, container_name, do_stop=False): if i.name == container_name: if do_stop: container = i + + if container_name == CONTRAINER_NAME and i.status == 'running': + # wrap up db + logger.info(f'inside {container_name}') + # cp nebula data + res = container.exec_run('''sh chatbot/dev_opsgpt/utils/nebula_cp.sh''') + logger.info(f'cp res={res}') + + # stop nebula service + res = container.exec_run('''/usr/local/nebula/scripts/nebula.service stop all''') + logger.info(f'stop res={res}') + container.stop() container.remove() return True @@ -56,6 +68,7 @@ def start_docker(client, script_shs, ports, image_name, container_name, mounts=N command="bash", mounts=mounts, name=container_name, + mem_limit="8g", # device_requests=[DeviceRequest(count=-1, capabilities=[['gpu']])], # network_mode="host", ports=ports, @@ -163,12 +176,23 @@ def start_api_service(sandbox_host=DEFAULT_BIND_HOST): f"{API_SERVER['docker_port']}/tcp": f"{API_SERVER['port']}/tcp", f"{WEBUI_SERVER['docker_port']}/tcp": f"{WEBUI_SERVER['port']}/tcp", f"{SDFILE_API_SERVER['docker_port']}/tcp": f"{SDFILE_API_SERVER['port']}/tcp", + f"{NEBULA_GRAPH_SERVER['docker_port']}/tcp": f"{NEBULA_GRAPH_SERVER['port']}/tcp" } mounts = [mount, mount_database, mount_code_database] script_shs = [ "mkdir -p /home/user/logs", - "pip install jsonref", - "pip install javalang", + ''' + if [ -d "/home/user/chatbot/data/nebula_data/data/meta" ]; then + cp -r /home/user/chatbot/data/nebula_data/data /usr/local/nebula/ + fi + ''', + "/usr/local/nebula/scripts/nebula.service start all", + "/usr/local/nebula/scripts/nebula.service status all", + "sleep 2", + '''curl -X PUT -H "Content-Type: application/json" -d'{"heartbeat_interval_secs":"2"}' -s "http://127.0.0.1:19559/flags"''', + '''curl -X PUT -H "Content-Type: application/json" -d'{"heartbeat_interval_secs":"2"}' -s "http://127.0.0.1:19669/flags"''', + '''curl -X PUT -H "Content-Type: application/json" -d'{"heartbeat_interval_secs":"2"}' -s "http://127.0.0.1:19779/flags"''', + "nohup python chatbot/dev_opsgpt/service/sdfile_api.py > /home/user/logs/sdfile_api.log 2>&1 &", f"export DUCKDUCKGO_PROXY=socks5://host.docker.internal:13659 && export SANDBOX_HOST={sandbox_host} &&\ nohup python chatbot/dev_opsgpt/service/api.py > /home/user/logs/api.log 2>&1 &", @@ -209,15 +233,16 @@ def start_api_service(sandbox_host=DEFAULT_BIND_HOST): if __name__ == "__main__": start_sandbox_service() + sandbox_host = DEFAULT_BIND_HOST if SANDBOX_SERVER["do_remote"]: client = docker.from_env() containers = client.containers.list(all=True) - sandbox_host = DEFAULT_BIND_HOST for container in containers: container_a_info = client.containers.get(container.id) if container_a_info.name == SANDBOX_CONTRAINER_NAME: container1_networks = container.attrs['NetworkSettings']['Networks'] sandbox_host = container1_networks.get(network_name)["IPAddress"] break - start_api_service() + start_api_service(sandbox_host) + diff --git a/examples/start_sandbox.py b/examples/start_sandbox.py deleted file mode 100644 index 4ba53ff..0000000 --- a/examples/start_sandbox.py +++ /dev/null @@ -1,49 +0,0 @@ -import docker, sys, os, time, requests - -from loguru import logger - -src_dir = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -) -sys.path.append(src_dir) - -from configs.server_config import SANDBOX_SERVER, SANDBOX_IMAGE_NAME, SANDBOX_CONTRAINER_NAME - - -if SANDBOX_SERVER["do_remote"]: - client = docker.from_env() - for i in client.containers.list(all=True): - if i.name == SANDBOX_CONTRAINER_NAME: - container = i - container.stop() - container.remove() - break - # 启动容器 - logger.info("start ot init container & notebook") - container = client.containers.run( - image=SANDBOX_IMAGE_NAME, - command="bash", - name=SANDBOX_CONTRAINER_NAME, - ports={f"{SANDBOX_SERVER['docker_port']}/tcp": SANDBOX_SERVER["port"]}, - stdin_open=True, - detach=True, - tty=True, - ) - - # 启动notebook - exec_command = container.exec_run("bash jupyter_start.sh") - - # 判断notebook是否启动 - retry_nums = 3 - while retry_nums>0: - response = requests.get(f"http://localhost:{SANDBOX_SERVER['port']}", timeout=270) - if response.status_code == 200: - logger.info("container & notebook init success") - break - else: - retry_nums -= 1 - logger.info(client.containers.list()) - logger.info("wait container running ...") - time.sleep(5) -else: - logger.info("启动local的notebook环境支持代码执行") diff --git a/examples/start_service_docker.py b/examples/start_service_docker.py deleted file mode 100644 index 017ff85..0000000 --- a/examples/start_service_docker.py +++ /dev/null @@ -1,68 +0,0 @@ -import docker, sys, os, time, requests -from docker.types import Mount - -from loguru import logger - -src_dir = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -) -sys.path.append(src_dir) - -from configs.server_config import WEBUI_SERVER, API_SERVER, SDFILE_API_SERVER, CONTRAINER_NAME, IMAGE_NAME -from configs.model_config import USE_FASTCHAT - - - -logger.info(f"IMAGE_NAME: {IMAGE_NAME}, CONTRAINER_NAME: {CONTRAINER_NAME}, ") - - -client = docker.from_env() -for i in client.containers.list(all=True): - if i.name == CONTRAINER_NAME: - container = i - container.stop() - container.remove() - break - - - -# 启动容器 -logger.info("start service") - -mount = Mount( - type='bind', - source=src_dir, - target='/home/user/chatbot/', - read_only=True # 如果需要只读访问,将此选项设置为True -) - -container = client.containers.run( - image=IMAGE_NAME, - command="bash", - mounts=[mount], - name=CONTRAINER_NAME, - ports={ - f"{WEBUI_SERVER['docker_port']}/tcp": API_SERVER['port'], - f"{API_SERVER['docker_port']}/tcp": WEBUI_SERVER['port'], - f"{SDFILE_API_SERVER['docker_port']}/tcp": SDFILE_API_SERVER['port'], - }, - stdin_open=True, - detach=True, - tty=True, -) - -# 启动notebook -exec_command = container.exec_run("bash jupyter_start.sh") -# -exec_command = container.exec_run("cd /homse/user/chatbot && nohup python devops_gpt/service/sdfile_api.py > /homse/user/logs/sdfile_api.log &") -# -exec_command = container.exec_run("cd /homse/user/chatbot && nohup python devops_gpt/service/api.py > /homse/user/logs/api.log &") - -if USE_FASTCHAT: - # 启动fastchat的服务 - exec_command = container.exec_run("cd /homse/user/chatbot && nohup python devops_gpt/service/llm_api.py > /homse/user/logs/llm_api.log &") -# -exec_command = container.exec_run("cd /homse/user/chatbot/examples && nohup bash start_webui.sh > /homse/user/logs/start_webui.log &") - - - diff --git a/examples/start_webui.sh b/examples/start_webui.sh deleted file mode 100644 index 1e06295..0000000 --- a/examples/start_webui.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - - -set -e - -# python ../dev_opsgpt/service/llm_api.py - -# 启动独立的沙箱环境 -python start_sandbox.py - -# python ../dev_opsgpt/service/llm_api.py -streamlit run webui.py diff --git a/examples/stop.py b/examples/stop.py index c87991d..9d1c495 100644 --- a/examples/stop.py +++ b/examples/stop.py @@ -10,7 +10,6 @@ from configs.server_config import ( SANDBOX_CONTRAINER_NAME, CONTRAINER_NAME, SANDBOX_SERVER, DOCKER_SERVICE ) - from start import check_docker, check_process try: diff --git a/examples/stop_sandbox.py b/examples/stop_sandbox.py deleted file mode 100644 index e243f8b..0000000 --- a/examples/stop_sandbox.py +++ /dev/null @@ -1,32 +0,0 @@ -import docker, sys, os, time, requests - -from loguru import logger - -src_dir = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -) -sys.path.append(src_dir) - -from configs.server_config import CONTRAINER_NAME, SANDBOX_SERVER - - -if SANDBOX_SERVER["do_remote"]: - # stop and remove the container - client = docker.from_env() - for i in client.containers.list(all=True): - if i.name == CONTRAINER_NAME: - container = i - container.stop() - container.remove() - break -else: - # stop local - import psutil - for process in psutil.process_iter(["pid", "name", "cmdline"]): - # check process name contains "jupyter" and port=xx - if f"port={SANDBOX_SERVER['port']}" in str(process.info["cmdline"]).lower() and \ - "jupyter" in process.info['name'].lower(): - - logger.warning(f"port={SANDBOX_SERVER['port']}, {process.info}") - # 关闭进程 - process.terminate() diff --git a/examples/stop_webui.sh b/examples/stop_webui.sh deleted file mode 100644 index 60a5ff8..0000000 --- a/examples/stop_webui.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# stop sandbox -python stop_sandbox.py diff --git a/requirements.txt b/requirements.txt index 8c5213e..168a06e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,3 +42,6 @@ streamlit-aggrid>=0.3.4.post3 httpx~=0.24.1 javalang==0.13.0 +jsonref==1.1.0 +chromadb==0.4.17 +nebula3-python==3.1.0 diff --git a/sources/docs_imgs/devops-chatbot-module-v2.png b/sources/docs_imgs/devops-chatbot-module-v2.png new file mode 100644 index 0000000..b905e86 Binary files /dev/null and b/sources/docs_imgs/devops-chatbot-module-v2.png differ diff --git a/sources/tool_datas/stock.json b/sources/tool_datas/stock.json new file mode 100644 index 0000000..50f755b --- /dev/null +++ b/sources/tool_datas/stock.json @@ -0,0 +1 @@ +[{"dm":"603062","mc":"N麦加","jys":"sh"},{"dm":"300507","mc":"苏奥传感","jys":"sz"},{"dm":"301202","mc":"朗威股份","jys":"sz"},{"dm":"300951","mc":"博硕科技","jys":"sz"},{"dm":"300608","mc":"思特奇","jys":"sz"},{"dm":"300063","mc":"天龙集团","jys":"sz"},{"dm":"300502","mc":"新易盛","jys":"sz"},{"dm":"300210","mc":"森远股份","jys":"sz"},{"dm":"300364","mc":"中文在线","jys":"sz"},{"dm":"688577","mc":"浙海德曼","jys":"sh"},{"dm":"300913","mc":"兆龙互连","jys":"sz"},{"dm":"300522","mc":"世名科技","jys":"sz"},{"dm":"301312","mc":"智立方","jys":"sz"},{"dm":"300480","mc":"光力科技","jys":"sz"},{"dm":"301191","mc":"菲菱科思","jys":"sz"},{"dm":"300551","mc":"古鳌科技","jys":"sz"},{"dm":"688629","mc":"华丰科技","jys":"sh"},{"dm":"688048","mc":"长光华芯","jys":"sh"},{"dm":"300883","mc":"龙利得","jys":"sz"},{"dm":"688031","mc":"星环科技-U","jys":"sh"},{"dm":"300684","mc":"中石科技","jys":"sz"},{"dm":"300570","mc":"太辰光","jys":"sz"},{"dm":"300694","mc":"蠡湖股份","jys":"sz"},{"dm":"000677","mc":"恒天海龙","jys":"sz"},{"dm":"601901","mc":"方正证券","jys":"sh"},{"dm":"002238","mc":"天威视讯","jys":"sz"},{"dm":"000829","mc":"天音控股","jys":"sz"},{"dm":"002712","mc":"思美传媒","jys":"sz"},{"dm":"600892","mc":"大晟文化","jys":"sh"},{"dm":"600592","mc":"龙溪股份","jys":"sh"},{"dm":"000056","mc":"皇庭国际","jys":"sz"},{"dm":"600630","mc":"龙头股份","jys":"sh"},{"dm":"603050","mc":"科林电气","jys":"sh"},{"dm":"603726","mc":"朗迪集团","jys":"sh"},{"dm":"600360","mc":"华微电子","jys":"sh"},{"dm":"002055","mc":"得润电子","jys":"sz"},{"dm":"001238","mc":"浙江正特","jys":"sz"},{"dm":"603729","mc":"龙韵股份","jys":"sh"},{"dm":"688328","mc":"深科达","jys":"sh"},{"dm":"603305","mc":"旭升集团","jys":"sh"},{"dm":"002222","mc":"福晶科技","jys":"sz"},{"dm":"603220","mc":"中贝通信","jys":"sh"},{"dm":"002615","mc":"哈尔斯","jys":"sz"},{"dm":"002889","mc":"东方嘉盛","jys":"sz"},{"dm":"000628","mc":"高新发展","jys":"sz"},{"dm":"600520","mc":"文一科技","jys":"sh"},{"dm":"603380","mc":"易德龙","jys":"sh"},{"dm":"605218","mc":"伟时电子","jys":"sh"},{"dm":"600692","mc":"亚通股份","jys":"sh"},{"dm":"600222","mc":"太龙药业","jys":"sh"},{"dm":"002981","mc":"朝阳科技","jys":"sz"},{"dm":"002692","mc":"远程股份","jys":"sz"},{"dm":"002313","mc":"日海智能","jys":"sz"},{"dm":"000026","mc":"飞亚达","jys":"sz"},{"dm":"603985","mc":"恒润股份","jys":"sh"},{"dm":"002229","mc":"鸿博股份","jys":"sz"},{"dm":"603266","mc":"天龙股份","jys":"sh"},{"dm":"000815","mc":"美利云","jys":"sz"},{"dm":"002988","mc":"豪美新材","jys":"sz"},{"dm":"600679","mc":"上海凤凰","jys":"sh"},{"dm":"002931","mc":"锋龙股份","jys":"sz"},{"dm":"603037","mc":"凯众股份","jys":"sh"},{"dm":"000828","mc":"东莞控股","jys":"sz"},{"dm":"605255","mc":"天普股份","jys":"sh"},{"dm":"603667","mc":"五洲新春","jys":"sh"},{"dm":"003015","mc":"日久光电","jys":"sz"},{"dm":"603003","mc":"龙宇股份","jys":"sh"},{"dm":"603390","mc":"通达电气","jys":"sh"},{"dm":"600816","mc":"建元信托","jys":"sh"},{"dm":"600331","mc":"宏达股份","jys":"sh"},{"dm":"002682","mc":"龙洲股份","jys":"sz"},{"dm":"688010","mc":"福光股份","jys":"sh"},{"dm":"300629","mc":"新劲刚","jys":"sz"},{"dm":"605577","mc":"龙版传媒","jys":"sh"},{"dm":"300264","mc":"佳创视讯","jys":"sz"},{"dm":"300475","mc":"香农芯创","jys":"sz"},{"dm":"688498","mc":"源杰科技","jys":"sh"},{"dm":"600131","mc":"国网信通","jys":"sh"},{"dm":"300757","mc":"罗博特科","jys":"sz"},{"dm":"300308","mc":"中际旭创","jys":"sz"},{"dm":"300657","mc":"弘信电子","jys":"sz"},{"dm":"300088","mc":"长信科技","jys":"sz"},{"dm":"300807","mc":"天迈科技","jys":"sz"},{"dm":"600133","mc":"东湖高新","jys":"sh"},{"dm":"688662","mc":"富信科技","jys":"sh"},{"dm":"002993","mc":"奥海科技","jys":"sz"},{"dm":"300701","mc":"森霸传感","jys":"sz"},{"dm":"688593","mc":"新相微","jys":"sh"},{"dm":"300620","mc":"光库科技","jys":"sz"},{"dm":"300272","mc":"开能健康","jys":"sz"},{"dm":"300820","mc":"英杰电气","jys":"sz"},{"dm":"300846","mc":"首都在线","jys":"sz"},{"dm":"301085","mc":"亚康股份","jys":"sz"},{"dm":"300523","mc":"辰安科技","jys":"sz"},{"dm":"300394","mc":"天孚通信","jys":"sz"},{"dm":"603353","mc":"和顺石油","jys":"sh"},{"dm":"688066","mc":"航天宏图","jys":"sh"},{"dm":"688313","mc":"仕佳光子","jys":"sh"},{"dm":"688115","mc":"思林杰","jys":"sh"},{"dm":"688141","mc":"杰华特","jys":"sh"},{"dm":"300976","mc":"达瑞电子","jys":"sz"},{"dm":"301387","mc":"光大同创","jys":"sz"},{"dm":"603629","mc":"利通电子","jys":"sh"},{"dm":"301360","mc":"荣旗科技","jys":"sz"},{"dm":"002929","mc":"润建股份","jys":"sz"},{"dm":"300211","mc":"亿通科技","jys":"sz"},{"dm":"300781","mc":"因赛集团","jys":"sz"},{"dm":"300293","mc":"蓝英装备","jys":"sz"},{"dm":"301489","mc":"思泉新材","jys":"sz"},{"dm":"300798","mc":"锦鸡股份","jys":"sz"},{"dm":"600159","mc":"大龙地产","jys":"sh"},{"dm":"002515","mc":"金字火腿","jys":"sz"},{"dm":"300101","mc":"振芯科技","jys":"sz"},{"dm":"301486","mc":"致尚科技","jys":"sz"},{"dm":"000925","mc":"众合科技","jys":"sz"},{"dm":"300032","mc":"金龙机电","jys":"sz"},{"dm":"301389","mc":"隆扬电子","jys":"sz"},{"dm":"603496","mc":"恒为科技","jys":"sh"},{"dm":"300691","mc":"联合光电","jys":"sz"},{"dm":"002281","mc":"光迅科技","jys":"sz"},{"dm":"601599","mc":"浙文影业","jys":"sh"},{"dm":"688167","mc":"炬光科技","jys":"sh"},{"dm":"301421","mc":"波长光电","jys":"sz"},{"dm":"002902","mc":"铭普光磁","jys":"sz"},{"dm":"603779","mc":"威龙股份","jys":"sh"},{"dm":"300678","mc":"中科信息","jys":"sz"},{"dm":"300912","mc":"凯龙高科","jys":"sz"},{"dm":"600071","mc":"凤凰光学","jys":"sh"},{"dm":"301262","mc":"海看股份","jys":"sz"},{"dm":"601595","mc":"上海电影","jys":"sh"},{"dm":"688668","mc":"鼎通科技","jys":"sh"},{"dm":"300603","mc":"立昂技术","jys":"sz"},{"dm":"688280","mc":"精进电动-UW","jys":"sh"},{"dm":"300025","mc":"华星创业","jys":"sz"},{"dm":"603626","mc":"科森科技","jys":"sh"},{"dm":"688311","mc":"盟升电子","jys":"sh"},{"dm":"688159","mc":"有方科技","jys":"sh"},{"dm":"300939","mc":"秋田微","jys":"sz"},{"dm":"605086","mc":"龙高股份","jys":"sh"},{"dm":"301205","mc":"联特科技","jys":"sz"},{"dm":"002771","mc":"真视通","jys":"sz"},{"dm":"688039","mc":"当虹科技","jys":"sh"},{"dm":"688249","mc":"晶合集成","jys":"sh"},{"dm":"688683","mc":"莱尔科技","jys":"sh"},{"dm":"301015","mc":"百洋医药","jys":"sz"},{"dm":"300742","mc":"*ST越博","jys":"sz"},{"dm":"603322","mc":"超讯通信","jys":"sh"},{"dm":"300752","mc":"隆利科技","jys":"sz"},{"dm":"301297","mc":"富乐德","jys":"sz"},{"dm":"300878","mc":"维康药业","jys":"sz"},{"dm":"300400","mc":"劲拓股份","jys":"sz"},{"dm":"688337","mc":"普源精电","jys":"sh"},{"dm":"600552","mc":"凯盛科技","jys":"sh"},{"dm":"603912","mc":"佳力图","jys":"sh"},{"dm":"301321","mc":"翰博高新","jys":"sz"},{"dm":"600211","mc":"西藏药业","jys":"sh"},{"dm":"300631","mc":"久吾高科","jys":"sz"},{"dm":"002876","mc":"三利谱","jys":"sz"},{"dm":"301298","mc":"东利机械","jys":"sz"},{"dm":"603863","mc":"松炀资源","jys":"sh"},{"dm":"601059","mc":"信达证券","jys":"sh"},{"dm":"301128","mc":"强瑞技术","jys":"sz"},{"dm":"002750","mc":"龙津药业","jys":"sz"},{"dm":"688105","mc":"诺唯赞","jys":"sh"},{"dm":"688343","mc":"云天励飞-U","jys":"sh"},{"dm":"002609","mc":"捷顺科技","jys":"sz"},{"dm":"688256","mc":"寒武纪-U","jys":"sh"},{"dm":"600335","mc":"国机汽车","jys":"sh"},{"dm":"603078","mc":"江化微","jys":"sh"},{"dm":"301183","mc":"东田微","jys":"sz"},{"dm":"688097","mc":"博众精工","jys":"sh"},{"dm":"002776","mc":"*ST柏龙","jys":"sz"},{"dm":"301165","mc":"锐捷网络","jys":"sz"},{"dm":"600200","mc":"江苏吴中","jys":"sh"},{"dm":"600775","mc":"南京熊猫","jys":"sh"},{"dm":"002437","mc":"誉衡药业","jys":"sz"},{"dm":"600589","mc":"*ST榕泰","jys":"sh"},{"dm":"600265","mc":"ST景谷","jys":"sh"},{"dm":"603906","mc":"龙蟠科技","jys":"sh"},{"dm":"600326","mc":"西藏天路","jys":"sh"},{"dm":"301052","mc":"果麦文化","jys":"sz"},{"dm":"300844","mc":"山水比德","jys":"sz"},{"dm":"603186","mc":"华正新材","jys":"sh"},{"dm":"000046","mc":"*ST泛海","jys":"sz"},{"dm":"002251","mc":"*ST步高","jys":"sz"},{"dm":"002592","mc":"ST八菱","jys":"sz"},{"dm":"603603","mc":"*ST博天","jys":"sh"},{"dm":"300548","mc":"博创科技","jys":"sz"},{"dm":"301139","mc":"元道通信","jys":"sz"},{"dm":"002587","mc":"奥拓电子","jys":"sz"},{"dm":"688316","mc":"青云科技-U","jys":"sh"},{"dm":"300366","mc":"创意信息","jys":"sz"},{"dm":"300105","mc":"龙源技术","jys":"sz"},{"dm":"300506","mc":"名家汇","jys":"sz"},{"dm":"603083","mc":"剑桥科技","jys":"sh"},{"dm":"688409","mc":"富创精密","jys":"sh"},{"dm":"688698","mc":"伟创电气","jys":"sh"},{"dm":"002300","mc":"太阳电缆","jys":"sz"},{"dm":"600338","mc":"西藏珠峰","jys":"sh"},{"dm":"000068","mc":"华控赛格","jys":"sz"},{"dm":"603286","mc":"日盈电子","jys":"sh"},{"dm":"688258","mc":"卓易信息","jys":"sh"},{"dm":"300766","mc":"每日互动","jys":"sz"},{"dm":"300010","mc":"*ST豆神","jys":"sz"},{"dm":"688468","mc":"科美诊断","jys":"sh"},{"dm":"002409","mc":"雅克科技","jys":"sz"},{"dm":"600716","mc":"凤凰股份","jys":"sh"},{"dm":"600418","mc":"江淮汽车","jys":"sh"},{"dm":"002536","mc":"飞龙股份","jys":"sz"},{"dm":"688279","mc":"峰岹科技","jys":"sh"},{"dm":"300528","mc":"幸福蓝海","jys":"sz"},{"dm":"688716","mc":"中研股份","jys":"sh"},{"dm":"603466","mc":"风语筑","jys":"sh"},{"dm":"603089","mc":"正裕工业","jys":"sh"},{"dm":"600804","mc":"ST鹏博士","jys":"sh"},{"dm":"000593","mc":"德龙汇能","jys":"sz"},{"dm":"002998","mc":"优彩资源","jys":"sz"},{"dm":"688025","mc":"杰普特","jys":"sh"},{"dm":"300707","mc":"威唐工业","jys":"sz"},{"dm":"000520","mc":"凤凰航运","jys":"sz"},{"dm":"300808","mc":"久量股份","jys":"sz"},{"dm":"603238","mc":"诺邦股份","jys":"sh"},{"dm":"300841","mc":"康华生物","jys":"sz"},{"dm":"002122","mc":"汇洲智能","jys":"sz"},{"dm":"301503","mc":"智迪科技","jys":"sz"},{"dm":"600895","mc":"张江高科","jys":"sh"},{"dm":"603002","mc":"宏昌电子","jys":"sh"},{"dm":"603969","mc":"银龙股份","jys":"sh"},{"dm":"301391","mc":"卡莱特","jys":"sz"},{"dm":"605289","mc":"罗曼股份","jys":"sh"},{"dm":"300650","mc":"太龙股份","jys":"sz"},{"dm":"301313","mc":"凡拓数创","jys":"sz"},{"dm":"301013","mc":"利和兴","jys":"sz"},{"dm":"300092","mc":"科新机电","jys":"sz"},{"dm":"688456","mc":"有研粉材","jys":"sh"},{"dm":"688170","mc":"德龙激光","jys":"sh"},{"dm":"605188","mc":"国光连锁","jys":"sh"},{"dm":"300327","mc":"中颖电子","jys":"sz"},{"dm":"300379","mc":"东方通","jys":"sz"},{"dm":"603615","mc":"茶花股份","jys":"sh"},{"dm":"301070","mc":"开勒股份","jys":"sz"},{"dm":"600807","mc":"济南高新","jys":"sh"},{"dm":"002827","mc":"高争民爆","jys":"sz"},{"dm":"002802","mc":"洪汇新材","jys":"sz"},{"dm":"688182","mc":"灿勤科技","jys":"sh"},{"dm":"600493","mc":"凤竹纺织","jys":"sh"},{"dm":"688521","mc":"芯原股份","jys":"sh"},{"dm":"002845","mc":"同兴达","jys":"sz"},{"dm":"001229","mc":"魅视科技","jys":"sz"},{"dm":"300304","mc":"云意电气","jys":"sz"},{"dm":"300256","mc":"星星科技","jys":"sz"},{"dm":"300499","mc":"高澜股份","jys":"sz"},{"dm":"000034","mc":"神州数码","jys":"sz"},{"dm":"688702","mc":"盛科通信-U","jys":"sh"},{"dm":"300588","mc":"熙菱信息","jys":"sz"},{"dm":"000058","mc":"深 赛 格","jys":"sz"},{"dm":"300472","mc":"新元科技","jys":"sz"},{"dm":"300552","mc":"万集科技","jys":"sz"},{"dm":"300302","mc":"同有科技","jys":"sz"},{"dm":"002338","mc":"奥普光电","jys":"sz"},{"dm":"002657","mc":"中科金财","jys":"sz"},{"dm":"000550","mc":"江铃汽车","jys":"sz"},{"dm":"300835","mc":"龙磁科技","jys":"sz"},{"dm":"688007","mc":"光峰科技","jys":"sh"},{"dm":"600602","mc":"云赛智联","jys":"sh"},{"dm":"688218","mc":"江苏北人","jys":"sh"},{"dm":"002106","mc":"莱宝高科","jys":"sz"},{"dm":"300442","mc":"润泽科技","jys":"sz"},{"dm":"002564","mc":"*ST天沃","jys":"sz"},{"dm":"002432","mc":"九安医疗","jys":"sz"},{"dm":"300870","mc":"欧陆通","jys":"sz"},{"dm":"601886","mc":"江河集团","jys":"sh"},{"dm":"002476","mc":"宝莫股份","jys":"sz"},{"dm":"688502","mc":"茂莱光学","jys":"sh"},{"dm":"300236","mc":"上海新阳","jys":"sz"},{"dm":"600987","mc":"航民股份","jys":"sh"},{"dm":"000625","mc":"长安汽车","jys":"sz"},{"dm":"002548","mc":"金新农","jys":"sz"},{"dm":"301185","mc":"鸥玛软件","jys":"sz"},{"dm":"000045","mc":"深纺织A","jys":"sz"},{"dm":"300331","mc":"苏大维格","jys":"sz"},{"dm":"603042","mc":"华脉科技","jys":"sh"},{"dm":"301268","mc":"铭利达","jys":"sz"},{"dm":"002322","mc":"理工能科","jys":"sz"},{"dm":"600449","mc":"宁夏建材","jys":"sh"},{"dm":"688111","mc":"金山办公","jys":"sh"},{"dm":"002217","mc":"合力泰","jys":"sz"},{"dm":"688426","mc":"康为世纪","jys":"sh"},{"dm":"300589","mc":"江龙船艇","jys":"sz"},{"dm":"688418","mc":"震有科技","jys":"sh"},{"dm":"300533","mc":"冰川网络","jys":"sz"},{"dm":"002947","mc":"恒铭达","jys":"sz"},{"dm":"688071","mc":"华依科技","jys":"sh"},{"dm":"002847","mc":"盐津铺子","jys":"sz"},{"dm":"600619","mc":"海立股份","jys":"sh"},{"dm":"000988","mc":"华工科技","jys":"sz"},{"dm":"600839","mc":"四川长虹","jys":"sh"},{"dm":"688205","mc":"德科立","jys":"sh"},{"dm":"300555","mc":"ST路通","jys":"sz"},{"dm":"300843","mc":"胜蓝股份","jys":"sz"},{"dm":"603203","mc":"快克智能","jys":"sh"},{"dm":"300921","mc":"南凌科技","jys":"sz"},{"dm":"603005","mc":"晶方科技","jys":"sh"},{"dm":"688501","mc":"青达环保","jys":"sh"},{"dm":"000050","mc":"深天马A","jys":"sz"},{"dm":"300780","mc":"德恩精工","jys":"sz"},{"dm":"600666","mc":"ST瑞德","jys":"sh"},{"dm":"301131","mc":"聚赛龙","jys":"sz"},{"dm":"300135","mc":"宝利国际","jys":"sz"},{"dm":"002152","mc":"广电运通","jys":"sz"},{"dm":"002602","mc":"世纪华通","jys":"sz"},{"dm":"601001","mc":"晋控煤业","jys":"sh"},{"dm":"688078","mc":"龙软科技","jys":"sh"},{"dm":"300218","mc":"安利股份","jys":"sz"},{"dm":"000712","mc":"锦龙股份","jys":"sz"},{"dm":"300654","mc":"世纪天鸿","jys":"sz"},{"dm":"000809","mc":"铁岭新城","jys":"sz"},{"dm":"688055","mc":"龙腾光电","jys":"sh"},{"dm":"300337","mc":"银邦股份","jys":"sz"},{"dm":"300687","mc":"赛意信息","jys":"sz"},{"dm":"601099","mc":"太平洋","jys":"sh"},{"dm":"001380","mc":"华纬科技","jys":"sz"},{"dm":"301383","mc":"天键股份","jys":"sz"},{"dm":"688609","mc":"九联科技","jys":"sh"},{"dm":"688486","mc":"龙迅股份","jys":"sh"},{"dm":"002428","mc":"云南锗业","jys":"sz"},{"dm":"688515","mc":"裕太微-U","jys":"sh"},{"dm":"600853","mc":"龙建股份","jys":"sh"},{"dm":"300128","mc":"锦富技术","jys":"sz"},{"dm":"688229","mc":"博睿数据","jys":"sh"},{"dm":"688047","mc":"龙芯中科","jys":"sh"},{"dm":"688158","mc":"优刻得-W","jys":"sh"},{"dm":"603283","mc":"赛腾股份","jys":"sh"},{"dm":"603019","mc":"中科曙光","jys":"sh"},{"dm":"603918","mc":"金桥信息","jys":"sh"},{"dm":"000936","mc":"华西股份","jys":"sz"},{"dm":"002882","mc":"金龙羽","jys":"sz"},{"dm":"688183","mc":"生益电子","jys":"sh"},{"dm":"688143","mc":"长盈通","jys":"sh"},{"dm":"300637","mc":"扬帆新材","jys":"sz"},{"dm":"603605","mc":"珀莱雅","jys":"sh"},{"dm":"300179","mc":"四方达","jys":"sz"},{"dm":"300249","mc":"依米康","jys":"sz"},{"dm":"000009","mc":"中国宝安","jys":"sz"},{"dm":"301419","mc":"阿莱德","jys":"sz"},{"dm":"301110","mc":"青木股份","jys":"sz"},{"dm":"600410","mc":"华胜天成","jys":"sh"},{"dm":"688286","mc":"敏芯股份","jys":"sh"},{"dm":"001336","mc":"楚环科技","jys":"sz"},{"dm":"002456","mc":"欧菲光","jys":"sz"},{"dm":"002178","mc":"延华智能","jys":"sz"},{"dm":"000010","mc":"美丽生态","jys":"sz"},{"dm":"002261","mc":"拓维信息","jys":"sz"},{"dm":"002555","mc":"三七互娱","jys":"sz"},{"dm":"300909","mc":"汇创达","jys":"sz"},{"dm":"002855","mc":"捷荣技术","jys":"sz"},{"dm":"603332","mc":"苏州龙杰","jys":"sh"},{"dm":"300074","mc":"华平股份","jys":"sz"},{"dm":"688041","mc":"海光信息","jys":"sh"},{"dm":"002795","mc":"永和智控","jys":"sz"},{"dm":"002962","mc":"五方光电","jys":"sz"},{"dm":"001314","mc":"亿道信息","jys":"sz"},{"dm":"300166","mc":"东方国信","jys":"sz"},{"dm":"688687","mc":"凯因科技","jys":"sh"},{"dm":"002864","mc":"盘龙药业","jys":"sz"},{"dm":"603988","mc":"中电电机","jys":"sh"},{"dm":"301021","mc":"英诺激光","jys":"sz"},{"dm":"000889","mc":"ST中嘉","jys":"sz"},{"dm":"002760","mc":"凤形股份","jys":"sz"},{"dm":"300790","mc":"宇瞳光学","jys":"sz"},{"dm":"605008","mc":"长鸿高科","jys":"sh"},{"dm":"300397","mc":"天和防务","jys":"sz"},{"dm":"603970","mc":"中农立华","jys":"sh"},{"dm":"688639","mc":"华恒生物","jys":"sh"},{"dm":"300727","mc":"润禾材料","jys":"sz"},{"dm":"601028","mc":"玉龙股份","jys":"sh"},{"dm":"301151","mc":"冠龙节能","jys":"sz"},{"dm":"688298","mc":"东方生物","jys":"sh"},{"dm":"600100","mc":"同方股份","jys":"sh"},{"dm":"001288","mc":"运机集团","jys":"sz"},{"dm":"605588","mc":"冠石科技","jys":"sh"},{"dm":"300316","mc":"晶盛机电","jys":"sz"},{"dm":"600498","mc":"烽火通信","jys":"sh"},{"dm":"300279","mc":"和晶科技","jys":"sz"},{"dm":"688253","mc":"英诺特","jys":"sh"},{"dm":"688737","mc":"中自科技","jys":"sh"},{"dm":"300427","mc":"*ST红相","jys":"sz"},{"dm":"000955","mc":"欣龙控股","jys":"sz"},{"dm":"301558","mc":"三态股份","jys":"sz"},{"dm":"002785","mc":"万里石","jys":"sz"},{"dm":"603677","mc":"奇精机械","jys":"sh"},{"dm":"301218","mc":"华是科技","jys":"sz"},{"dm":"300822","mc":"贝仕达克","jys":"sz"},{"dm":"300819","mc":"聚杰微纤","jys":"sz"},{"dm":"688162","mc":"巨一科技","jys":"sh"},{"dm":"300667","mc":"必创科技","jys":"sz"},{"dm":"603922","mc":"金鸿顺","jys":"sh"},{"dm":"688798","mc":"艾为电子","jys":"sh"},{"dm":"301086","mc":"鸿富瀚","jys":"sz"},{"dm":"300496","mc":"中科创达","jys":"sz"},{"dm":"600975","mc":"新五丰","jys":"sh"},{"dm":"300007","mc":"汉威科技","jys":"sz"},{"dm":"300738","mc":"奥飞数据","jys":"sz"},{"dm":"000753","mc":"漳州发展","jys":"sz"},{"dm":"688027","mc":"国盾量子","jys":"sh"},{"dm":"301041","mc":"金百泽","jys":"sz"},{"dm":"300011","mc":"鼎汉技术","jys":"sz"},{"dm":"600353","mc":"旭光电子","jys":"sh"},{"dm":"300863","mc":"卡倍亿","jys":"sz"},{"dm":"600889","mc":"南京化纤","jys":"sh"},{"dm":"601138","mc":"工业富联","jys":"sh"},{"dm":"300708","mc":"聚灿光电","jys":"sz"},{"dm":"000021","mc":"深科技","jys":"sz"},{"dm":"300828","mc":"锐新科技","jys":"sz"},{"dm":"002892","mc":"科力尔","jys":"sz"},{"dm":"603579","mc":"荣泰健康","jys":"sh"},{"dm":"300160","mc":"秀强股份","jys":"sz"},{"dm":"300521","mc":"爱司凯","jys":"sz"},{"dm":"002912","mc":"中新赛克","jys":"sz"},{"dm":"002291","mc":"遥望科技","jys":"sz"},{"dm":"300956","mc":"英力股份","jys":"sz"},{"dm":"000096","mc":"广聚能源","jys":"sz"},{"dm":"002885","mc":"京泉华","jys":"sz"},{"dm":"605128","mc":"上海沿浦","jys":"sh"},{"dm":"300576","mc":"容大感光","jys":"sz"},{"dm":"300383","mc":"光环新网","jys":"sz"},{"dm":"300001","mc":"特锐德","jys":"sz"},{"dm":"300029","mc":"ST天龙","jys":"sz"},{"dm":"688525","mc":"佰维存储","jys":"sh"},{"dm":"600491","mc":"龙元建设","jys":"sh"},{"dm":"600766","mc":"*ST园城","jys":"sh"},{"dm":"300893","mc":"松原股份","jys":"sz"},{"dm":"603111","mc":"康尼机电","jys":"sh"},{"dm":"000509","mc":"华塑控股","jys":"sz"},{"dm":"688269","mc":"凯立新材","jys":"sh"},{"dm":"002279","mc":"久其软件","jys":"sz"},{"dm":"002387","mc":"维信诺","jys":"sz"},{"dm":"300994","mc":"久祺股份","jys":"sz"},{"dm":"000818","mc":"航锦科技","jys":"sz"},{"dm":"688129","mc":"东来技术","jys":"sh"},{"dm":"688114","mc":"华大智造","jys":"sh"},{"dm":"000570","mc":"苏常柴A","jys":"sz"},{"dm":"601456","mc":"国联证券","jys":"sh"},{"dm":"301338","mc":"凯格精机","jys":"sz"},{"dm":"688259","mc":"创耀科技","jys":"sh"},{"dm":"688606","mc":"奥泰生物","jys":"sh"},{"dm":"301036","mc":"双乐股份","jys":"sz"},{"dm":"300998","mc":"宁波方正","jys":"sz"},{"dm":"688578","mc":"艾力斯","jys":"sh"},{"dm":"688362","mc":"甬矽电子","jys":"sh"},{"dm":"003019","mc":"宸展光电","jys":"sz"},{"dm":"300992","mc":"泰福泵业","jys":"sz"},{"dm":"301396","mc":"宏景科技","jys":"sz"},{"dm":"600114","mc":"东睦股份","jys":"sh"},{"dm":"688520","mc":"神州细胞-U","jys":"sh"},{"dm":"002881","mc":"美格智能","jys":"sz"},{"dm":"603013","mc":"亚普股份","jys":"sh"},{"dm":"000637","mc":"ST实华","jys":"sz"},{"dm":"300941","mc":"创识科技","jys":"sz"},{"dm":"688387","mc":"信科移动-U","jys":"sh"},{"dm":"603398","mc":"沐邦高科","jys":"sh"},{"dm":"603598","mc":"引力传媒","jys":"sh"},{"dm":"600661","mc":"昂立教育","jys":"sh"},{"dm":"600237","mc":"铜峰电子","jys":"sh"},{"dm":"301169","mc":"零点有数","jys":"sz"},{"dm":"300597","mc":"吉大通信","jys":"sz"},{"dm":"002607","mc":"中公教育","jys":"sz"},{"dm":"300303","mc":"聚飞光电","jys":"sz"},{"dm":"002726","mc":"龙大美食","jys":"sz"},{"dm":"688095","mc":"福昕软件","jys":"sh"},{"dm":"300796","mc":"贝斯美","jys":"sz"},{"dm":"300799","mc":"*ST左江","jys":"sz"},{"dm":"300945","mc":"曼卡龙","jys":"sz"},{"dm":"002086","mc":"*ST东洋","jys":"sz"},{"dm":"688416","mc":"恒烁股份","jys":"sh"},{"dm":"600388","mc":"龙净环保","jys":"sh"},{"dm":"603160","mc":"汇顶科技","jys":"sh"},{"dm":"002199","mc":"东晶电子","jys":"sz"},{"dm":"000938","mc":"紫光股份","jys":"sz"},{"dm":"600867","mc":"通化东宝","jys":"sh"},{"dm":"301381","mc":"赛维时代","jys":"sz"},{"dm":"605050","mc":"福然德","jys":"sh"},{"dm":"000608","mc":"阳光股份","jys":"sz"},{"dm":"300711","mc":"广哈通信","jys":"sz"},{"dm":"688292","mc":"浩瀚深度","jys":"sh"},{"dm":"300429","mc":"强力新材","jys":"sz"},{"dm":"002228","mc":"合兴包装","jys":"sz"},{"dm":"688310","mc":"迈得医疗","jys":"sh"},{"dm":"002895","mc":"川恒股份","jys":"sz"},{"dm":"603887","mc":"城地香江","jys":"sh"},{"dm":"300255","mc":"常山药业","jys":"sz"},{"dm":"603648","mc":"畅联股份","jys":"sh"},{"dm":"300171","mc":"东富龙","jys":"sz"},{"dm":"300274","mc":"阳光电源","jys":"sz"},{"dm":"301133","mc":"金钟股份","jys":"sz"},{"dm":"603236","mc":"移远通信","jys":"sh"},{"dm":"300763","mc":"锦浪科技","jys":"sz"},{"dm":"003021","mc":"兆威机电","jys":"sz"},{"dm":"300076","mc":"GQY视讯","jys":"sz"},{"dm":"300643","mc":"万通智控","jys":"sz"},{"dm":"603118","mc":"共进股份","jys":"sh"},{"dm":"603773","mc":"沃格光电","jys":"sh"},{"dm":"000985","mc":"大庆华科","jys":"sz"},{"dm":"300098","mc":"高新兴","jys":"sz"},{"dm":"002797","mc":"第一创业","jys":"sz"},{"dm":"300736","mc":"百邦科技","jys":"sz"},{"dm":"300553","mc":"集智股份","jys":"sz"},{"dm":"002729","mc":"好利科技","jys":"sz"},{"dm":"300968","mc":"格林精密","jys":"sz"},{"dm":"002077","mc":"大港股份","jys":"sz"},{"dm":"300719","mc":"安达维尔","jys":"sz"},{"dm":"601555","mc":"东吴证券","jys":"sh"},{"dm":"600530","mc":"ST交昂","jys":"sh"},{"dm":"688496","mc":"清越科技","jys":"sh"},{"dm":"002725","mc":"跃岭股份","jys":"sz"},{"dm":"002312","mc":"川发龙蟒","jys":"sz"},{"dm":"300242","mc":"佳云科技","jys":"sz"},{"dm":"603725","mc":"天安新材","jys":"sh"},{"dm":"688195","mc":"腾景科技","jys":"sh"},{"dm":"300157","mc":"新锦动力","jys":"sz"},{"dm":"688273","mc":"麦澜德","jys":"sh"},{"dm":"600605","mc":"汇通能源","jys":"sh"},{"dm":"603713","mc":"密尔克卫","jys":"sh"},{"dm":"300131","mc":"英唐智控","jys":"sz"},{"dm":"000977","mc":"浪潮信息","jys":"sz"},{"dm":"600105","mc":"永鼎股份","jys":"sh"},{"dm":"603861","mc":"白云电器","jys":"sh"},{"dm":"002740","mc":"*ST爱迪","jys":"sz"},{"dm":"301326","mc":"捷邦科技","jys":"sz"},{"dm":"002674","mc":"兴业科技","jys":"sz"},{"dm":"003004","mc":"声迅股份","jys":"sz"},{"dm":"300193","mc":"佳士科技","jys":"sz"},{"dm":"300473","mc":"德尔股份","jys":"sz"},{"dm":"603306","mc":"华懋科技","jys":"sh"},{"dm":"688327","mc":"云从科技-UW","jys":"sh"},{"dm":"601666","mc":"平煤股份","jys":"sh"},{"dm":"300081","mc":"恒信东方","jys":"sz"},{"dm":"688300","mc":"联瑞新材","jys":"sh"},{"dm":"300177","mc":"中海达","jys":"sz"},{"dm":"002358","mc":"森源电气","jys":"sz"},{"dm":"300225","mc":"金力泰","jys":"sz"},{"dm":"300612","mc":"宣亚国际","jys":"sz"},{"dm":"600136","mc":"*ST明诚","jys":"sh"},{"dm":"002530","mc":"金财互联","jys":"sz"},{"dm":"688489","mc":"三未信安","jys":"sh"},{"dm":"603439","mc":"贵州三力","jys":"sh"},{"dm":"301004","mc":"嘉益股份","jys":"sz"},{"dm":"300721","mc":"怡达股份","jys":"sz"},{"dm":"301018","mc":"申菱环境","jys":"sz"},{"dm":"002463","mc":"沪电股份","jys":"sz"},{"dm":"000801","mc":"四川九洲","jys":"sz"},{"dm":"300585","mc":"奥联电子","jys":"sz"},{"dm":"301398","mc":"星源卓镁","jys":"sz"},{"dm":"002253","mc":"川大智胜","jys":"sz"},{"dm":"688079","mc":"美迪凯","jys":"sh"},{"dm":"000700","mc":"模塑科技","jys":"sz"},{"dm":"003007","mc":"直真科技","jys":"sz"},{"dm":"600173","mc":"卧龙地产","jys":"sh"},{"dm":"688102","mc":"斯瑞新材","jys":"sh"},{"dm":"301117","mc":"佳缘科技","jys":"sz"},{"dm":"002385","mc":"大北农","jys":"sz"},{"dm":"300825","mc":"阿尔特","jys":"sz"},{"dm":"002416","mc":"爱施德","jys":"sz"},{"dm":"002940","mc":"昂利康","jys":"sz"},{"dm":"000032","mc":"深桑达A","jys":"sz"},{"dm":"300812","mc":"易天股份","jys":"sz"},{"dm":"300592","mc":"华凯易佰","jys":"sz"},{"dm":"002197","mc":"证通电子","jys":"sz"},{"dm":"688382","mc":"益方生物-U","jys":"sh"},{"dm":"688787","mc":"海天瑞声","jys":"sh"},{"dm":"601188","mc":"龙江交通","jys":"sh"},{"dm":"300884","mc":"狄耐克","jys":"sz"},{"dm":"601788","mc":"光大证券","jys":"sh"},{"dm":"301348","mc":"蓝箭电子","jys":"sz"},{"dm":"605138","mc":"盛泰集团","jys":"sh"},{"dm":"688535","mc":"华海诚科","jys":"sh"},{"dm":"002748","mc":"世龙实业","jys":"sz"},{"dm":"600006","mc":"东风汽车","jys":"sh"},{"dm":"605058","mc":"澳弘电子","jys":"sh"},{"dm":"002008","mc":"大族激光","jys":"sz"},{"dm":"688296","mc":"和达科技","jys":"sh"},{"dm":"301159","mc":"三维天地","jys":"sz"},{"dm":"300560","mc":"中富通","jys":"sz"},{"dm":"603669","mc":"灵康药业","jys":"sh"},{"dm":"300426","mc":"唐德影视","jys":"sz"},{"dm":"603650","mc":"彤程新材","jys":"sh"},{"dm":"002490","mc":"山东墨龙","jys":"sz"},{"dm":"600426","mc":"华鲁恒升","jys":"sh"},{"dm":"000810","mc":"创维数字","jys":"sz"},{"dm":"600460","mc":"士兰微","jys":"sh"},{"dm":"605298","mc":"必得科技","jys":"sh"},{"dm":"600561","mc":"江西长运","jys":"sh"},{"dm":"002295","mc":"精艺股份","jys":"sz"},{"dm":"688020","mc":"方邦股份","jys":"sh"},{"dm":"603388","mc":"元成股份","jys":"sh"},{"dm":"688636","mc":"智明达","jys":"sh"},{"dm":"688588","mc":"凌志软件","jys":"sh"},{"dm":"688322","mc":"奥比中光-UW","jys":"sh"},{"dm":"688001","mc":"华兴源创","jys":"sh"},{"dm":"002491","mc":"通鼎互联","jys":"sz"},{"dm":"300964","mc":"本川智能","jys":"sz"},{"dm":"300556","mc":"丝路视觉","jys":"sz"},{"dm":"001339","mc":"智微智能","jys":"sz"},{"dm":"600256","mc":"广汇能源","jys":"sh"},{"dm":"605118","mc":"力鼎光电","jys":"sh"},{"dm":"600985","mc":"淮北矿业","jys":"sh"},{"dm":"002453","mc":"华软科技","jys":"sz"},{"dm":"300856","mc":"科思股份","jys":"sz"},{"dm":"300192","mc":"科德教育","jys":"sz"},{"dm":"688793","mc":"倍轻松","jys":"sh"},{"dm":"002331","mc":"皖通科技","jys":"sz"},{"dm":"000705","mc":"浙江震元","jys":"sz"},{"dm":"000980","mc":"众泰汽车","jys":"sz"},{"dm":"300141","mc":"和顺电气","jys":"sz"},{"dm":"000620","mc":"*ST新联","jys":"sz"},{"dm":"002137","mc":"实益达","jys":"sz"},{"dm":"000615","mc":"*ST美谷","jys":"sz"},{"dm":"603393","mc":"新天然气","jys":"sh"},{"dm":"603977","mc":"国泰集团","jys":"sh"},{"dm":"002769","mc":"普路通","jys":"sz"},{"dm":"002792","mc":"通宇通讯","jys":"sz"},{"dm":"688590","mc":"新致软件","jys":"sh"},{"dm":"002176","mc":"江特电机","jys":"sz"},{"dm":"002658","mc":"雪迪龙","jys":"sz"},{"dm":"605228","mc":"神通科技","jys":"sh"},{"dm":"600601","mc":"方正科技","jys":"sh"},{"dm":"300150","mc":"世纪瑞尔","jys":"sz"},{"dm":"301030","mc":"仕净科技","jys":"sz"},{"dm":"002134","mc":"天津普林","jys":"sz"},{"dm":"002467","mc":"二六三","jys":"sz"},{"dm":"301382","mc":"蜂助手","jys":"sz"},{"dm":"002377","mc":"国创高新","jys":"sz"},{"dm":"301252","mc":"同星科技","jys":"sz"},{"dm":"001260","mc":"坤泰股份","jys":"sz"},{"dm":"300895","mc":"铜牛信息","jys":"sz"},{"dm":"000820","mc":"神雾节能","jys":"sz"},{"dm":"603158","mc":"腾龙股份","jys":"sh"},{"dm":"300990","mc":"同飞股份","jys":"sz"},{"dm":"301357","mc":"北方长龙","jys":"sz"},{"dm":"600733","mc":"北汽蓝谷","jys":"sh"},{"dm":"688357","mc":"建龙微纳","jys":"sh"},{"dm":"300520","mc":"科大国创","jys":"sz"},{"dm":"000416","mc":"*ST民控","jys":"sz"},{"dm":"301172","mc":"君逸数码","jys":"sz"},{"dm":"002643","mc":"万润股份","jys":"sz"},{"dm":"605198","mc":"安德利","jys":"sh"},{"dm":"600983","mc":"惠而浦","jys":"sh"},{"dm":"000718","mc":"苏宁环球","jys":"sz"},{"dm":"600780","mc":"通宝能源","jys":"sh"},{"dm":"300455","mc":"航天智装","jys":"sz"},{"dm":"002190","mc":"成飞集成","jys":"sz"},{"dm":"600989","mc":"宝丰能源","jys":"sh"},{"dm":"002362","mc":"汉王科技","jys":"sz"},{"dm":"688112","mc":"鼎阳科技","jys":"sh"},{"dm":"002979","mc":"雷赛智能","jys":"sz"},{"dm":"601162","mc":"天风证券","jys":"sh"},{"dm":"002640","mc":"跨境通","jys":"sz"},{"dm":"002306","mc":"中科云网","jys":"sz"},{"dm":"603556","mc":"海兴电力","jys":"sh"},{"dm":"603297","mc":"永新光学","jys":"sh"},{"dm":"688696","mc":"极米科技","jys":"sh"},{"dm":"002442","mc":"龙星化工","jys":"sz"},{"dm":"000429","mc":"粤高速A","jys":"sz"},{"dm":"603966","mc":"法兰泰克","jys":"sh"},{"dm":"002488","mc":"金固股份","jys":"sz"},{"dm":"601236","mc":"红塔证券","jys":"sh"},{"dm":"601328","mc":"交通银行","jys":"sh"},{"dm":"300045","mc":"华力创通","jys":"sz"},{"dm":"301251","mc":"威尔高","jys":"sz"},{"dm":"688051","mc":"佳华科技","jys":"sh"},{"dm":"000039","mc":"中集集团","jys":"sz"},{"dm":"002843","mc":"泰嘉股份","jys":"sz"},{"dm":"300215","mc":"电科院","jys":"sz"},{"dm":"002410","mc":"广联达","jys":"sz"},{"dm":"002224","mc":"三 力 士","jys":"sz"},{"dm":"300802","mc":"矩子科技","jys":"sz"},{"dm":"000665","mc":"湖北广电","jys":"sz"},{"dm":"301512","mc":"智信精密","jys":"sz"},{"dm":"300221","mc":"银禧科技","jys":"sz"},{"dm":"002457","mc":"青龙管业","jys":"sz"},{"dm":"301529","mc":"福赛科技","jys":"sz"},{"dm":"301288","mc":"清研环境","jys":"sz"},{"dm":"300199","mc":"翰宇药业","jys":"sz"},{"dm":"603686","mc":"福龙马","jys":"sh"},{"dm":"605133","mc":"嵘泰股份","jys":"sh"},{"dm":"301181","mc":"标榜股份","jys":"sz"},{"dm":"300167","mc":"ST迪威迅","jys":"sz"},{"dm":"300730","mc":"科创信息","jys":"sz"},{"dm":"002454","mc":"松芝股份","jys":"sz"},{"dm":"301328","mc":"维峰电子","jys":"sz"},{"dm":"300659","mc":"中孚信息","jys":"sz"},{"dm":"600993","mc":"马应龙","jys":"sh"},{"dm":"688246","mc":"嘉和美康","jys":"sh"},{"dm":"603881","mc":"数据港","jys":"sh"},{"dm":"300614","mc":"百川畅银","jys":"sz"},{"dm":"600689","mc":"上海三毛","jys":"sh"},{"dm":"002073","mc":"软控股份","jys":"sz"},{"dm":"300344","mc":"立方数科","jys":"sz"},{"dm":"301129","mc":"瑞纳智能","jys":"sz"},{"dm":"600012","mc":"皖通高速","jys":"sh"},{"dm":"300333","mc":"兆日科技","jys":"sz"},{"dm":"603392","mc":"万泰生物","jys":"sh"},{"dm":"000638","mc":"万方发展","jys":"sz"},{"dm":"002357","mc":"富临运业","jys":"sz"},{"dm":"603086","mc":"先达股份","jys":"sh"},{"dm":"688776","mc":"国光电气","jys":"sh"},{"dm":"301372","mc":"科净源","jys":"sz"},{"dm":"301428","mc":"世纪恒通","jys":"sz"},{"dm":"300525","mc":"博思软件","jys":"sz"},{"dm":"603139","mc":"康惠制药","jys":"sh"},{"dm":"688628","mc":"优利德","jys":"sh"},{"dm":"300461","mc":"田中精机","jys":"sz"},{"dm":"300517","mc":"海波重科","jys":"sz"},{"dm":"300591","mc":"万里马","jys":"sz"},{"dm":"300241","mc":"瑞丰光电","jys":"sz"},{"dm":"688429","mc":"时创能源","jys":"sh"},{"dm":"688138","mc":"清溢光电","jys":"sh"},{"dm":"600290","mc":"*ST华仪","jys":"sh"},{"dm":"300084","mc":"海默科技","jys":"sz"},{"dm":"002632","mc":"道明光学","jys":"sz"},{"dm":"300547","mc":"川环科技","jys":"sz"},{"dm":"600615","mc":"丰华股份","jys":"sh"},{"dm":"002919","mc":"名臣健康","jys":"sz"},{"dm":"001965","mc":"招商公路","jys":"sz"},{"dm":"002268","mc":"电科网安","jys":"sz"},{"dm":"603289","mc":"泰瑞机器","jys":"sh"},{"dm":"300866","mc":"安克创新","jys":"sz"},{"dm":"300277","mc":"海联讯","jys":"sz"},{"dm":"688077","mc":"大地熊","jys":"sh"},{"dm":"603937","mc":"丽岛新材","jys":"sh"},{"dm":"003005","mc":"竞业达","jys":"sz"},{"dm":"688693","mc":"锴威特","jys":"sh"},{"dm":"600793","mc":"宜宾纸业","jys":"sh"},{"dm":"300229","mc":"拓尔思","jys":"sz"},{"dm":"688621","mc":"阳光诺和","jys":"sh"},{"dm":"001289","mc":"龙源电力","jys":"sz"},{"dm":"688076","mc":"诺泰生物","jys":"sh"},{"dm":"002777","mc":"久远银海","jys":"sz"},{"dm":"002811","mc":"郑中设计","jys":"sz"},{"dm":"601789","mc":"宁波建工","jys":"sh"},{"dm":"600841","mc":"动力新科","jys":"sh"},{"dm":"300120","mc":"经纬辉开","jys":"sz"},{"dm":"688766","mc":"普冉股份","jys":"sh"},{"dm":"002148","mc":"北纬科技","jys":"sz"},{"dm":"688099","mc":"晶晨股份","jys":"sh"},{"dm":"603059","mc":"倍加洁","jys":"sh"},{"dm":"600684","mc":"珠江股份","jys":"sh"},{"dm":"601699","mc":"潞安环能","jys":"sh"},{"dm":"688160","mc":"步科股份","jys":"sh"},{"dm":"002590","mc":"万安科技","jys":"sz"},{"dm":"603189","mc":"网达软件","jys":"sh"},{"dm":"001269","mc":"欧晶科技","jys":"sz"},{"dm":"000856","mc":"冀东装备","jys":"sz"},{"dm":"603230","mc":"内蒙新华","jys":"sh"},{"dm":"300061","mc":"旗天科技","jys":"sz"},{"dm":"301248","mc":"杰创智能","jys":"sz"},{"dm":"605318","mc":"法狮龙","jys":"sh"},{"dm":"300051","mc":"琏升科技","jys":"sz"},{"dm":"603052","mc":"可川科技","jys":"sh"},{"dm":"300723","mc":"一品红","jys":"sz"},{"dm":"603197","mc":"保隆科技","jys":"sh"},{"dm":"300445","mc":"康斯特","jys":"sz"},{"dm":"301068","mc":"大地海洋","jys":"sz"},{"dm":"300503","mc":"昊志机电","jys":"sz"},{"dm":"300334","mc":"津膜科技","jys":"sz"},{"dm":"002952","mc":"亚世光电","jys":"sz"},{"dm":"688181","mc":"八亿时空","jys":"sh"},{"dm":"301162","mc":"国能日新","jys":"sz"},{"dm":"300537","mc":"广信材料","jys":"sz"},{"dm":"002368","mc":"太极股份","jys":"sz"},{"dm":"603586","mc":"金麒麟","jys":"sh"},{"dm":"300601","mc":"康泰生物","jys":"sz"},{"dm":"603039","mc":"泛微网络","jys":"sh"},{"dm":"605020","mc":"永和股份","jys":"sh"},{"dm":"688373","mc":"盟科药业-U","jys":"sh"},{"dm":"301188","mc":"力诺特玻","jys":"sz"},{"dm":"603229","mc":"奥翔药业","jys":"sh"},{"dm":"301231","mc":"荣信文化","jys":"sz"},{"dm":"001296","mc":"长江材料","jys":"sz"},{"dm":"601198","mc":"东兴证券","jys":"sh"},{"dm":"002166","mc":"莱茵生物","jys":"sz"},{"dm":"603936","mc":"博敏电子","jys":"sh"},{"dm":"688579","mc":"山大地纬","jys":"sh"},{"dm":"300057","mc":"万顺新材","jys":"sz"},{"dm":"688045","mc":"必易微","jys":"sh"},{"dm":"301299","mc":"卓创资讯","jys":"sz"},{"dm":"300217","mc":"东方电热","jys":"sz"},{"dm":"600558","mc":"大西洋","jys":"sh"},{"dm":"300805","mc":"电声股份","jys":"sz"},{"dm":"600415","mc":"小商品城","jys":"sh"},{"dm":"000571","mc":"新大洲A","jys":"sz"},{"dm":"002094","mc":"青岛金王","jys":"sz"},{"dm":"002031","mc":"巨轮智能","jys":"sz"},{"dm":"688582","mc":"芯动联科","jys":"sh"},{"dm":"688323","mc":"瑞华泰","jys":"sh"},{"dm":"603992","mc":"松霖科技","jys":"sh"},{"dm":"688237","mc":"超卓航科","jys":"sh"},{"dm":"600129","mc":"太极集团","jys":"sh"},{"dm":"002671","mc":"龙泉股份","jys":"sz"},{"dm":"000795","mc":"英洛华","jys":"sz"},{"dm":"688191","mc":"智洋创新","jys":"sh"},{"dm":"600355","mc":"精伦电子","jys":"sh"},{"dm":"002263","mc":"大东南","jys":"sz"},{"dm":"688573","mc":"信宇人","jys":"sh"},{"dm":"300877","mc":"金春股份","jys":"sz"},{"dm":"600053","mc":"九鼎投资","jys":"sh"},{"dm":"601500","mc":"通用股份","jys":"sh"},{"dm":"001282","mc":"三联锻造","jys":"sz"},{"dm":"603501","mc":"韦尔股份","jys":"sh"},{"dm":"300949","mc":"奥雅股份","jys":"sz"},{"dm":"000971","mc":"ST高升","jys":"sz"},{"dm":"600769","mc":"祥龙电业","jys":"sh"},{"dm":"600032","mc":"浙江新能","jys":"sh"},{"dm":"600039","mc":"四川路桥","jys":"sh"},{"dm":"603319","mc":"湘油泵","jys":"sh"},{"dm":"600345","mc":"长江通信","jys":"sh"},{"dm":"603530","mc":"神马电力","jys":"sh"},{"dm":"301302","mc":"华如科技","jys":"sz"},{"dm":"603928","mc":"兴业股份","jys":"sh"},{"dm":"001206","mc":"依依股份","jys":"sz"},{"dm":"002862","mc":"实丰文化","jys":"sz"},{"dm":"600735","mc":"新华锦","jys":"sh"},{"dm":"300916","mc":"朗特智能","jys":"sz"},{"dm":"002752","mc":"昇兴股份","jys":"sz"},{"dm":"603327","mc":"福蓉科技","jys":"sh"},{"dm":"603188","mc":"亚邦股份","jys":"sh"},{"dm":"605199","mc":"葫芦娃","jys":"sh"},{"dm":"600758","mc":"辽宁能源","jys":"sh"},{"dm":"601969","mc":"海南矿业","jys":"sh"},{"dm":"002521","mc":"齐峰新材","jys":"sz"},{"dm":"688147","mc":"微导纳米","jys":"sh"},{"dm":"301266","mc":"宇邦新材","jys":"sz"},{"dm":"300474","mc":"景嘉微","jys":"sz"},{"dm":"002215","mc":"诺 普 信","jys":"sz"},{"dm":"603119","mc":"浙江荣泰","jys":"sh"},{"dm":"002591","mc":"恒大高新","jys":"sz"},{"dm":"300703","mc":"创源股份","jys":"sz"},{"dm":"603363","mc":"傲农生物","jys":"sh"},{"dm":"300365","mc":"恒华科技","jys":"sz"},{"dm":"301267","mc":"华厦眼科","jys":"sz"},{"dm":"002395","mc":"双象股份","jys":"sz"},{"dm":"688329","mc":"艾隆科技","jys":"sh"},{"dm":"605088","mc":"冠盛股份","jys":"sh"},{"dm":"600604","mc":"市北高新","jys":"sh"},{"dm":"000968","mc":"蓝焰控股","jys":"sz"},{"dm":"600375","mc":"汉马科技","jys":"sh"},{"dm":"300935","mc":"盈建科","jys":"sz"},{"dm":"601918","mc":"新集能源","jys":"sh"},{"dm":"603662","mc":"柯力传感","jys":"sh"},{"dm":"301366","mc":"一博科技","jys":"sz"},{"dm":"600864","mc":"哈投股份","jys":"sh"},{"dm":"002400","mc":"省广集团","jys":"sz"},{"dm":"600125","mc":"铁龙物流","jys":"sh"},{"dm":"002576","mc":"通达动力","jys":"sz"},{"dm":"001298","mc":"好上好","jys":"sz"},{"dm":"300414","mc":"中光防雷","jys":"sz"},{"dm":"002315","mc":"焦点科技","jys":"sz"},{"dm":"002109","mc":"兴化股份","jys":"sz"},{"dm":"688004","mc":"博汇科技","jys":"sh"},{"dm":"300314","mc":"戴维医疗","jys":"sz"},{"dm":"301203","mc":"国泰环保","jys":"sz"},{"dm":"002593","mc":"日上集团","jys":"sz"},{"dm":"300248","mc":"新开普","jys":"sz"},{"dm":"601999","mc":"出版传媒","jys":"sh"},{"dm":"002787","mc":"华源控股","jys":"sz"},{"dm":"000525","mc":"ST红太阳","jys":"sz"},{"dm":"300936","mc":"中英科技","jys":"sz"},{"dm":"300497","mc":"富祥药业","jys":"sz"},{"dm":"603618","mc":"杭电股份","jys":"sh"},{"dm":"002189","mc":"中光学","jys":"sz"},{"dm":"300368","mc":"汇金股份","jys":"sz"},{"dm":"300227","mc":"光韵达","jys":"sz"},{"dm":"000823","mc":"超声电子","jys":"sz"},{"dm":"600330","mc":"天通股份","jys":"sh"},{"dm":"003040","mc":"楚天龙","jys":"sz"},{"dm":"001226","mc":"拓山重工","jys":"sz"},{"dm":"300260","mc":"新莱应材","jys":"sz"},{"dm":"300237","mc":"美晨生态","jys":"sz"},{"dm":"600773","mc":"西藏城投","jys":"sh"},{"dm":"603138","mc":"海量数据","jys":"sh"},{"dm":"002015","mc":"协鑫能科","jys":"sz"},{"dm":"000727","mc":"冠捷科技","jys":"sz"},{"dm":"000920","mc":"沃顿科技","jys":"sz"},{"dm":"600671","mc":"*ST目药","jys":"sh"},{"dm":"002585","mc":"双星新材","jys":"sz"},{"dm":"300403","mc":"汉宇集团","jys":"sz"},{"dm":"002087","mc":"*ST新纺","jys":"sz"},{"dm":"688435","mc":"英方软件","jys":"sh"},{"dm":"300355","mc":"蒙草生态","jys":"sz"},{"dm":"600738","mc":"丽尚国潮","jys":"sh"},{"dm":"600641","mc":"万业企业","jys":"sh"},{"dm":"002230","mc":"科大讯飞","jys":"sz"},{"dm":"300604","mc":"长川科技","jys":"sz"},{"dm":"300638","mc":"广和通","jys":"sz"},{"dm":"301528","mc":"多浦乐","jys":"sz"},{"dm":"002512","mc":"达华智能","jys":"sz"},{"dm":"300546","mc":"雄帝科技","jys":"sz"},{"dm":"600329","mc":"达仁堂","jys":"sh"},{"dm":"603379","mc":"三美股份","jys":"sh"},{"dm":"600241","mc":"时代万恒","jys":"sh"},{"dm":"300281","mc":"金明精机","jys":"sz"},{"dm":"603010","mc":"万盛股份","jys":"sh"},{"dm":"600088","mc":"中视传媒","jys":"sh"},{"dm":"688372","mc":"伟测科技","jys":"sh"},{"dm":"301019","mc":"宁波色母","jys":"sz"},{"dm":"600567","mc":"山鹰国际","jys":"sh"},{"dm":"600281","mc":"华阳新材","jys":"sh"},{"dm":"300693","mc":"盛弘股份","jys":"sz"},{"dm":"000953","mc":"河化股份","jys":"sz"},{"dm":"002825","mc":"纳尔股份","jys":"sz"},{"dm":"002757","mc":"南兴股份","jys":"sz"},{"dm":"301380","mc":"挖金客","jys":"sz"},{"dm":"002336","mc":"人人乐","jys":"sz"},{"dm":"603926","mc":"铁流股份","jys":"sh"},{"dm":"300504","mc":"天邑股份","jys":"sz"},{"dm":"300079","mc":"数码视讯","jys":"sz"},{"dm":"600626","mc":"申达股份","jys":"sh"},{"dm":"300322","mc":"硕贝德","jys":"sz"},{"dm":"688619","mc":"罗普特","jys":"sh"},{"dm":"301007","mc":"德迈仕","jys":"sz"},{"dm":"300130","mc":"新国都","jys":"sz"},{"dm":"300250","mc":"初灵信息","jys":"sz"},{"dm":"000407","mc":"胜利股份","jys":"sz"},{"dm":"603823","mc":"百合花","jys":"sh"},{"dm":"002661","mc":"克明食品","jys":"sz"},{"dm":"000554","mc":"泰山石油","jys":"sz"},{"dm":"002234","mc":"民和股份","jys":"sz"},{"dm":"002662","mc":"京威股份","jys":"sz"},{"dm":"002642","mc":"荣联科技","jys":"sz"},{"dm":"002212","mc":"天融信","jys":"sz"},{"dm":"000599","mc":"青岛双星","jys":"sz"},{"dm":"600783","mc":"鲁信创投","jys":"sh"},{"dm":"688012","mc":"中微公司","jys":"sh"},{"dm":"002857","mc":"三晖电气","jys":"sz"},{"dm":"300745","mc":"欣锐科技","jys":"sz"},{"dm":"600199","mc":"金种子酒","jys":"sh"},{"dm":"600734","mc":"ST实达","jys":"sh"},{"dm":"300543","mc":"朗科智能","jys":"sz"},{"dm":"002418","mc":"康盛股份","jys":"sz"},{"dm":"301016","mc":"雷尔伟","jys":"sz"},{"dm":"300561","mc":"汇金科技","jys":"sz"},{"dm":"600523","mc":"贵航股份","jys":"sh"},{"dm":"605166","mc":"聚合顺","jys":"sh"},{"dm":"601858","mc":"中国科传","jys":"sh"},{"dm":"603948","mc":"建业股份","jys":"sh"},{"dm":"688600","mc":"皖仪科技","jys":"sh"},{"dm":"301261","mc":"恒工精密","jys":"sz"},{"dm":"301062","mc":"上海艾录","jys":"sz"},{"dm":"000421","mc":"南京公用","jys":"sz"},{"dm":"301208","mc":"中亦科技","jys":"sz"},{"dm":"002761","mc":"浙江建投","jys":"sz"},{"dm":"002316","mc":"亚联发展","jys":"sz"},{"dm":"600288","mc":"大恒科技","jys":"sh"},{"dm":"300451","mc":"创业慧康","jys":"sz"},{"dm":"603109","mc":"神驰机电","jys":"sh"},{"dm":"603040","mc":"新坐标","jys":"sh"},{"dm":"001259","mc":"利仁科技","jys":"sz"},{"dm":"688667","mc":"菱电电控","jys":"sh"},{"dm":"600658","mc":"电子城","jys":"sh"},{"dm":"603172","mc":"万丰股份","jys":"sh"},{"dm":"300004","mc":"南风股份","jys":"sz"},{"dm":"301315","mc":"威士顿","jys":"sz"},{"dm":"603486","mc":"科沃斯","jys":"sh"},{"dm":"688448","mc":"磁谷科技","jys":"sh"},{"dm":"300513","mc":"恒实科技","jys":"sz"},{"dm":"600599","mc":"ST熊猫","jys":"sh"},{"dm":"000070","mc":"特发信息","jys":"sz"},{"dm":"601988","mc":"中国银行","jys":"sh"},{"dm":"600128","mc":"苏豪弘业","jys":"sh"},{"dm":"600007","mc":"中国国贸","jys":"sh"},{"dm":"300319","mc":"麦捷科技","jys":"sz"},{"dm":"603813","mc":"原尚股份","jys":"sh"},{"dm":"300762","mc":"上海瀚讯","jys":"sz"},{"dm":"002679","mc":"福建金森","jys":"sz"},{"dm":"002310","mc":"东方园林","jys":"sz"},{"dm":"688568","mc":"中科星图","jys":"sh"},{"dm":"002715","mc":"登云股份","jys":"sz"},{"dm":"603788","mc":"宁波高发","jys":"sh"},{"dm":"301399","mc":"英特科技","jys":"sz"},{"dm":"300471","mc":"厚普股份","jys":"sz"},{"dm":"688480","mc":"赛恩斯","jys":"sh"},{"dm":"002019","mc":"亿帆医药","jys":"sz"},{"dm":"600229","mc":"城市传媒","jys":"sh"},{"dm":"600885","mc":"宏发股份","jys":"sh"},{"dm":"002835","mc":"同为股份","jys":"sz"},{"dm":"688209","mc":"英集芯","jys":"sh"},{"dm":"600796","mc":"钱江生化","jys":"sh"},{"dm":"000025","mc":"特 力A","jys":"sz"},{"dm":"605333","mc":"沪光股份","jys":"sh"},{"dm":"002840","mc":"华统股份","jys":"sz"},{"dm":"001268","mc":"联合精密","jys":"sz"},{"dm":"301113","mc":"雅艺科技","jys":"sz"},{"dm":"300296","mc":"利亚德","jys":"sz"},{"dm":"688228","mc":"开普云","jys":"sh"},{"dm":"300689","mc":"澄天伟业","jys":"sz"},{"dm":"300041","mc":"回天新材","jys":"sz"},{"dm":"300958","mc":"建工修复","jys":"sz"},{"dm":"300880","mc":"迦南智能","jys":"sz"},{"dm":"600546","mc":"山煤国际","jys":"sh"},{"dm":"300498","mc":"温氏股份","jys":"sz"},{"dm":"600118","mc":"中国卫星","jys":"sh"},{"dm":"002875","mc":"安奈儿","jys":"sz"},{"dm":"002698","mc":"博实股份","jys":"sz"},{"dm":"301331","mc":"恩威医药","jys":"sz"},{"dm":"688103","mc":"国力股份","jys":"sh"},{"dm":"603360","mc":"百傲化学","jys":"sh"},{"dm":"002775","mc":"文科园林","jys":"sz"},{"dm":"688290","mc":"景业智能","jys":"sh"},{"dm":"000811","mc":"冰轮环境","jys":"sz"},{"dm":"301225","mc":"恒勃股份","jys":"sz"},{"dm":"301157","mc":"华塑科技","jys":"sz"},{"dm":"603776","mc":"永安行","jys":"sh"},{"dm":"688800","mc":"瑞可达","jys":"sh"},{"dm":"300761","mc":"立华股份","jys":"sz"},{"dm":"300173","mc":"福能东方","jys":"sz"},{"dm":"300153","mc":"科泰电源","jys":"sz"},{"dm":"688522","mc":"纳睿雷达","jys":"sh"},{"dm":"300213","mc":"佳讯飞鸿","jys":"sz"},{"dm":"301525","mc":"儒竞科技","jys":"sz"},{"dm":"300377","mc":"赢时胜","jys":"sz"},{"dm":"603660","mc":"苏州科达","jys":"sh"},{"dm":"300494","mc":"盛天网络","jys":"sz"},{"dm":"688618","mc":"三旺通信","jys":"sh"},{"dm":"002083","mc":"孚日股份","jys":"sz"},{"dm":"000998","mc":"隆平高科","jys":"sz"},{"dm":"003010","mc":"若羽臣","jys":"sz"},{"dm":"600938","mc":"中国海油","jys":"sh"},{"dm":"600584","mc":"长电科技","jys":"sh"},{"dm":"300634","mc":"彩讯股份","jys":"sz"},{"dm":"300598","mc":"诚迈科技","jys":"sz"},{"dm":"301083","mc":"百胜智能","jys":"sz"},{"dm":"002803","mc":"吉宏股份","jys":"sz"},{"dm":"603123","mc":"翠微股份","jys":"sh"},{"dm":"300720","mc":"海川智能","jys":"sz"},{"dm":"688571","mc":"杭华股份","jys":"sh"},{"dm":"000678","mc":"襄阳轴承","jys":"sz"},{"dm":"600792","mc":"云煤能源","jys":"sh"},{"dm":"688169","mc":"石头科技","jys":"sh"},{"dm":"600536","mc":"中国软件","jys":"sh"},{"dm":"300288","mc":"朗玛信息","jys":"sz"},{"dm":"600059","mc":"古越龙山","jys":"sh"},{"dm":"600232","mc":"金鹰股份","jys":"sh"},{"dm":"603959","mc":"百利科技","jys":"sh"},{"dm":"002628","mc":"成都路桥","jys":"sz"},{"dm":"300085","mc":"银之杰","jys":"sz"},{"dm":"688767","mc":"博拓生物","jys":"sh"},{"dm":"300621","mc":"维业股份","jys":"sz"},{"dm":"600250","mc":"南纺股份","jys":"sh"},{"dm":"000826","mc":"启迪环境","jys":"sz"},{"dm":"300399","mc":"天利科技","jys":"sz"},{"dm":"000757","mc":"浩物股份","jys":"sz"},{"dm":"002276","mc":"万马股份","jys":"sz"},{"dm":"688201","mc":"信安世纪","jys":"sh"},{"dm":"600909","mc":"华安证券","jys":"sh"},{"dm":"300928","mc":"华安鑫创","jys":"sz"},{"dm":"688282","mc":"理工导航","jys":"sh"},{"dm":"002808","mc":"ST恒久","jys":"sz"},{"dm":"601225","mc":"陕西煤业","jys":"sh"},{"dm":"688610","mc":"埃科光电","jys":"sh"},{"dm":"002335","mc":"科华数据","jys":"sz"},{"dm":"688626","mc":"翔宇医疗","jys":"sh"},{"dm":"688591","mc":"泰凌微","jys":"sh"},{"dm":"688021","mc":"奥福环保","jys":"sh"},{"dm":"300113","mc":"顺网科技","jys":"sz"},{"dm":"002763","mc":"汇洁股份","jys":"sz"},{"dm":"300493","mc":"润欣科技","jys":"sz"},{"dm":"001331","mc":"胜通能源","jys":"sz"},{"dm":"603278","mc":"大业股份","jys":"sh"},{"dm":"300410","mc":"正业科技","jys":"sz"},{"dm":"002630","mc":"华西能源","jys":"sz"},{"dm":"603908","mc":"牧高笛","jys":"sh"},{"dm":"300226","mc":"上海钢联","jys":"sz"},{"dm":"688113","mc":"联测科技","jys":"sh"},{"dm":"300948","mc":"冠中生态","jys":"sz"},{"dm":"688068","mc":"热景生物","jys":"sh"},{"dm":"600369","mc":"西南证券","jys":"sh"},{"dm":"002673","mc":"西部证券","jys":"sz"},{"dm":"300454","mc":"深信服","jys":"sz"},{"dm":"002577","mc":"雷柏科技","jys":"sz"},{"dm":"300112","mc":"万讯自控","jys":"sz"},{"dm":"603206","mc":"嘉环科技","jys":"sh"},{"dm":"600025","mc":"华能水电","jys":"sh"},{"dm":"300219","mc":"鸿利智汇","jys":"sz"},{"dm":"002235","mc":"安妮股份","jys":"sz"},{"dm":"002446","mc":"盛路通信","jys":"sz"},{"dm":"605180","mc":"华生科技","jys":"sh"},{"dm":"688439","mc":"振华风光","jys":"sh"},{"dm":"300577","mc":"开润股份","jys":"sz"},{"dm":"002893","mc":"京能热力","jys":"sz"},{"dm":"300323","mc":"华灿光电","jys":"sz"},{"dm":"300559","mc":"佳发教育","jys":"sz"},{"dm":"002360","mc":"同德化工","jys":"sz"},{"dm":"600423","mc":"柳化股份","jys":"sh"},{"dm":"002351","mc":"漫步者","jys":"sz"},{"dm":"605016","mc":"百龙创园","jys":"sh"},{"dm":"300627","mc":"华测导航","jys":"sz"},{"dm":"688539","mc":"高华科技","jys":"sh"},{"dm":"600076","mc":"康欣新材","jys":"sh"},{"dm":"300830","mc":"金现代","jys":"sz"},{"dm":"300342","mc":"天银机电","jys":"sz"},{"dm":"000631","mc":"顺发恒业","jys":"sz"},{"dm":"688580","mc":"伟思医疗","jys":"sh"},{"dm":"600686","mc":"金龙汽车","jys":"sh"},{"dm":"600061","mc":"国投资本","jys":"sh"},{"dm":"688360","mc":"德马科技","jys":"sh"},{"dm":"002518","mc":"科士达","jys":"sz"},{"dm":"603176","mc":"汇通集团","jys":"sh"},{"dm":"000782","mc":"美达股份","jys":"sz"},{"dm":"605066","mc":"天正电气","jys":"sh"},{"dm":"002023","mc":"海特高新","jys":"sz"},{"dm":"002133","mc":"广宇集团","jys":"sz"},{"dm":"300077","mc":"国民技术","jys":"sz"},{"dm":"002957","mc":"科瑞技术","jys":"sz"},{"dm":"002348","mc":"高乐股份","jys":"sz"},{"dm":"600707","mc":"彩虹股份","jys":"sh"},{"dm":"002396","mc":"星网锐捷","jys":"sz"},{"dm":"300713","mc":"英可瑞","jys":"sz"},{"dm":"000762","mc":"西藏矿业","jys":"sz"},{"dm":"300338","mc":"开元教育","jys":"sz"},{"dm":"000962","mc":"东方钽业","jys":"sz"},{"dm":"300937","mc":"药易购","jys":"sz"},{"dm":"688365","mc":"光云科技","jys":"sh"},{"dm":"300961","mc":"深水海纳","jys":"sz"},{"dm":"300115","mc":"长盈精密","jys":"sz"},{"dm":"601869","mc":"长飞光纤","jys":"sh"},{"dm":"600971","mc":"恒源煤电","jys":"sh"},{"dm":"688219","mc":"会通股份","jys":"sh"},{"dm":"002749","mc":"国光股份","jys":"sz"},{"dm":"000166","mc":"申万宏源","jys":"sz"},{"dm":"300440","mc":"运达科技","jys":"sz"},{"dm":"300476","mc":"胜宏科技","jys":"sz"},{"dm":"000911","mc":"广农糖业","jys":"sz"},{"dm":"002058","mc":"威尔泰","jys":"sz"},{"dm":"300605","mc":"恒锋信息","jys":"sz"},{"dm":"002207","mc":"准油股份","jys":"sz"},{"dm":"600830","mc":"香溢融通","jys":"sh"},{"dm":"002354","mc":"天娱数科","jys":"sz"},{"dm":"002722","mc":"物产金轮","jys":"sz"},{"dm":"688123","mc":"聚辰股份","jys":"sh"},{"dm":"603825","mc":"华扬联众","jys":"sh"},{"dm":"000679","mc":"大连友谊","jys":"sz"},{"dm":"600997","mc":"开滦股份","jys":"sh"},{"dm":"600336","mc":"澳柯玛","jys":"sh"},{"dm":"688620","mc":"安凯微","jys":"sh"},{"dm":"605388","mc":"均瑶健康","jys":"sh"},{"dm":"600185","mc":"格力地产","jys":"sh"},{"dm":"002766","mc":"索菱股份","jys":"sz"},{"dm":"000011","mc":"深物业A","jys":"sz"},{"dm":"601377","mc":"兴业证券","jys":"sh"},{"dm":"002480","mc":"新筑股份","jys":"sz"},{"dm":"688090","mc":"瑞松科技","jys":"sh"},{"dm":"600683","mc":"京投发展","jys":"sh"},{"dm":"603728","mc":"鸣志电器","jys":"sh"},{"dm":"002649","mc":"博彦科技","jys":"sz"},{"dm":"002828","mc":"贝肯能源","jys":"sz"},{"dm":"301153","mc":"中科江南","jys":"sz"},{"dm":"603927","mc":"中科软","jys":"sh"},{"dm":"301379","mc":"天山电子","jys":"sz"},{"dm":"300557","mc":"理工光科","jys":"sz"},{"dm":"301105","mc":"鸿铭股份","jys":"sz"},{"dm":"000821","mc":"京山轻机","jys":"sz"},{"dm":"603980","mc":"吉华集团","jys":"sh"},{"dm":"300353","mc":"东土科技","jys":"sz"},{"dm":"301311","mc":"昆船智能","jys":"sz"},{"dm":"300858","mc":"科拓生物","jys":"sz"},{"dm":"688309","mc":"恒誉环保","jys":"sh"},{"dm":"603329","mc":"上海雅仕","jys":"sh"},{"dm":"002479","mc":"富春环保","jys":"sz"},{"dm":"688022","mc":"瀚川智能","jys":"sh"},{"dm":"300201","mc":"海伦哲","jys":"sz"},{"dm":"688612","mc":"威迈斯","jys":"sh"},{"dm":"002406","mc":"远东传动","jys":"sz"},{"dm":"603273","mc":"天元智能","jys":"sh"},{"dm":"301369","mc":"联动科技","jys":"sz"},{"dm":"000766","mc":"通化金马","jys":"sz"},{"dm":"600227","mc":"赤天化","jys":"sh"},{"dm":"600016","mc":"民生银行","jys":"sh"},{"dm":"300722","mc":"新余国科","jys":"sz"},{"dm":"301032","mc":"新柴股份","jys":"sz"},{"dm":"603607","mc":"京华激光","jys":"sh"},{"dm":"688786","mc":"悦安新材","jys":"sh"},{"dm":"002975","mc":"博杰股份","jys":"sz"},{"dm":"601929","mc":"吉视传媒","jys":"sh"},{"dm":"300058","mc":"蓝色光标","jys":"sz"},{"dm":"002647","mc":"仁东控股","jys":"sz"},{"dm":"688376","mc":"美埃科技","jys":"sh"},{"dm":"300162","mc":"雷曼光电","jys":"sz"},{"dm":"002953","mc":"日丰股份","jys":"sz"},{"dm":"600722","mc":"金牛化工","jys":"sh"},{"dm":"300425","mc":"中建环能","jys":"sz"},{"dm":"000868","mc":"安凯客车","jys":"sz"},{"dm":"603511","mc":"爱慕股份","jys":"sh"},{"dm":"600549","mc":"厦门钨业","jys":"sh"},{"dm":"600202","mc":"哈空调","jys":"sh"},{"dm":"000973","mc":"佛塑科技","jys":"sz"},{"dm":"603880","mc":"ST南卫","jys":"sh"},{"dm":"603500","mc":"祥和实业","jys":"sh"},{"dm":"002465","mc":"海格通信","jys":"sz"},{"dm":"300127","mc":"银河磁体","jys":"sz"},{"dm":"600653","mc":"申华控股","jys":"sh"},{"dm":"300969","mc":"恒帅股份","jys":"sz"},{"dm":"600751","mc":"海航科技","jys":"sh"},{"dm":"301339","mc":"通行宝","jys":"sz"},{"dm":"002547","mc":"春兴精工","jys":"sz"},{"dm":"300466","mc":"赛摩智能","jys":"sz"},{"dm":"002117","mc":"东港股份","jys":"sz"},{"dm":"002820","mc":"桂发祥","jys":"sz"},{"dm":"603221","mc":"爱丽家居","jys":"sh"},{"dm":"603800","mc":"道森股份","jys":"sh"},{"dm":"603610","mc":"麒盛科技","jys":"sh"},{"dm":"000909","mc":"ST数源","jys":"sz"},{"dm":"688075","mc":"安旭生物","jys":"sh"},{"dm":"300987","mc":"川网传媒","jys":"sz"},{"dm":"603178","mc":"圣龙股份","jys":"sh"},{"dm":"301020","mc":"密封科技","jys":"sz"},{"dm":"002899","mc":"英派斯","jys":"sz"},{"dm":"002884","mc":"凌霄泵业","jys":"sz"},{"dm":"300228","mc":"富瑞特装","jys":"sz"},{"dm":"300380","mc":"安硕信息","jys":"sz"},{"dm":"002211","mc":"宏达新材","jys":"sz"},{"dm":"002856","mc":"美芝股份","jys":"sz"},{"dm":"002124","mc":"天邦食品","jys":"sz"},{"dm":"002063","mc":"远光软件","jys":"sz"},{"dm":"300515","mc":"三德科技","jys":"sz"},{"dm":"300509","mc":"新美星","jys":"sz"},{"dm":"002997","mc":"瑞鹄模具","jys":"sz"},{"dm":"300840","mc":"酷特智能","jys":"sz"},{"dm":"000040","mc":"东旭蓝天","jys":"sz"},{"dm":"600161","mc":"天坛生物","jys":"sh"},{"dm":"688225","mc":"亚信安全","jys":"sh"},{"dm":"600352","mc":"浙江龙盛","jys":"sh"},{"dm":"301272","mc":"英华特","jys":"sz"},{"dm":"300872","mc":"天阳科技","jys":"sz"},{"dm":"300625","mc":"三雄极光","jys":"sz"},{"dm":"300516","mc":"久之洋","jys":"sz"},{"dm":"688248","mc":"南网科技","jys":"sh"},{"dm":"688661","mc":"和林微纳","jys":"sh"},{"dm":"603681","mc":"永冠新材","jys":"sh"},{"dm":"300478","mc":"杭州高新","jys":"sz"},{"dm":"002184","mc":"海得控制","jys":"sz"},{"dm":"600206","mc":"有研新材","jys":"sh"},{"dm":"300386","mc":"飞天诚信","jys":"sz"},{"dm":"300885","mc":"海昌新材","jys":"sz"},{"dm":"300306","mc":"远方信息","jys":"sz"},{"dm":"300069","mc":"金利华电","jys":"sz"},{"dm":"000521","mc":"长虹美菱","jys":"sz"},{"dm":"603859","mc":"能科科技","jys":"sh"},{"dm":"002298","mc":"中电兴发","jys":"sz"},{"dm":"300991","mc":"创益通","jys":"sz"},{"dm":"301080","mc":"百普赛斯","jys":"sz"},{"dm":"002915","mc":"中欣氟材","jys":"sz"},{"dm":"000960","mc":"锡业股份","jys":"sz"},{"dm":"002852","mc":"道道全","jys":"sz"},{"dm":"300200","mc":"高盟新材","jys":"sz"},{"dm":"600051","mc":"宁波联合","jys":"sh"},{"dm":"605099","mc":"共创草坪","jys":"sh"},{"dm":"002274","mc":"华昌化工","jys":"sz"},{"dm":"300464","mc":"星徽股份","jys":"sz"},{"dm":"000752","mc":"*ST西发","jys":"sz"},{"dm":"688378","mc":"奥来德","jys":"sh"},{"dm":"002772","mc":"众兴菌业","jys":"sz"},{"dm":"300050","mc":"世纪鼎利","jys":"sz"},{"dm":"603166","mc":"福达股份","jys":"sh"},{"dm":"600365","mc":"ST通葡","jys":"sh"},{"dm":"300283","mc":"温州宏丰","jys":"sz"},{"dm":"600877","mc":"电科芯片","jys":"sh"},{"dm":"002196","mc":"方正电机","jys":"sz"},{"dm":"000576","mc":"甘化科工","jys":"sz"},{"dm":"301307","mc":"美利信","jys":"sz"},{"dm":"003001","mc":"中岩大地","jys":"sz"},{"dm":"600566","mc":"济川药业","jys":"sh"},{"dm":"603848","mc":"好太太","jys":"sh"},{"dm":"600083","mc":"博信股份","jys":"sh"},{"dm":"600319","mc":"亚星化学","jys":"sh"},{"dm":"002815","mc":"崇达技术","jys":"sz"},{"dm":"300035","mc":"中科电气","jys":"sz"},{"dm":"688701","mc":"卓锦股份","jys":"sh"},{"dm":"600366","mc":"宁波韵升","jys":"sh"},{"dm":"300578","mc":"会畅通讯","jys":"sz"},{"dm":"601066","mc":"中信建投","jys":"sh"},{"dm":"300111","mc":"向日葵","jys":"sz"},{"dm":"301237","mc":"和顺科技","jys":"sz"},{"dm":"001215","mc":"千味央厨","jys":"sz"},{"dm":"301259","mc":"艾布鲁","jys":"sz"},{"dm":"600461","mc":"洪城环境","jys":"sh"},{"dm":"301072","mc":"中捷精工","jys":"sz"},{"dm":"300075","mc":"数字政通","jys":"sz"},{"dm":"002528","mc":"英飞拓","jys":"sz"},{"dm":"000420","mc":"吉林化纤","jys":"sz"},{"dm":"002350","mc":"北京科锐","jys":"sz"},{"dm":"600448","mc":"华纺股份","jys":"sh"},{"dm":"002373","mc":"千方科技","jys":"sz"},{"dm":"300369","mc":"绿盟科技","jys":"sz"},{"dm":"301337","mc":"亚华电子","jys":"sz"},{"dm":"300590","mc":"移为通信","jys":"sz"},{"dm":"600548","mc":"深高速","jys":"sh"},{"dm":"000969","mc":"安泰科技","jys":"sz"},{"dm":"688262","mc":"国芯科技","jys":"sh"},{"dm":"000970","mc":"中科三环","jys":"sz"},{"dm":"002206","mc":"海 利 得","jys":"sz"},{"dm":"001202","mc":"炬申股份","jys":"sz"},{"dm":"002489","mc":"浙江永强","jys":"sz"},{"dm":"300785","mc":"值得买","jys":"sz"},{"dm":"688306","mc":"均普智能","jys":"sh"},{"dm":"002168","mc":"惠程科技","jys":"sz"},{"dm":"688073","mc":"毕得医药","jys":"sh"},{"dm":"002017","mc":"东信和平","jys":"sz"},{"dm":"601828","mc":"美凯龙","jys":"sh"},{"dm":"002174","mc":"游族网络","jys":"sz"},{"dm":"300716","mc":"泉为科技","jys":"sz"},{"dm":"301327","mc":"华宝新能","jys":"sz"},{"dm":"300170","mc":"汉得信息","jys":"sz"},{"dm":"300110","mc":"华仁药业","jys":"sz"},{"dm":"002140","mc":"东华科技","jys":"sz"},{"dm":"600768","mc":"宁波富邦","jys":"sh"},{"dm":"600081","mc":"东风科技","jys":"sh"},{"dm":"002879","mc":"长缆科技","jys":"sz"},{"dm":"002652","mc":"扬子新材","jys":"sz"},{"dm":"000798","mc":"中水渔业","jys":"sz"},{"dm":"603679","mc":"华体科技","jys":"sh"},{"dm":"603161","mc":"科华控股","jys":"sh"},{"dm":"001205","mc":"盛航股份","jys":"sz"},{"dm":"600302","mc":"标准股份","jys":"sh"},{"dm":"603505","mc":"金石资源","jys":"sh"},{"dm":"002011","mc":"盾安环境","jys":"sz"},{"dm":"003030","mc":"祖名股份","jys":"sz"},{"dm":"000551","mc":"创元科技","jys":"sz"},{"dm":"002796","mc":"世嘉科技","jys":"sz"},{"dm":"603311","mc":"金海高科","jys":"sh"},{"dm":"000682","mc":"东方电子","jys":"sz"},{"dm":"000572","mc":"海马汽车","jys":"sz"},{"dm":"601698","mc":"中国卫通","jys":"sh"},{"dm":"688272","mc":"*ST富吉","jys":"sh"},{"dm":"603121","mc":"华培动力","jys":"sh"},{"dm":"300108","mc":"ST吉药","jys":"sz"},{"dm":"300920","mc":"润阳科技","jys":"sz"},{"dm":"300432","mc":"富临精工","jys":"sz"},{"dm":"600236","mc":"桂冠电力","jys":"sh"},{"dm":"000949","mc":"新乡化纤","jys":"sz"},{"dm":"300565","mc":"科信技术","jys":"sz"},{"dm":"300042","mc":"朗科科技","jys":"sz"},{"dm":"000004","mc":"国华网安","jys":"sz"},{"dm":"603950","mc":"长源东谷","jys":"sh"},{"dm":"600810","mc":"神马股份","jys":"sh"},{"dm":"002050","mc":"三花智控","jys":"sz"},{"dm":"600381","mc":"青海春天","jys":"sh"},{"dm":"605056","mc":"咸亨国际","jys":"sh"},{"dm":"688008","mc":"澜起科技","jys":"sh"},{"dm":"300268","mc":"*ST佳沃","jys":"sz"},{"dm":"688215","mc":"瑞晟智能","jys":"sh"},{"dm":"603879","mc":"永悦科技","jys":"sh"},{"dm":"300809","mc":"华辰装备","jys":"sz"},{"dm":"000014","mc":"沙河股份","jys":"sz"},{"dm":"002098","mc":"浔兴股份","jys":"sz"},{"dm":"688125","mc":"安达智能","jys":"sh"},{"dm":"300189","mc":"神农科技","jys":"sz"},{"dm":"300632","mc":"光莆股份","jys":"sz"},{"dm":"001210","mc":"金房能源","jys":"sz"},{"dm":"301499","mc":"维科精密","jys":"sz"},{"dm":"002909","mc":"集泰股份","jys":"sz"},{"dm":"600730","mc":"中国高科","jys":"sh"},{"dm":"600468","mc":"百利电气","jys":"sh"},{"dm":"605500","mc":"森林包装","jys":"sh"},{"dm":"603703","mc":"盛洋科技","jys":"sh"},{"dm":"000622","mc":"恒立实业","jys":"sz"},{"dm":"603757","mc":"大元泵业","jys":"sh"},{"dm":"603958","mc":"哈森股份","jys":"sh"},{"dm":"002288","mc":"超华科技","jys":"sz"},{"dm":"002286","mc":"保龄宝","jys":"sz"},{"dm":"300903","mc":"科翔股份","jys":"sz"},{"dm":"605028","mc":"世茂能源","jys":"sh"},{"dm":"300582","mc":"英飞特","jys":"sz"},{"dm":"688403","mc":"汇成股份","jys":"sh"},{"dm":"001317","mc":"三羊马","jys":"sz"},{"dm":"601000","mc":"唐山港","jys":"sh"},{"dm":"603569","mc":"长久物流","jys":"sh"},{"dm":"688080","mc":"映翰通","jys":"sh"},{"dm":"300644","mc":"南京聚隆","jys":"sz"},{"dm":"300036","mc":"超图软件","jys":"sz"},{"dm":"603320","mc":"迪贝电气","jys":"sh"},{"dm":"002159","mc":"三特索道","jys":"sz"},{"dm":"600831","mc":"广电网络","jys":"sh"},{"dm":"603960","mc":"克来机电","jys":"sh"},{"dm":"603709","mc":"中源家居","jys":"sh"},{"dm":"603006","mc":"联明股份","jys":"sh"},{"dm":"600638","mc":"新黄浦","jys":"sh"},{"dm":"002323","mc":"雅博股份","jys":"sz"},{"dm":"301229","mc":"纽泰格","jys":"sz"},{"dm":"603516","mc":"淳中科技","jys":"sh"},{"dm":"688270","mc":"臻镭科技","jys":"sh"},{"dm":"002290","mc":"禾盛新材","jys":"sz"},{"dm":"688567","mc":"孚能科技","jys":"sh"},{"dm":"688338","mc":"赛科希德","jys":"sh"},{"dm":"002474","mc":"榕基软件","jys":"sz"},{"dm":"601126","mc":"四方股份","jys":"sh"},{"dm":"600261","mc":"阳光照明","jys":"sh"},{"dm":"600198","mc":"大唐电信","jys":"sh"},{"dm":"300382","mc":"斯莱克","jys":"sz"},{"dm":"300017","mc":"网宿科技","jys":"sz"},{"dm":"301487","mc":"盟固利","jys":"sz"},{"dm":"002363","mc":"隆基机械","jys":"sz"},{"dm":"002278","mc":"神开股份","jys":"sz"},{"dm":"688109","mc":"品茗科技","jys":"sh"},{"dm":"300917","mc":"特发服务","jys":"sz"},{"dm":"000691","mc":"亚太实业","jys":"sz"},{"dm":"601288","mc":"农业银行","jys":"sh"},{"dm":"688206","mc":"概伦电子","jys":"sh"},{"dm":"600169","mc":"太原重工","jys":"sh"},{"dm":"002181","mc":"粤 传 媒","jys":"sz"},{"dm":"000413","mc":"东旭光电","jys":"sz"},{"dm":"300072","mc":"海新能科","jys":"sz"},{"dm":"002599","mc":"盛通股份","jys":"sz"},{"dm":"000880","mc":"潍柴重机","jys":"sz"},{"dm":"600526","mc":"菲达环保","jys":"sh"},{"dm":"600135","mc":"乐凯胶片","jys":"sh"},{"dm":"688420","mc":"美腾科技","jys":"sh"},{"dm":"300465","mc":"高伟达","jys":"sz"},{"dm":"688175","mc":"高凌信息","jys":"sh"},{"dm":"300743","mc":"天地数码","jys":"sz"},{"dm":"688062","mc":"迈威生物-U","jys":"sh"},{"dm":"688772","mc":"珠海冠宇","jys":"sh"},{"dm":"300774","mc":"倍杰特","jys":"sz"},{"dm":"301548","mc":"崇德科技","jys":"sz"},{"dm":"600737","mc":"中粮糖业","jys":"sh"},{"dm":"301361","mc":"众智科技","jys":"sz"},{"dm":"600784","mc":"鲁银投资","jys":"sh"},{"dm":"600580","mc":"卧龙电驱","jys":"sh"},{"dm":"600571","mc":"信雅达","jys":"sh"},{"dm":"300563","mc":"神宇股份","jys":"sz"},{"dm":"600272","mc":"开开实业","jys":"sh"},{"dm":"001283","mc":"豪鹏科技","jys":"sz"},{"dm":"300505","mc":"川金诺","jys":"sz"},{"dm":"002574","mc":"明牌珠宝","jys":"sz"},{"dm":"300180","mc":"华峰超纤","jys":"sz"},{"dm":"300099","mc":"尤洛卡","jys":"sz"},{"dm":"301132","mc":"满坤科技","jys":"sz"},{"dm":"688213","mc":"思特威-W","jys":"sh"},{"dm":"300483","mc":"首华燃气","jys":"sz"},{"dm":"688655","mc":"迅捷兴","jys":"sh"},{"dm":"601939","mc":"建设银行","jys":"sh"},{"dm":"002560","mc":"通达股份","jys":"sz"},{"dm":"300119","mc":"瑞普生物","jys":"sz"},{"dm":"301517","mc":"陕西华达","jys":"sz"},{"dm":"600408","mc":"安泰集团","jys":"sh"},{"dm":"002282","mc":"博深股份","jys":"sz"},{"dm":"002270","mc":"华明装备","jys":"sz"},{"dm":"300459","mc":"汤姆猫","jys":"sz"},{"dm":"301286","mc":"侨源股份","jys":"sz"},{"dm":"688400","mc":"凌云光","jys":"sh"},{"dm":"002213","mc":"大为股份","jys":"sz"},{"dm":"688428","mc":"诺诚健华-U","jys":"sh"},{"dm":"603998","mc":"方盛制药","jys":"sh"},{"dm":"000761","mc":"本钢板材","jys":"sz"},{"dm":"603053","mc":"成都燃气","jys":"sh"},{"dm":"000536","mc":"华映科技","jys":"sz"},{"dm":"002431","mc":"棕榈股份","jys":"sz"},{"dm":"300957","mc":"贝泰妮","jys":"sz"},{"dm":"002644","mc":"佛慈制药","jys":"sz"},{"dm":"002863","mc":"今飞凯达","jys":"sz"},{"dm":"002971","mc":"和远气体","jys":"sz"},{"dm":"603580","mc":"艾艾精工","jys":"sh"},{"dm":"300624","mc":"万兴科技","jys":"sz"},{"dm":"300600","mc":"国瑞科技","jys":"sz"},{"dm":"301293","mc":"三博脑科","jys":"sz"},{"dm":"300615","mc":"欣天科技","jys":"sz"},{"dm":"600610","mc":"中毅达","jys":"sh"},{"dm":"002376","mc":"新北洋","jys":"sz"},{"dm":"603081","mc":"大丰实业","jys":"sh"},{"dm":"300070","mc":"碧水源","jys":"sz"},{"dm":"002089","mc":"*ST新海","jys":"sz"},{"dm":"601881","mc":"中国银河","jys":"sh"},{"dm":"301178","mc":"天亿马","jys":"sz"},{"dm":"600072","mc":"中船科技","jys":"sh"},{"dm":"300133","mc":"华策影视","jys":"sz"},{"dm":"300460","mc":"惠伦晶体","jys":"sz"},{"dm":"300437","mc":"清水源","jys":"sz"},{"dm":"002516","mc":"旷达科技","jys":"sz"},{"dm":"002755","mc":"奥赛康","jys":"sz"},{"dm":"605288","mc":"凯迪股份","jys":"sh"},{"dm":"002115","mc":"三维通信","jys":"sz"},{"dm":"002188","mc":"中天服务","jys":"sz"},{"dm":"688401","mc":"路维光电","jys":"sh"},{"dm":"000548","mc":"湖南投资","jys":"sz"},{"dm":"600732","mc":"爱旭股份","jys":"sh"},{"dm":"688592","mc":"司南导航","jys":"sh"},{"dm":"688660","mc":"电气风电","jys":"sh"},{"dm":"300977","mc":"深圳瑞捷","jys":"sz"},{"dm":"600084","mc":"中信尼雅","jys":"sh"},{"dm":"002380","mc":"科远智慧","jys":"sz"},{"dm":"000061","mc":"农 产 品","jys":"sz"},{"dm":"603100","mc":"川仪股份","jys":"sh"},{"dm":"600714","mc":"金瑞矿业","jys":"sh"},{"dm":"600368","mc":"五洲交通","jys":"sh"},{"dm":"301226","mc":"祥明智能","jys":"sz"},{"dm":"600969","mc":"郴电国际","jys":"sh"},{"dm":"600313","mc":"农发种业","jys":"sh"},{"dm":"600757","mc":"长江传媒","jys":"sh"},{"dm":"002655","mc":"共达电声","jys":"sz"},{"dm":"603949","mc":"雪龙集团","jys":"sh"},{"dm":"601360","mc":"三六零","jys":"sh"},{"dm":"600886","mc":"国投电力","jys":"sh"},{"dm":"301329","mc":"信音电子","jys":"sz"},{"dm":"002495","mc":"佳隆股份","jys":"sz"},{"dm":"688500","mc":"*ST慧辰","jys":"sh"},{"dm":"002600","mc":"领益智造","jys":"sz"},{"dm":"300982","mc":"苏文电能","jys":"sz"},{"dm":"603712","mc":"七一二","jys":"sh"},{"dm":"601375","mc":"中原证券","jys":"sh"},{"dm":"605069","mc":"正和生态","jys":"sh"},{"dm":"688299","mc":"长阳科技","jys":"sh"},{"dm":"603270","mc":"金帝股份","jys":"sh"},{"dm":"300538","mc":"同益股份","jys":"sz"},{"dm":"002040","mc":"南 京 港","jys":"sz"},{"dm":"301355","mc":"南王科技","jys":"sz"},{"dm":"000790","mc":"华神科技","jys":"sz"},{"dm":"301081","mc":"严牌股份","jys":"sz"},{"dm":"603033","mc":"三维股份","jys":"sh"},{"dm":"600213","mc":"亚星客车","jys":"sh"},{"dm":"688466","mc":"金科环境","jys":"sh"},{"dm":"300817","mc":"双飞集团","jys":"sz"},{"dm":"300671","mc":"富满微","jys":"sz"},{"dm":"688227","mc":"品高股份","jys":"sh"},{"dm":"600157","mc":"永泰能源","jys":"sh"},{"dm":"002537","mc":"海联金汇","jys":"sz"},{"dm":"300066","mc":"三川智慧","jys":"sz"},{"dm":"605259","mc":"绿田机械","jys":"sh"},{"dm":"002969","mc":"嘉美包装","jys":"sz"},{"dm":"600481","mc":"双良节能","jys":"sh"},{"dm":"300847","mc":"中船汉光","jys":"sz"},{"dm":"002194","mc":"武汉凡谷","jys":"sz"},{"dm":"000659","mc":"珠海中富","jys":"sz"},{"dm":"688127","mc":"蓝特光学","jys":"sh"},{"dm":"000933","mc":"神火股份","jys":"sz"},{"dm":"002629","mc":"仁智股份","jys":"sz"},{"dm":"688060","mc":"云涌科技","jys":"sh"},{"dm":"301314","mc":"科瑞思","jys":"sz"},{"dm":"600277","mc":"亿利洁能","jys":"sh"},{"dm":"002047","mc":"宝鹰股份","jys":"sz"},{"dm":"300468","mc":"四方精创","jys":"sz"},{"dm":"688281","mc":"华秦科技","jys":"sh"},{"dm":"300124","mc":"汇川技术","jys":"sz"},{"dm":"301359","mc":"东南电子","jys":"sz"},{"dm":"300649","mc":"杭州园林","jys":"sz"},{"dm":"300579","mc":"数字认证","jys":"sz"},{"dm":"300663","mc":"科蓝软件","jys":"sz"},{"dm":"600725","mc":"云维股份","jys":"sh"},{"dm":"600279","mc":"重庆港","jys":"sh"},{"dm":"688366","mc":"昊海生科","jys":"sh"},{"dm":"601696","mc":"中银证券","jys":"sh"},{"dm":"002161","mc":"远 望 谷","jys":"sz"},{"dm":"688697","mc":"纽威数控","jys":"sh"},{"dm":"001228","mc":"永泰运","jys":"sz"},{"dm":"301320","mc":"豪江智能","jys":"sz"},{"dm":"603700","mc":"宁水集团","jys":"sh"},{"dm":"002848","mc":"高斯贝尔","jys":"sz"},{"dm":"002799","mc":"环球印务","jys":"sz"},{"dm":"002002","mc":"ST鸿达","jys":"sz"},{"dm":"002527","mc":"新时达","jys":"sz"},{"dm":"002822","mc":"中装建设","jys":"sz"},{"dm":"600551","mc":"时代出版","jys":"sh"},{"dm":"688083","mc":"中望软件","jys":"sh"},{"dm":"600218","mc":"全柴动力","jys":"sh"},{"dm":"000533","mc":"顺钠股份","jys":"sz"},{"dm":"600712","mc":"南宁百货","jys":"sh"},{"dm":"603633","mc":"徕木股份","jys":"sh"},{"dm":"600837","mc":"海通证券","jys":"sh"},{"dm":"002743","mc":"富煌钢构","jys":"sz"},{"dm":"300448","mc":"浩云科技","jys":"sz"},{"dm":"301283","mc":"聚胶股份","jys":"sz"},{"dm":"603803","mc":"瑞斯康达","jys":"sh"},{"dm":"603860","mc":"中公高科","jys":"sh"},{"dm":"002995","mc":"天地在线","jys":"sz"},{"dm":"688689","mc":"银河微电","jys":"sh"},{"dm":"000586","mc":"汇源通信","jys":"sz"},{"dm":"000035","mc":"中国天楹","jys":"sz"},{"dm":"300554","mc":"三超新材","jys":"sz"},{"dm":"300698","mc":"万马科技","jys":"sz"},{"dm":"002032","mc":"苏 泊 尔","jys":"sz"},{"dm":"000702","mc":"正虹科技","jys":"sz"},{"dm":"300751","mc":"迈为股份","jys":"sz"},{"dm":"002069","mc":"獐子岛","jys":"sz"},{"dm":"688450","mc":"光格科技","jys":"sh"},{"dm":"600805","mc":"悦达投资","jys":"sh"},{"dm":"300154","mc":"瑞凌股份","jys":"sz"},{"dm":"002972","mc":"科安达","jys":"sz"},{"dm":"300020","mc":"银江技术","jys":"sz"},{"dm":"605580","mc":"恒盛能源","jys":"sh"},{"dm":"300407","mc":"凯发电气","jys":"sz"},{"dm":"002949","mc":"华阳国际","jys":"sz"},{"dm":"002569","mc":"ST步森","jys":"sz"},{"dm":"000017","mc":"深中华A","jys":"sz"},{"dm":"300305","mc":"裕兴股份","jys":"sz"},{"dm":"002514","mc":"宝馨科技","jys":"sz"},{"dm":"600238","mc":"海南椰岛","jys":"sh"},{"dm":"688391","mc":"钜泉科技","jys":"sh"},{"dm":"601019","mc":"山东出版","jys":"sh"},{"dm":"601878","mc":"浙商证券","jys":"sh"},{"dm":"600894","mc":"广日股份","jys":"sh"},{"dm":"301037","mc":"保立佳","jys":"sz"},{"dm":"300300","mc":"海峡创新","jys":"sz"},{"dm":"002675","mc":"东诚药业","jys":"sz"},{"dm":"000639","mc":"西王食品","jys":"sz"},{"dm":"603127","mc":"昭衍新药","jys":"sh"},{"dm":"002613","mc":"北玻股份","jys":"sz"},{"dm":"000661","mc":"长春高新","jys":"sz"},{"dm":"605179","mc":"一鸣食品","jys":"sh"},{"dm":"601899","mc":"紫金矿业","jys":"sh"},{"dm":"300424","mc":"航新科技","jys":"sz"},{"dm":"688557","mc":"兰剑智能","jys":"sh"},{"dm":"300989","mc":"蕾奥规划","jys":"sz"},{"dm":"603920","mc":"世运电路","jys":"sh"},{"dm":"002401","mc":"中远海科","jys":"sz"},{"dm":"300996","mc":"普联软件","jys":"sz"},{"dm":"002851","mc":"麦格米特","jys":"sz"},{"dm":"688117","mc":"圣诺生物","jys":"sh"},{"dm":"600791","mc":"京能置业","jys":"sh"},{"dm":"300245","mc":"天玑科技","jys":"sz"},{"dm":"600611","mc":"大众交通","jys":"sh"},{"dm":"002299","mc":"圣农发展","jys":"sz"},{"dm":"688603","mc":"天承科技","jys":"sh"},{"dm":"300203","mc":"聚光科技","jys":"sz"},{"dm":"300172","mc":"中电环保","jys":"sz"},{"dm":"600075","mc":"新疆天业","jys":"sh"},{"dm":"002639","mc":"雪人股份","jys":"sz"},{"dm":"301505","mc":"苏州规划","jys":"sz"},{"dm":"000803","mc":"山高环能","jys":"sz"},{"dm":"301067","mc":"显盈科技","jys":"sz"},{"dm":"000409","mc":"云鼎科技","jys":"sz"},{"dm":"300899","mc":"上海凯鑫","jys":"sz"},{"dm":"003041","mc":"真爱美家","jys":"sz"},{"dm":"300140","mc":"节能环境","jys":"sz"},{"dm":"688288","mc":"鸿泉物联","jys":"sh"},{"dm":"301197","mc":"工大科雅","jys":"sz"},{"dm":"603036","mc":"如通股份","jys":"sh"},{"dm":"688699","mc":"明微电子","jys":"sh"},{"dm":"002519","mc":"银河电子","jys":"sz"},{"dm":"300168","mc":"万达信息","jys":"sz"},{"dm":"002633","mc":"申科股份","jys":"sz"},{"dm":"301179","mc":"泽宇智能","jys":"sz"},{"dm":"603061","mc":"金海通","jys":"sh"},{"dm":"688622","mc":"禾信仪器","jys":"sh"},{"dm":"603075","mc":"热威股份","jys":"sh"},{"dm":"688267","mc":"中触媒","jys":"sh"},{"dm":"688517","mc":"金冠电气","jys":"sh"},{"dm":"603535","mc":"嘉诚国际","jys":"sh"},{"dm":"002105","mc":"信隆健康","jys":"sz"},{"dm":"002753","mc":"永东股份","jys":"sz"},{"dm":"605151","mc":"西上海","jys":"sh"},{"dm":"300904","mc":"威力传动","jys":"sz"},{"dm":"002871","mc":"伟隆股份","jys":"sz"},{"dm":"300868","mc":"杰美特","jys":"sz"},{"dm":"603999","mc":"读者传媒","jys":"sh"},{"dm":"300161","mc":"华中数控","jys":"sz"},{"dm":"688485","mc":"九州一轨","jys":"sh"},{"dm":"601921","mc":"浙版传媒","jys":"sh"},{"dm":"301196","mc":"唯科科技","jys":"sz"},{"dm":"000797","mc":"中国武夷","jys":"sz"},{"dm":"600435","mc":"北方导航","jys":"sh"},{"dm":"600698","mc":"湖南天雁","jys":"sh"},{"dm":"600855","mc":"航天长峰","jys":"sh"},{"dm":"600422","mc":"昆药集团","jys":"sh"},{"dm":"300315","mc":"掌趣科技","jys":"sz"},{"dm":"603183","mc":"建研院","jys":"sh"},{"dm":"605376","mc":"博迁新材","jys":"sh"},{"dm":"300489","mc":"光智科技","jys":"sz"},{"dm":"300879","mc":"大叶股份","jys":"sz"},{"dm":"688339","mc":"亿华通-U","jys":"sh"},{"dm":"002956","mc":"西麦食品","jys":"sz"},{"dm":"688509","mc":"正元地信","jys":"sh"},{"dm":"300679","mc":"电连技术","jys":"sz"},{"dm":"002810","mc":"山东赫达","jys":"sz"},{"dm":"300282","mc":"*ST三盛","jys":"sz"},{"dm":"002996","mc":"顺博合金","jys":"sz"},{"dm":"688700","mc":"东威科技","jys":"sh"},{"dm":"300252","mc":"金信诺","jys":"sz"},{"dm":"300955","mc":"嘉亨家化","jys":"sz"},{"dm":"603738","mc":"泰晶科技","jys":"sh"},{"dm":"600872","mc":"中炬高新","jys":"sh"},{"dm":"688519","mc":"南亚新材","jys":"sh"},{"dm":"603333","mc":"尚纬股份","jys":"sh"},{"dm":"601995","mc":"中金公司","jys":"sh"},{"dm":"002926","mc":"华西证券","jys":"sz"},{"dm":"300351","mc":"永贵电器","jys":"sz"},{"dm":"688506","mc":"百利天恒-U","jys":"sh"},{"dm":"603058","mc":"永吉股份","jys":"sh"},{"dm":"300967","mc":"晓鸣股份","jys":"sz"},{"dm":"603595","mc":"东尼电子","jys":"sh"},{"dm":"300925","mc":"法本信息","jys":"sz"},{"dm":"001696","mc":"宗申动力","jys":"sz"},{"dm":"000529","mc":"广弘控股","jys":"sz"},{"dm":"605018","mc":"长华集团","jys":"sh"},{"dm":"600833","mc":"第一医药","jys":"sh"},{"dm":"603655","mc":"朗博科技","jys":"sh"},{"dm":"300882","mc":"万胜智能","jys":"sz"},{"dm":"300672","mc":"国科微","jys":"sz"},{"dm":"600667","mc":"太极实业","jys":"sh"},{"dm":"002006","mc":"精工科技","jys":"sz"},{"dm":"603608","mc":"天创时尚","jys":"sh"},{"dm":"002621","mc":"美吉姆","jys":"sz"},{"dm":"301376","mc":"致欧科技","jys":"sz"},{"dm":"688197","mc":"首药控股-U","jys":"sh"},{"dm":"300695","mc":"兆丰股份","jys":"sz"},{"dm":"600865","mc":"百大集团","jys":"sh"},{"dm":"600770","mc":"综艺股份","jys":"sh"},{"dm":"603536","mc":"惠发食品","jys":"sh"},{"dm":"603258","mc":"电魂网络","jys":"sh"},{"dm":"002440","mc":"闰土股份","jys":"sz"},{"dm":"002487","mc":"大金重工","jys":"sz"},{"dm":"300824","mc":"北鼎股份","jys":"sz"},{"dm":"300321","mc":"同大股份","jys":"sz"},{"dm":"300611","mc":"美力科技","jys":"sz"},{"dm":"000948","mc":"南天信息","jys":"sz"},{"dm":"600609","mc":"金杯汽车","jys":"sh"},{"dm":"300690","mc":"双一科技","jys":"sz"},{"dm":"000860","mc":"顺鑫农业","jys":"sz"},{"dm":"002156","mc":"通富微电","jys":"sz"},{"dm":"605011","mc":"杭州热电","jys":"sh"},{"dm":"601898","mc":"中煤能源","jys":"sh"},{"dm":"300875","mc":"捷强装备","jys":"sz"},{"dm":"600160","mc":"巨化股份","jys":"sh"},{"dm":"300814","mc":"中富电路","jys":"sz"},{"dm":"002398","mc":"垒知集团","jys":"sz"},{"dm":"003017","mc":"大洋生物","jys":"sz"},{"dm":"301160","mc":"翔楼新材","jys":"sz"},{"dm":"001217","mc":"华尔泰","jys":"sz"},{"dm":"600854","mc":"春兰股份","jys":"sh"},{"dm":"300630","mc":"普利制药","jys":"sz"},{"dm":"605098","mc":"行动教育","jys":"sh"},{"dm":"300826","mc":"测绘股份","jys":"sz"},{"dm":"301260","mc":"格力博","jys":"sz"},{"dm":"601928","mc":"凤凰传媒","jys":"sh"},{"dm":"300348","mc":"长亮科技","jys":"sz"},{"dm":"002723","mc":"小崧股份","jys":"sz"},{"dm":"603819","mc":"神力股份","jys":"sh"},{"dm":"601018","mc":"宁波港","jys":"sh"},{"dm":"603759","mc":"海天股份","jys":"sh"},{"dm":"600753","mc":"庚星股份","jys":"sh"},{"dm":"002939","mc":"长城证券","jys":"sz"},{"dm":"601113","mc":"华鼎股份","jys":"sh"},{"dm":"001211","mc":"双枪科技","jys":"sz"},{"dm":"601133","mc":"柏诚股份","jys":"sh"},{"dm":"301378","mc":"通达海","jys":"sz"},{"dm":"300352","mc":"北信源","jys":"sz"},{"dm":"000983","mc":"山西焦煤","jys":"sz"},{"dm":"000895","mc":"双汇发展","jys":"sz"},{"dm":"000510","mc":"新金路","jys":"sz"},{"dm":"603106","mc":"恒银科技","jys":"sh"},{"dm":"300647","mc":"超频三","jys":"sz"},{"dm":"688565","mc":"力源科技","jys":"sh"},{"dm":"002728","mc":"特一药业","jys":"sz"},{"dm":"002605","mc":"姚记科技","jys":"sz"},{"dm":"688063","mc":"派能科技","jys":"sh"},{"dm":"002806","mc":"华锋股份","jys":"sz"},{"dm":"301059","mc":"金三江","jys":"sz"},{"dm":"600267","mc":"海正药业","jys":"sh"},{"dm":"601088","mc":"中国神华","jys":"sh"},{"dm":"300810","mc":"中科海讯","jys":"sz"},{"dm":"603117","mc":"ST万林","jys":"sh"},{"dm":"601168","mc":"西部矿业","jys":"sh"},{"dm":"003016","mc":"欣贺股份","jys":"sz"},{"dm":"600699","mc":"均胜电子","jys":"sh"},{"dm":"601798","mc":"蓝科高新","jys":"sh"},{"dm":"000555","mc":"神州信息","jys":"sz"},{"dm":"003032","mc":"传智教育","jys":"sz"},{"dm":"000981","mc":"山子股份","jys":"sz"},{"dm":"300581","mc":"晨曦航空","jys":"sz"},{"dm":"000779","mc":"甘咨询","jys":"sz"},{"dm":"600675","mc":"中华企业","jys":"sh"},{"dm":"601900","mc":"南方传媒","jys":"sh"},{"dm":"300482","mc":"万孚生物","jys":"sz"},{"dm":"301082","mc":"久盛电气","jys":"sz"},{"dm":"600928","mc":"西安银行","jys":"sh"},{"dm":"300129","mc":"泰胜风能","jys":"sz"},{"dm":"002719","mc":"麦趣尔","jys":"sz"},{"dm":"002036","mc":"联创电子","jys":"sz"},{"dm":"001231","mc":"农心科技","jys":"sz"},{"dm":"300836","mc":"佰奥智能","jys":"sz"},{"dm":"002809","mc":"红墙股份","jys":"sz"},{"dm":"603528","mc":"多伦科技","jys":"sh"},{"dm":"000655","mc":"金岭矿业","jys":"sz"},{"dm":"300660","mc":"江苏雷利","jys":"sz"},{"dm":"002343","mc":"慈文传媒","jys":"sz"},{"dm":"600363","mc":"联创光电","jys":"sh"},{"dm":"300747","mc":"锐科激光","jys":"sz"},{"dm":"002823","mc":"凯中精密","jys":"sz"},{"dm":"605378","mc":"野马电池","jys":"sh"},{"dm":"300132","mc":"青松股份","jys":"sz"},{"dm":"300469","mc":"信息发展","jys":"sz"},{"dm":"000703","mc":"恒逸石化","jys":"sz"},{"dm":"600756","mc":"浪潮软件","jys":"sh"},{"dm":"300082","mc":"奥克股份","jys":"sz"},{"dm":"002970","mc":"锐明技术","jys":"sz"},{"dm":"001267","mc":"汇绿生态","jys":"sz"},{"dm":"688348","mc":"昱能科技","jys":"sh"},{"dm":"002264","mc":"新 华 都","jys":"sz"},{"dm":"301031","mc":"中熔电气","jys":"sz"},{"dm":"000063","mc":"中兴通讯","jys":"sz"},{"dm":"300801","mc":"泰和科技","jys":"sz"},{"dm":"002910","mc":"庄园牧场","jys":"sz"},{"dm":"300065","mc":"海兰信","jys":"sz"},{"dm":"002455","mc":"百川股份","jys":"sz"},{"dm":"300811","mc":"铂科新材","jys":"sz"},{"dm":"600822","mc":"上海物贸","jys":"sh"},{"dm":"002789","mc":"建艺集团","jys":"sz"},{"dm":"000019","mc":"深粮控股","jys":"sz"},{"dm":"300718","mc":"长盛轴承","jys":"sz"},{"dm":"688627","mc":"精智达","jys":"sh"},{"dm":"300444","mc":"双杰电气","jys":"sz"},{"dm":"688331","mc":"荣昌生物","jys":"sh"},{"dm":"688199","mc":"久日新材","jys":"sh"},{"dm":"301107","mc":"瑜欣电子","jys":"sz"},{"dm":"600399","mc":"抚顺特钢","jys":"sh"},{"dm":"002601","mc":"龙佰集团","jys":"sz"},{"dm":"688608","mc":"恒玄科技","jys":"sh"},{"dm":"600249","mc":"两面针","jys":"sh"},{"dm":"600107","mc":"美尔雅","jys":"sh"},{"dm":"688161","mc":"威高骨科","jys":"sh"},{"dm":"301362","mc":"民爆光电","jys":"sz"},{"dm":"605196","mc":"华通线缆","jys":"sh"},{"dm":"001208","mc":"华菱线缆","jys":"sz"},{"dm":"002185","mc":"华天科技","jys":"sz"},{"dm":"002730","mc":"电光科技","jys":"sz"},{"dm":"002129","mc":"TCL中环","jys":"sz"},{"dm":"688037","mc":"芯源微","jys":"sh"},{"dm":"002861","mc":"瀛通通讯","jys":"sz"},{"dm":"301155","mc":"海力风电","jys":"sz"},{"dm":"600234","mc":"科新发展","jys":"sh"},{"dm":"300906","mc":"日月明","jys":"sz"},{"dm":"002374","mc":"中锐股份","jys":"sz"},{"dm":"688015","mc":"交控科技","jys":"sh"},{"dm":"002660","mc":"茂硕电源","jys":"sz"},{"dm":"301386","mc":"未来电器","jys":"sz"},{"dm":"600188","mc":"兖矿能源","jys":"sh"},{"dm":"603507","mc":"振江股份","jys":"sh"},{"dm":"000151","mc":"中成股份","jys":"sz"},{"dm":"300071","mc":"福石控股","jys":"sz"},{"dm":"300542","mc":"新晨科技","jys":"sz"},{"dm":"002614","mc":"奥佳华","jys":"sz"},{"dm":"300412","mc":"迦南科技","jys":"sz"},{"dm":"000921","mc":"海信家电","jys":"sz"},{"dm":"001324","mc":"长青科技","jys":"sz"},{"dm":"300655","mc":"晶瑞电材","jys":"sz"},{"dm":"301213","mc":"观想科技","jys":"sz"},{"dm":"300688","mc":"创业黑马","jys":"sz"},{"dm":"688398","mc":"赛特新材","jys":"sh"},{"dm":"002980","mc":"华盛昌","jys":"sz"},{"dm":"603228","mc":"景旺电子","jys":"sh"},{"dm":"300040","mc":"九洲集团","jys":"sz"},{"dm":"601608","mc":"中信重工","jys":"sh"},{"dm":"601011","mc":"宝泰隆","jys":"sh"},{"dm":"300212","mc":"易华录","jys":"sz"},{"dm":"600990","mc":"四创电子","jys":"sh"},{"dm":"300586","mc":"美联新材","jys":"sz"},{"dm":"601777","mc":"力帆科技","jys":"sh"},{"dm":"000156","mc":"华数传媒","jys":"sz"},{"dm":"688682","mc":"霍莱沃","jys":"sh"},{"dm":"002575","mc":"群兴玩具","jys":"sz"},{"dm":"000735","mc":"罗 牛 山","jys":"sz"},{"dm":"600109","mc":"国金证券","jys":"sh"},{"dm":"601218","mc":"吉鑫科技","jys":"sh"},{"dm":"688139","mc":"海尔生物","jys":"sh"},{"dm":"000036","mc":"华联控股","jys":"sz"},{"dm":"300993","mc":"玉马遮阳","jys":"sz"},{"dm":"002819","mc":"东方中科","jys":"sz"},{"dm":"688106","mc":"金宏气体","jys":"sh"},{"dm":"300940","mc":"南极光","jys":"sz"},{"dm":"601811","mc":"新华文轩","jys":"sh"},{"dm":"600560","mc":"金自天正","jys":"sh"},{"dm":"002500","mc":"山西证券","jys":"sz"},{"dm":"000850","mc":"华茂股份","jys":"sz"},{"dm":"688234","mc":"天岳先进","jys":"sh"},{"dm":"002381","mc":"双箭股份","jys":"sz"},{"dm":"603798","mc":"康普顿","jys":"sh"},{"dm":"301373","mc":"凌玮科技","jys":"sz"},{"dm":"603331","mc":"百达精工","jys":"sh"},{"dm":"600235","mc":"民丰特纸","jys":"sh"},{"dm":"002913","mc":"奥士康","jys":"sz"},{"dm":"300862","mc":"蓝盾光电","jys":"sz"},{"dm":"600356","mc":"恒丰纸业","jys":"sh"},{"dm":"002093","mc":"国脉科技","jys":"sz"},{"dm":"688586","mc":"江航装备","jys":"sh"},{"dm":"603903","mc":"中持股份","jys":"sh"},{"dm":"300346","mc":"南大光电","jys":"sz"},{"dm":"603018","mc":"华设集团","jys":"sh"},{"dm":"000833","mc":"粤桂股份","jys":"sz"},{"dm":"601007","mc":"金陵饭店","jys":"sh"},{"dm":"300952","mc":"恒辉安防","jys":"sz"},{"dm":"600379","mc":"宝光股份","jys":"sh"},{"dm":"688251","mc":"井松智能","jys":"sh"},{"dm":"301150","mc":"中一科技","jys":"sz"},{"dm":"300845","mc":"捷安高科","jys":"sz"},{"dm":"603520","mc":"司太立","jys":"sh"},{"dm":"605089","mc":"味知香","jys":"sh"},{"dm":"000636","mc":"风华高科","jys":"sz"},{"dm":"300700","mc":"岱勒新材","jys":"sz"},{"dm":"688395","mc":"正弦电气","jys":"sh"},{"dm":"603290","mc":"斯达半导","jys":"sh"},{"dm":"603022","mc":"新通联","jys":"sh"},{"dm":"301233","mc":"盛帮股份","jys":"sz"},{"dm":"600850","mc":"电科数字","jys":"sh"},{"dm":"002877","mc":"智能自控","jys":"sz"},{"dm":"300670","mc":"大烨智能","jys":"sz"},{"dm":"605117","mc":"德业股份","jys":"sh"},{"dm":"603261","mc":"立航科技","jys":"sh"},{"dm":"002042","mc":"华孚时尚","jys":"sz"},{"dm":"688116","mc":"天奈科技","jys":"sh"},{"dm":"600421","mc":"华嵘控股","jys":"sh"},{"dm":"688056","mc":"莱伯泰科","jys":"sh"},{"dm":"600386","mc":"北巴传媒","jys":"sh"},{"dm":"301199","mc":"迈赫股份","jys":"sz"},{"dm":"000701","mc":"厦门信达","jys":"sz"},{"dm":"000338","mc":"潍柴动力","jys":"sz"},{"dm":"603066","mc":"音飞储存","jys":"sh"},{"dm":"300449","mc":"汉邦高科","jys":"sz"},{"dm":"603596","mc":"伯特利","jys":"sh"},{"dm":"600906","mc":"财达证券","jys":"sh"},{"dm":"300165","mc":"天瑞仪器","jys":"sz"},{"dm":"002830","mc":"名雕股份","jys":"sz"},{"dm":"000400","mc":"许继电气","jys":"sz"},{"dm":"000851","mc":"高鸿股份","jys":"sz"},{"dm":"000408","mc":"藏格矿业","jys":"sz"},{"dm":"002625","mc":"光启技术","jys":"sz"},{"dm":"300640","mc":"德艺文创","jys":"sz"},{"dm":"688390","mc":"固德威","jys":"sh"},{"dm":"603636","mc":"南威软件","jys":"sh"},{"dm":"300746","mc":"汉嘉设计","jys":"sz"},{"dm":"002364","mc":"中恒电气","jys":"sz"},{"dm":"688380","mc":"中微半导","jys":"sh"},{"dm":"300518","mc":"新迅达","jys":"sz"},{"dm":"002588","mc":"史丹利","jys":"sz"},{"dm":"002021","mc":"*ST中捷","jys":"sz"},{"dm":"603088","mc":"宁波精达","jys":"sh"},{"dm":"301291","mc":"明阳电气","jys":"sz"},{"dm":"002344","mc":"海宁皮城","jys":"sz"},{"dm":"603182","mc":"嘉华股份","jys":"sh"},{"dm":"000698","mc":"沈阳化工","jys":"sz"},{"dm":"300095","mc":"华伍股份","jys":"sz"},{"dm":"605005","mc":"合兴股份","jys":"sh"},{"dm":"600126","mc":"杭钢股份","jys":"sh"},{"dm":"301137","mc":"哈焊华通","jys":"sz"},{"dm":"002637","mc":"赞宇科技","jys":"sz"},{"dm":"300136","mc":"信维通信","jys":"sz"},{"dm":"600220","mc":"江苏阳光","jys":"sh"},{"dm":"002689","mc":"远大智能","jys":"sz"},{"dm":"300531","mc":"优博讯","jys":"sz"},{"dm":"002236","mc":"大华股份","jys":"sz"},{"dm":"600676","mc":"交运股份","jys":"sh"},{"dm":"600962","mc":"国投中鲁","jys":"sh"},{"dm":"688528","mc":"秦川物联","jys":"sh"},{"dm":"000403","mc":"派林生物","jys":"sz"},{"dm":"002534","mc":"西子洁能","jys":"sz"},{"dm":"301193","mc":"家联科技","jys":"sz"},{"dm":"688768","mc":"容知日新","jys":"sh"},{"dm":"600881","mc":"亚泰集团","jys":"sh"},{"dm":"002221","mc":"东华能源","jys":"sz"},{"dm":"300190","mc":"维尔利","jys":"sz"},{"dm":"300008","mc":"天海防务","jys":"sz"},{"dm":"002072","mc":"凯瑞德","jys":"sz"},{"dm":"600203","mc":"福日电子","jys":"sh"},{"dm":"603893","mc":"瑞芯微","jys":"sh"},{"dm":"600996","mc":"贵广网络","jys":"sh"},{"dm":"300759","mc":"康龙化成","jys":"sz"},{"dm":"600590","mc":"泰豪科技","jys":"sh"},{"dm":"300295","mc":"三六五网","jys":"sz"},{"dm":"688165","mc":"埃夫特-U","jys":"sh"},{"dm":"300673","mc":"佩蒂股份","jys":"sz"},{"dm":"600797","mc":"浙大网新","jys":"sh"},{"dm":"300411","mc":"金盾股份","jys":"sz"},{"dm":"603938","mc":"三孚股份","jys":"sh"},{"dm":"688665","mc":"四方光电","jys":"sh"},{"dm":"002302","mc":"西部建设","jys":"sz"},{"dm":"000557","mc":"西部创业","jys":"sz"},{"dm":"000532","mc":"华金资本","jys":"sz"},{"dm":"688597","mc":"煜邦电力","jys":"sh"},{"dm":"000430","mc":"张家界","jys":"sz"},{"dm":"001311","mc":"多利科技","jys":"sz"},{"dm":"600318","mc":"新力金融","jys":"sh"},{"dm":"002208","mc":"合肥城建","jys":"sz"},{"dm":"301318","mc":"维海德","jys":"sz"},{"dm":"002449","mc":"国星光电","jys":"sz"},{"dm":"002923","mc":"润都股份","jys":"sz"},{"dm":"002833","mc":"弘亚数控","jys":"sz"},{"dm":"000913","mc":"钱江摩托","jys":"sz"},{"dm":"300047","mc":"天源迪科","jys":"sz"},{"dm":"300479","mc":"神思电子","jys":"sz"},{"dm":"002617","mc":"露笑科技","jys":"sz"},{"dm":"002144","mc":"宏达高科","jys":"sz"},{"dm":"688981","mc":"中芯国际","jys":"sh"},{"dm":"301519","mc":"舜禹股份","jys":"sz"},{"dm":"002866","mc":"传艺科技","jys":"sz"},{"dm":"603717","mc":"天域生态","jys":"sh"},{"dm":"002155","mc":"湖南黄金","jys":"sz"},{"dm":"600444","mc":"国机通用","jys":"sh"},{"dm":"603408","mc":"建霖家居","jys":"sh"},{"dm":"603101","mc":"汇嘉时代","jys":"sh"},{"dm":"600278","mc":"东方创业","jys":"sh"},{"dm":"688135","mc":"利扬芯片","jys":"sh"},{"dm":"603790","mc":"雅运股份","jys":"sh"},{"dm":"002841","mc":"视源股份","jys":"sz"},{"dm":"000723","mc":"美锦能源","jys":"sz"},{"dm":"688092","mc":"爱科科技","jys":"sh"},{"dm":"688315","mc":"诺禾致源","jys":"sh"},{"dm":"300724","mc":"捷佳伟创","jys":"sz"},{"dm":"688656","mc":"浩欧博","jys":"sh"},{"dm":"603600","mc":"永艺股份","jys":"sh"},{"dm":"301148","mc":"嘉戎技术","jys":"sz"},{"dm":"603288","mc":"海天味业","jys":"sh"},{"dm":"688271","mc":"联影医疗","jys":"sh"},{"dm":"603069","mc":"海汽集团","jys":"sh"},{"dm":"001225","mc":"和泰机电","jys":"sz"},{"dm":"603499","mc":"翔港科技","jys":"sh"},{"dm":"000008","mc":"神州高铁","jys":"sz"},{"dm":"002267","mc":"陕天然气","jys":"sz"},{"dm":"000877","mc":"天山股份","jys":"sz"},{"dm":"600152","mc":"维科技术","jys":"sh"},{"dm":"603133","mc":"*ST碳元","jys":"sh"},{"dm":"688659","mc":"元琛科技","jys":"sh"},{"dm":"600327","mc":"大东方","jys":"sh"},{"dm":"300860","mc":"锋尚文化","jys":"sz"},{"dm":"001323","mc":"慕思股份","jys":"sz"},{"dm":"002890","mc":"弘宇股份","jys":"sz"},{"dm":"603696","mc":"安记食品","jys":"sh"},{"dm":"600736","mc":"苏州高新","jys":"sh"},{"dm":"300238","mc":"冠昊生物","jys":"sz"},{"dm":"300243","mc":"瑞丰高材","jys":"sz"},{"dm":"300756","mc":"金马游乐","jys":"sz"},{"dm":"300865","mc":"大宏立","jys":"sz"},{"dm":"688089","mc":"嘉必优","jys":"sh"},{"dm":"688350","mc":"富淼科技","jys":"sh"},{"dm":"000901","mc":"航天科技","jys":"sz"},{"dm":"002079","mc":"苏州固锝","jys":"sz"},{"dm":"301076","mc":"新瀚新材","jys":"sz"},{"dm":"300706","mc":"阿石创","jys":"sz"},{"dm":"002095","mc":"生 意 宝","jys":"sz"},{"dm":"000715","mc":"中兴商业","jys":"sz"},{"dm":"600210","mc":"紫江企业","jys":"sh"},{"dm":"002746","mc":"仙坛股份","jys":"sz"},{"dm":"301000","mc":"肇民科技","jys":"sz"},{"dm":"603068","mc":"博通集成","jys":"sh"},{"dm":"600819","mc":"耀皮玻璃","jys":"sh"},{"dm":"300852","mc":"四会富仕","jys":"sz"},{"dm":"301138","mc":"华研精机","jys":"sz"},{"dm":"300222","mc":"科大智能","jys":"sz"},{"dm":"002999","mc":"天禾股份","jys":"sz"},{"dm":"603968","mc":"醋化股份","jys":"sh"},{"dm":"603815","mc":"交建股份","jys":"sh"},{"dm":"600103","mc":"青山纸业","jys":"sh"},{"dm":"300402","mc":"宝色股份","jys":"sz"},{"dm":"000812","mc":"陕西金叶","jys":"sz"},{"dm":"603931","mc":"格林达","jys":"sh"},{"dm":"601010","mc":"文峰股份","jys":"sh"},{"dm":"688244","mc":"永信至诚","jys":"sh"},{"dm":"001366","mc":"播恩集团","jys":"sz"},{"dm":"300786","mc":"国林科技","jys":"sz"},{"dm":"002603","mc":"以岭药业","jys":"sz"},{"dm":"002645","mc":"华宏科技","jys":"sz"},{"dm":"002783","mc":"凯龙股份","jys":"sz"},{"dm":"002666","mc":"德联集团","jys":"sz"},{"dm":"600637","mc":"东方明珠","jys":"sh"},{"dm":"603041","mc":"美思德","jys":"sh"},{"dm":"600893","mc":"航发动力","jys":"sh"},{"dm":"300458","mc":"全志科技","jys":"sz"},{"dm":"603227","mc":"雪峰科技","jys":"sh"},{"dm":"300527","mc":"中船应急","jys":"sz"},{"dm":"688069","mc":"德林海","jys":"sh"},{"dm":"600259","mc":"广晟有色","jys":"sh"},{"dm":"600777","mc":"新潮能源","jys":"sh"},{"dm":"600829","mc":"人民同泰","jys":"sh"},{"dm":"688016","mc":"心脉医疗","jys":"sh"},{"dm":"600540","mc":"新赛股份","jys":"sh"},{"dm":"000090","mc":"天健集团","jys":"sz"},{"dm":"301056","mc":"森赫股份","jys":"sz"},{"dm":"002158","mc":"汉钟精机","jys":"sz"},{"dm":"300966","mc":"共同药业","jys":"sz"},{"dm":"300787","mc":"海能实业","jys":"sz"},{"dm":"688193","mc":"仁度生物","jys":"sh"},{"dm":"301303","mc":"真兰仪表","jys":"sz"},{"dm":"600636","mc":"国新文化","jys":"sh"},{"dm":"600512","mc":"腾达建设","jys":"sh"},{"dm":"002542","mc":"中化岩土","jys":"sz"},{"dm":"002620","mc":"瑞和股份","jys":"sz"},{"dm":"002383","mc":"合众思壮","jys":"sz"},{"dm":"300438","mc":"鹏辉能源","jys":"sz"},{"dm":"000830","mc":"鲁西化工","jys":"sz"},{"dm":"300656","mc":"民德电子","jys":"sz"},{"dm":"600348","mc":"华阳股份","jys":"sh"},{"dm":"002502","mc":"ST鼎龙","jys":"sz"},{"dm":"603687","mc":"大胜达","jys":"sh"},{"dm":"301192","mc":"泰祥股份","jys":"sz"},{"dm":"301156","mc":"美农生物","jys":"sz"},{"dm":"300234","mc":"开尔新材","jys":"sz"},{"dm":"688203","mc":"海正生材","jys":"sh"},{"dm":"301510","mc":"固高科技","jys":"sz"},{"dm":"603399","mc":"吉翔股份","jys":"sh"},{"dm":"600643","mc":"爱建集团","jys":"sh"},{"dm":"688469","mc":"中芯集成-U","jys":"sh"},{"dm":"000600","mc":"建投能源","jys":"sz"},{"dm":"000989","mc":"九 芝 堂","jys":"sz"},{"dm":"002616","mc":"长青集团","jys":"sz"},{"dm":"688133","mc":"泰坦科技","jys":"sh"},{"dm":"002241","mc":"歌尔股份","jys":"sz"},{"dm":"688335","mc":"复洁环保","jys":"sh"},{"dm":"002869","mc":"金溢科技","jys":"sz"},{"dm":"605589","mc":"圣泉集团","jys":"sh"},{"dm":"300901","mc":"中胤时尚","jys":"sz"},{"dm":"688101","mc":"三达膜","jys":"sh"},{"dm":"300886","mc":"华业香料","jys":"sz"},{"dm":"301028","mc":"东亚机械","jys":"sz"},{"dm":"688038","mc":"中科通达","jys":"sh"},{"dm":"002901","mc":"大博医疗","jys":"sz"},{"dm":"600371","mc":"万向德农","jys":"sh"},{"dm":"300508","mc":"维宏股份","jys":"sz"},{"dm":"002153","mc":"石基信息","jys":"sz"},{"dm":"605366","mc":"宏柏新材","jys":"sh"},{"dm":"600678","mc":"四川金顶","jys":"sh"},{"dm":"002104","mc":"恒宝股份","jys":"sz"},{"dm":"300097","mc":"智云股份","jys":"sz"},{"dm":"600195","mc":"中牧股份","jys":"sh"},{"dm":"002832","mc":"比音勒芬","jys":"sz"},{"dm":"603085","mc":"天成自控","jys":"sh"},{"dm":"000066","mc":"中国长城","jys":"sz"},{"dm":"000990","mc":"诚志股份","jys":"sz"},{"dm":"002523","mc":"天桥起重","jys":"sz"},{"dm":"002718","mc":"友邦吊顶","jys":"sz"},{"dm":"301273","mc":"瑞晨环保","jys":"sz"},{"dm":"688381","mc":"帝奥微","jys":"sh"},{"dm":"002559","mc":"亚威股份","jys":"sz"},{"dm":"300984","mc":"金沃股份","jys":"sz"},{"dm":"603963","mc":"大理药业","jys":"sh"},{"dm":"300641","mc":"正丹股份","jys":"sz"},{"dm":"000612","mc":"焦作万方","jys":"sz"},{"dm":"688479","mc":"友车科技","jys":"sh"},{"dm":"002014","mc":"永新股份","jys":"sz"},{"dm":"600439","mc":"瑞贝卡","jys":"sh"},{"dm":"002945","mc":"华林证券","jys":"sz"},{"dm":"002330","mc":"得利斯","jys":"sz"},{"dm":"002164","mc":"宁波东力","jys":"sz"},{"dm":"600271","mc":"航天信息","jys":"sh"},{"dm":"001286","mc":"陕西能源","jys":"sz"},{"dm":"688728","mc":"格科微","jys":"sh"},{"dm":"300187","mc":"永清环保","jys":"sz"},{"dm":"601101","mc":"昊华能源","jys":"sh"},{"dm":"600844","mc":"丹化科技","jys":"sh"},{"dm":"301295","mc":"美硕科技","jys":"sz"},{"dm":"001227","mc":"兰州银行","jys":"sz"},{"dm":"603882","mc":"金域医学","jys":"sh"},{"dm":"002646","mc":"天佑德酒","jys":"sz"},{"dm":"000783","mc":"长江证券","jys":"sz"},{"dm":"300191","mc":"潜能恒信","jys":"sz"},{"dm":"601956","mc":"东贝集团","jys":"sh"},{"dm":"300849","mc":"锦盛新材","jys":"sz"},{"dm":"300665","mc":"飞鹿股份","jys":"sz"},{"dm":"600397","mc":"安源煤业","jys":"sh"},{"dm":"002586","mc":"*ST围海","jys":"sz"},{"dm":"300125","mc":"聆达股份","jys":"sz"},{"dm":"688367","mc":"工大高科","jys":"sh"},{"dm":"300152","mc":"新动力","jys":"sz"},{"dm":"301120","mc":"新特电气","jys":"sz"},{"dm":"601633","mc":"长城汽车","jys":"sh"},{"dm":"301176","mc":"逸豪新材","jys":"sz"},{"dm":"002492","mc":"恒基达鑫","jys":"sz"},{"dm":"301108","mc":"洁雅股份","jys":"sz"},{"dm":"301330","mc":"熵基科技","jys":"sz"},{"dm":"300311","mc":"任子行","jys":"sz"},{"dm":"300359","mc":"全通教育","jys":"sz"},{"dm":"002717","mc":"岭南股份","jys":"sz"},{"dm":"300566","mc":"激智科技","jys":"sz"},{"dm":"688599","mc":"天合光能","jys":"sh"},{"dm":"300740","mc":"水羊股份","jys":"sz"},{"dm":"603339","mc":"四方科技","jys":"sh"},{"dm":"003042","mc":"中农联合","jys":"sz"},{"dm":"300892","mc":"品渥食品","jys":"sz"},{"dm":"002044","mc":"美年健康","jys":"sz"},{"dm":"300675","mc":"建科院","jys":"sz"},{"dm":"301215","mc":"中汽股份","jys":"sz"},{"dm":"301011","mc":"华立科技","jys":"sz"},{"dm":"000605","mc":"渤海股份","jys":"sz"},{"dm":"603630","mc":"拉芳家化","jys":"sh"},{"dm":"600184","mc":"光电股份","jys":"sh"},{"dm":"300420","mc":"五洋停车","jys":"sz"},{"dm":"300514","mc":"友讯达","jys":"sz"},{"dm":"300224","mc":"正海磁材","jys":"sz"},{"dm":"603112","mc":"华翔股份","jys":"sh"},{"dm":"300422","mc":"博世科","jys":"sz"},{"dm":"600633","mc":"浙数文化","jys":"sh"},{"dm":"300947","mc":"德必集团","jys":"sz"},{"dm":"300145","mc":"中金环境","jys":"sz"},{"dm":"300087","mc":"荃银高科","jys":"sz"},{"dm":"002328","mc":"新朋股份","jys":"sz"},{"dm":"300067","mc":"安诺其","jys":"sz"},{"dm":"000816","mc":"智慧农业","jys":"sz"},{"dm":"605208","mc":"永茂泰","jys":"sh"},{"dm":"688369","mc":"致远互联","jys":"sh"},{"dm":"300262","mc":"巴安水务","jys":"sz"},{"dm":"002721","mc":"*ST金一","jys":"sz"},{"dm":"000545","mc":"金浦钛业","jys":"sz"},{"dm":"003011","mc":"海象新材","jys":"sz"},{"dm":"002556","mc":"辉隆股份","jys":"sz"},{"dm":"002314","mc":"南山控股","jys":"sz"},{"dm":"301163","mc":"宏德股份","jys":"sz"},{"dm":"300195","mc":"长荣股份","jys":"sz"},{"dm":"000716","mc":"黑芝麻","jys":"sz"},{"dm":"000513","mc":"丽珠集团","jys":"sz"},{"dm":"000908","mc":"景峰医药","jys":"sz"},{"dm":"600033","mc":"福建高速","jys":"sh"},{"dm":"003036","mc":"泰坦股份","jys":"sz"},{"dm":"300384","mc":"三联虹普","jys":"sz"},{"dm":"600284","mc":"浦东建设","jys":"sh"},{"dm":"000523","mc":"广州浪奇","jys":"sz"},{"dm":"603869","mc":"新智认知","jys":"sh"},{"dm":"002132","mc":"恒星科技","jys":"sz"},{"dm":"301010","mc":"晶雪节能","jys":"sz"},{"dm":"688013","mc":"天臣医疗","jys":"sh"},{"dm":"300876","mc":"蒙泰高新","jys":"sz"},{"dm":"000157","mc":"中联重科","jys":"sz"},{"dm":"002544","mc":"普天科技","jys":"sz"},{"dm":"600846","mc":"同济科技","jys":"sh"},{"dm":"001216","mc":"华瓷股份","jys":"sz"},{"dm":"605033","mc":"美邦股份","jys":"sh"},{"dm":"301287","mc":"康力源","jys":"sz"},{"dm":"688455","mc":"科捷智能","jys":"sh"},{"dm":"003022","mc":"联泓新科","jys":"sz"},{"dm":"688172","mc":"燕东微","jys":"sh"},{"dm":"300114","mc":"中航电测","jys":"sz"},{"dm":"002092","mc":"中泰化学","jys":"sz"},{"dm":"300692","mc":"中环环保","jys":"sz"},{"dm":"300927","mc":"江天化学","jys":"sz"},{"dm":"603216","mc":"梦天家居","jys":"sh"},{"dm":"600963","mc":"岳阳林纸","jys":"sh"},{"dm":"600623","mc":"华谊集团","jys":"sh"},{"dm":"603153","mc":"上海建科","jys":"sh"},{"dm":"688386","mc":"泛亚微透","jys":"sh"},{"dm":"601616","mc":"广电电气","jys":"sh"},{"dm":"600257","mc":"大湖股份","jys":"sh"},{"dm":"000922","mc":"佳电股份","jys":"sz"},{"dm":"603070","mc":"万控智造","jys":"sh"},{"dm":"300962","mc":"中金辐照","jys":"sz"},{"dm":"000722","mc":"湖南发展","jys":"sz"},{"dm":"603706","mc":"东方环宇","jys":"sh"},{"dm":"600339","mc":"中油工程","jys":"sh"},{"dm":"600818","mc":"中路股份","jys":"sh"},{"dm":"600438","mc":"通威股份","jys":"sh"},{"dm":"301127","mc":"天源环保","jys":"sz"},{"dm":"605268","mc":"王力安防","jys":"sh"},{"dm":"600979","mc":"广安爱众","jys":"sh"},{"dm":"002388","mc":"新亚制程","jys":"sz"},{"dm":"300626","mc":"华瑞股份","jys":"sz"},{"dm":"600420","mc":"国药现代","jys":"sh"},{"dm":"600343","mc":"航天动力","jys":"sh"},{"dm":"300096","mc":"易联众","jys":"sz"},{"dm":"000531","mc":"穗恒运A","jys":"sz"},{"dm":"688198","mc":"佰仁医疗","jys":"sh"},{"dm":"688355","mc":"明志科技","jys":"sh"},{"dm":"600705","mc":"中航产融","jys":"sh"},{"dm":"300905","mc":"宝丽迪","jys":"sz"},{"dm":"605287","mc":"德才股份","jys":"sh"},{"dm":"688432","mc":"有研硅","jys":"sh"},{"dm":"300567","mc":"精测电子","jys":"sz"},{"dm":"603065","mc":"宿迁联盛","jys":"sh"},{"dm":"301290","mc":"东星医疗","jys":"sz"},{"dm":"603739","mc":"蔚蓝生物","jys":"sh"},{"dm":"600573","mc":"惠泉啤酒","jys":"sh"},{"dm":"300854","mc":"中兰环保","jys":"sz"},{"dm":"000838","mc":"财信发展","jys":"sz"},{"dm":"301281","mc":"科源制药","jys":"sz"},{"dm":"002066","mc":"瑞泰科技","jys":"sz"},{"dm":"600579","mc":"克劳斯","jys":"sh"},{"dm":"600794","mc":"保税科技","jys":"sh"},{"dm":"000544","mc":"中原环保","jys":"sz"},{"dm":"600392","mc":"盛和资源","jys":"sh"},{"dm":"000863","mc":"三湘印象","jys":"sz"},{"dm":"301212","mc":"联盛化学","jys":"sz"},{"dm":"600570","mc":"恒生电子","jys":"sh"},{"dm":"301180","mc":"万祥科技","jys":"sz"},{"dm":"301061","mc":"匠心家居","jys":"sz"},{"dm":"002982","mc":"湘佳股份","jys":"sz"},{"dm":"300789","mc":"唐源电气","jys":"sz"},{"dm":"300324","mc":"旋极信息","jys":"sz"},{"dm":"301209","mc":"联合化学","jys":"sz"},{"dm":"688563","mc":"航材股份","jys":"sh"},{"dm":"300118","mc":"东方日升","jys":"sz"},{"dm":"605068","mc":"明新旭腾","jys":"sh"},{"dm":"601058","mc":"赛轮轮胎","jys":"sh"},{"dm":"600282","mc":"南钢股份","jys":"sh"},{"dm":"002392","mc":"北京利尔","jys":"sz"},{"dm":"301220","mc":"亚香股份","jys":"sz"},{"dm":"600918","mc":"中泰证券","jys":"sh"},{"dm":"600212","mc":"绿能慧充","jys":"sh"},{"dm":"300259","mc":"新天科技","jys":"sz"},{"dm":"002635","mc":"安洁科技","jys":"sz"},{"dm":"002549","mc":"凯美特气","jys":"sz"},{"dm":"600668","mc":"尖峰集团","jys":"sh"},{"dm":"000069","mc":"华侨城A","jys":"sz"},{"dm":"600120","mc":"浙江东方","jys":"sh"},{"dm":"002908","mc":"德生科技","jys":"sz"},{"dm":"001287","mc":"中电港","jys":"sz"},{"dm":"603255","mc":"鼎际得","jys":"sh"},{"dm":"300739","mc":"明阳电路","jys":"sz"},{"dm":"000750","mc":"国海证券","jys":"sz"},{"dm":"300741","mc":"华宝股份","jys":"sz"},{"dm":"002606","mc":"大连电瓷","jys":"sz"},{"dm":"002925","mc":"盈趣科技","jys":"sz"},{"dm":"300793","mc":"佳禾智能","jys":"sz"},{"dm":"603839","mc":"安正时尚","jys":"sh"},{"dm":"600976","mc":"健民集团","jys":"sh"},{"dm":"603324","mc":"盛剑环境","jys":"sh"},{"dm":"600080","mc":"金花股份","jys":"sh"},{"dm":"000633","mc":"合金投资","jys":"sz"},{"dm":"603051","mc":"鹿山新材","jys":"sh"},{"dm":"002346","mc":"柘中股份","jys":"sz"},{"dm":"600127","mc":"金健米业","jys":"sh"},{"dm":"300205","mc":"天喻信息","jys":"sz"},{"dm":"002813","mc":"路畅科技","jys":"sz"},{"dm":"000686","mc":"东北证券","jys":"sz"},{"dm":"300999","mc":"金龙鱼","jys":"sz"},{"dm":"300562","mc":"乐心医疗","jys":"sz"},{"dm":"603191","mc":"望变电气","jys":"sh"},{"dm":"601009","mc":"南京银行","jys":"sh"},{"dm":"300253","mc":"卫宁健康","jys":"sz"},{"dm":"600860","mc":"京城股份","jys":"sh"},{"dm":"301448","mc":"开创电气","jys":"sz"},{"dm":"002318","mc":"久立特材","jys":"sz"},{"dm":"600495","mc":"晋西车轴","jys":"sh"},{"dm":"001270","mc":"铖昌科技","jys":"sz"},{"dm":"002759","mc":"天际股份","jys":"sz"},{"dm":"003026","mc":"中晶科技","jys":"sz"},{"dm":"003000","mc":"劲仔食品","jys":"sz"},{"dm":"000923","mc":"河钢资源","jys":"sz"},{"dm":"002724","mc":"海洋王","jys":"sz"},{"dm":"000099","mc":"中信海直","jys":"sz"},{"dm":"601515","mc":"东风股份","jys":"sh"},{"dm":"600879","mc":"航天电子","jys":"sh"},{"dm":"000652","mc":"泰达股份","jys":"sz"},{"dm":"301345","mc":"涛涛车业","jys":"sz"},{"dm":"600836","mc":"上海易连","jys":"sh"},{"dm":"001212","mc":"中旗新材","jys":"sz"},{"dm":"601990","mc":"南京证券","jys":"sh"},{"dm":"603173","mc":"福斯达","jys":"sh"},{"dm":"301270","mc":"汉仪股份","jys":"sz"},{"dm":"002878","mc":"元隆雅图","jys":"sz"},{"dm":"300484","mc":"蓝海华腾","jys":"sz"},{"dm":"000402","mc":"金 融 街","jys":"sz"},{"dm":"300681","mc":"英搏尔","jys":"sz"},{"dm":"002650","mc":"加加食品","jys":"sz"},{"dm":"300831","mc":"派瑞股份","jys":"sz"},{"dm":"603113","mc":"金能科技","jys":"sh"},{"dm":"603933","mc":"睿能科技","jys":"sh"},{"dm":"002399","mc":"海普瑞","jys":"sz"},{"dm":"600095","mc":"湘财股份","jys":"sh"},{"dm":"300491","mc":"通合科技","jys":"sz"},{"dm":"688216","mc":"气派科技","jys":"sh"},{"dm":"600608","mc":"ST沪科","jys":"sh"},{"dm":"600982","mc":"宁波能源","jys":"sh"},{"dm":"301390","mc":"经纬股份","jys":"sz"},{"dm":"600452","mc":"涪陵电力","jys":"sh"},{"dm":"300838","mc":"浙江力诺","jys":"sz"},{"dm":"000020","mc":"深华发A","jys":"sz"},{"dm":"002227","mc":"奥 特 迅","jys":"sz"},{"dm":"000584","mc":"ST工智","jys":"sz"},{"dm":"688307","mc":"中润光学","jys":"sh"},{"dm":"688293","mc":"奥浦迈","jys":"sh"},{"dm":"000632","mc":"三木集团","jys":"sz"},{"dm":"300053","mc":"航宇微","jys":"sz"},{"dm":"688488","mc":"艾迪药业","jys":"sh"},{"dm":"300963","mc":"中洲特材","jys":"sz"},{"dm":"301078","mc":"孩子王","jys":"sz"},{"dm":"000514","mc":"渝 开 发","jys":"sz"},{"dm":"002334","mc":"英威腾","jys":"sz"},{"dm":"603810","mc":"丰山集团","jys":"sh"},{"dm":"600618","mc":"氯碱化工","jys":"sh"},{"dm":"002942","mc":"新农股份","jys":"sz"},{"dm":"300564","mc":"筑博设计","jys":"sz"},{"dm":"601022","mc":"宁波远洋","jys":"sh"},{"dm":"301198","mc":"喜悦智行","jys":"sz"},{"dm":"003008","mc":"开普检测","jys":"sz"},{"dm":"603568","mc":"伟明环保","jys":"sh"},{"dm":"600243","mc":"青海华鼎","jys":"sh"},{"dm":"301023","mc":"江南奕帆","jys":"sz"},{"dm":"300834","mc":"星辉环材","jys":"sz"},{"dm":"300960","mc":"通业科技","jys":"sz"},{"dm":"603282","mc":"亚光股份","jys":"sh"},{"dm":"600163","mc":"中闽能源","jys":"sh"},{"dm":"000755","mc":"山西路桥","jys":"sz"},{"dm":"000526","mc":"学大教育","jys":"sz"},{"dm":"688107","mc":"安路科技","jys":"sh"},{"dm":"300121","mc":"阳谷华泰","jys":"sz"},{"dm":"600663","mc":"陆家嘴","jys":"sh"},{"dm":"600650","mc":"锦江在线","jys":"sh"},{"dm":"603663","mc":"三祥新材","jys":"sh"},{"dm":"600052","mc":"东望时代","jys":"sh"},{"dm":"601398","mc":"工商银行","jys":"sh"},{"dm":"600315","mc":"上海家化","jys":"sh"},{"dm":"605339","mc":"南侨食品","jys":"sh"},{"dm":"688651","mc":"盛邦安全","jys":"sh"},{"dm":"002522","mc":"浙江众成","jys":"sz"},{"dm":"688093","mc":"世华科技","jys":"sh"},{"dm":"601068","mc":"中铝国际","jys":"sh"},{"dm":"601002","mc":"晋亿实业","jys":"sh"},{"dm":"300022","mc":"吉峰科技","jys":"sz"},{"dm":"301092","mc":"争光股份","jys":"sz"},{"dm":"301089","mc":"拓新药业","jys":"sz"},{"dm":"301040","mc":"中环海陆","jys":"sz"},{"dm":"002226","mc":"江南化工","jys":"sz"},{"dm":"300142","mc":"沃森生物","jys":"sz"},{"dm":"002737","mc":"葵花药业","jys":"sz"},{"dm":"600276","mc":"恒瑞医药","jys":"sh"},{"dm":"002145","mc":"中核钛白","jys":"sz"},{"dm":"603826","mc":"坤彩科技","jys":"sh"},{"dm":"600207","mc":"安彩高科","jys":"sh"},{"dm":"603895","mc":"天永智能","jys":"sh"},{"dm":"300487","mc":"蓝晓科技","jys":"sz"},{"dm":"300897","mc":"山科智能","jys":"sz"},{"dm":"300923","mc":"研奥股份","jys":"sz"},{"dm":"002546","mc":"新联电子","jys":"sz"},{"dm":"600535","mc":"天士力","jys":"sh"},{"dm":"000929","mc":"兰州黄河","jys":"sz"},{"dm":"603029","mc":"天鹅股份","jys":"sh"},{"dm":"001255","mc":"博菲电气","jys":"sz"},{"dm":"601816","mc":"京沪高铁","jys":"sh"},{"dm":"301055","mc":"张小泉","jys":"sz"},{"dm":"603063","mc":"禾望电气","jys":"sh"},{"dm":"002918","mc":"蒙娜丽莎","jys":"sz"},{"dm":"603721","mc":"中广天择","jys":"sh"},{"dm":"002139","mc":"拓邦股份","jys":"sz"},{"dm":"603336","mc":"宏辉果蔬","jys":"sh"},{"dm":"301189","mc":"奥尼电子","jys":"sz"},{"dm":"601568","mc":"北元集团","jys":"sh"},{"dm":"002872","mc":"ST天圣","jys":"sz"},{"dm":"000786","mc":"北新建材","jys":"sz"},{"dm":"605222","mc":"起帆电缆","jys":"sh"},{"dm":"301035","mc":"润丰股份","jys":"sz"},{"dm":"300254","mc":"仟源医药","jys":"sz"},{"dm":"002255","mc":"海陆重工","jys":"sz"},{"dm":"300174","mc":"元力股份","jys":"sz"},{"dm":"002494","mc":"华斯股份","jys":"sz"},{"dm":"300317","mc":"珈伟新能","jys":"sz"},{"dm":"300285","mc":"国瓷材料","jys":"sz"},{"dm":"603291","mc":"联合水务","jys":"sh"},{"dm":"603103","mc":"横店影视","jys":"sh"},{"dm":"300164","mc":"通源石油","jys":"sz"},{"dm":"000719","mc":"中原传媒","jys":"sz"},{"dm":"600802","mc":"福建水泥","jys":"sh"},{"dm":"300389","mc":"艾比森","jys":"sz"},{"dm":"600506","mc":"统一股份","jys":"sh"},{"dm":"300839","mc":"博汇股份","jys":"sz"},{"dm":"605178","mc":"时空科技","jys":"sh"},{"dm":"688163","mc":"赛伦生物","jys":"sh"},{"dm":"603348","mc":"文灿股份","jys":"sh"},{"dm":"603097","mc":"江苏华辰","jys":"sh"},{"dm":"002046","mc":"国机精工","jys":"sz"},{"dm":"000301","mc":"东方盛虹","jys":"sz"},{"dm":"301161","mc":"唯万密封","jys":"sz"},{"dm":"300788","mc":"中信出版","jys":"sz"},{"dm":"603214","mc":"爱婴室","jys":"sh"},{"dm":"300536","mc":"农尚环境","jys":"sz"},{"dm":"301175","mc":"中科环保","jys":"sz"},{"dm":"600525","mc":"长园集团","jys":"sh"},{"dm":"601998","mc":"中信银行","jys":"sh"},{"dm":"600149","mc":"廊坊发展","jys":"sh"},{"dm":"301418","mc":"协昌科技","jys":"sz"},{"dm":"002012","mc":"凯恩股份","jys":"sz"},{"dm":"600887","mc":"伊利股份","jys":"sh"},{"dm":"002059","mc":"云南旅游","jys":"sz"},{"dm":"000859","mc":"国风新材","jys":"sz"},{"dm":"003035","mc":"南网能源","jys":"sz"},{"dm":"002678","mc":"珠江钢琴","jys":"sz"},{"dm":"688081","mc":"兴图新科","jys":"sh"},{"dm":"688575","mc":"亚辉龙","jys":"sh"},{"dm":"600925","mc":"苏能股份","jys":"sh"},{"dm":"002121","mc":"科陆电子","jys":"sz"},{"dm":"603690","mc":"至纯科技","jys":"sh"},{"dm":"603181","mc":"皇马科技","jys":"sh"},{"dm":"300284","mc":"苏交科","jys":"sz"},{"dm":"000530","mc":"冰山冷热","jys":"sz"},{"dm":"301255","mc":"通力科技","jys":"sz"},{"dm":"300995","mc":"奇德新材","jys":"sz"},{"dm":"003003","mc":"天元股份","jys":"sz"},{"dm":"300674","mc":"宇信科技","jys":"sz"},{"dm":"300046","mc":"台基股份","jys":"sz"},{"dm":"000902","mc":"新洋丰","jys":"sz"},{"dm":"688334","mc":"西高院","jys":"sh"},{"dm":"600543","mc":"*ST莫高","jys":"sh"},{"dm":"301469","mc":"恒达新材","jys":"sz"},{"dm":"300573","mc":"兴齐眼药","jys":"sz"},{"dm":"300354","mc":"东华测试","jys":"sz"},{"dm":"603226","mc":"菲林格尔","jys":"sh"},{"dm":"688529","mc":"豪森股份","jys":"sh"},{"dm":"301158","mc":"德石股份","jys":"sz"},{"dm":"000504","mc":"南华生物","jys":"sz"},{"dm":"300134","mc":"大富科技","jys":"sz"},{"dm":"002880","mc":"卫光生物","jys":"sz"},{"dm":"300950","mc":"德固特","jys":"sz"},{"dm":"603856","mc":"东宏股份","jys":"sh"},{"dm":"605303","mc":"园林股份","jys":"sh"},{"dm":"000059","mc":"华锦股份","jys":"sz"},{"dm":"603272","mc":"联翔股份","jys":"sh"},{"dm":"300137","mc":"先河环保","jys":"sz"},{"dm":"000565","mc":"渝三峡A","jys":"sz"},{"dm":"600455","mc":"博通股份","jys":"sh"},{"dm":"002469","mc":"三维化学","jys":"sz"},{"dm":"002911","mc":"佛燃能源","jys":"sz"},{"dm":"600168","mc":"武汉控股","jys":"sh"},{"dm":"600056","mc":"中国医药","jys":"sh"},{"dm":"001300","mc":"三柏硕","jys":"sz"},{"dm":"002333","mc":"罗普斯金","jys":"sz"},{"dm":"603389","mc":"亚振家居","jys":"sh"},{"dm":"003027","mc":"同兴环保","jys":"sz"},{"dm":"002097","mc":"山河智能","jys":"sz"},{"dm":"300289","mc":"利德曼","jys":"sz"},{"dm":"300054","mc":"鼎龙股份","jys":"sz"},{"dm":"603727","mc":"博迈科","jys":"sh"},{"dm":"605305","mc":"中际联合","jys":"sh"},{"dm":"002404","mc":"嘉欣丝绸","jys":"sz"},{"dm":"300971","mc":"博亚精工","jys":"sz"},{"dm":"002987","mc":"京北方","jys":"sz"},{"dm":"603105","mc":"芯能科技","jys":"sh"},{"dm":"002696","mc":"百洋股份","jys":"sz"},{"dm":"600965","mc":"福成股份","jys":"sh"},{"dm":"300062","mc":"中能电气","jys":"sz"},{"dm":"000937","mc":"冀中能源","jys":"sz"},{"dm":"603900","mc":"莱绅通灵","jys":"sh"},{"dm":"002567","mc":"唐人神","jys":"sz"},{"dm":"300859","mc":"*ST西域","jys":"sz"},{"dm":"300385","mc":"雪浪环境","jys":"sz"},{"dm":"300510","mc":"金冠股份","jys":"sz"},{"dm":"002558","mc":"巨人网络","jys":"sz"},{"dm":"002946","mc":"新乳业","jys":"sz"},{"dm":"002065","mc":"东华软件","jys":"sz"},{"dm":"300699","mc":"光威复材","jys":"sz"},{"dm":"300635","mc":"中达安","jys":"sz"},{"dm":"002391","mc":"长青股份","jys":"sz"},{"dm":"300376","mc":"易事特","jys":"sz"},{"dm":"300374","mc":"中铁装配","jys":"sz"},{"dm":"688458","mc":"美芯晟","jys":"sh"},{"dm":"301043","mc":"绿岛风","jys":"sz"},{"dm":"603656","mc":"泰禾智能","jys":"sh"},{"dm":"002860","mc":"星帅尔","jys":"sz"},{"dm":"603356","mc":"华菱精工","jys":"sh"},{"dm":"001318","mc":"阳光乳业","jys":"sz"},{"dm":"688207","mc":"格灵深瞳","jys":"sh"},{"dm":"002444","mc":"巨星科技","jys":"sz"},{"dm":"600644","mc":"乐山电力","jys":"sh"},{"dm":"300307","mc":"慈星股份","jys":"sz"},{"dm":"601609","mc":"金田股份","jys":"sh"},{"dm":"300609","mc":"汇纳科技","jys":"sz"},{"dm":"600268","mc":"国电南自","jys":"sh"},{"dm":"002448","mc":"中原内配","jys":"sz"},{"dm":"301066","mc":"万事利","jys":"sz"},{"dm":"002927","mc":"泰永长征","jys":"sz"},{"dm":"001373","mc":"翔腾新材","jys":"sz"},{"dm":"601006","mc":"大秦铁路","jys":"sh"},{"dm":"002985","mc":"北摩高科","jys":"sz"},{"dm":"300126","mc":"锐奇股份","jys":"sz"},{"dm":"002100","mc":"天康生物","jys":"sz"},{"dm":"301166","mc":"优宁维","jys":"sz"},{"dm":"603396","mc":"金辰股份","jys":"sh"},{"dm":"002169","mc":"智光电气","jys":"sz"},{"dm":"600674","mc":"川投能源","jys":"sh"},{"dm":"600814","mc":"杭州解百","jys":"sh"},{"dm":"600111","mc":"北方稀土","jys":"sh"},{"dm":"300823","mc":"建科机械","jys":"sz"},{"dm":"600998","mc":"九州通","jys":"sh"},{"dm":"300419","mc":"浩丰科技","jys":"sz"},{"dm":"002130","mc":"沃尔核材","jys":"sz"},{"dm":"002741","mc":"光华科技","jys":"sz"},{"dm":"301232","mc":"飞沃科技","jys":"sz"},{"dm":"002171","mc":"楚江新材","jys":"sz"},{"dm":"000731","mc":"四川美丰","jys":"sz"},{"dm":"600182","mc":"S佳通","jys":"sh"},{"dm":"002693","mc":"双成药业","jys":"sz"},{"dm":"600900","mc":"长江电力","jys":"sh"},{"dm":"000831","mc":"中国稀土","jys":"sz"},{"dm":"688146","mc":"中船特气","jys":"sh"},{"dm":"688389","mc":"普门科技","jys":"sh"},{"dm":"300387","mc":"富邦股份","jys":"sz"},{"dm":"000707","mc":"双环科技","jys":"sz"},{"dm":"603444","mc":"吉比特","jys":"sh"},{"dm":"603685","mc":"晨丰科技","jys":"sh"},{"dm":"300550","mc":"和仁科技","jys":"sz"},{"dm":"300795","mc":"米奥会展","jys":"sz"},{"dm":"001333","mc":"光华股份","jys":"sz"},{"dm":"688681","mc":"科汇股份","jys":"sh"},{"dm":"688192","mc":"迪哲医药-U","jys":"sh"},{"dm":"300270","mc":"中威电子","jys":"sz"},{"dm":"002297","mc":"博云新材","jys":"sz"},{"dm":"605122","mc":"四方新材","jys":"sh"},{"dm":"001872","mc":"招商港口","jys":"sz"},{"dm":"688353","mc":"华盛锂电","jys":"sh"},{"dm":"002951","mc":"ST金时","jys":"sz"},{"dm":"002414","mc":"高德红外","jys":"sz"},{"dm":"605168","mc":"三人行","jys":"sh"},{"dm":"002891","mc":"中宠股份","jys":"sz"},{"dm":"605499","mc":"东鹏饮料","jys":"sh"},{"dm":"600980","mc":"北矿科技","jys":"sh"},{"dm":"601369","mc":"陕鼓动力","jys":"sh"},{"dm":"600927","mc":"永安期货","jys":"sh"},{"dm":"300512","mc":"中亚股份","jys":"sz"},{"dm":"301323","mc":"新莱福","jys":"sz"},{"dm":"600883","mc":"博闻科技","jys":"sh"},{"dm":"600228","mc":"返利科技","jys":"sh"},{"dm":"603110","mc":"东方材料","jys":"sh"},{"dm":"301003","mc":"江苏博云","jys":"sz"},{"dm":"002543","mc":"万和电气","jys":"sz"},{"dm":"002101","mc":"广东鸿图","jys":"sz"},{"dm":"600273","mc":"嘉化能源","jys":"sh"},{"dm":"000848","mc":"承德露露","jys":"sz"},{"dm":"603201","mc":"常润股份","jys":"sh"},{"dm":"301097","mc":"天益医疗","jys":"sz"},{"dm":"600483","mc":"福能股份","jys":"sh"},{"dm":"002403","mc":"爱仕达","jys":"sz"},{"dm":"603871","mc":"嘉友国际","jys":"sh"},{"dm":"600721","mc":"百花医药","jys":"sh"},{"dm":"000547","mc":"航天发展","jys":"sz"},{"dm":"001209","mc":"洪兴股份","jys":"sz"},{"dm":"603689","mc":"皖天然气","jys":"sh"},{"dm":"002842","mc":"翔鹭钨业","jys":"sz"},{"dm":"300806","mc":"斯迪克","jys":"sz"},{"dm":"688283","mc":"坤恒顺维","jys":"sh"},{"dm":"603275","mc":"众辰科技","jys":"sh"},{"dm":"603609","mc":"禾丰股份","jys":"sh"},{"dm":"000663","mc":"永安林业","jys":"sz"},{"dm":"603338","mc":"浙江鼎力","jys":"sh"},{"dm":"002561","mc":"徐家汇","jys":"sz"},{"dm":"002394","mc":"联发股份","jys":"sz"},{"dm":"301363","mc":"美好医疗","jys":"sz"},{"dm":"603020","mc":"爱普股份","jys":"sh"},{"dm":"600463","mc":"空港股份","jys":"sh"},{"dm":"603565","mc":"中谷物流","jys":"sh"},{"dm":"002026","mc":"山东威达","jys":"sz"},{"dm":"301316","mc":"慧博云通","jys":"sz"},{"dm":"603193","mc":"润本股份","jys":"sh"},{"dm":"688478","mc":"晶升股份","jys":"sh"},{"dm":"300749","mc":"顶固集创","jys":"sz"},{"dm":"301201","mc":"诚达药业","jys":"sz"},{"dm":"000423","mc":"东阿阿胶","jys":"sz"},{"dm":"002745","mc":"木林森","jys":"sz"},{"dm":"603680","mc":"今创集团","jys":"sh"},{"dm":"301507","mc":"民生健康","jys":"sz"},{"dm":"300639","mc":"凯普生物","jys":"sz"},{"dm":"688096","mc":"京源环保","jys":"sh"},{"dm":"603967","mc":"中创物流","jys":"sh"},{"dm":"002736","mc":"国信证券","jys":"sz"},{"dm":"603073","mc":"彩蝶实业","jys":"sh"},{"dm":"688543","mc":"国科军工","jys":"sh"},{"dm":"300052","mc":"中青宝","jys":"sz"},{"dm":"688030","mc":"山石网科","jys":"sh"},{"dm":"002670","mc":"国盛金控","jys":"sz"},{"dm":"688061","mc":"灿瑞科技","jys":"sh"},{"dm":"688685","mc":"迈信林","jys":"sh"},{"dm":"600620","mc":"天宸股份","jys":"sh"},{"dm":"603125","mc":"常青科技","jys":"sh"},{"dm":"002205","mc":"国统股份","jys":"sz"},{"dm":"603313","mc":"梦百合","jys":"sh"},{"dm":"002096","mc":"易普力","jys":"sz"},{"dm":"688287","mc":"观典防务","jys":"sh"},{"dm":"600312","mc":"平高电气","jys":"sh"},{"dm":"600764","mc":"中国海防","jys":"sh"},{"dm":"002731","mc":"萃华珠宝","jys":"sz"},{"dm":"003037","mc":"三和管桩","jys":"sz"},{"dm":"300929","mc":"华骐环保","jys":"sz"},{"dm":"688171","mc":"纬德信息","jys":"sh"},{"dm":"301217","mc":"铜冠铜箔","jys":"sz"},{"dm":"603489","mc":"八方股份","jys":"sh"},{"dm":"688602","mc":"康鹏科技","jys":"sh"},{"dm":"688006","mc":"杭可科技","jys":"sh"},{"dm":"300349","mc":"金卡智能","jys":"sz"},{"dm":"002553","mc":"南方精工","jys":"sz"},{"dm":"688352","mc":"颀中科技","jys":"sh"},{"dm":"300138","mc":"晨光生物","jys":"sz"},{"dm":"300068","mc":"南都电源","jys":"sz"},{"dm":"603365","mc":"水星家纺","jys":"sh"},{"dm":"300943","mc":"春晖智控","jys":"sz"},{"dm":"688277","mc":"天智航-U","jys":"sh"},{"dm":"301029","mc":"怡合达","jys":"sz"},{"dm":"300357","mc":"我武生物","jys":"sz"},{"dm":"603269","mc":"海鸥股份","jys":"sh"},{"dm":"603585","mc":"苏利股份","jys":"sh"},{"dm":"000681","mc":"视觉中国","jys":"sz"},{"dm":"603829","mc":"洛凯股份","jys":"sh"},{"dm":"301365","mc":"矩阵股份","jys":"sz"},{"dm":"002088","mc":"鲁阳节能","jys":"sz"},{"dm":"002552","mc":"宝鼎科技","jys":"sz"},{"dm":"300188","mc":"美亚柏科","jys":"sz"},{"dm":"603045","mc":"福达合金","jys":"sh"},{"dm":"605155","mc":"西大门","jys":"sh"},{"dm":"300804","mc":"广康生化","jys":"sz"},{"dm":"600171","mc":"上海贝岭","jys":"sh"},{"dm":"300628","mc":"亿联网络","jys":"sz"},{"dm":"301278","mc":"快可电子","jys":"sz"},{"dm":"600588","mc":"用友网络","jys":"sh"},{"dm":"300717","mc":"华信新材","jys":"sz"},{"dm":"300922","mc":"天秦装备","jys":"sz"},{"dm":"000997","mc":"新 大 陆","jys":"sz"},{"dm":"605365","mc":"立达信","jys":"sh"},{"dm":"600262","mc":"北方股份","jys":"sh"},{"dm":"300850","mc":"新强联","jys":"sz"},{"dm":"301263","mc":"泰恩康","jys":"sz"},{"dm":"688070","mc":"纵横股份","jys":"sh"},{"dm":"688678","mc":"福立旺","jys":"sh"},{"dm":"300109","mc":"新开源","jys":"sz"},{"dm":"301038","mc":"深水规院","jys":"sz"},{"dm":"603090","mc":"宏盛股份","jys":"sh"},{"dm":"605296","mc":"神农集团","jys":"sh"},{"dm":"301077","mc":"星华新材","jys":"sz"},{"dm":"688499","mc":"利元亨","jys":"sh"},{"dm":"688190","mc":"云路股份","jys":"sh"},{"dm":"300540","mc":"蜀道装备","jys":"sz"},{"dm":"603355","mc":"莱克电气","jys":"sh"},{"dm":"300779","mc":"惠城环保","jys":"sz"},{"dm":"301049","mc":"超越科技","jys":"sz"},{"dm":"002653","mc":"海思科","jys":"sz"},{"dm":"300900","mc":"广联航空","jys":"sz"},{"dm":"301182","mc":"凯旺科技","jys":"sz"},{"dm":"688370","mc":"丛麟科技","jys":"sh"},{"dm":"688255","mc":"凯尔达","jys":"sh"},{"dm":"688091","mc":"上海谊众","jys":"sh"},{"dm":"300803","mc":"指南针","jys":"sz"},{"dm":"001328","mc":"登康口腔","jys":"sz"},{"dm":"300938","mc":"信测标准","jys":"sz"},{"dm":"300033","mc":"同花顺","jys":"sz"},{"dm":"688559","mc":"海目星","jys":"sh"},{"dm":"688598","mc":"金博股份","jys":"sh"},{"dm":"688202","mc":"美迪西","jys":"sh"},{"dm":"688185","mc":"康希诺","jys":"sh"},{"dm":"688718","mc":"唯赛勃","jys":"sh"},{"dm":"688686","mc":"奥普特","jys":"sh"},{"dm":"688653","mc":"康希通信","jys":"sh"},{"dm":"688648","mc":"中邮科技","jys":"sh"},{"dm":"688638","mc":"誉辰智能","jys":"sh"},{"dm":"688585","mc":"上纬新材","jys":"sh"},{"dm":"688561","mc":"奇安信-U","jys":"sh"},{"dm":"688555","mc":"退市泽达","jys":"sh"},{"dm":"688505","mc":"复旦张江","jys":"sh"},{"dm":"688425","mc":"铁建重工","jys":"sh"},{"dm":"688119","mc":"中钢洛耐","jys":"sh"},{"dm":"688098","mc":"申联生物","jys":"sh"},{"dm":"688086","mc":"退市紫晶","jys":"sh"},{"dm":"688057","mc":"金达莱","jys":"sh"},{"dm":"605158","mc":"华达新材","jys":"sh"},{"dm":"603996","mc":"退市中新","jys":"sh"},{"dm":"603896","mc":"寿仙谷","jys":"sh"},{"dm":"603818","mc":"曲美家居","jys":"sh"},{"dm":"603816","mc":"顾家家居","jys":"sh"},{"dm":"603797","mc":"联泰环保","jys":"sh"},{"dm":"603637","mc":"镇海股份","jys":"sh"},{"dm":"603602","mc":"纵横通信","jys":"sh"},{"dm":"603577","mc":"汇金通","jys":"sh"},{"dm":"603558","mc":"健盛集团","jys":"sh"},{"dm":"603557","mc":"ST起步","jys":"sh"},{"dm":"603527","mc":"众源新材","jys":"sh"},{"dm":"603488","mc":"展鹏科技","jys":"sh"},{"dm":"603377","mc":"东方时尚","jys":"sh"},{"dm":"603361","mc":"浙江国祥","jys":"sh"},{"dm":"603359","mc":"东珠生态","jys":"sh"},{"dm":"603357","mc":"设计总院","jys":"sh"},{"dm":"603328","mc":"依顿电子","jys":"sh"},{"dm":"603326","mc":"我乐家居","jys":"sh"},{"dm":"603318","mc":"水发燃气","jys":"sh"},{"dm":"603212","mc":"赛伍技术","jys":"sh"},{"dm":"603169","mc":"兰石重装","jys":"sh"},{"dm":"603157","mc":"退市拉夏","jys":"sh"},{"dm":"603131","mc":"上海沪工","jys":"sh"},{"dm":"603122","mc":"合富中国","jys":"sh"},{"dm":"603067","mc":"振华股份","jys":"sh"},{"dm":"603025","mc":"大豪科技","jys":"sh"},{"dm":"603017","mc":"中衡设计","jys":"sh"},{"dm":"603012","mc":"创力集团","jys":"sh"},{"dm":"603011","mc":"合锻智能","jys":"sh"},{"dm":"601916","mc":"浙商银行","jys":"sh"},{"dm":"601890","mc":"亚星锚链","jys":"sh"},{"dm":"601880","mc":"辽港股份","jys":"sh"},{"dm":"601857","mc":"中国石油","jys":"sh"},{"dm":"601838","mc":"成都银行","jys":"sh"},{"dm":"601808","mc":"中海油服","jys":"sh"},{"dm":"601619","mc":"嘉泽新能","jys":"sh"},{"dm":"601606","mc":"长城军工","jys":"sh"},{"dm":"601588","mc":"北辰实业","jys":"sh"},{"dm":"601558","mc":"退市锐电","jys":"sh"},{"dm":"601518","mc":"吉林高速","jys":"sh"},{"dm":"601388","mc":"怡球资源","jys":"sh"},{"dm":"601368","mc":"绿城水务","jys":"sh"},{"dm":"601326","mc":"秦港股份","jys":"sh"},{"dm":"601299","mc":"中国北车","jys":"sh"},{"dm":"601268","mc":"*ST二重","jys":"sh"},{"dm":"601258","mc":"*ST庞大","jys":"sh"},{"dm":"601238","mc":"广汽集团","jys":"sh"},{"dm":"601229","mc":"上海银行","jys":"sh"},{"dm":"601216","mc":"君正集团","jys":"sh"},{"dm":"601199","mc":"江南水务","jys":"sh"},{"dm":"601169","mc":"北京银行","jys":"sh"},{"dm":"601158","mc":"重庆水务","jys":"sh"},{"dm":"601121","mc":"宝地矿业","jys":"sh"},{"dm":"601108","mc":"财通证券","jys":"sh"},{"dm":"601107","mc":"四川成渝","jys":"sh"},{"dm":"601106","mc":"中国一重","jys":"sh"},{"dm":"601069","mc":"西部黄金","jys":"sh"},{"dm":"601015","mc":"陕西黑猫","jys":"sh"},{"dm":"601008","mc":"连云港","jys":"sh"},{"dm":"600999","mc":"招商证券","jys":"sh"},{"dm":"600991","mc":"广汽长丰","jys":"sh"},{"dm":"600984","mc":"建设机械","jys":"sh"},{"dm":"600978","mc":"*ST宜生","jys":"sh"},{"dm":"600959","mc":"江苏有线","jys":"sh"},{"dm":"600929","mc":"雪天盐业","jys":"sh"},{"dm":"600908","mc":"无锡银行","jys":"sh"},{"dm":"600899","mc":"*ST信联","jys":"sh"},{"dm":"600898","mc":"ST美讯","jys":"sh"},{"dm":"600896","mc":"退市海医","jys":"sh"},{"dm":"600891","mc":"退市秋林","jys":"sh"},{"dm":"600890","mc":"退市中房","jys":"sh"},{"dm":"600880","mc":"博瑞传播","jys":"sh"},{"dm":"600878","mc":"*ST北科","jys":"sh"},{"dm":"600873","mc":"梅花生物","jys":"sh"},{"dm":"600870","mc":"退市厦华","jys":"sh"},{"dm":"600868","mc":"梅雁吉祥","jys":"sh"},{"dm":"600856","mc":"退市中天","jys":"sh"},{"dm":"600852","mc":"*ST中川","jys":"sh"},{"dm":"600849","mc":"上药转换","jys":"sh"},{"dm":"600848","mc":"上海临港","jys":"sh"},{"dm":"600842","mc":"中西药业","jys":"sh"},{"dm":"600840","mc":"新湖创业","jys":"sh"},{"dm":"600832","mc":"东方明珠","jys":"sh"},{"dm":"600824","mc":"益民集团","jys":"sh"},{"dm":"600823","mc":"ST世茂","jys":"sh"},{"dm":"600820","mc":"隧道股份","jys":"sh"},{"dm":"600815","mc":"厦工股份","jys":"sh"},{"dm":"600813","mc":"ST鞍一工","jys":"sh"},{"dm":"600806","mc":"退市昆机","jys":"sh"},{"dm":"600800","mc":"渤海化学","jys":"sh"},{"dm":"600799","mc":"*ST龙科","jys":"sh"},{"dm":"600798","mc":"宁波海运","jys":"sh"},{"dm":"600795","mc":"国电电力","jys":"sh"},{"dm":"600789","mc":"鲁抗医药","jys":"sh"},{"dm":"600788","mc":"*ST达曼","jys":"sh"},{"dm":"600786","mc":"东方锅炉","jys":"sh"},{"dm":"600781","mc":"退市辅仁","jys":"sh"},{"dm":"600772","mc":"S*ST龙昌","jys":"sh"},{"dm":"600767","mc":"退市运盛","jys":"sh"},{"dm":"600762","mc":"S*ST金荔","jys":"sh"},{"dm":"600752","mc":"*ST哈慈","jys":"sh"},{"dm":"600747","mc":"退市大控","jys":"sh"},{"dm":"600739","mc":"辽宁成大","jys":"sh"},{"dm":"600728","mc":"佳都科技","jys":"sh"},{"dm":"600727","mc":"鲁北化工","jys":"sh"},{"dm":"600724","mc":"宁波富达","jys":"sh"},{"dm":"600723","mc":"首商股份","jys":"sh"},{"dm":"600718","mc":"东软集团","jys":"sh"},{"dm":"600715","mc":"文投控股","jys":"sh"},{"dm":"600710","mc":"苏美达","jys":"sh"},{"dm":"600709","mc":"ST生态","jys":"sh"},{"dm":"600708","mc":"光明地产","jys":"sh"},{"dm":"600704","mc":"物产中大","jys":"sh"},{"dm":"600701","mc":"退市工新","jys":"sh"},{"dm":"600700","mc":"*ST数码","jys":"sh"},{"dm":"600695","mc":"退市绿庭","jys":"sh"},{"dm":"600693","mc":"东百集团","jys":"sh"},{"dm":"600691","mc":"阳煤化工","jys":"sh"},{"dm":"600688","mc":"上海石化","jys":"sh"},{"dm":"600687","mc":"退市刚泰","jys":"sh"},{"dm":"600682","mc":"南京新百","jys":"sh"},{"dm":"600681","mc":"百川能源","jys":"sh"},{"dm":"600680","mc":"*ST上普","jys":"sh"},{"dm":"600677","mc":"*ST航通","jys":"sh"},{"dm":"600672","mc":"*ST华圣","jys":"sh"},{"dm":"600670","mc":"*ST斯达","jys":"sh"},{"dm":"600669","mc":"*ST鞍成","jys":"sh"},{"dm":"600664","mc":"哈药股份","jys":"sh"},{"dm":"600659","mc":"*ST花雕","jys":"sh"},{"dm":"600656","mc":"退市博元","jys":"sh"},{"dm":"600652","mc":"退市游久","jys":"sh"},{"dm":"600651","mc":"飞乐音响","jys":"sh"},{"dm":"600648","mc":"外高桥","jys":"sh"},{"dm":"600646","mc":"ST国嘉","jys":"sh"},{"dm":"600634","mc":"退市富控","jys":"sh"},{"dm":"600632","mc":"华联商厦","jys":"sh"},{"dm":"600631","mc":"百联股份","jys":"sh"},{"dm":"600627","mc":"上电股份","jys":"sh"},{"dm":"600625","mc":"PT水仙","jys":"sh"},{"dm":"600624","mc":"复旦复华","jys":"sh"},{"dm":"600622","mc":"光大嘉宝","jys":"sh"},{"dm":"600614","mc":"退市鹏起","jys":"sh"},{"dm":"600607","mc":"上实医药","jys":"sh"},{"dm":"600606","mc":"绿地控股","jys":"sh"},{"dm":"600591","mc":"*ST上航","jys":"sh"},{"dm":"600577","mc":"精达股份","jys":"sh"},{"dm":"600568","mc":"ST中珠","jys":"sh"},{"dm":"600555","mc":"退市海创","jys":"sh"},{"dm":"600553","mc":"太行水泥","jys":"sh"},{"dm":"600550","mc":"保变电气","jys":"sh"},{"dm":"600545","mc":"卓郎智能","jys":"sh"},{"dm":"600539","mc":"狮头股份","jys":"sh"},{"dm":"600537","mc":"亿晶光电","jys":"sh"},{"dm":"600533","mc":"栖霞建设","jys":"sh"},{"dm":"600532","mc":"退市未来","jys":"sh"},{"dm":"600515","mc":"海南机场","jys":"sh"},{"dm":"600510","mc":"黑牡丹","jys":"sh"},{"dm":"600505","mc":"西昌电力","jys":"sh"},{"dm":"600503","mc":"华丽家族","jys":"sh"},{"dm":"600485","mc":"*ST信威","jys":"sh"},{"dm":"600472","mc":"包头铝业","jys":"sh"},{"dm":"600470","mc":"六国化工","jys":"sh"},{"dm":"600469","mc":"风神股份","jys":"sh"},{"dm":"600466","mc":"*ST蓝光","jys":"sh"},{"dm":"600462","mc":"ST九有","jys":"sh"},{"dm":"600458","mc":"时代新材","jys":"sh"},{"dm":"600432","mc":"退市吉恩","jys":"sh"},{"dm":"600403","mc":"大有能源","jys":"sh"},{"dm":"600401","mc":"退市海润","jys":"sh"},{"dm":"600400","mc":"红豆股份","jys":"sh"},{"dm":"600393","mc":"ST粤泰","jys":"sh"},{"dm":"600390","mc":"五矿资本","jys":"sh"},{"dm":"600387","mc":"ST海越","jys":"sh"},{"dm":"600385","mc":"退市金泰","jys":"sh"},{"dm":"600357","mc":"承德钒钛","jys":"sh"},{"dm":"600354","mc":"敦煌种业","jys":"sh"},{"dm":"600321","mc":"正源股份","jys":"sh"},{"dm":"600317","mc":"营口港","jys":"sh"},{"dm":"600311","mc":"*ST荣华","jys":"sh"},{"dm":"600310","mc":"广西能源","jys":"sh"},{"dm":"600307","mc":"酒钢宏兴","jys":"sh"},{"dm":"600296","mc":"S兰铝","jys":"sh"},{"dm":"600292","mc":"远达环保","jys":"sh"},{"dm":"600291","mc":"退市西水","jys":"sh"},{"dm":"600289","mc":"ST信通","jys":"sh"},{"dm":"600287","mc":"江苏舜天","jys":"sh"},{"dm":"600286","mc":"S*ST国瓷","jys":"sh"},{"dm":"600275","mc":"退市昌鱼","jys":"sh"},{"dm":"600270","mc":"外运发展","jys":"sh"},{"dm":"600263","mc":"路桥建设","jys":"sh"},{"dm":"600260","mc":"*ST凯乐","jys":"sh"},{"dm":"600253","mc":"天方药业","jys":"sh"},{"dm":"600247","mc":"*ST成城","jys":"sh"},{"dm":"600242","mc":"退市中昌","jys":"sh"},{"dm":"600240","mc":"退市华业","jys":"sh"},{"dm":"600225","mc":"卓朗科技","jys":"sh"},{"dm":"600209","mc":"退市罗顿","jys":"sh"},{"dm":"600205","mc":"S山东铝","jys":"sh"},{"dm":"600192","mc":"长城电工","jys":"sh"},{"dm":"600191","mc":"华资实业","jys":"sh"},{"dm":"600181","mc":"S*ST云大","jys":"sh"},{"dm":"600177","mc":"雅戈尔","jys":"sh"},{"dm":"600175","mc":"退市美都","jys":"sh"},{"dm":"600172","mc":"黄河旋风","jys":"sh"},{"dm":"600170","mc":"上海建工","jys":"sh"},{"dm":"600162","mc":"香江控股","jys":"sh"},{"dm":"600156","mc":"华升股份","jys":"sh"},{"dm":"600146","mc":"退市环球","jys":"sh"},{"dm":"600145","mc":"退市新亿","jys":"sh"},{"dm":"600139","mc":"*ST西源","jys":"sh"},{"dm":"600122","mc":"*ST宏图","jys":"sh"},{"dm":"600121","mc":"郑州煤电","jys":"sh"},{"dm":"600113","mc":"浙江东日","jys":"sh"},{"dm":"600112","mc":"ST天成","jys":"sh"},{"dm":"600102","mc":"莱钢股份","jys":"sh"},{"dm":"600093","mc":"退市易见","jys":"sh"},{"dm":"600092","mc":"S*ST精密","jys":"sh"},{"dm":"600091","mc":"退市明科","jys":"sh"},{"dm":"600090","mc":"退市济堂","jys":"sh"},{"dm":"600087","mc":"退市长油","jys":"sh"},{"dm":"600086","mc":"退市金钰","jys":"sh"},{"dm":"600082","mc":"海泰发展","jys":"sh"},{"dm":"600077","mc":"*ST宋都","jys":"sh"},{"dm":"600074","mc":"退市保千","jys":"sh"},{"dm":"600069","mc":"退市银鸽","jys":"sh"},{"dm":"600068","mc":"葛洲坝","jys":"sh"},{"dm":"600067","mc":"冠城大通","jys":"sh"},{"dm":"600065","mc":"*ST联谊","jys":"sh"},{"dm":"600050","mc":"中国联通","jys":"sh"},{"dm":"600035","mc":"楚天高速","jys":"sh"},{"dm":"600028","mc":"中国石化","jys":"sh"},{"dm":"600022","mc":"山东钢铁","jys":"sh"},{"dm":"600019","mc":"宝钢股份","jys":"sh"},{"dm":"600018","mc":"上港集团","jys":"sh"},{"dm":"600005","mc":"武钢股份","jys":"sh"},{"dm":"600003","mc":"ST东北高","jys":"sh"},{"dm":"600002","mc":"齐鲁石化","jys":"sh"},{"dm":"600001","mc":"邯郸钢铁","jys":"sh"},{"dm":"301439","mc":"泓淋电力","jys":"sz"},{"dm":"301317","mc":"鑫磊股份","jys":"sz"},{"dm":"301282","mc":"金禄电子","jys":"sz"},{"dm":"301223","mc":"中荣股份","jys":"sz"},{"dm":"301187","mc":"欧圣电气","jys":"sz"},{"dm":"301136","mc":"招标股份","jys":"sz"},{"dm":"301058","mc":"中粮科工","jys":"sz"},{"dm":"301048","mc":"金鹰重工","jys":"sz"},{"dm":"301039","mc":"中集车辆","jys":"sz"},{"dm":"300975","mc":"商络电子","jys":"sz"},{"dm":"300970","mc":"华绿生物","jys":"sz"},{"dm":"300931","mc":"通用电梯","jys":"sz"},{"dm":"300908","mc":"仲景食品","jys":"sz"},{"dm":"300898","mc":"熊猫乳品","jys":"sz"},{"dm":"300891","mc":"惠云钛业","jys":"sz"},{"dm":"300889","mc":"爱克股份","jys":"sz"},{"dm":"300758","mc":"七彩化学","jys":"sz"},{"dm":"300732","mc":"设研院","jys":"sz"},{"dm":"300710","mc":"万隆光电","jys":"sz"},{"dm":"300696","mc":"爱乐达","jys":"sz"},{"dm":"300610","mc":"晨化股份","jys":"sz"},{"dm":"300587","mc":"天铁股份","jys":"sz"},{"dm":"300526","mc":"中潜退","jys":"sz"},{"dm":"300495","mc":"*ST美尚","jys":"sz"},{"dm":"300485","mc":"赛升药业","jys":"sz"},{"dm":"300456","mc":"赛微电子","jys":"sz"},{"dm":"300447","mc":"全信股份","jys":"sz"},{"dm":"300431","mc":"暴风退","jys":"sz"},{"dm":"300392","mc":"腾信退","jys":"sz"},{"dm":"300388","mc":"节能国祯","jys":"sz"},{"dm":"300372","mc":"欣泰退","jys":"sz"},{"dm":"300371","mc":"汇中股份","jys":"sz"},{"dm":"300367","mc":"网力退","jys":"sz"},{"dm":"300362","mc":"天翔退","jys":"sz"},{"dm":"300356","mc":"光一退","jys":"sz"},{"dm":"300343","mc":"联创股份","jys":"sz"},{"dm":"300336","mc":"新文退","jys":"sz"},{"dm":"300330","mc":"计通退","jys":"sz"},{"dm":"300329","mc":"海伦钢琴","jys":"sz"},{"dm":"300325","mc":"德威退","jys":"sz"},{"dm":"300320","mc":"海达股份","jys":"sz"},{"dm":"300312","mc":"邦讯退","jys":"sz"},{"dm":"300310","mc":"宜通世纪","jys":"sz"},{"dm":"300309","mc":"吉艾退","jys":"sz"},{"dm":"300297","mc":"蓝盾退","jys":"sz"},{"dm":"300292","mc":"吴通控股","jys":"sz"},{"dm":"300273","mc":"和佳退","jys":"sz"},{"dm":"300263","mc":"隆华科技","jys":"sz"},{"dm":"300247","mc":"融捷健康","jys":"sz"},{"dm":"300230","mc":"永利股份","jys":"sz"},{"dm":"300223","mc":"北京君正","jys":"sz"},{"dm":"300216","mc":"千山退","jys":"sz"},{"dm":"300209","mc":"ST有棵树","jys":"sz"},{"dm":"300202","mc":"聚龙退","jys":"sz"},{"dm":"300186","mc":"大华农","jys":"sz"},{"dm":"300185","mc":"通裕重工","jys":"sz"},{"dm":"300184","mc":"力源信息","jys":"sz"},{"dm":"300178","mc":"腾邦退","jys":"sz"},{"dm":"300163","mc":"先锋新材","jys":"sz"},{"dm":"300156","mc":"神雾退","jys":"sz"},{"dm":"300155","mc":"安居宝","jys":"sz"},{"dm":"300117","mc":"嘉寓股份","jys":"sz"},{"dm":"300116","mc":"保力新","jys":"sz"},{"dm":"300107","mc":"建新股份","jys":"sz"},{"dm":"300104","mc":"乐视退","jys":"sz"},{"dm":"300090","mc":"盛运退","jys":"sz"},{"dm":"300089","mc":"文化退","jys":"sz"},{"dm":"300078","mc":"思创医惠","jys":"sz"},{"dm":"300064","mc":"金刚退","jys":"sz"},{"dm":"300038","mc":"数知退","jys":"sz"},{"dm":"300028","mc":"金亚退","jys":"sz"},{"dm":"300027","mc":"华谊兄弟","jys":"sz"},{"dm":"300024","mc":"机器人","jys":"sz"},{"dm":"300023","mc":"宝德退","jys":"sz"},{"dm":"300019","mc":"硅宝科技","jys":"sz"},{"dm":"300009","mc":"安科生物","jys":"sz"},{"dm":"003013","mc":"地铁设计","jys":"sz"},{"dm":"002958","mc":"青农商行","jys":"sz"},{"dm":"002936","mc":"郑州银行","jys":"sz"},{"dm":"002933","mc":"新兴装备","jys":"sz"},{"dm":"002839","mc":"张家港行","jys":"sz"},{"dm":"002801","mc":"微光股份","jys":"sz"},{"dm":"002786","mc":"银宝山新","jys":"sz"},{"dm":"002781","mc":"奇信退","jys":"sz"},{"dm":"002770","mc":"科迪退","jys":"sz"},{"dm":"002762","mc":"金发拉比","jys":"sz"},{"dm":"002751","mc":"易尚退","jys":"sz"},{"dm":"002711","mc":"欧浦退","jys":"sz"},{"dm":"002685","mc":"华东重机","jys":"sz"},{"dm":"002684","mc":"猛狮退","jys":"sz"},{"dm":"002680","mc":"长生退","jys":"sz"},{"dm":"002665","mc":"首航高科","jys":"sz"},{"dm":"002659","mc":"凯文教育","jys":"sz"},{"dm":"002656","mc":"ST摩登","jys":"sz"},{"dm":"002641","mc":"公元股份","jys":"sz"},{"dm":"002638","mc":"勤上股份","jys":"sz"},{"dm":"002622","mc":"皓宸医疗","jys":"sz"},{"dm":"002619","mc":"*ST艾格","jys":"sz"},{"dm":"002618","mc":"丹邦退","jys":"sz"},{"dm":"002610","mc":"爱康科技","jys":"sz"},{"dm":"002604","mc":"龙力退","jys":"sz"},{"dm":"002583","mc":"海能达","jys":"sz"},{"dm":"002580","mc":"圣阳股份","jys":"sz"},{"dm":"002578","mc":"闽发铝业","jys":"sz"},{"dm":"002554","mc":"惠博普","jys":"sz"},{"dm":"002540","mc":"亚太科技","jys":"sz"},{"dm":"002539","mc":"云图控股","jys":"sz"},{"dm":"002533","mc":"金杯电工","jys":"sz"},{"dm":"002517","mc":"恺英网络","jys":"sz"},{"dm":"002509","mc":"天茂退","jys":"sz"},{"dm":"002506","mc":"协鑫集成","jys":"sz"},{"dm":"002504","mc":"*ST弘高","jys":"sz"},{"dm":"002503","mc":"*ST搜特","jys":"sz"},{"dm":"002499","mc":"科林退","jys":"sz"},{"dm":"002496","mc":"辉丰股份","jys":"sz"},{"dm":"002486","mc":"嘉麟杰","jys":"sz"},{"dm":"002478","mc":"常宝股份","jys":"sz"},{"dm":"002477","mc":"雏鹰退","jys":"sz"},{"dm":"002473","mc":"圣莱退","jys":"sz"},{"dm":"002464","mc":"众应退","jys":"sz"},{"dm":"002450","mc":"康得退","jys":"sz"},{"dm":"002447","mc":"晨鑫退","jys":"sz"},{"dm":"002443","mc":"金洲管道","jys":"sz"},{"dm":"002439","mc":"启明星辰","jys":"sz"},{"dm":"002427","mc":"尤夫股份","jys":"sz"},{"dm":"002421","mc":"达实智能","jys":"sz"},{"dm":"002417","mc":"深南退","jys":"sz"},{"dm":"002411","mc":"必康退","jys":"sz"},{"dm":"002408","mc":"齐翔腾达","jys":"sz"},{"dm":"002397","mc":"梦洁股份","jys":"sz"},{"dm":"002378","mc":"章源钨业","jys":"sz"},{"dm":"002369","mc":"卓翼科技","jys":"sz"},{"dm":"002361","mc":"神剑股份","jys":"sz"},{"dm":"002359","mc":"北讯退","jys":"sz"},{"dm":"002325","mc":"洪涛股份","jys":"sz"},{"dm":"002321","mc":"华英农业","jys":"sz"},{"dm":"002307","mc":"北新路桥","jys":"sz"},{"dm":"002303","mc":"美盈森","jys":"sz"},{"dm":"002283","mc":"天润工业","jys":"sz"},{"dm":"002260","mc":"德奥退","jys":"sz"},{"dm":"002259","mc":"ST升达","jys":"sz"},{"dm":"002256","mc":"兆新股份","jys":"sz"},{"dm":"002249","mc":"大洋电机","jys":"sz"},{"dm":"002248","mc":"华东数控","jys":"sz"},{"dm":"002246","mc":"北化股份","jys":"sz"},{"dm":"002243","mc":"力合科创","jys":"sz"},{"dm":"002239","mc":"奥特佳","jys":"sz"},{"dm":"002231","mc":"奥维通信","jys":"sz"},{"dm":"002220","mc":"天宝退","jys":"sz"},{"dm":"002214","mc":"大立科技","jys":"sz"},{"dm":"002177","mc":"御银股份","jys":"sz"},{"dm":"002165","mc":"红 宝 丽","jys":"sz"},{"dm":"002163","mc":"海南发展","jys":"sz"},{"dm":"002160","mc":"常铝股份","jys":"sz"},{"dm":"002147","mc":"新光退","jys":"sz"},{"dm":"002143","mc":"印纪退","jys":"sz"},{"dm":"002136","mc":"安 纳 达","jys":"sz"},{"dm":"002135","mc":"东南网架","jys":"sz"},{"dm":"002127","mc":"南极电商","jys":"sz"},{"dm":"002118","mc":"*ST紫鑫","jys":"sz"},{"dm":"002113","mc":"*ST天润","jys":"sz"},{"dm":"002076","mc":"星光股份","jys":"sz"},{"dm":"002071","mc":"长城退","jys":"sz"},{"dm":"002070","mc":"众和退","jys":"sz"},{"dm":"002064","mc":"华峰化学","jys":"sz"},{"dm":"002062","mc":"宏润建设","jys":"sz"},{"dm":"002054","mc":"德美化工","jys":"sz"},{"dm":"002049","mc":"紫光国微","jys":"sz"},{"dm":"002038","mc":"双鹭药业","jys":"sz"},{"dm":"002037","mc":"保利联合","jys":"sz"},{"dm":"002030","mc":"达安基因","jys":"sz"},{"dm":"002024","mc":"ST易购","jys":"sz"},{"dm":"002018","mc":"华信退","jys":"sz"},{"dm":"002013","mc":"中航机电","jys":"sz"},{"dm":"002005","mc":"ST德豪","jys":"sz"},{"dm":"001326","mc":"联域股份","jys":"sz"},{"dm":"001306","mc":"夏厦精密","jys":"sz"},{"dm":"001234","mc":"泰慕士","jys":"sz"},{"dm":"001222","mc":"源飞宠物","jys":"sz"},{"dm":"001219","mc":"青岛食品","jys":"sz"},{"dm":"001218","mc":"丽臣实业","jys":"sz"},{"dm":"000987","mc":"越秀资本","jys":"sz"},{"dm":"000982","mc":"中银绒业","jys":"sz"},{"dm":"000979","mc":"中弘退","jys":"sz"},{"dm":"000972","mc":"中基健康","jys":"sz"},{"dm":"000965","mc":"天保基建","jys":"sz"},{"dm":"000956","mc":"中原油气","jys":"sz"},{"dm":"000939","mc":"凯迪退","jys":"sz"},{"dm":"000926","mc":"福星股份","jys":"sz"},{"dm":"000918","mc":"*ST嘉凯","jys":"sz"},{"dm":"000916","mc":"华北高速","jys":"sz"},{"dm":"000903","mc":"云内动力","jys":"sz"},{"dm":"000876","mc":"新 希 望","jys":"sz"},{"dm":"000866","mc":"扬子石化","jys":"sz"},{"dm":"000852","mc":"石化机械","jys":"sz"},{"dm":"000839","mc":"ST国安","jys":"sz"},{"dm":"000836","mc":"富通信息","jys":"sz"},{"dm":"000835","mc":"长动退","jys":"sz"},{"dm":"000832","mc":"*ST龙涤","jys":"sz"},{"dm":"000827","mc":"*ST长兴","jys":"sz"},{"dm":"000817","mc":"辽河油田","jys":"sz"},{"dm":"000806","mc":"银河退","jys":"sz"},{"dm":"000805","mc":"*ST炎黄","jys":"sz"},{"dm":"000800","mc":"一汽解放","jys":"sz"},{"dm":"000787","mc":"*ST创智","jys":"sz"},{"dm":"000780","mc":"ST平能","jys":"sz"},{"dm":"000769","mc":"*ST大菲","jys":"sz"},{"dm":"000765","mc":"*ST华信","jys":"sz"},{"dm":"000763","mc":"锦州石化","jys":"sz"},{"dm":"000760","mc":"斯太退","jys":"sz"},{"dm":"000751","mc":"锌业股份","jys":"sz"},{"dm":"000748","mc":"长城信息","jys":"sz"},{"dm":"000736","mc":"中交地产","jys":"sz"},{"dm":"000732","mc":"ST泰禾","jys":"sz"},{"dm":"000730","mc":"*ST环保","jys":"sz"},{"dm":"000728","mc":"国元证券","jys":"sz"},{"dm":"000726","mc":"鲁 泰A","jys":"sz"},{"dm":"000699","mc":"S*ST佳纸","jys":"sz"},{"dm":"000693","mc":"华泽退","jys":"sz"},{"dm":"000689","mc":"ST宏业","jys":"sz"},{"dm":"000687","mc":"华讯退","jys":"sz"},{"dm":"000675","mc":"ST银山","jys":"sz"},{"dm":"000673","mc":"当代退","jys":"sz"},{"dm":"000671","mc":"ST阳光城","jys":"sz"},{"dm":"000667","mc":"ST美置","jys":"sz"},{"dm":"000666","mc":"经纬纺机","jys":"sz"},{"dm":"000662","mc":"天夏退","jys":"sz"},{"dm":"000660","mc":"*ST南华","jys":"sz"},{"dm":"000658","mc":"ST海洋","jys":"sz"},{"dm":"000653","mc":"ST九州","jys":"sz"},{"dm":"000626","mc":"远大控股","jys":"sz"},{"dm":"000621","mc":"*ST比特","jys":"sz"},{"dm":"000618","mc":"吉林化工","jys":"sz"},{"dm":"000616","mc":"*ST海投","jys":"sz"},{"dm":"000613","mc":"东海A退","jys":"sz"},{"dm":"000611","mc":"天首退","jys":"sz"},{"dm":"000606","mc":"顺利退","jys":"sz"},{"dm":"000602","mc":"金马集团","jys":"sz"},{"dm":"000601","mc":"韶能股份","jys":"sz"},{"dm":"000595","mc":"宝塔实业","jys":"sz"},{"dm":"000594","mc":"国恒退","jys":"sz"},{"dm":"000588","mc":"PT粤金曼","jys":"sz"},{"dm":"000587","mc":"*ST金洲","jys":"sz"},{"dm":"000585","mc":"东电退","jys":"sz"},{"dm":"000583","mc":"S*ST托普","jys":"sz"},{"dm":"000578","mc":"盐湖集团","jys":"sz"},{"dm":"000573","mc":"粤宏远A","jys":"sz"},{"dm":"000569","mc":"长城股份","jys":"sz"},{"dm":"000566","mc":"海南海药","jys":"sz"},{"dm":"000564","mc":"ST大集","jys":"sz"},{"dm":"000563","mc":"陕国投A","jys":"sz"},{"dm":"000562","mc":"宏源证券","jys":"sz"},{"dm":"000558","mc":"莱茵体育","jys":"sz"},{"dm":"000556","mc":"PT南洋","jys":"sz"},{"dm":"000552","mc":"甘肃能化","jys":"sz"},{"dm":"000549","mc":"S湘火炬","jys":"sz"},{"dm":"000542","mc":"TCL通讯","jys":"sz"},{"dm":"000540","mc":"*ST中天","jys":"sz"},{"dm":"000539","mc":"粤电力A","jys":"sz"},{"dm":"000535","mc":"*ST猴王","jys":"sz"},{"dm":"000527","mc":"美的电器","jys":"sz"},{"dm":"000522","mc":"白云山A","jys":"sz"},{"dm":"000518","mc":"四环生物","jys":"sz"},{"dm":"000515","mc":"攀渝钛业","jys":"sz"},{"dm":"000511","mc":"烯碳退","jys":"sz"},{"dm":"000508","mc":"琼民源A","jys":"sz"},{"dm":"000505","mc":"京粮控股","jys":"sz"},{"dm":"000502","mc":"绿景退","jys":"sz"},{"dm":"000418","mc":"小天鹅A","jys":"sz"},{"dm":"000412","mc":"ST五环","jys":"sz"},{"dm":"000406","mc":"石油大明","jys":"sz"},{"dm":"000405","mc":"ST鑫光","jys":"sz"},{"dm":"000404","mc":"长虹华意","jys":"sz"},{"dm":"000153","mc":"丰原药业","jys":"sz"},{"dm":"000150","mc":"*ST宜康","jys":"sz"},{"dm":"000100","mc":"TCL科技","jys":"sz"},{"dm":"000088","mc":"盐 田 港","jys":"sz"},{"dm":"000047","mc":"ST中侨","jys":"sz"},{"dm":"000038","mc":"大通退","jys":"sz"},{"dm":"000037","mc":"深南电A","jys":"sz"},{"dm":"000033","mc":"新都退","jys":"sz"},{"dm":"000024","mc":"招商地产","jys":"sz"},{"dm":"000018","mc":"神城A退","jys":"sz"},{"dm":"000015","mc":"PT中浩A","jys":"sz"},{"dm":"000013","mc":"*ST石化A","jys":"sz"},{"dm":"000012","mc":"南 玻A","jys":"sz"},{"dm":"000005","mc":"ST星源","jys":"sz"},{"dm":"000003","mc":"PT金田A","jys":"sz"},{"dm":"600941","mc":"中国移动","jys":"sh"},{"dm":"002991","mc":"甘源食品","jys":"sz"},{"dm":"300492","mc":"华图山鼎","jys":"sz"},{"dm":"301051","mc":"信濠光电","jys":"sz"},{"dm":"003043","mc":"华亚智能","jys":"sz"},{"dm":"688671","mc":"碧兴物联","jys":"sh"},{"dm":"688419","mc":"耐科装备","jys":"sh"},{"dm":"688232","mc":"新点软件","jys":"sh"},{"dm":"301170","mc":"锡南科技","jys":"sz"},{"dm":"300827","mc":"上能电气","jys":"sz"},{"dm":"603888","mc":"新华网","jys":"sh"},{"dm":"301045","mc":"天禄科技","jys":"sz"},{"dm":"688711","mc":"宏微科技","jys":"sh"},{"dm":"300816","mc":"艾可蓝","jys":"sz"},{"dm":"688359","mc":"三孚新科","jys":"sh"},{"dm":"301279","mc":"金道科技","jys":"sz"},{"dm":"301135","mc":"瑞德智能","jys":"sz"},{"dm":"603276","mc":"恒兴新材","jys":"sh"},{"dm":"688231","mc":"隆达股份","jys":"sh"},{"dm":"301096","mc":"百诚医药","jys":"sz"},{"dm":"002459","mc":"晶澳科技","jys":"sz"},{"dm":"301336","mc":"趣睡科技","jys":"sz"},{"dm":"300907","mc":"康平科技","jys":"sz"},{"dm":"000756","mc":"新华制药","jys":"sz"},{"dm":"301395","mc":"仁信新材","jys":"sz"},{"dm":"300602","mc":"飞荣达","jys":"sz"},{"dm":"603170","mc":"宝立食品","jys":"sh"},{"dm":"603917","mc":"合力科技","jys":"sh"},{"dm":"301125","mc":"腾亚精工","jys":"sz"},{"dm":"301115","mc":"建科股份","jys":"sz"},{"dm":"002897","mc":"意华股份","jys":"sz"},{"dm":"301006","mc":"迈拓股份","jys":"sz"},{"dm":"600055","mc":"万东医疗","jys":"sh"},{"dm":"002935","mc":"天奥电子","jys":"sz"},{"dm":"301079","mc":"邵阳液压","jys":"sz"},{"dm":"601607","mc":"上海医药","jys":"sh"},{"dm":"603639","mc":"海利尔","jys":"sh"},{"dm":"300416","mc":"苏试试验","jys":"sz"},{"dm":"301446","mc":"福事特","jys":"sz"},{"dm":"300946","mc":"恒而达","jys":"sz"},{"dm":"002961","mc":"瑞达期货","jys":"sz"},{"dm":"300777","mc":"中简科技","jys":"sz"},{"dm":"603171","mc":"税友股份","jys":"sh"},{"dm":"300932","mc":"三友联众","jys":"sz"},{"dm":"688361","mc":"中科飞测-U","jys":"sh"},{"dm":"600378","mc":"昊华科技","jys":"sh"},{"dm":"301130","mc":"西点药业","jys":"sz"},{"dm":"603151","mc":"邦基科技","jys":"sh"},{"dm":"002034","mc":"旺能环境","jys":"sz"},{"dm":"000951","mc":"中国重汽","jys":"sz"},{"dm":"600706","mc":"曲江文旅","jys":"sh"},{"dm":"600884","mc":"杉杉股份","jys":"sh"},{"dm":"002859","mc":"洁美科技","jys":"sz"},{"dm":"600089","mc":"特变电工","jys":"sh"},{"dm":"600760","mc":"中航沈飞","jys":"sh"},{"dm":"300800","mc":"力合科技","jys":"sz"},{"dm":"603701","mc":"德宏股份","jys":"sh"},{"dm":"600508","mc":"上海能源","jys":"sh"},{"dm":"600487","mc":"亨通光电","jys":"sh"},{"dm":"300396","mc":"迪瑞医疗","jys":"sz"},{"dm":"000333","mc":"美的集团","jys":"sz"},{"dm":"002943","mc":"宇晶股份","jys":"sz"},{"dm":"000777","mc":"中核科技","jys":"sz"},{"dm":"300942","mc":"易瑞生物","jys":"sz"},{"dm":"301388","mc":"欣灵电气","jys":"sz"},{"dm":"300792","mc":"壹网壹创","jys":"sz"},{"dm":"003025","mc":"思进智能","jys":"sz"},{"dm":"300340","mc":"科恒股份","jys":"sz"},{"dm":"603890","mc":"春秋电子","jys":"sh"},{"dm":"000567","mc":"海德股份","jys":"sz"},{"dm":"600847","mc":"万里股份","jys":"sh"},{"dm":"002883","mc":"中设股份","jys":"sz"},{"dm":"001378","mc":"德冠新材","jys":"sz"},{"dm":"601012","mc":"隆基绿能","jys":"sh"},{"dm":"600060","mc":"海信视像","jys":"sh"},{"dm":"300409","mc":"道氏技术","jys":"sz"},{"dm":"300462","mc":"华铭智能","jys":"sz"},{"dm":"301353","mc":"普莱得","jys":"sz"},{"dm":"002493","mc":"荣盛石化","jys":"sz"},{"dm":"301335","mc":"天元宠物","jys":"sz"},{"dm":"603057","mc":"紫燕食品","jys":"sh"},{"dm":"603213","mc":"镇洋发展","jys":"sh"},{"dm":"002458","mc":"益生股份","jys":"sz"},{"dm":"600048","mc":"保利发展","jys":"sh"},{"dm":"603307","mc":"扬州金泉","jys":"sh"},{"dm":"000999","mc":"华润三九","jys":"sz"},{"dm":"300771","mc":"智莱科技","jys":"sz"},{"dm":"688511","mc":"天微电子","jys":"sh"},{"dm":"300980","mc":"祥源新材","jys":"sz"},{"dm":"000688","mc":"国城矿业","jys":"sz"},{"dm":"002677","mc":"浙江美大","jys":"sz"},{"dm":"603043","mc":"广州酒家","jys":"sh"},{"dm":"600645","mc":"中源协和","jys":"sh"},{"dm":"688330","mc":"宏力达","jys":"sh"},{"dm":"600223","mc":"福瑞达","jys":"sh"},{"dm":"003029","mc":"吉大正元","jys":"sz"},{"dm":"002405","mc":"四维图新","jys":"sz"},{"dm":"301002","mc":"崧盛股份","jys":"sz"},{"dm":"002598","mc":"山东章鼓","jys":"sz"},{"dm":"002920","mc":"德赛西威","jys":"sz"},{"dm":"688035","mc":"德邦科技","jys":"sh"},{"dm":"603038","mc":"华立股份","jys":"sh"},{"dm":"688788","mc":"科思科技","jys":"sh"},{"dm":"002706","mc":"良信股份","jys":"sz"},{"dm":"600099","mc":"林海股份","jys":"sh"},{"dm":"601100","mc":"恒立液压","jys":"sh"},{"dm":"688663","mc":"新风光","jys":"sh"},{"dm":"601177","mc":"杭齿前进","jys":"sh"},{"dm":"300265","mc":"通光线缆","jys":"sz"},{"dm":"603150","mc":"万朗磁塑","jys":"sh"},{"dm":"002349","mc":"精华制药","jys":"sz"},{"dm":"600995","mc":"南网储能","jys":"sh"},{"dm":"001256","mc":"炜冈科技","jys":"sz"},{"dm":"603345","mc":"安井食品","jys":"sh"},{"dm":"601949","mc":"中国出版","jys":"sh"},{"dm":"002824","mc":"和胜股份","jys":"sz"},{"dm":"688320","mc":"禾川科技","jys":"sh"},{"dm":"002955","mc":"鸿合科技","jys":"sz"},{"dm":"300271","mc":"华宇软件","jys":"sz"},{"dm":"300902","mc":"国安达","jys":"sz"},{"dm":"000635","mc":"英 力 特","jys":"sz"},{"dm":"300541","mc":"先进数通","jys":"sz"},{"dm":"000561","mc":"烽火电子","jys":"sz"},{"dm":"603789","mc":"星光农机","jys":"sh"},{"dm":"600480","mc":"凌云股份","jys":"sh"},{"dm":"600158","mc":"中体产业","jys":"sh"},{"dm":"603080","mc":"新疆火炬","jys":"sh"},{"dm":"301167","mc":"建研设计","jys":"sz"},{"dm":"600062","mc":"华润双鹤","jys":"sh"},{"dm":"002916","mc":"深南电路","jys":"sz"},{"dm":"300613","mc":"富瀚微","jys":"sz"},{"dm":"002836","mc":"新宏泽","jys":"sz"},{"dm":"600101","mc":"明星电力","jys":"sh"},{"dm":"000713","mc":"丰乐种业","jys":"sz"},{"dm":"300030","mc":"阳普医疗","jys":"sz"},{"dm":"600037","mc":"歌华有线","jys":"sh"},{"dm":"300599","mc":"雄塑科技","jys":"sz"},{"dm":"688321","mc":"微芯生物","jys":"sh"},{"dm":"600834","mc":"申通地铁","jys":"sh"},{"dm":"002254","mc":"泰和新材","jys":"sz"},{"dm":"601519","mc":"大智慧","jys":"sh"},{"dm":"300617","mc":"安靠智电","jys":"sz"},{"dm":"600143","mc":"金发科技","jys":"sh"},{"dm":"300261","mc":"雅本化学","jys":"sz"},{"dm":"688347","mc":"华虹公司","jys":"sh"},{"dm":"605186","mc":"健麾信息","jys":"sh"},{"dm":"000935","mc":"四川双马","jys":"sz"},{"dm":"002200","mc":"ST交投","jys":"sz"},{"dm":"688082","mc":"盛美上海","jys":"sh"},{"dm":"603167","mc":"渤海轮渡","jys":"sh"},{"dm":"300618","mc":"寒锐钴业","jys":"sz"},{"dm":"301099","mc":"雅创电子","jys":"sz"},{"dm":"002041","mc":"登海种业","jys":"sz"},{"dm":"000776","mc":"广发证券","jys":"sz"},{"dm":"300871","mc":"回盛生物","jys":"sz"},{"dm":"688523","mc":"航天环宇","jys":"sh"},{"dm":"688230","mc":"芯导科技","jys":"sh"},{"dm":"000685","mc":"中山公用","jys":"sz"},{"dm":"000881","mc":"中广核技","jys":"sz"},{"dm":"605399","mc":"晨光新材","jys":"sh"},{"dm":"300972","mc":"万辰集团","jys":"sz"},{"dm":"605055","mc":"迎丰股份","jys":"sh"},{"dm":"000906","mc":"浙商中拓","jys":"sz"},{"dm":"002937","mc":"兴瑞科技","jys":"sz"},{"dm":"600522","mc":"中天科技","jys":"sh"},{"dm":"300016","mc":"北陆药业","jys":"sz"},{"dm":"301075","mc":"多瑞医药","jys":"sz"},{"dm":"002402","mc":"和而泰","jys":"sz"},{"dm":"002768","mc":"国恩股份","jys":"sz"},{"dm":"000905","mc":"厦门港务","jys":"sz"},{"dm":"002451","mc":"摩恩电气","jys":"sz"},{"dm":"601801","mc":"皖新传媒","jys":"sh"},{"dm":"600230","mc":"沧州大化","jys":"sh"},{"dm":"002452","mc":"长高电新","jys":"sz"},{"dm":"002668","mc":"奥马电器","jys":"sz"},{"dm":"600586","mc":"金晶科技","jys":"sh"},{"dm":"300829","mc":"金丹科技","jys":"sz"},{"dm":"002114","mc":"罗平锌电","jys":"sz"},{"dm":"300290","mc":"荣科科技","jys":"sz"},{"dm":"603303","mc":"得邦照明","jys":"sh"},{"dm":"000978","mc":"桂林旅游","jys":"sz"},{"dm":"002301","mc":"齐心集团","jys":"sz"},{"dm":"600456","mc":"宝钛股份","jys":"sh"},{"dm":"002837","mc":"英维克","jys":"sz"},{"dm":"301126","mc":"达嘉维康","jys":"sz"},{"dm":"300232","mc":"洲明科技","jys":"sz"},{"dm":"002747","mc":"埃斯顿","jys":"sz"},{"dm":"688616","mc":"西力科技","jys":"sh"},{"dm":"603016","mc":"新宏泰","jys":"sh"},{"dm":"600141","mc":"兴发集团","jys":"sh"},{"dm":"603358","mc":"华达科技","jys":"sh"},{"dm":"000683","mc":"远兴能源","jys":"sz"},{"dm":"600057","mc":"厦门象屿","jys":"sh"},{"dm":"688130","mc":"晶华微","jys":"sh"},{"dm":"688151","mc":"华强科技","jys":"sh"},{"dm":"603658","mc":"安图生物","jys":"sh"},{"dm":"002571","mc":"德力股份","jys":"sz"},{"dm":"603185","mc":"弘元绿能","jys":"sh"},{"dm":"002142","mc":"宁波银行","jys":"sz"},{"dm":"688023","mc":"安恒信息","jys":"sh"},{"dm":"603177","mc":"德创环保","jys":"sh"},{"dm":"301027","mc":"华蓝集团","jys":"sz"},{"dm":"688003","mc":"天准科技","jys":"sh"},{"dm":"002990","mc":"盛视科技","jys":"sz"},{"dm":"300911","mc":"亿田智能","jys":"sz"},{"dm":"600778","mc":"友好集团","jys":"sh"},{"dm":"600446","mc":"金证股份","jys":"sh"},{"dm":"300018","mc":"中元股份","jys":"sz"},{"dm":"600845","mc":"宝信软件","jys":"sh"},{"dm":"002557","mc":"洽洽食品","jys":"sz"},{"dm":"002103","mc":"广博股份","jys":"sz"},{"dm":"600395","mc":"盘江股份","jys":"sh"},{"dm":"301009","mc":"可靠股份","jys":"sz"},{"dm":"002272","mc":"川润股份","jys":"sz"},{"dm":"601279","mc":"英利汽车","jys":"sh"},{"dm":"603225","mc":"新凤鸣","jys":"sh"},{"dm":"301356","mc":"天振股份","jys":"sz"},{"dm":"300417","mc":"南华仪器","jys":"sz"},{"dm":"002340","mc":"格林美","jys":"sz"},{"dm":"001230","mc":"劲旅环境","jys":"sz"},{"dm":"603280","mc":"南方路机","jys":"sh"},{"dm":"000668","mc":"荣丰控股","jys":"sz"},{"dm":"688155","mc":"先惠技术","jys":"sh"},{"dm":"600183","mc":"生益科技","jys":"sh"},{"dm":"601136","mc":"首创证券","jys":"sh"},{"dm":"600516","mc":"方大炭素","jys":"sh"},{"dm":"000498","mc":"山东路桥","jys":"sz"},{"dm":"600180","mc":"瑞茂通","jys":"sh"},{"dm":"002529","mc":"海源复材","jys":"sz"},{"dm":"601098","mc":"中南传媒","jys":"sh"},{"dm":"601825","mc":"沪农商行","jys":"sh"},{"dm":"301265","mc":"华新环保","jys":"sz"},{"dm":"600030","mc":"中信证券","jys":"sh"},{"dm":"600406","mc":"国电南瑞","jys":"sh"},{"dm":"603021","mc":"山东华鹏","jys":"sh"},{"dm":"002170","mc":"芭田股份","jys":"sz"},{"dm":"605169","mc":"洪通燃气","jys":"sh"},{"dm":"600015","mc":"华夏银行","jys":"sh"},{"dm":"000837","mc":"秦川机床","jys":"sz"},{"dm":"001201","mc":"东瑞股份","jys":"sz"},{"dm":"301309","mc":"万得凯","jys":"sz"},{"dm":"000791","mc":"甘肃能源","jys":"sz"},{"dm":"688680","mc":"海优新材","jys":"sh"},{"dm":"605111","mc":"新洁能","jys":"sh"},{"dm":"301087","mc":"可孚医疗","jys":"sz"},{"dm":"603683","mc":"晶华新材","jys":"sh"},{"dm":"603211","mc":"晋拓股份","jys":"sh"},{"dm":"000932","mc":"华菱钢铁","jys":"sz"},{"dm":"300772","mc":"运达股份","jys":"sz"},{"dm":"601339","mc":"百隆东方","jys":"sh"},{"dm":"301109","mc":"军信股份","jys":"sz"},{"dm":"301119","mc":"正强股份","jys":"sz"},{"dm":"601366","mc":"利群股份","jys":"sh"},{"dm":"002347","mc":"泰尔股份","jys":"sz"},{"dm":"301468","mc":"博盈特焊","jys":"sz"},{"dm":"603508","mc":"思维列控","jys":"sh"},{"dm":"600660","mc":"福耀玻璃","jys":"sh"},{"dm":"002681","mc":"奋达科技","jys":"sz"},{"dm":"002627","mc":"三峡旅游","jys":"sz"},{"dm":"002060","mc":"粤 水 电","jys":"sz"},{"dm":"002415","mc":"海康威视","jys":"sz"},{"dm":"600916","mc":"中国黄金","jys":"sh"},{"dm":"300867","mc":"圣元环保","jys":"sz"},{"dm":"002507","mc":"涪陵榨菜","jys":"sz"},{"dm":"600193","mc":"创兴资源","jys":"sh"},{"dm":"002438","mc":"江苏神通","jys":"sz"},{"dm":"000957","mc":"中通客车","jys":"sz"},{"dm":"002573","mc":"清新环境","jys":"sz"},{"dm":"300651","mc":"金陵体育","jys":"sz"},{"dm":"002485","mc":"*ST雪发","jys":"sz"},{"dm":"605183","mc":"确成股份","jys":"sh"},{"dm":"601211","mc":"国泰君安","jys":"sh"},{"dm":"605488","mc":"福莱新材","jys":"sh"},{"dm":"002365","mc":"永安药业","jys":"sz"},{"dm":"600740","mc":"山西焦化","jys":"sh"},{"dm":"605358","mc":"立昂微","jys":"sh"},{"dm":"688385","mc":"复旦微电","jys":"sh"},{"dm":"002090","mc":"金智科技","jys":"sz"},{"dm":"600750","mc":"江中药业","jys":"sh"},{"dm":"601700","mc":"风范股份","jys":"sh"},{"dm":"600459","mc":"贵研铂业","jys":"sh"},{"dm":"002922","mc":"伊戈尔","jys":"sz"},{"dm":"002917","mc":"金奥博","jys":"sz"},{"dm":"002688","mc":"金河生物","jys":"sz"},{"dm":"603060","mc":"国检集团","jys":"sh"},{"dm":"002462","mc":"嘉事堂","jys":"sz"},{"dm":"300021","mc":"大禹节水","jys":"sz"},{"dm":"000158","mc":"常山北明","jys":"sz"},{"dm":"002611","mc":"东方精工","jys":"sz"},{"dm":"000417","mc":"合肥百货","jys":"sz"},{"dm":"002989","mc":"中天精装","jys":"sz"},{"dm":"002004","mc":"华邦健康","jys":"sz"},{"dm":"600901","mc":"江苏金租","jys":"sh"},{"dm":"002497","mc":"雅化集团","jys":"sz"},{"dm":"002441","mc":"众业达","jys":"sz"},{"dm":"000993","mc":"闽东电力","jys":"sz"},{"dm":"002025","mc":"航天电器","jys":"sz"},{"dm":"603587","mc":"地素时尚","jys":"sh"},{"dm":"601233","mc":"桐昆股份","jys":"sh"},{"dm":"301246","mc":"宏源药业","jys":"sz"},{"dm":"002232","mc":"启明信息","jys":"sz"},{"dm":"301500","mc":"飞南资源","jys":"sz"},{"dm":"688549","mc":"中巨芯-U","jys":"sh"},{"dm":"300959","mc":"线上线下","jys":"sz"},{"dm":"600217","mc":"中再资环","jys":"sh"},{"dm":"688122","mc":"西部超导","jys":"sh"},{"dm":"002778","mc":"中晟高科","jys":"sz"},{"dm":"300529","mc":"健帆生物","jys":"sz"},{"dm":"600382","mc":"广东明珠","jys":"sh"},{"dm":"600857","mc":"宁波中百","jys":"sh"},{"dm":"600058","mc":"五矿发展","jys":"sh"},{"dm":"600303","mc":"ST曙光","jys":"sh"},{"dm":"000758","mc":"中色股份","jys":"sz"},{"dm":"600711","mc":"盛屯矿业","jys":"sh"},{"dm":"600251","mc":"冠农股份","jys":"sh"},{"dm":"605162","mc":"新中港","jys":"sh"},{"dm":"300094","mc":"国联水产","jys":"sz"},{"dm":"603601","mc":"再升科技","jys":"sh"},{"dm":"300623","mc":"捷捷微电","jys":"sz"},{"dm":"300026","mc":"红日药业","jys":"sz"},{"dm":"301238","mc":"瑞泰新材","jys":"sz"},{"dm":"300481","mc":"濮阳惠成","jys":"sz"},{"dm":"688166","mc":"博瑞医药","jys":"sh"},{"dm":"601598","mc":"中国外运","jys":"sh"},{"dm":"600123","mc":"兰花科创","jys":"sh"},{"dm":"600197","mc":"伊力特","jys":"sh"},{"dm":"600538","mc":"国发股份","jys":"sh"},{"dm":"300106","mc":"西部牧业","jys":"sz"},{"dm":"000733","mc":"振华科技","jys":"sz"},{"dm":"601727","mc":"上海电气","jys":"sh"},{"dm":"688375","mc":"国博电子","jys":"sh"},{"dm":"002162","mc":"悦心健康","jys":"sz"},{"dm":"300753","mc":"爱朋医疗","jys":"sz"},{"dm":"000759","mc":"中百集团","jys":"sz"},{"dm":"000912","mc":"泸天化","jys":"sz"},{"dm":"301053","mc":"远信工业","jys":"sz"},{"dm":"603858","mc":"步长制药","jys":"sh"},{"dm":"603866","mc":"桃李面包","jys":"sh"},{"dm":"300287","mc":"飞利信","jys":"sz"},{"dm":"603323","mc":"苏农银行","jys":"sh"},{"dm":"002218","mc":"拓日新能","jys":"sz"},{"dm":"002057","mc":"中钢天源","jys":"sz"},{"dm":"688120","mc":"华海清科","jys":"sh"},{"dm":"688032","mc":"禾迈股份","jys":"sh"},{"dm":"300919","mc":"中伟股份","jys":"sz"},{"dm":"002327","mc":"富安娜","jys":"sz"},{"dm":"603583","mc":"捷昌驱动","jys":"sh"},{"dm":"300813","mc":"泰林生物","jys":"sz"},{"dm":"300477","mc":"合纵科技","jys":"sz"},{"dm":"002342","mc":"巨力索具","jys":"sz"},{"dm":"603093","mc":"南华期货","jys":"sh"},{"dm":"301256","mc":"华融化学","jys":"sz"},{"dm":"300733","mc":"西菱动力","jys":"sz"},{"dm":"000883","mc":"湖北能源","jys":"sz"},{"dm":"301518","mc":"长华化学","jys":"sz"},{"dm":"300423","mc":"昇辉科技","jys":"sz"},{"dm":"001299","mc":"美能能源","jys":"sz"},{"dm":"600148","mc":"长春一东","jys":"sh"},{"dm":"600096","mc":"云天化","jys":"sh"},{"dm":"001213","mc":"中铁特货","jys":"sz"},{"dm":"002108","mc":"沧州明珠","jys":"sz"},{"dm":"000958","mc":"电投产融","jys":"sz"},{"dm":"001337","mc":"四川黄金","jys":"sz"},{"dm":"600903","mc":"贵州燃气","jys":"sh"},{"dm":"603368","mc":"柳药集团","jys":"sh"},{"dm":"600617","mc":"国新能源","jys":"sh"},{"dm":"301559","mc":"中集环科","jys":"sz"},{"dm":"002562","mc":"兄弟科技","jys":"sz"},{"dm":"600956","mc":"新天绿能","jys":"sh"},{"dm":"002296","mc":"辉煌科技","jys":"sz"},{"dm":"002001","mc":"新 和 成","jys":"sz"},{"dm":"301069","mc":"凯盛新材","jys":"sz"},{"dm":"300490","mc":"华自科技","jys":"sz"},{"dm":"000900","mc":"现代投资","jys":"sz"},{"dm":"002774","mc":"快意电梯","jys":"sz"},{"dm":"688777","mc":"中控技术","jys":"sh"},{"dm":"601077","mc":"渝农商行","jys":"sh"},{"dm":"000428","mc":"华天酒店","jys":"sz"},{"dm":"603619","mc":"中曼石油","jys":"sh"},{"dm":"000725","mc":"京东方A","jys":"sz"},{"dm":"301234","mc":"五洲医疗","jys":"sz"},{"dm":"003039","mc":"顺控发展","jys":"sz"},{"dm":"301177","mc":"迪阿股份","jys":"sz"},{"dm":"601156","mc":"东航物流","jys":"sh"},{"dm":"301310","mc":"鑫宏业","jys":"sz"},{"dm":"600790","mc":"轻纺城","jys":"sh"},{"dm":"301100","mc":"风光股份","jys":"sz"},{"dm":"600425","mc":"青松建化","jys":"sh"},{"dm":"601877","mc":"正泰电器","jys":"sh"},{"dm":"301098","mc":"金埔园林","jys":"sz"},{"dm":"300005","mc":"探路者","jys":"sz"},{"dm":"001278","mc":"一彬科技","jys":"sz"},{"dm":"603956","mc":"威派格","jys":"sh"},{"dm":"301025","mc":"读客文化","jys":"sz"},{"dm":"300915","mc":"海融科技","jys":"sz"},{"dm":"603843","mc":"正平股份","jys":"sh"},{"dm":"002498","mc":"汉缆股份","jys":"sz"},{"dm":"002172","mc":"澳洋健康","jys":"sz"},{"dm":"600299","mc":"安迪苏","jys":"sh"},{"dm":"688018","mc":"乐鑫科技","jys":"sh"},{"dm":"002339","mc":"积成电子","jys":"sz"},{"dm":"603351","mc":"威尔药业","jys":"sh"},{"dm":"002186","mc":"全 聚 德","jys":"sz"},{"dm":"605007","mc":"五洲特纸","jys":"sh"},{"dm":"600961","mc":"株冶集团","jys":"sh"},{"dm":"603657","mc":"春光科技","jys":"sh"},{"dm":"300452","mc":"山河药辅","jys":"sz"},{"dm":"300500","mc":"启迪设计","jys":"sz"},{"dm":"002061","mc":"浙江交科","jys":"sz"},{"dm":"601827","mc":"三峰环境","jys":"sh"},{"dm":"601061","mc":"中信金属","jys":"sh"},{"dm":"300043","mc":"星辉娱乐","jys":"sz"},{"dm":"002568","mc":"百润股份","jys":"sz"},{"dm":"688560","mc":"明冠新材","jys":"sh"},{"dm":"601116","mc":"三江购物","jys":"sh"},{"dm":"300686","mc":"智动力","jys":"sz"},{"dm":"002266","mc":"浙富控股","jys":"sz"},{"dm":"002056","mc":"横店东磁","jys":"sz"},{"dm":"002984","mc":"森麒麟","jys":"sz"},{"dm":"603317","mc":"天味食品","jys":"sh"},{"dm":"300569","mc":"天能重工","jys":"sz"},{"dm":"300037","mc":"新宙邦","jys":"sz"},{"dm":"600828","mc":"茂业商业","jys":"sh"},{"dm":"300246","mc":"宝莱特","jys":"sz"},{"dm":"688265","mc":"南模生物","jys":"sh"},{"dm":"600746","mc":"江苏索普","jys":"sh"},{"dm":"688187","mc":"时代电气","jys":"sh"},{"dm":"688778","mc":"厦钨新能","jys":"sh"},{"dm":"003028","mc":"振邦智能","jys":"sz"},{"dm":"600137","mc":"浪莎股份","jys":"sh"},{"dm":"002807","mc":"江阴银行","jys":"sz"},{"dm":"603997","mc":"继峰股份","jys":"sh"},{"dm":"002695","mc":"煌上煌","jys":"sz"},{"dm":"300208","mc":"青岛中程","jys":"sz"},{"dm":"000670","mc":"盈方微","jys":"sz"},{"dm":"300486","mc":"东杰智能","jys":"sz"},{"dm":"002484","mc":"江海股份","jys":"sz"},{"dm":"300056","mc":"中创环保","jys":"sz"},{"dm":"688252","mc":"天德钰","jys":"sh"},{"dm":"002216","mc":"三全食品","jys":"sz"},{"dm":"301228","mc":"实朴检测","jys":"sz"},{"dm":"603698","mc":"航天工程","jys":"sh"},{"dm":"600346","mc":"恒力石化","jys":"sh"},{"dm":"601985","mc":"中国核电","jys":"sh"},{"dm":"300680","mc":"隆盛科技","jys":"sz"},{"dm":"002589","mc":"瑞康医药","jys":"sz"},{"dm":"688733","mc":"壹石通","jys":"sh"},{"dm":"603218","mc":"日月股份","jys":"sh"},{"dm":"600433","mc":"冠豪高新","jys":"sh"},{"dm":"688046","mc":"药康生物","jys":"sh"},{"dm":"605006","mc":"山东玻纤","jys":"sh"},{"dm":"301216","mc":"万凯新材","jys":"sz"},{"dm":"688226","mc":"威腾电气","jys":"sh"},{"dm":"600308","mc":"华泰股份","jys":"sh"},{"dm":"301300","mc":"远翔新材","jys":"sz"},{"dm":"002252","mc":"上海莱士","jys":"sz"},{"dm":"300887","mc":"谱尼测试","jys":"sz"},{"dm":"600477","mc":"杭萧钢构","jys":"sh"},{"dm":"600073","mc":"上海梅林","jys":"sh"},{"dm":"002116","mc":"中国海诚","jys":"sz"},{"dm":"600876","mc":"凯盛新能","jys":"sh"},{"dm":"301292","mc":"海科新源","jys":"sz"},{"dm":"688305","mc":"科德数控","jys":"sh"},{"dm":"600489","mc":"中金黄金","jys":"sh"},{"dm":"300658","mc":"延江股份","jys":"sz"},{"dm":"688576","mc":"西山科技","jys":"sh"},{"dm":"000650","mc":"仁和药业","jys":"sz"},{"dm":"301509","mc":"金凯生科","jys":"sz"},{"dm":"605598","mc":"上海港湾","jys":"sh"},{"dm":"600966","mc":"博汇纸业","jys":"sh"},{"dm":"603976","mc":"正川股份","jys":"sh"},{"dm":"600939","mc":"重庆建工","jys":"sh"},{"dm":"603335","mc":"迪生力","jys":"sh"},{"dm":"600917","mc":"重庆燃气","jys":"sh"},{"dm":"300326","mc":"凯利泰","jys":"sz"},{"dm":"002565","mc":"顺灏股份","jys":"sz"},{"dm":"002112","mc":"三变科技","jys":"sz"},{"dm":"002067","mc":"景兴纸业","jys":"sz"},{"dm":"600283","mc":"钱江水利","jys":"sh"},{"dm":"300997","mc":"欢乐家","jys":"sz"},{"dm":"603126","mc":"中材节能","jys":"sh"},{"dm":"300532","mc":"今天国际","jys":"sz"},{"dm":"002193","mc":"如意集团","jys":"sz"},{"dm":"603102","mc":"百合股份","jys":"sh"},{"dm":"688168","mc":"安博通","jys":"sh"},{"dm":"003006","mc":"百亚股份","jys":"sz"},{"dm":"002978","mc":"安宁股份","jys":"sz"},{"dm":"603192","mc":"汇得科技","jys":"sh"},{"dm":"002082","mc":"万邦德","jys":"sz"},{"dm":"000042","mc":"中洲控股","jys":"sz"},{"dm":"605001","mc":"威奥股份","jys":"sh"},{"dm":"600731","mc":"湖南海利","jys":"sh"},{"dm":"300519","mc":"新光药业","jys":"sz"},{"dm":"002636","mc":"金安国纪","jys":"sz"},{"dm":"002798","mc":"帝欧家居","jys":"sz"},{"dm":"600377","mc":"宁沪高速","jys":"sh"},{"dm":"002960","mc":"青鸟消防","jys":"sz"},{"dm":"600350","mc":"山东高速","jys":"sh"},{"dm":"000887","mc":"中鼎股份","jys":"sz"},{"dm":"002992","mc":"宝明科技","jys":"sz"},{"dm":"603421","mc":"鼎信通讯","jys":"sh"},{"dm":"688623","mc":"双元科技","jys":"sh"},{"dm":"600094","mc":"大名城","jys":"sh"},{"dm":"603137","mc":"恒尚节能","jys":"sh"},{"dm":"601200","mc":"上海环境","jys":"sh"},{"dm":"000995","mc":"皇台酒业","jys":"sz"},{"dm":"600933","mc":"爱柯迪","jys":"sh"},{"dm":"688459","mc":"哈铁科技","jys":"sh"},{"dm":"000619","mc":"海螺新材","jys":"sz"},{"dm":"300965","mc":"恒宇信通","jys":"sz"},{"dm":"603886","mc":"元祖股份","jys":"sh"},{"dm":"000023","mc":"ST深天","jys":"sz"},{"dm":"605286","mc":"同力日升","jys":"sh"},{"dm":"002938","mc":"鹏鼎控股","jys":"sz"},{"dm":"601228","mc":"广州港","jys":"sh"},{"dm":"000802","mc":"北京文化","jys":"sz"},{"dm":"603817","mc":"海峡环保","jys":"sh"},{"dm":"002849","mc":"威星智能","jys":"sz"},{"dm":"002538","mc":"司尔特","jys":"sz"},{"dm":"600064","mc":"南京高科","jys":"sh"},{"dm":"000078","mc":"海王生物","jys":"sz"},{"dm":"600635","mc":"大众公用","jys":"sh"},{"dm":"002029","mc":"七 匹 狼","jys":"sz"},{"dm":"300768","mc":"迪普科技","jys":"sz"},{"dm":"688011","mc":"新光光电","jys":"sh"},{"dm":"688297","mc":"中无人机","jys":"sh"},{"dm":"300594","mc":"朗进科技","jys":"sz"},{"dm":"300091","mc":"金通灵","jys":"sz"},{"dm":"603156","mc":"养元饮品","jys":"sh"},{"dm":"300983","mc":"尤安设计","jys":"sz"},{"dm":"603628","mc":"清源股份","jys":"sh"},{"dm":"600490","mc":"鹏欣资源","jys":"sh"},{"dm":"603458","mc":"勘设股份","jys":"sh"},{"dm":"600958","mc":"东方证券","jys":"sh"},{"dm":"688589","mc":"力合微","jys":"sh"},{"dm":"603991","mc":"至正股份","jys":"sh"},{"dm":"601399","mc":"国机重装","jys":"sh"},{"dm":"002202","mc":"金风科技","jys":"sz"},{"dm":"002141","mc":"贤丰控股","jys":"sz"},{"dm":"000617","mc":"中油资本","jys":"sz"},{"dm":"688677","mc":"海泰新光","jys":"sh"},{"dm":"600108","mc":"亚盛集团","jys":"sh"},{"dm":"000858","mc":"五 粮 液","jys":"sz"},{"dm":"603916","mc":"苏博特","jys":"sh"},{"dm":"605555","mc":"德昌股份","jys":"sh"},{"dm":"688349","mc":"三一重能","jys":"sh"},{"dm":"301325","mc":"曼恩斯特","jys":"sz"},{"dm":"002191","mc":"劲嘉股份","jys":"sz"},{"dm":"000002","mc":"万 科A","jys":"sz"},{"dm":"600817","mc":"宇通重工","jys":"sh"},{"dm":"600583","mc":"海油工程","jys":"sh"},{"dm":"688319","mc":"欧林生物","jys":"sh"},{"dm":"601818","mc":"光大银行","jys":"sh"},{"dm":"003816","mc":"中国广核","jys":"sz"},{"dm":"603682","mc":"锦和商管","jys":"sh"},{"dm":"688707","mc":"振华新材","jys":"sh"},{"dm":"000411","mc":"英特集团","jys":"sz"},{"dm":"002932","mc":"明德生物","jys":"sz"},{"dm":"688475","mc":"萤石网络","jys":"sh"},{"dm":"002471","mc":"中超控股","jys":"sz"},{"dm":"300511","mc":"雪榕生物","jys":"sz"},{"dm":"688819","mc":"天能股份","jys":"sh"},{"dm":"002817","mc":"黄山胶囊","jys":"sz"},{"dm":"002022","mc":"科华生物","jys":"sz"},{"dm":"300748","mc":"金力永磁","jys":"sz"},{"dm":"603001","mc":"ST奥康","jys":"sh"},{"dm":"603366","mc":"日出东方","jys":"sh"},{"dm":"688569","mc":"铁科轨道","jys":"sh"},{"dm":"002280","mc":"联络互动","jys":"sz"},{"dm":"600562","mc":"国睿科技","jys":"sh"},{"dm":"600547","mc":"山东黄金","jys":"sh"},{"dm":"300888","mc":"稳健医疗","jys":"sz"},{"dm":"601718","mc":"际华集团","jys":"sh"},{"dm":"002703","mc":"浙江世宝","jys":"sz"},{"dm":"002195","mc":"岩山科技","jys":"sz"},{"dm":"002983","mc":"芯瑞达","jys":"sz"},{"dm":"002581","mc":"未名医药","jys":"sz"},{"dm":"603387","mc":"基蛋生物","jys":"sh"},{"dm":"600017","mc":"日照港","jys":"sh"},{"dm":"300159","mc":"新研股份","jys":"sz"},{"dm":"002705","mc":"新宝股份","jys":"sz"},{"dm":"300413","mc":"芒果超媒","jys":"sz"},{"dm":"603668","mc":"天马科技","jys":"sh"},{"dm":"301063","mc":"海锅股份","jys":"sz"},{"dm":"600239","mc":"云南城投","jys":"sh"},{"dm":"002173","mc":"创新医疗","jys":"sz"},{"dm":"300393","mc":"中来股份","jys":"sz"},{"dm":"002157","mc":"*ST 正邦","jys":"sz"},{"dm":"001316","mc":"润贝航科","jys":"sz"},{"dm":"601311","mc":"骆驼股份","jys":"sh"},{"dm":"301305","mc":"朗坤环境","jys":"sz"},{"dm":"600981","mc":"汇鸿集团","jys":"sh"},{"dm":"000610","mc":"西安旅游","jys":"sz"},{"dm":"300769","mc":"德方纳米","jys":"sz"},{"dm":"600325","mc":"华发股份","jys":"sh"},{"dm":"000419","mc":"通程控股","jys":"sz"},{"dm":"601212","mc":"白银有色","jys":"sh"},{"dm":"603215","mc":"比依股份","jys":"sh"},{"dm":"600098","mc":"广州发展","jys":"sh"},{"dm":"600372","mc":"中航机载","jys":"sh"},{"dm":"002386","mc":"天原股份","jys":"sz"},{"dm":"000737","mc":"北方铜业","jys":"sz"},{"dm":"002125","mc":"湘潭电化","jys":"sz"},{"dm":"000721","mc":"西安饮食","jys":"sz"},{"dm":"000517","mc":"荣安地产","jys":"sz"},{"dm":"300791","mc":"仙乐健康","jys":"sz"},{"dm":"300182","mc":"捷成股份","jys":"sz"},{"dm":"000030","mc":"富奥股份","jys":"sz"},{"dm":"002758","mc":"浙农股份","jys":"sz"},{"dm":"301112","mc":"信邦智能","jys":"sz"},{"dm":"301088","mc":"戎美股份","jys":"sz"},{"dm":"300853","mc":"申昊科技","jys":"sz"},{"dm":"002048","mc":"宁波华翔","jys":"sz"},{"dm":"002201","mc":"正威新材","jys":"sz"},{"dm":"002702","mc":"海欣食品","jys":"sz"},{"dm":"688002","mc":"睿创微纳","jys":"sh"},{"dm":"002434","mc":"万里扬","jys":"sz"},{"dm":"002292","mc":"奥飞娱乐","jys":"sz"},{"dm":"300572","mc":"安车检测","jys":"sz"},{"dm":"601996","mc":"丰林集团","jys":"sh"},{"dm":"601086","mc":"国芳集团","jys":"sh"},{"dm":"000559","mc":"万向钱潮","jys":"sz"},{"dm":"603130","mc":"云中马","jys":"sh"},{"dm":"300220","mc":"ST金运","jys":"sz"},{"dm":"002850","mc":"科达利","jys":"sz"},{"dm":"600367","mc":"红星发展","jys":"sh"},{"dm":"002128","mc":"电投能源","jys":"sz"},{"dm":"603155","mc":"新亚强","jys":"sh"},{"dm":"600252","mc":"中恒集团","jys":"sh"},{"dm":"000927","mc":"中国铁物","jys":"sz"},{"dm":"000507","mc":"珠海港","jys":"sz"},{"dm":"002367","mc":"康力电梯","jys":"sz"},{"dm":"002429","mc":"兆驰股份","jys":"sz"},{"dm":"301195","mc":"北路智控","jys":"sz"},{"dm":"002353","mc":"杰瑞股份","jys":"sz"},{"dm":"002285","mc":"世联行","jys":"sz"},{"dm":"605319","mc":"无锡振华","jys":"sh"},{"dm":"002393","mc":"力生制药","jys":"sz"},{"dm":"002250","mc":"联化科技","jys":"sz"},{"dm":"300044","mc":"赛为智能","jys":"sz"},{"dm":"603955","mc":"大千生态","jys":"sh"},{"dm":"301322","mc":"绿通科技","jys":"sz"},{"dm":"688538","mc":"和辉光电-U","jys":"sh"},{"dm":"601728","mc":"中国电信","jys":"sh"},{"dm":"688211","mc":"中科微至","jys":"sh"},{"dm":"300350","mc":"华鹏飞","jys":"sz"},{"dm":"601860","mc":"紫金银行","jys":"sh"},{"dm":"600196","mc":"复星医药","jys":"sh"},{"dm":"002287","mc":"奇正藏药","jys":"sz"},{"dm":"300894","mc":"火星人","jys":"sz"},{"dm":"002697","mc":"红旗连锁","jys":"sz"},{"dm":"000568","mc":"泸州老窖","jys":"sz"},{"dm":"002085","mc":"万丰奥威","jys":"sz"},{"dm":"601528","mc":"瑞丰银行","jys":"sh"},{"dm":"600497","mc":"驰宏锌锗","jys":"sh"},{"dm":"600337","mc":"美克家居","jys":"sh"},{"dm":"300048","mc":"合康新能","jys":"sz"},{"dm":"600582","mc":"天地科技","jys":"sh"},{"dm":"600488","mc":"津药药业","jys":"sh"},{"dm":"000422","mc":"湖北宜化","jys":"sz"},{"dm":"600621","mc":"华鑫股份","jys":"sh"},{"dm":"605336","mc":"帅丰电器","jys":"sh"},{"dm":"301332","mc":"德尔玛","jys":"sz"},{"dm":"301121","mc":"紫建电子","jys":"sz"},{"dm":"600838","mc":"上海九百","jys":"sh"},{"dm":"600703","mc":"三安光电","jys":"sh"},{"dm":"600370","mc":"三房巷","jys":"sh"},{"dm":"003023","mc":"彩虹集团","jys":"sz"},{"dm":"603056","mc":"德邦股份","jys":"sh"},{"dm":"600897","mc":"厦门空港","jys":"sh"},{"dm":"603659","mc":"璞泰来","jys":"sh"},{"dm":"000553","mc":"安道麦A","jys":"sz"},{"dm":"603132","mc":"金徽股份","jys":"sh"},{"dm":"601963","mc":"重庆银行","jys":"sh"},{"dm":"301377","mc":"鼎泰高科","jys":"sz"},{"dm":"600713","mc":"南京医药","jys":"sh"},{"dm":"301168","mc":"通灵股份","jys":"sz"},{"dm":"600359","mc":"新农开发","jys":"sh"},{"dm":"600576","mc":"祥源文旅","jys":"sh"},{"dm":"002579","mc":"中京电子","jys":"sz"},{"dm":"600517","mc":"国网英大","jys":"sh"},{"dm":"605003","mc":"众望布艺","jys":"sh"},{"dm":"600697","mc":"欧亚集团","jys":"sh"},{"dm":"301227","mc":"森鹰窗业","jys":"sz"},{"dm":"002425","mc":"凯撒文化","jys":"sz"},{"dm":"301046","mc":"能辉科技","jys":"sz"},{"dm":"300080","mc":"易成新能","jys":"sz"},{"dm":"300501","mc":"海顺新材","jys":"sz"},{"dm":"000592","mc":"平潭发展","jys":"sz"},{"dm":"000560","mc":"我爱我家","jys":"sz"},{"dm":"000768","mc":"中航西飞","jys":"sz"},{"dm":"688396","mc":"华润微","jys":"sh"},{"dm":"301001","mc":"凯淳股份","jys":"sz"},{"dm":"001236","mc":"弘业期货","jys":"sz"},{"dm":"300851","mc":"交大思诺","jys":"sz"},{"dm":"001332","mc":"锡装股份","jys":"sz"},{"dm":"000967","mc":"盈峰环境","jys":"sz"},{"dm":"002265","mc":"建设工业","jys":"sz"},{"dm":"603982","mc":"泉峰汽车","jys":"sh"},{"dm":"300662","mc":"科锐国际","jys":"sz"},{"dm":"601615","mc":"明阳智能","jys":"sh"},{"dm":"603115","mc":"海星股份","jys":"sh"},{"dm":"600467","mc":"好当家","jys":"sh"},{"dm":"000519","mc":"中兵红箭","jys":"sz"},{"dm":"600628","mc":"新世界","jys":"sh"},{"dm":"601128","mc":"常熟银行","jys":"sh"},{"dm":"603995","mc":"甬金股份","jys":"sh"},{"dm":"688312","mc":"燕麦科技","jys":"sh"},{"dm":"600905","mc":"三峡能源","jys":"sh"},{"dm":"688208","mc":"道通科技","jys":"sh"},{"dm":"002651","mc":"利君股份","jys":"sz"},{"dm":"002545","mc":"东方铁塔","jys":"sz"},{"dm":"002930","mc":"宏川智慧","jys":"sz"},{"dm":"002965","mc":"祥鑫科技","jys":"sz"},{"dm":"688247","mc":"宣泰医药","jys":"sh"},{"dm":"603077","mc":"和邦生物","jys":"sh"},{"dm":"002468","mc":"申通快递","jys":"sz"},{"dm":"000607","mc":"华媒控股","jys":"sz"},{"dm":"600150","mc":"中国船舶","jys":"sh"},{"dm":"300666","mc":"江丰电子","jys":"sz"},{"dm":"600597","mc":"光明乳业","jys":"sh"},{"dm":"601038","mc":"一拖股份","jys":"sh"},{"dm":"600178","mc":"东安动力","jys":"sh"},{"dm":"603909","mc":"建发合诚","jys":"sh"},{"dm":"603506","mc":"南都物业","jys":"sh"},{"dm":"002319","mc":"乐通股份","jys":"sz"},{"dm":"601908","mc":"京运通","jys":"sh"},{"dm":"603787","mc":"新日股份","jys":"sh"},{"dm":"603678","mc":"火炬电子","jys":"sh"},{"dm":"300257","mc":"开山股份","jys":"sz"},{"dm":"300176","mc":"派生科技","jys":"sz"},{"dm":"002304","mc":"洋河股份","jys":"sz"},{"dm":"002382","mc":"蓝帆医疗","jys":"sz"},{"dm":"688009","mc":"中国通号","jys":"sh"},{"dm":"300435","mc":"中泰股份","jys":"sz"},{"dm":"688118","mc":"普元信息","jys":"sh"},{"dm":"688789","mc":"宏华数科","jys":"sh"},{"dm":"300055","mc":"万邦达","jys":"sz"},{"dm":"002027","mc":"分众传媒","jys":"sz"},{"dm":"300039","mc":"上海凯宝","jys":"sz"},{"dm":"603087","mc":"甘李药业","jys":"sh"},{"dm":"603693","mc":"江苏新能","jys":"sh"},{"dm":"600000","mc":"浦发银行","jys":"sh"},{"dm":"601658","mc":"邮储银行","jys":"sh"},{"dm":"300197","mc":"节能铁汉","jys":"sz"},{"dm":"001313","mc":"粤海饲料","jys":"sz"},{"dm":"600755","mc":"厦门国贸","jys":"sh"},{"dm":"002707","mc":"众信旅游","jys":"sz"},{"dm":"300783","mc":"三只松鼠","jys":"sz"},{"dm":"000159","mc":"国际实业","jys":"sz"},{"dm":"601866","mc":"中远海发","jys":"sh"},{"dm":"000897","mc":"津滨发展","jys":"sz"},{"dm":"301022","mc":"海泰科","jys":"sz"},{"dm":"603217","mc":"元利科技","jys":"sh"},{"dm":"300864","mc":"南大环境","jys":"sz"},{"dm":"688285","mc":"高铁电气","jys":"sh"},{"dm":"603162","mc":"海通发展","jys":"sh"},{"dm":"601139","mc":"深圳燃气","jys":"sh"},{"dm":"300183","mc":"东软载波","jys":"sz"},{"dm":"300390","mc":"天华新能","jys":"sz"},{"dm":"002986","mc":"宇新股份","jys":"sz"},{"dm":"601579","mc":"会稽山","jys":"sh"},{"dm":"002198","mc":"嘉应制药","jys":"sz"},{"dm":"601118","mc":"海南橡胶","jys":"sh"},{"dm":"000006","mc":"深振业A","jys":"sz"},{"dm":"301101","mc":"明月镜片","jys":"sz"},{"dm":"001360","mc":"南矿集团","jys":"sz"},{"dm":"601222","mc":"林洋能源","jys":"sh"},{"dm":"002865","mc":"钧达股份","jys":"sz"},{"dm":"603378","mc":"亚士创能","jys":"sh"},{"dm":"002183","mc":"怡 亚 通","jys":"sz"},{"dm":"605060","mc":"联德股份","jys":"sh"},{"dm":"600726","mc":"华电能源","jys":"sh"},{"dm":"603260","mc":"合盛硅业","jys":"sh"},{"dm":"300139","mc":"晓程科技","jys":"sz"},{"dm":"300773","mc":"拉卡拉","jys":"sz"},{"dm":"601868","mc":"中国能建","jys":"sh"},{"dm":"002887","mc":"绿茵生态","jys":"sz"},{"dm":"300298","mc":"三诺生物","jys":"sz"},{"dm":"002701","mc":"奥瑞金","jys":"sz"},{"dm":"301276","mc":"嘉曼服饰","jys":"sz"},{"dm":"688184","mc":"帕瓦股份","jys":"sh"},{"dm":"000886","mc":"海南高速","jys":"sz"},{"dm":"600079","mc":"人福医药","jys":"sh"},{"dm":"002016","mc":"世荣兆业","jys":"sz"},{"dm":"301152","mc":"天力锂能","jys":"sz"},{"dm":"300148","mc":"天舟文化","jys":"sz"},{"dm":"600004","mc":"白云机场","jys":"sh"},{"dm":"003009","mc":"中天火箭","jys":"sz"},{"dm":"300206","mc":"理邦仪器","jys":"sz"},{"dm":"300086","mc":"康芝药业","jys":"sz"},{"dm":"002223","mc":"鱼跃医疗","jys":"sz"},{"dm":"300450","mc":"先导智能","jys":"sz"},{"dm":"300682","mc":"朗新科技","jys":"sz"},{"dm":"601952","mc":"苏垦农发","jys":"sh"},{"dm":"605567","mc":"春雪食品","jys":"sh"},{"dm":"600717","mc":"天津港","jys":"sh"},{"dm":"002623","mc":"亚玛顿","jys":"sz"},{"dm":"603309","mc":"维力医疗","jys":"sh"},{"dm":"301122","mc":"采纳股份","jys":"sz"},{"dm":"600556","mc":"天下秀","jys":"sh"},{"dm":"002686","mc":"亿利达","jys":"sz"},{"dm":"600992","mc":"贵绳股份","jys":"sh"},{"dm":"301511","mc":"德福科技","jys":"sz"},{"dm":"002466","mc":"天齐锂业","jys":"sz"},{"dm":"002921","mc":"联诚精密","jys":"sz"},{"dm":"600598","mc":"北大荒","jys":"sh"},{"dm":"002691","mc":"冀凯股份","jys":"sz"},{"dm":"600696","mc":"岩石股份","jys":"sh"},{"dm":"301026","mc":"浩通科技","jys":"sz"},{"dm":"002782","mc":"可立克","jys":"sz"},{"dm":"605136","mc":"丽人丽妆","jys":"sh"},{"dm":"002734","mc":"利民股份","jys":"sz"},{"dm":"002570","mc":"贝因美","jys":"sz"},{"dm":"002669","mc":"康达新材","jys":"sz"},{"dm":"002631","mc":"德尔未来","jys":"sz"},{"dm":"601688","mc":"华泰证券","jys":"sh"},{"dm":"601020","mc":"华钰矿业","jys":"sh"},{"dm":"300616","mc":"尚品宅配","jys":"sz"},{"dm":"688669","mc":"聚石化学","jys":"sh"},{"dm":"600419","mc":"天润乳业","jys":"sh"},{"dm":"002873","mc":"新天药业","jys":"sz"},{"dm":"002209","mc":"达 意 隆","jys":"sz"},{"dm":"301333","mc":"诺思格","jys":"sz"},{"dm":"601992","mc":"金隅集团","jys":"sh"},{"dm":"002886","mc":"沃特股份","jys":"sz"},{"dm":"688178","mc":"万德斯","jys":"sh"},{"dm":"300345","mc":"华民股份","jys":"sz"},{"dm":"002898","mc":"赛隆药业","jys":"sz"},{"dm":"688100","mc":"威胜信息","jys":"sh"},{"dm":"000581","mc":"威孚高科","jys":"sz"},{"dm":"300530","mc":"领湃科技","jys":"sz"},{"dm":"301257","mc":"普蕊斯","jys":"sz"},{"dm":"300446","mc":"乐凯新材","jys":"sz"},{"dm":"002563","mc":"森马服饰","jys":"sz"},{"dm":"600774","mc":"汉商集团","jys":"sh"},{"dm":"603990","mc":"麦迪科技","jys":"sh"},{"dm":"601800","mc":"中国交建","jys":"sh"},{"dm":"603223","mc":"恒通股份","jys":"sh"},{"dm":"301118","mc":"恒光股份","jys":"sz"},{"dm":"688067","mc":"爱威科技","jys":"sh"},{"dm":"688233","mc":"神工股份","jys":"sh"},{"dm":"601989","mc":"中国重工","jys":"sh"},{"dm":"600380","mc":"健康元","jys":"sh"},{"dm":"000976","mc":"ST华铁","jys":"sz"},{"dm":"301370","mc":"国科恒泰","jys":"sz"},{"dm":"000029","mc":"深深房A","jys":"sz"},{"dm":"603877","mc":"太平鸟","jys":"sh"},{"dm":"600581","mc":"八一钢铁","jys":"sh"},{"dm":"002422","mc":"科伦药业","jys":"sz"},{"dm":"603200","mc":"上海洗霸","jys":"sh"},{"dm":"600328","mc":"中盐化工","jys":"sh"},{"dm":"301222","mc":"浙江恒威","jys":"sz"},{"dm":"600859","mc":"王府井","jys":"sh"},{"dm":"600038","mc":"中直股份","jys":"sh"},{"dm":"300006","mc":"莱美药业","jys":"sz"},{"dm":"603135","mc":"中重科技","jys":"sh"},{"dm":"002683","mc":"广东宏大","jys":"sz"},{"dm":"300890","mc":"翔丰华","jys":"sz"},{"dm":"002317","mc":"众生药业","jys":"sz"},{"dm":"002531","mc":"天顺风能","jys":"sz"},{"dm":"600527","mc":"江南高纤","jys":"sh"},{"dm":"688646","mc":"逸飞激光","jys":"sh"},{"dm":"300278","mc":"华昌达","jys":"sz"},{"dm":"600812","mc":"华北制药","jys":"sh"},{"dm":"002045","mc":"国光电器","jys":"sz"},{"dm":"600596","mc":"新安股份","jys":"sh"},{"dm":"300013","mc":"新宁物流","jys":"sz"},{"dm":"688613","mc":"奥精医疗","jys":"sh"},{"dm":"600665","mc":"天地源","jys":"sh"},{"dm":"002075","mc":"沙钢股份","jys":"sz"},{"dm":"002714","mc":"牧原股份","jys":"sz"},{"dm":"601137","mc":"博威合金","jys":"sh"},{"dm":"600936","mc":"广西广电","jys":"sh"},{"dm":"600559","mc":"老白干酒","jys":"sh"},{"dm":"600167","mc":"联美控股","jys":"sh"},{"dm":"688552","mc":"航天南湖","jys":"sh"},{"dm":"301219","mc":"腾远钴业","jys":"sz"},{"dm":"000062","mc":"深圳华强","jys":"sz"},{"dm":"688388","mc":"嘉元科技","jys":"sh"},{"dm":"002043","mc":"兔 宝 宝","jys":"sz"},{"dm":"002210","mc":"飞马国际","jys":"sz"},{"dm":"605116","mc":"奥锐特","jys":"sh"},{"dm":"600391","mc":"航发科技","jys":"sh"},{"dm":"601919","mc":"中远海控","jys":"sh"},{"dm":"688266","mc":"泽璟制药-U","jys":"sh"},{"dm":"600871","mc":"石化油服","jys":"sh"},{"dm":"300421","mc":"力星股份","jys":"sz"},{"dm":"301211","mc":"亨迪药业","jys":"sz"},{"dm":"002765","mc":"蓝黛科技","jys":"sz"},{"dm":"600874","mc":"创业环保","jys":"sh"},{"dm":"600340","mc":"华夏幸福","jys":"sh"},{"dm":"603606","mc":"东方电缆","jys":"sh"},{"dm":"688128","mc":"中国电研","jys":"sh"},{"dm":"300335","mc":"迪森股份","jys":"sz"},{"dm":"002663","mc":"普邦股份","jys":"sz"},{"dm":"601166","mc":"兴业银行","jys":"sh"},{"dm":"688690","mc":"纳微科技","jys":"sh"},{"dm":"600409","mc":"三友化工","jys":"sh"},{"dm":"600803","mc":"新奥股份","jys":"sh"},{"dm":"002790","mc":"瑞尔特","jys":"sz"},{"dm":"600742","mc":"一汽富维","jys":"sh"},{"dm":"300778","mc":"新城市","jys":"sz"},{"dm":"601968","mc":"宝钢包装","jys":"sh"},{"dm":"600518","mc":"ST康美","jys":"sh"},{"dm":"600130","mc":"波导股份","jys":"sh"},{"dm":"300328","mc":"宜安科技","jys":"sz"},{"dm":"300301","mc":"*ST长方","jys":"sz"},{"dm":"688268","mc":"华特气体","jys":"sh"},{"dm":"600479","mc":"千金药业","jys":"sh"},{"dm":"000669","mc":"ST金鸿","jys":"sz"},{"dm":"600629","mc":"华建集团","jys":"sh"},{"dm":"000582","mc":"北部湾港","jys":"sz"},{"dm":"688186","mc":"广大特材","jys":"sh"},{"dm":"000869","mc":"张 裕A","jys":"sz"},{"dm":"300697","mc":"电工合金","jys":"sz"},{"dm":"600835","mc":"上海机电","jys":"sh"},{"dm":"300373","mc":"扬杰科技","jys":"sz"},{"dm":"300147","mc":"香雪制药","jys":"sz"},{"dm":"600858","mc":"银座股份","jys":"sh"},{"dm":"600373","mc":"中文传媒","jys":"sh"},{"dm":"600478","mc":"科力远","jys":"sh"},{"dm":"300869","mc":"康泰医学","jys":"sz"},{"dm":"300244","mc":"迪安诊断","jys":"sz"},{"dm":"688136","mc":"科兴制药","jys":"sh"},{"dm":"300770","mc":"新媒股份","jys":"sz"},{"dm":"300100","mc":"双林股份","jys":"sz"},{"dm":"600165","mc":"宁科生物","jys":"sh"},{"dm":"301429","mc":"森泰股份","jys":"sz"},{"dm":"001203","mc":"大中矿业","jys":"sz"},{"dm":"301206","mc":"三元生物","jys":"sz"},{"dm":"603298","mc":"杭叉集团","jys":"sh"},{"dm":"002329","mc":"皇氏集团","jys":"sz"},{"dm":"688026","mc":"洁特生物","jys":"sh"},{"dm":"000048","mc":"京基智农","jys":"sz"},{"dm":"002846","mc":"英联股份","jys":"sz"},{"dm":"600280","mc":"中央商场","jys":"sh"},{"dm":"002269","mc":"美邦服饰","jys":"sz"},{"dm":"300782","mc":"卓胜微","jys":"sz"},{"dm":"300837","mc":"浙矿股份","jys":"sz"},{"dm":"001368","mc":"通达创智","jys":"sz"},{"dm":"002179","mc":"中航光电","jys":"sz"},{"dm":"002550","mc":"千红制药","jys":"sz"},{"dm":"601021","mc":"春秋航空","jys":"sh"},{"dm":"003033","mc":"征和工业","jys":"sz"},{"dm":"688508","mc":"芯朋微","jys":"sh"},{"dm":"002780","mc":"三夫户外","jys":"sz"},{"dm":"002687","mc":"乔治白","jys":"sz"},{"dm":"605009","mc":"豪悦护理","jys":"sh"},{"dm":"002742","mc":"ST三圣","jys":"sz"},{"dm":"002051","mc":"中工国际","jys":"sz"},{"dm":"601611","mc":"中国核建","jys":"sh"},{"dm":"603299","mc":"苏盐井神","jys":"sh"},{"dm":"600306","mc":"*ST商城","jys":"sh"},{"dm":"688317","mc":"之江生物","jys":"sh"},{"dm":"600106","mc":"重庆路桥","jys":"sh"},{"dm":"002039","mc":"黔源电力","jys":"sz"},{"dm":"301296","mc":"新巨丰","jys":"sz"},{"dm":"600021","mc":"上海电力","jys":"sh"},{"dm":"688148","mc":"芳源股份","jys":"sh"},{"dm":"603031","mc":"安孚科技","jys":"sh"},{"dm":"000950","mc":"重药控股","jys":"sz"},{"dm":"001338","mc":"永顺泰","jys":"sz"},{"dm":"600501","mc":"航天晨光","jys":"sh"},{"dm":"002905","mc":"金逸影视","jys":"sz"},{"dm":"301017","mc":"漱玉平民","jys":"sz"},{"dm":"300896","mc":"爱美客","jys":"sz"},{"dm":"605338","mc":"巴比食品","jys":"sh"},{"dm":"603337","mc":"杰克股份","jys":"sh"},{"dm":"000031","mc":"大悦城","jys":"sz"},{"dm":"603256","mc":"宏和科技","jys":"sh"},{"dm":"300549","mc":"优德精密","jys":"sz"},{"dm":"002352","mc":"顺丰控股","jys":"sz"},{"dm":"600967","mc":"内蒙一机","jys":"sh"},{"dm":"688630","mc":"芯碁微装","jys":"sh"},{"dm":"601997","mc":"贵阳银行","jys":"sh"},{"dm":"600827","mc":"百联股份","jys":"sh"},{"dm":"600151","mc":"航天机电","jys":"sh"},{"dm":"300443","mc":"金雷股份","jys":"sz"},{"dm":"601330","mc":"绿色动力","jys":"sh"},{"dm":"600320","mc":"振华重工","jys":"sh"},{"dm":"688368","mc":"晶丰明源","jys":"sh"},{"dm":"600787","mc":"中储股份","jys":"sh"},{"dm":"300861","mc":"美畅股份","jys":"sz"},{"dm":"600973","mc":"宝胜股份","jys":"sh"},{"dm":"301033","mc":"迈普医学","jys":"sz"},{"dm":"000007","mc":"*ST全新","jys":"sz"},{"dm":"300607","mc":"拓斯达","jys":"sz"},{"dm":"000799","mc":"酒鬼酒","jys":"sz"},{"dm":"600744","mc":"华银电力","jys":"sh"},{"dm":"002412","mc":"汉森制药","jys":"sz"},{"dm":"000788","mc":"北大医药","jys":"sz"},{"dm":"000792","mc":"盐湖股份","jys":"sz"},{"dm":"600875","mc":"东方电气","jys":"sh"},{"dm":"003020","mc":"立方制药","jys":"sz"},{"dm":"600603","mc":"广汇物流","jys":"sh"},{"dm":"300775","mc":"三角防务","jys":"sz"},{"dm":"300457","mc":"赢合科技","jys":"sz"},{"dm":"002052","mc":"ST同洲","jys":"sz"},{"dm":"600361","mc":"创新新材","jys":"sh"},{"dm":"002244","mc":"滨江集团","jys":"sz"},{"dm":"002732","mc":"燕塘乳业","jys":"sz"},{"dm":"300575","mc":"中旗股份","jys":"sz"},{"dm":"001309","mc":"德明利","jys":"sz"},{"dm":"603032","mc":"德新科技","jys":"sh"},{"dm":"301515","mc":"港通医疗","jys":"sz"},{"dm":"002968","mc":"新大正","jys":"sz"},{"dm":"002010","mc":"传化智联","jys":"sz"},{"dm":"601231","mc":"环旭电子","jys":"sh"},{"dm":"600104","mc":"上汽集团","jys":"sh"},{"dm":"603316","mc":"诚邦股份","jys":"sh"},{"dm":"601888","mc":"中国中免","jys":"sh"},{"dm":"688176","mc":"亚虹医药-U","jys":"sh"},{"dm":"600496","mc":"精工钢构","jys":"sh"},{"dm":"300815","mc":"玉禾田","jys":"sz"},{"dm":"688625","mc":"呈和科技","jys":"sh"},{"dm":"600097","mc":"开创国际","jys":"sh"},{"dm":"301047","mc":"义翘神州","jys":"sz"},{"dm":"300239","mc":"东宝生物","jys":"sz"},{"dm":"002149","mc":"西部材料","jys":"sz"},{"dm":"600010","mc":"包钢股份","jys":"sh"},{"dm":"600027","mc":"华电国际","jys":"sh"},{"dm":"600685","mc":"中船防务","jys":"sh"},{"dm":"688084","mc":"晶品特装","jys":"sh"},{"dm":"603296","mc":"华勤技术","jys":"sh"},{"dm":"600655","mc":"豫园股份","jys":"sh"},{"dm":"002948","mc":"青岛银行","jys":"sz"},{"dm":"000656","mc":"金科股份","jys":"sz"},{"dm":"002167","mc":"东方锆业","jys":"sz"},{"dm":"300848","mc":"美瑞新材","jys":"sz"},{"dm":"300981","mc":"中红医疗","jys":"sz"},{"dm":"600132","mc":"重庆啤酒","jys":"sh"},{"dm":"603665","mc":"康隆达","jys":"sh"},{"dm":"688179","mc":"阿拉丁","jys":"sh"},{"dm":"605389","mc":"长龄液压","jys":"sh"},{"dm":"603321","mc":"梅轮电梯","jys":"sh"},{"dm":"300428","mc":"立中集团","jys":"sz"},{"dm":"603023","mc":"威帝股份","jys":"sh"},{"dm":"000690","mc":"宝新能源","jys":"sz"},{"dm":"002187","mc":"广百股份","jys":"sz"},{"dm":"600639","mc":"浦东金桥","jys":"sh"},{"dm":"600565","mc":"迪马股份","jys":"sh"},{"dm":"601965","mc":"中国汽研","jys":"sh"},{"dm":"603386","mc":"骏亚科技","jys":"sh"},{"dm":"600358","mc":"国旅联合","jys":"sh"},{"dm":"300595","mc":"欧普康视","jys":"sz"},{"dm":"688196","mc":"卓越新能","jys":"sh"},{"dm":"600616","mc":"金枫酒业","jys":"sh"},{"dm":"301057","mc":"汇隆新材","jys":"sz"},{"dm":"000819","mc":"岳阳兴长","jys":"sz"},{"dm":"300214","mc":"日科化学","jys":"sz"},{"dm":"601016","mc":"节能风电","jys":"sh"},{"dm":"600216","mc":"浙江医药","jys":"sh"},{"dm":"600293","mc":"三峡新材","jys":"sh"},{"dm":"603883","mc":"老百姓","jys":"sh"},{"dm":"603708","mc":"家家悦","jys":"sh"},{"dm":"601689","mc":"拓普集团","jys":"sh"},{"dm":"600300","mc":"维维股份","jys":"sh"},{"dm":"001979","mc":"招商蛇口","jys":"sz"},{"dm":"301111","mc":"粤万年青","jys":"sz"},{"dm":"301050","mc":"雷电微力","jys":"sz"},{"dm":"002966","mc":"苏州银行","jys":"sz"},{"dm":"600063","mc":"皖维高新","jys":"sh"},{"dm":"600117","mc":"*ST西钢","jys":"sh"},{"dm":"002020","mc":"京新药业","jys":"sz"},{"dm":"688072","mc":"拓荆科技","jys":"sh"},{"dm":"002028","mc":"思源电气","jys":"sz"},{"dm":"300619","mc":"金银河","jys":"sz"},{"dm":"600557","mc":"康缘药业","jys":"sh"},{"dm":"601179","mc":"中国西电","jys":"sh"},{"dm":"600166","mc":"福田汽车","jys":"sh"},{"dm":"000928","mc":"中钢国际","jys":"sz"},{"dm":"301071","mc":"力量钻石","jys":"sz"},{"dm":"603385","mc":"惠达卫浴","jys":"sh"},{"dm":"605100","mc":"华丰股份","jys":"sh"},{"dm":"002204","mc":"大连重工","jys":"sz"},{"dm":"000966","mc":"长源电力","jys":"sz"},{"dm":"300453","mc":"三鑫医疗","jys":"sz"},{"dm":"300642","mc":"透景生命","jys":"sz"},{"dm":"300776","mc":"帝尔激光","jys":"sz"},{"dm":"600110","mc":"诺德股份","jys":"sh"},{"dm":"603099","mc":"长白山","jys":"sh"},{"dm":"000767","mc":"晋控电力","jys":"sz"},{"dm":"300146","mc":"汤臣倍健","jys":"sz"},{"dm":"688518","mc":"联赢激光","jys":"sh"},{"dm":"000426","mc":"兴业银锡","jys":"sz"},{"dm":"002612","mc":"朗姿股份","jys":"sz"},{"dm":"301200","mc":"大族数控","jys":"sz"},{"dm":"301277","mc":"新天地","jys":"sz"},{"dm":"688550","mc":"瑞联新材","jys":"sh"},{"dm":"600190","mc":"锦州港","jys":"sh"},{"dm":"002091","mc":"江苏国泰","jys":"sz"},{"dm":"603661","mc":"恒林股份","jys":"sh"},{"dm":"301221","mc":"光庭信息","jys":"sz"},{"dm":"688278","mc":"特宝生物","jys":"sh"},{"dm":"002816","mc":"*ST和科","jys":"sz"},{"dm":"603515","mc":"欧普照明","jys":"sh"},{"dm":"000630","mc":"铜陵有色","jys":"sz"},{"dm":"300378","mc":"鼎捷软件","jys":"sz"},{"dm":"002372","mc":"伟星新材","jys":"sz"},{"dm":"002584","mc":"西陇科学","jys":"sz"},{"dm":"000961","mc":"中南建设","jys":"sz"},{"dm":"600955","mc":"维远股份","jys":"sh"},{"dm":"605398","mc":"新炬网络","jys":"sh"},{"dm":"601298","mc":"青岛港","jys":"sh"},{"dm":"002733","mc":"雄韬股份","jys":"sz"},{"dm":"002501","mc":"利源股份","jys":"sz"},{"dm":"001330","mc":"博纳影业","jys":"sz"},{"dm":"605077","mc":"华康股份","jys":"sh"},{"dm":"300149","mc":"睿智医药","jys":"sz"},{"dm":"600578","mc":"京能电力","jys":"sh"},{"dm":"300676","mc":"华大基因","jys":"sz"},{"dm":"000789","mc":"万年青","jys":"sz"},{"dm":"601678","mc":"滨化股份","jys":"sh"},{"dm":"300797","mc":"钢研纳克","jys":"sz"},{"dm":"603300","mc":"华铁应急","jys":"sh"},{"dm":"603868","mc":"飞科电器","jys":"sh"},{"dm":"002407","mc":"多氟多","jys":"sz"},{"dm":"603519","mc":"立霸股份","jys":"sh"},{"dm":"300729","mc":"乐歌股份","jys":"sz"},{"dm":"300669","mc":"沪宁股份","jys":"sz"},{"dm":"000720","mc":"新能泰山","jys":"sz"},{"dm":"688739","mc":"成大生物","jys":"sh"},{"dm":"301073","mc":"君亭酒店","jys":"sz"},{"dm":"002003","mc":"伟星股份","jys":"sz"},{"dm":"601600","mc":"中国铝业","jys":"sh"},{"dm":"002595","mc":"豪迈科技","jys":"sz"},{"dm":"002950","mc":"奥美医疗","jys":"sz"},{"dm":"605090","mc":"九丰能源","jys":"sh"},{"dm":"600594","mc":"益佰制药","jys":"sh"},{"dm":"600389","mc":"江山股份","jys":"sh"},{"dm":"688363","mc":"华熙生物","jys":"sh"},{"dm":"002423","mc":"中粮资本","jys":"sz"},{"dm":"300821","mc":"东岳硅材","jys":"sz"},{"dm":"600316","mc":"洪都航空","jys":"sh"},{"dm":"000016","mc":"深康佳A","jys":"sz"},{"dm":"600009","mc":"上海机场","jys":"sh"},{"dm":"300636","mc":"同和药业","jys":"sz"},{"dm":"600031","mc":"三一重工","jys":"sh"},{"dm":"603533","mc":"掌阅科技","jys":"sh"},{"dm":"002102","mc":"冠福股份","jys":"sz"},{"dm":"601226","mc":"华电重工","jys":"sh"},{"dm":"301095","mc":"广立微","jys":"sz"},{"dm":"003031","mc":"中瓷电子","jys":"sz"},{"dm":"600248","mc":"陕建股份","jys":"sh"},{"dm":"603978","mc":"深圳新星","jys":"sh"},{"dm":"000425","mc":"徐工机械","jys":"sz"},{"dm":"300347","mc":"泰格医药","jys":"sz"},{"dm":"688722","mc":"同益中","jys":"sh"},{"dm":"603613","mc":"国联股份","jys":"sh"},{"dm":"300463","mc":"迈克生物","jys":"sz"},{"dm":"000401","mc":"冀东水泥","jys":"sz"},{"dm":"002524","mc":"光正眼科","jys":"sz"},{"dm":"002355","mc":"兴民智通","jys":"sz"},{"dm":"600085","mc":"同仁堂","jys":"sh"},{"dm":"002829","mc":"星网宇达","jys":"sz"},{"dm":"603878","mc":"武进不锈","jys":"sh"},{"dm":"301258","mc":"富士莱","jys":"sz"},{"dm":"002551","mc":"尚荣医疗","jys":"sz"},{"dm":"301103","mc":"何氏眼科","jys":"sz"},{"dm":"300568","mc":"星源材质","jys":"sz"},{"dm":"603199","mc":"九华旅游","jys":"sh"},{"dm":"002626","mc":"金达威","jys":"sz"},{"dm":"300842","mc":"帝科股份","jys":"sz"},{"dm":"600269","mc":"赣粤高速","jys":"sh"},{"dm":"300652","mc":"雷迪克","jys":"sz"},{"dm":"002767","mc":"先锋电子","jys":"sz"},{"dm":"603719","mc":"良品铺子","jys":"sh"},{"dm":"603330","mc":"天洋新材","jys":"sh"},{"dm":"600475","mc":"华光环能","jys":"sh"},{"dm":"000598","mc":"兴蓉环境","jys":"sz"},{"dm":"600008","mc":"首创环保","jys":"sh"},{"dm":"600919","mc":"江苏银行","jys":"sh"},{"dm":"000930","mc":"中粮科技","jys":"sz"},{"dm":"002080","mc":"中材科技","jys":"sz"},{"dm":"688132","mc":"邦彦技术","jys":"sh"},{"dm":"002081","mc":"金 螳 螂","jys":"sz"},{"dm":"603237","mc":"五芳斋","jys":"sh"},{"dm":"000885","mc":"城发环境","jys":"sz"},{"dm":"002271","mc":"东方雨虹","jys":"sz"},{"dm":"605599","mc":"菜百股份","jys":"sh"},{"dm":"600405","mc":"动力源","jys":"sh"},{"dm":"600187","mc":"国中水务","jys":"sh"},{"dm":"002716","mc":"金贵银业","jys":"sz"},{"dm":"688548","mc":"广钢气体","jys":"sh"},{"dm":"002779","mc":"中坚科技","jys":"sz"},{"dm":"600785","mc":"新华百货","jys":"sh"},{"dm":"002419","mc":"天虹股份","jys":"sz"},{"dm":"603889","mc":"新澳股份","jys":"sh"},{"dm":"002033","mc":"丽江股份","jys":"sz"},{"dm":"000597","mc":"东北制药","jys":"sz"},{"dm":"600201","mc":"生物股份","jys":"sh"},{"dm":"301149","mc":"隆华新材","jys":"sz"},{"dm":"600808","mc":"马钢股份","jys":"sh"},{"dm":"300702","mc":"天宇股份","jys":"sz"},{"dm":"300003","mc":"乐普医疗","jys":"sz"},{"dm":"000089","mc":"深圳机场","jys":"sz"},{"dm":"600765","mc":"中航重机","jys":"sh"},{"dm":"002275","mc":"桂林三金","jys":"sz"},{"dm":"002084","mc":"海鸥住工","jys":"sz"},{"dm":"600138","mc":"中青旅","jys":"sh"},{"dm":"688399","mc":"硕世生物","jys":"sh"},{"dm":"600866","mc":"星湖科技","jys":"sh"},{"dm":"000778","mc":"新兴铸管","jys":"sz"},{"dm":"603993","mc":"洛阳钼业","jys":"sh"},{"dm":"600351","mc":"亚宝药业","jys":"sh"},{"dm":"603026","mc":"胜华新材","jys":"sh"},{"dm":"603822","mc":"嘉澳环保","jys":"sh"},{"dm":"600295","mc":"鄂尔多斯","jys":"sh"},{"dm":"300558","mc":"贝达药业","jys":"sz"},{"dm":"603165","mc":"荣晟环保","jys":"sh"},{"dm":"002896","mc":"中大力德","jys":"sz"},{"dm":"300059","mc":"东方财富","jys":"sz"},{"dm":"600226","mc":"瀚叶股份","jys":"sh"},{"dm":"002526","mc":"山东矿机","jys":"sz"},{"dm":"002366","mc":"融发核电","jys":"sz"},{"dm":"603808","mc":"歌力思","jys":"sh"},{"dm":"601669","mc":"中国电建","jys":"sh"},{"dm":"002225","mc":"濮耐股份","jys":"sz"},{"dm":"002791","mc":"坚朗五金","jys":"sz"},{"dm":"300339","mc":"润和软件","jys":"sz"},{"dm":"300103","mc":"达刚控股","jys":"sz"},{"dm":"600702","mc":"舍得酒业","jys":"sh"},{"dm":"603611","mc":"诺力股份","jys":"sh"},{"dm":"600759","mc":"*ST洲际","jys":"sh"},{"dm":"002654","mc":"万润科技","jys":"sz"},{"dm":"002326","mc":"永太科技","jys":"sz"},{"dm":"000822","mc":"山东海化","jys":"sz"},{"dm":"002694","mc":"顾地科技","jys":"sz"},{"dm":"600662","mc":"外服控股","jys":"sh"},{"dm":"605123","mc":"派克新材","jys":"sh"},{"dm":"000825","mc":"太钢不锈","jys":"sz"},{"dm":"605167","mc":"利柏特","jys":"sh"},{"dm":"603778","mc":"乾景园林","jys":"sh"},{"dm":"601991","mc":"大唐发电","jys":"sh"},{"dm":"603179","mc":"新泉股份","jys":"sh"},{"dm":"002345","mc":"潮宏基","jys":"sz"},{"dm":"600396","mc":"*ST金山","jys":"sh"},{"dm":"002739","mc":"万达电影","jys":"sz"},{"dm":"300534","mc":"陇神戎发","jys":"sz"},{"dm":"300715","mc":"凯伦股份","jys":"sz"},{"dm":"600322","mc":"津投城开","jys":"sh"},{"dm":"300433","mc":"蓝思科技","jys":"sz"},{"dm":"301005","mc":"超捷股份","jys":"sz"},{"dm":"002293","mc":"罗莱生活","jys":"sz"},{"dm":"603599","mc":"广信股份","jys":"sh"},{"dm":"600509","mc":"天富能源","jys":"sh"},{"dm":"300158","mc":"振东制药","jys":"sz"},{"dm":"300918","mc":"南山智尚","jys":"sz"},{"dm":"000729","mc":"燕京啤酒","jys":"sz"},{"dm":"000917","mc":"电广传媒","jys":"sz"},{"dm":"002900","mc":"哈三联","jys":"sz"},{"dm":"301456","mc":"盘古智能","jys":"sz"},{"dm":"601566","mc":"九牧王","jys":"sh"},{"dm":"002594","mc":"比亚迪","jys":"sz"},{"dm":"600376","mc":"首开股份","jys":"sh"},{"dm":"600690","mc":"海尔智家","jys":"sh"},{"dm":"603116","mc":"红蜻蜓","jys":"sh"},{"dm":"603209","mc":"兴通股份","jys":"sh"},{"dm":"600748","mc":"上实发展","jys":"sh"},{"dm":"301090","mc":"华润材料","jys":"sz"},{"dm":"301065","mc":"本立科技","jys":"sz"},{"dm":"600500","mc":"中化国际","jys":"sh"},{"dm":"603279","mc":"景津装备","jys":"sh"},{"dm":"000027","mc":"深圳能源","jys":"sz"},{"dm":"002053","mc":"云南能投","jys":"sz"},{"dm":"603301","mc":"振德医疗","jys":"sh"},{"dm":"002973","mc":"侨银股份","jys":"sz"},{"dm":"603799","mc":"华友钴业","jys":"sh"},{"dm":"600521","mc":"华海药业","jys":"sh"},{"dm":"002420","mc":"毅昌科技","jys":"sz"},{"dm":"000931","mc":"中 关 村","jys":"sz"},{"dm":"000546","mc":"金圆股份","jys":"sz"},{"dm":"300294","mc":"博雅生物","jys":"sz"},{"dm":"601778","mc":"晶科科技","jys":"sh"},{"dm":"000959","mc":"首钢股份","jys":"sz"},{"dm":"002535","mc":"林州重机","jys":"sz"},{"dm":"603095","mc":"越剑智能","jys":"sh"},{"dm":"603989","mc":"艾华集团","jys":"sh"},{"dm":"002967","mc":"广电计量","jys":"sz"},{"dm":"300405","mc":"科隆股份","jys":"sz"},{"dm":"000589","mc":"贵州轮胎","jys":"sz"},{"dm":"000410","mc":"沈阳机床","jys":"sz"},{"dm":"000915","mc":"华特达因","jys":"sz"},{"dm":"688087","mc":"英科再生","jys":"sh"},{"dm":"603833","mc":"欧派家居","jys":"sh"},{"dm":"002289","mc":"ST宇顺","jys":"sz"},{"dm":"603128","mc":"华贸物流","jys":"sh"},{"dm":"600502","mc":"安徽建工","jys":"sh"},{"dm":"603811","mc":"诚意药业","jys":"sh"},{"dm":"603235","mc":"天新药业","jys":"sh"},{"dm":"301116","mc":"益客食品","jys":"sz"},{"dm":"002150","mc":"通润装备","jys":"sz"},{"dm":"600029","mc":"南方航空","jys":"sh"},{"dm":"002821","mc":"凯莱英","jys":"sz"},{"dm":"002035","mc":"华帝股份","jys":"sz"},{"dm":"605299","mc":"舒华体育","jys":"sh"},{"dm":"603697","mc":"有友食品","jys":"sh"},{"dm":"002700","mc":"ST浩源","jys":"sz"},{"dm":"000596","mc":"古井贡酒","jys":"sz"},{"dm":"605080","mc":"浙江自然","jys":"sh"},{"dm":"000534","mc":"万泽股份","jys":"sz"},{"dm":"002413","mc":"雷科防务","jys":"sz"},{"dm":"002868","mc":"绿康生化","jys":"sz"},{"dm":"600926","mc":"杭州银行","jys":"sh"},{"dm":"002424","mc":"贵州百灵","jys":"sz"},{"dm":"600429","mc":"三元股份","jys":"sh"},{"dm":"600332","mc":"白云山","jys":"sh"},{"dm":"002245","mc":"蔚蓝锂芯","jys":"sz"},{"dm":"688393","mc":"安必平","jys":"sh"},{"dm":"000001","mc":"平安银行","jys":"sz"},{"dm":"603367","mc":"辰欣药业","jys":"sh"},{"dm":"600298","mc":"安琪酵母","jys":"sh"},{"dm":"600863","mc":"内蒙华电","jys":"sh"},{"dm":"002309","mc":"ST中利","jys":"sz"},{"dm":"600020","mc":"中原高速","jys":"sh"},{"dm":"002311","mc":"海大集团","jys":"sz"},{"dm":"688512","mc":"慧智微-U","jys":"sh"},{"dm":"000919","mc":"金陵药业","jys":"sz"},{"dm":"300725","mc":"药石科技","jys":"sz"},{"dm":"300622","mc":"博士眼镜","jys":"sz"},{"dm":"000623","mc":"吉林敖东","jys":"sz"},{"dm":"000875","mc":"吉电股份","jys":"sz"},{"dm":"000651","mc":"格力电器","jys":"sz"},{"dm":"600986","mc":"浙文互联","jys":"sh"},{"dm":"601117","mc":"中国化学","jys":"sh"},{"dm":"601512","mc":"中新集团","jys":"sh"},{"dm":"688533","mc":"上声电子","jys":"sh"},{"dm":"002676","mc":"顺威股份","jys":"sz"},{"dm":"000738","mc":"航发控制","jys":"sz"},{"dm":"600642","mc":"申能股份","jys":"sh"},{"dm":"603517","mc":"绝味食品","jys":"sh"},{"dm":"301008","mc":"宏昌科技","jys":"sz"},{"dm":"002131","mc":"利欧股份","jys":"sz"},{"dm":"000890","mc":"法尔胜","jys":"sz"},{"dm":"000591","mc":"太阳能","jys":"sz"},{"dm":"688033","mc":"天宜上佳","jys":"sh"},{"dm":"601717","mc":"郑煤机","jys":"sh"},{"dm":"600825","mc":"新华传媒","jys":"sh"},{"dm":"000055","mc":"方大集团","jys":"sz"},{"dm":"000952","mc":"广济药业","jys":"sz"},{"dm":"300267","mc":"尔康制药","jys":"sz"},{"dm":"688503","mc":"聚和材料","jys":"sh"},{"dm":"600869","mc":"远东股份","jys":"sh"},{"dm":"301301","mc":"川宁生物","jys":"sz"},{"dm":"600861","mc":"北京人力","jys":"sh"},{"dm":"300031","mc":"宝通科技","jys":"sz"},{"dm":"001258","mc":"立新能源","jys":"sz"},{"dm":"688410","mc":"山外山","jys":"sh"},{"dm":"002435","mc":"长江健康","jys":"sz"},{"dm":"601003","mc":"柳钢股份","jys":"sh"},{"dm":"002735","mc":"王子新材","jys":"sz"},{"dm":"000899","mc":"赣能股份","jys":"sz"},{"dm":"000910","mc":"大亚圣象","jys":"sz"},{"dm":"600694","mc":"大商股份","jys":"sh"},{"dm":"600231","mc":"凌钢股份","jys":"sh"},{"dm":"002445","mc":"中南文化","jys":"sz"},{"dm":"300978","mc":"东箭科技","jys":"sz"},{"dm":"002510","mc":"天汽模","jys":"sz"},{"dm":"603707","mc":"健友股份","jys":"sh"},{"dm":"600054","mc":"黄山旅游","jys":"sh"},{"dm":"603809","mc":"豪能股份","jys":"sh"},{"dm":"600416","mc":"湘电股份","jys":"sh"},{"dm":"002907","mc":"华森制药","jys":"sz"},{"dm":"603076","mc":"乐惠国际","jys":"sh"},{"dm":"301123","mc":"奕东电子","jys":"sz"},{"dm":"002258","mc":"利尔化学","jys":"sz"},{"dm":"688289","mc":"圣湘生物","jys":"sh"},{"dm":"301280","mc":"珠城科技","jys":"sz"},{"dm":"600595","mc":"中孚实业","jys":"sh"},{"dm":"603518","mc":"锦泓集团","jys":"sh"},{"dm":"002672","mc":"东江环保","jys":"sz"},{"dm":"603198","mc":"迎驾贡酒","jys":"sh"},{"dm":"002805","mc":"丰元股份","jys":"sz"},{"dm":"002483","mc":"润邦股份","jys":"sz"},{"dm":"002959","mc":"小熊电器","jys":"sz"},{"dm":"603979","mc":"金诚信","jys":"sh"},{"dm":"605507","mc":"国邦医药","jys":"sh"},{"dm":"603477","mc":"巨星农牧","jys":"sh"},{"dm":"603030","mc":"*ST全筑","jys":"sh"},{"dm":"603159","mc":"上海亚虹","jys":"sh"},{"dm":"002294","mc":"信立泰","jys":"sz"},{"dm":"600862","mc":"中航高科","jys":"sh"},{"dm":"000506","mc":"中润资源","jys":"sz"},{"dm":"601618","mc":"中国中冶","jys":"sh"},{"dm":"300073","mc":"当升科技","jys":"sz"},{"dm":"000709","mc":"河钢股份","jys":"sz"},{"dm":"000538","mc":"云南白药","jys":"sz"},{"dm":"688633","mc":"星球石墨","jys":"sh"},{"dm":"603308","mc":"应流股份","jys":"sh"},{"dm":"300930","mc":"屹通新材","jys":"sz"},{"dm":"002305","mc":"南国置业","jys":"sz"},{"dm":"300760","mc":"迈瑞医疗","jys":"sz"},{"dm":"300360","mc":"炬华科技","jys":"sz"},{"dm":"603551","mc":"奥普家居","jys":"sh"},{"dm":"002511","mc":"中顺洁柔","jys":"sz"},{"dm":"688551","mc":"科威尔","jys":"sh"},{"dm":"001207","mc":"联科科技","jys":"sz"},{"dm":"605081","mc":"太和水","jys":"sh"},{"dm":"603876","mc":"鼎胜新材","jys":"sh"},{"dm":"603777","mc":"来伊份","jys":"sh"},{"dm":"002371","mc":"北方华创","jys":"sz"},{"dm":"300712","mc":"永福股份","jys":"sz"},{"dm":"300467","mc":"迅游科技","jys":"sz"},{"dm":"600612","mc":"老凤祥","jys":"sh"},{"dm":"300401","mc":"花园生物","jys":"sz"},{"dm":"000524","mc":"岭南控股","jys":"sz"},{"dm":"600888","mc":"新疆众和","jys":"sh"},{"dm":"603897","mc":"长城科技","jys":"sh"},{"dm":"600720","mc":"祁连山","jys":"sh"},{"dm":"601187","mc":"厦门银行","jys":"sh"},{"dm":"000609","mc":"中迪投资","jys":"sz"},{"dm":"688049","mc":"炬芯科技","jys":"sh"},{"dm":"600600","mc":"青岛啤酒","jys":"sh"},{"dm":"603722","mc":"阿科力","jys":"sh"},{"dm":"000627","mc":"天茂集团","jys":"sz"},{"dm":"300653","mc":"正海生物","jys":"sz"},{"dm":"301308","mc":"江波龙","jys":"sz"},{"dm":"300755","mc":"华致酒行","jys":"sz"},{"dm":"600436","mc":"片仔癀","jys":"sh"},{"dm":"601686","mc":"友发集团","jys":"sh"},{"dm":"603836","mc":"海程邦达","jys":"sh"},{"dm":"002838","mc":"道恩股份","jys":"sz"},{"dm":"688336","mc":"三生国健","jys":"sh"},{"dm":"002690","mc":"美亚光电","jys":"sz"},{"dm":"603000","mc":"人民网","jys":"sh"},{"dm":"603718","mc":"海利生物","jys":"sh"},{"dm":"600585","mc":"海螺水泥","jys":"sh"},{"dm":"300240","mc":"飞力达","jys":"sz"},{"dm":"688126","mc":"沪硅产业","jys":"sh"},{"dm":"002356","mc":"赫美集团","jys":"sz"},{"dm":"001223","mc":"欧克科技","jys":"sz"},{"dm":"601065","mc":"江盐集团","jys":"sh"},{"dm":"300596","mc":"利安隆","jys":"sz"},{"dm":"688516","mc":"奥特维","jys":"sh"},{"dm":"002126","mc":"银轮股份","jys":"sz"},{"dm":"603267","mc":"鸿远电子","jys":"sh"},{"dm":"000028","mc":"国药一致","jys":"sz"},{"dm":"002192","mc":"融捷股份","jys":"sz"},{"dm":"301171","mc":"易点天下","jys":"sz"},{"dm":"688314","mc":"康拓医疗","jys":"sh"},{"dm":"600428","mc":"中远海特","jys":"sh"},{"dm":"301060","mc":"兰卫医学","jys":"sz"},{"dm":"603578","mc":"三星新材","jys":"sh"},{"dm":"601933","mc":"永辉超市","jys":"sh"},{"dm":"300276","mc":"三丰智能","jys":"sz"},{"dm":"600255","mc":"鑫科材料","jys":"sh"},{"dm":"688257","mc":"新锐股份","jys":"sh"},{"dm":"300286","mc":"安科瑞","jys":"sz"},{"dm":"000862","mc":"银星能源","jys":"sz"},{"dm":"601155","mc":"新城控股","jys":"sh"},{"dm":"688611","mc":"杭州柯林","jys":"sh"},{"dm":"002941","mc":"新疆交建","jys":"sz"},{"dm":"002472","mc":"双环传动","jys":"sz"},{"dm":"603315","mc":"福鞍股份","jys":"sh"},{"dm":"000861","mc":"海印股份","jys":"sz"},{"dm":"603035","mc":"常熟汽饰","jys":"sh"},{"dm":"600613","mc":"神奇制药","jys":"sh"},{"dm":"002582","mc":"好想你","jys":"sz"},{"dm":"000537","mc":"广宇发展","jys":"sz"},{"dm":"600779","mc":"水井坊","jys":"sh"},{"dm":"600528","mc":"中铁工业","jys":"sh"},{"dm":"300381","mc":"溢多利","jys":"sz"},{"dm":"002389","mc":"航天彩虹","jys":"sz"},{"dm":"002436","mc":"兴森科技","jys":"sz"},{"dm":"603588","mc":"高能环境","jys":"sh"},{"dm":"301102","mc":"兆讯传媒","jys":"sz"},{"dm":"600970","mc":"中材国际","jys":"sh"},{"dm":"600968","mc":"海油发展","jys":"sh"},{"dm":"000710","mc":"贝瑞基因","jys":"sz"},{"dm":"603638","mc":"艾迪精密","jys":"sh"},{"dm":"603589","mc":"口子窖","jys":"sh"},{"dm":"300668","mc":"杰恩设计","jys":"sz"},{"dm":"601111","mc":"中国国航","jys":"sh"},{"dm":"301393","mc":"昊帆生物","jys":"sz"},{"dm":"603567","mc":"珍宝岛","jys":"sh"},{"dm":"301520","mc":"万邦医药","jys":"sz"},{"dm":"601865","mc":"福莱特","jys":"sh"},{"dm":"300973","mc":"立高食品","jys":"sz"},{"dm":"300235","mc":"方直科技","jys":"sz"},{"dm":"301106","mc":"骏成科技","jys":"sz"},{"dm":"688379","mc":"华光新材","jys":"sh"},{"dm":"600153","mc":"建发股份","jys":"sh"},{"dm":"600297","mc":"广汇汽车","jys":"sh"},{"dm":"002146","mc":"荣盛发展","jys":"sz"},{"dm":"301024","mc":"霍普股份","jys":"sz"},{"dm":"002390","mc":"信邦制药","jys":"sz"},{"dm":"688222","mc":"成都先导","jys":"sh"},{"dm":"603919","mc":"金徽酒","jys":"sh"},{"dm":"600507","mc":"方大特钢","jys":"sh"},{"dm":"001896","mc":"豫能控股","jys":"sz"},{"dm":"600821","mc":"金开新能","jys":"sh"},{"dm":"600826","mc":"兰生股份","jys":"sh"},{"dm":"603915","mc":"国茂股份","jys":"sh"},{"dm":"003002","mc":"壶化股份","jys":"sz"},{"dm":"600745","mc":"闻泰科技","jys":"sh"},{"dm":"300204","mc":"舒泰神","jys":"sz"},{"dm":"688553","mc":"汇宇制药-W","jys":"sh"},{"dm":"300012","mc":"华测检测","jys":"sz"},{"dm":"002074","mc":"国轩高科","jys":"sz"},{"dm":"301371","mc":"敷尔佳","jys":"sz"},{"dm":"000717","mc":"中南股份","jys":"sz"},{"dm":"300194","mc":"福安药业","jys":"sz"},{"dm":"002384","mc":"东山精密","jys":"sz"},{"dm":"688017","mc":"绿的谐波","jys":"sh"},{"dm":"001301","mc":"尚太科技","jys":"sz"},{"dm":"603129","mc":"春风动力","jys":"sh"},{"dm":"002773","mc":"康弘药业","jys":"sz"},{"dm":"301367","mc":"怡和嘉业","jys":"sz"},{"dm":"688507","mc":"索辰科技","jys":"sh"},{"dm":"002111","mc":"威海广泰","jys":"sz"},{"dm":"601882","mc":"海天精工","jys":"sh"},{"dm":"688326","mc":"经纬恒润-W","jys":"sh"},{"dm":"300439","mc":"美康生物","jys":"sz"},{"dm":"688065","mc":"凯赛生物","jys":"sh"},{"dm":"603838","mc":"四通股份","jys":"sh"},{"dm":"301207","mc":"华兰疫苗","jys":"sz"},{"dm":"688303","mc":"大全能源","jys":"sh"},{"dm":"300726","mc":"宏达电子","jys":"sz"},{"dm":"603232","mc":"格尔软件","jys":"sh"},{"dm":"002481","mc":"双塔食品","jys":"sz"},{"dm":"603828","mc":"柯利达","jys":"sh"},{"dm":"300198","mc":"纳川股份","jys":"sz"},{"dm":"002505","mc":"鹏都农牧","jys":"sz"},{"dm":"688189","mc":"南新制药","jys":"sh"},{"dm":"603187","mc":"海容冷链","jys":"sh"},{"dm":"301012","mc":"扬电科技","jys":"sz"},{"dm":"300406","mc":"九强生物","jys":"sz"},{"dm":"300341","mc":"麦克奥迪","jys":"sz"},{"dm":"600309","mc":"万华化学","jys":"sh"},{"dm":"000590","mc":"启迪药业","jys":"sz"},{"dm":"002460","mc":"赣锋锂业","jys":"sz"},{"dm":"601577","mc":"长沙银行","jys":"sh"},{"dm":"600036","mc":"招商银行","jys":"sh"},{"dm":"603612","mc":"索通发展","jys":"sh"},{"dm":"603096","mc":"新经典","jys":"sh"},{"dm":"688157","mc":"松井股份","jys":"sh"},{"dm":"688053","mc":"思科瑞","jys":"sh"},{"dm":"603676","mc":"卫信康","jys":"sh"},{"dm":"600070","mc":"ST富润","jys":"sh"},{"dm":"002433","mc":"*ST太安","jys":"sz"},{"dm":"603277","mc":"银都股份","jys":"sh"},{"dm":"603190","mc":"亚通精工","jys":"sh"},{"dm":"603737","mc":"三棵树","jys":"sh"},{"dm":"002308","mc":"威创股份","jys":"sz"},{"dm":"002853","mc":"皮阿诺","jys":"sz"},{"dm":"301533","mc":"威马农机","jys":"sz"},{"dm":"003038","mc":"鑫铂股份","jys":"sz"},{"dm":"603733","mc":"仙鹤股份","jys":"sh"},{"dm":"300677","mc":"英科医疗","jys":"sz"},{"dm":"600531","mc":"豫光金铅","jys":"sh"},{"dm":"603555","mc":"ST贵人","jys":"sh"},{"dm":"601766","mc":"中国中车","jys":"sh"},{"dm":"002242","mc":"九阳股份","jys":"sz"},{"dm":"601636","mc":"旗滨集团","jys":"sh"},{"dm":"600935","mc":"华塑股份","jys":"sh"},{"dm":"300881","mc":"盛德鑫泰","jys":"sz"},{"dm":"688607","mc":"康众医疗","jys":"sh"},{"dm":"688085","mc":"三友医疗","jys":"sh"},{"dm":"600882","mc":"妙可蓝多","jys":"sh"},{"dm":"300181","mc":"佐力药业","jys":"sz"},{"dm":"603758","mc":"秦安股份","jys":"sh"},{"dm":"000785","mc":"居然之家","jys":"sz"},{"dm":"600116","mc":"三峡水利","jys":"sh"},{"dm":"300015","mc":"爱尔眼科","jys":"sz"},{"dm":"000692","mc":"*ST惠天","jys":"sz"},{"dm":"300986","mc":"志特新材","jys":"sz"},{"dm":"002508","mc":"老板电器","jys":"sz"},{"dm":"002800","mc":"ST天顺","jys":"sz"},{"dm":"301239","mc":"普瑞眼科","jys":"sz"},{"dm":"600383","mc":"金地集团","jys":"sh"},{"dm":"301555","mc":"惠柏新材","jys":"sz"},{"dm":"000629","mc":"钒钛股份","jys":"sz"},{"dm":"000882","mc":"华联股份","jys":"sz"},{"dm":"605258","mc":"协和电子","jys":"sh"},{"dm":"605337","mc":"李子园","jys":"sh"},{"dm":"603590","mc":"康辰药业","jys":"sh"},{"dm":"300648","mc":"星云股份","jys":"sz"},{"dm":"600519","mc":"贵州茅台","jys":"sh"},{"dm":"001322","mc":"箭牌家居","jys":"sz"},{"dm":"600511","mc":"国药股份","jys":"sh"},{"dm":"000898","mc":"鞍钢股份","jys":"sz"},{"dm":"000501","mc":"武商集团","jys":"sz"},{"dm":"301498","mc":"乖宝宠物","jys":"sz"},{"dm":"301186","mc":"超达装备","jys":"sz"},{"dm":"688217","mc":"睿昂基因","jys":"sh"},{"dm":"600189","mc":"泉阳泉","jys":"sh"},{"dm":"688295","mc":"中复神鹰","jys":"sh"},{"dm":"600843","mc":"上工申贝","jys":"sh"},{"dm":"605189","mc":"富春染织","jys":"sh"},{"dm":"601390","mc":"中国中铁","jys":"sh"},{"dm":"688570","mc":"天玛智控","jys":"sh"},{"dm":"600960","mc":"渤海汽车","jys":"sh"},{"dm":"688472","mc":"阿特斯","jys":"sh"},{"dm":"002120","mc":"韵达股份","jys":"sz"},{"dm":"603688","mc":"石英股份","jys":"sh"},{"dm":"300002","mc":"神州泰岳","jys":"sz"},{"dm":"600115","mc":"中国东航","jys":"sh"},{"dm":"300583","mc":"赛托生物","jys":"sz"},{"dm":"600011","mc":"华能国际","jys":"sh"},{"dm":"000878","mc":"云南铜业","jys":"sz"},{"dm":"002332","mc":"仙琚制药","jys":"sz"},{"dm":"603855","mc":"华荣股份","jys":"sh"},{"dm":"600743","mc":"华远地产","jys":"sh"},{"dm":"600673","mc":"东阳光","jys":"sh"},{"dm":"603071","mc":"物产环能","jys":"sh"},{"dm":"002667","mc":"威领股份","jys":"sz"},{"dm":"600499","mc":"科达制造","jys":"sh"},{"dm":"300251","mc":"光线传媒","jys":"sz"},{"dm":"603369","mc":"今世缘","jys":"sh"},{"dm":"301408","mc":"华人健康","jys":"sz"},{"dm":"600729","mc":"重庆百货","jys":"sh"},{"dm":"002461","mc":"珠江啤酒","jys":"sz"},{"dm":"300358","mc":"楚天科技","jys":"sz"},{"dm":"300988","mc":"津荣天宇","jys":"sz"},{"dm":"603755","mc":"日辰股份","jys":"sh"},{"dm":"603281","mc":"江瀚新材","jys":"sh"},{"dm":"688601","mc":"力芯微","jys":"sh"},{"dm":"603233","mc":"大参林","jys":"sh"},{"dm":"002219","mc":"新里程","jys":"sz"},{"dm":"603007","mc":"ST花王","jys":"sh"},{"dm":"002624","mc":"完美世界","jys":"sz"},{"dm":"300705","mc":"九典制药","jys":"sz"},{"dm":"600809","mc":"山西汾酒","jys":"sh"},{"dm":"600761","mc":"安徽合力","jys":"sh"},{"dm":"002597","mc":"金禾实业","jys":"sz"},{"dm":"601567","mc":"三星医疗","jys":"sh"},{"dm":"600323","mc":"瀚蓝环境","jys":"sh"},{"dm":"002520","mc":"日发精机","jys":"sz"},{"dm":"002812","mc":"恩捷股份","jys":"sz"},{"dm":"600741","mc":"华域汽车","jys":"sh"},{"dm":"002240","mc":"盛新锂能","jys":"sz"},{"dm":"001266","mc":"宏英智能","jys":"sz"},{"dm":"300404","mc":"博济医药","jys":"sz"},{"dm":"301141","mc":"中科磁业","jys":"sz"},{"dm":"300664","mc":"鹏鹞环保","jys":"sz"},{"dm":"600208","mc":"新湖中宝","jys":"sh"},{"dm":"000657","mc":"中钨高新","jys":"sz"},{"dm":"688526","mc":"科前生物","jys":"sh"},{"dm":"605177","mc":"东亚药业","jys":"sh"},{"dm":"603259","mc":"药明康德","jys":"sh"},{"dm":"601186","mc":"中国铁建","jys":"sh"},{"dm":"600977","mc":"中国电影","jys":"sh"},{"dm":"605300","mc":"佳禾食品","jys":"sh"},{"dm":"002324","mc":"普利特","jys":"sz"},{"dm":"688779","mc":"长远锂科","jys":"sh"},{"dm":"603867","mc":"新化股份","jys":"sh"},{"dm":"300633","mc":"开立医疗","jys":"sz"},{"dm":"603180","mc":"金牌厨柜","jys":"sh"},{"dm":"601208","mc":"东材科技","jys":"sh"},{"dm":"000415","mc":"渤海租赁","jys":"sz"},{"dm":"002175","mc":"东方智造","jys":"sz"},{"dm":"300731","mc":"科创新源","jys":"sz"},{"dm":"600776","mc":"东方通信","jys":"sh"},{"dm":"600811","mc":"东方集团","jys":"sh"},{"dm":"605377","mc":"华旺科技","jys":"sh"},{"dm":"688200","mc":"华峰测控","jys":"sh"},{"dm":"603566","mc":"普莱柯","jys":"sh"},{"dm":"300415","mc":"伊之密","jys":"sz"},{"dm":"600640","mc":"国脉文化","jys":"sh"},{"dm":"000796","mc":"*ST凯撒","jys":"sz"},{"dm":"002867","mc":"周大生","jys":"sz"},{"dm":"300593","mc":"新雷能","jys":"sz"},{"dm":"301190","mc":"善水科技","jys":"sz"},{"dm":"300332","mc":"天壕能源","jys":"sz"},{"dm":"000065","mc":"北方国际","jys":"sz"},{"dm":"600782","mc":"新钢股份","jys":"sh"},{"dm":"002119","mc":"康强电子","jys":"sz"},{"dm":"603079","mc":"圣达生物","jys":"sh"},{"dm":"688005","mc":"容百科技","jys":"sh"},{"dm":"002009","mc":"天奇股份","jys":"sz"},{"dm":"002608","mc":"江苏国信","jys":"sz"},{"dm":"002482","mc":"*ST广田","jys":"sz"},{"dm":"300436","mc":"广生堂","jys":"sz"},{"dm":"688121","mc":"卓然股份","jys":"sh"},{"dm":"600219","mc":"南山铝业","jys":"sh"},{"dm":"600301","mc":"华锡有色","jys":"sh"},{"dm":"603806","mc":"福斯特","jys":"sh"},{"dm":"603239","mc":"浙江仙通","jys":"sh"},{"dm":"603711","mc":"香飘飘","jys":"sh"},{"dm":"300363","mc":"博腾股份","jys":"sz"},{"dm":"601975","mc":"招商南油","jys":"sh"},{"dm":"002247","mc":"聚力文化","jys":"sz"},{"dm":"002078","mc":"太阳纸业","jys":"sz"},{"dm":"002379","mc":"宏创控股","jys":"sz"},{"dm":"301093","mc":"华兰股份","jys":"sz"},{"dm":"003012","mc":"东鹏控股","jys":"sz"},{"dm":"002237","mc":"恒邦股份","jys":"sz"},{"dm":"603616","mc":"韩建河山","jys":"sh"},{"dm":"000813","mc":"德展健康","jys":"sz"},{"dm":"002858","mc":"力盛体育","jys":"sz"},{"dm":"002233","mc":"塔牌集团","jys":"sz"},{"dm":"600654","mc":"ST中安","jys":"sh"},{"dm":"002110","mc":"三钢闽光","jys":"sz"},{"dm":"688536","mc":"思瑞浦","jys":"sh"},{"dm":"601668","mc":"中国建筑","jys":"sh"},{"dm":"688566","mc":"吉贝尔","jys":"sh"},{"dm":"600572","mc":"康恩贝","jys":"sh"},{"dm":"300144","mc":"宋城演艺","jys":"sz"},{"dm":"600176","mc":"中国巨石","jys":"sh"},{"dm":"601005","mc":"重庆钢铁","jys":"sh"},{"dm":"300143","mc":"盈康生命","jys":"sz"},{"dm":"688108","mc":"赛诺医疗","jys":"sh"},{"dm":"603768","mc":"常青股份","jys":"sh"},{"dm":"002831","mc":"裕同科技","jys":"sz"},{"dm":"603268","mc":"松发股份","jys":"sh"},{"dm":"002154","mc":"报 喜 鸟","jys":"sz"},{"dm":"603559","mc":"ST通脉","jys":"sh"},{"dm":"002475","mc":"立讯精密","jys":"sz"},{"dm":"688513","mc":"苑东生物","jys":"sh"},{"dm":"688137","mc":"近岸蛋白","jys":"sh"},{"dm":"603898","mc":"好莱客","jys":"sh"},{"dm":"603801","mc":"志邦家居","jys":"sh"},{"dm":"600486","mc":"扬农化工","jys":"sh"},{"dm":"000807","mc":"云铝股份","jys":"sz"},{"dm":"002906","mc":"华阳集团","jys":"sz"},{"dm":"002284","mc":"亚太股份","jys":"sz"},{"dm":"600221","mc":"海航控股","jys":"sh"},{"dm":"300370","mc":"安控科技","jys":"sz"},{"dm":"300175","mc":"朗源股份","jys":"sz"},{"dm":"300275","mc":"梅安森","jys":"sz"},{"dm":"688670","mc":"金迪克","jys":"sh"},{"dm":"603027","mc":"千禾味业","jys":"sh"},{"dm":"301349","mc":"信德新材","jys":"sz"},{"dm":"000516","mc":"国际医学","jys":"sz"},{"dm":"300855","mc":"图南股份","jys":"sz"},{"dm":"688180","mc":"君实生物-U","jys":"sh"},{"dm":"002928","mc":"华夏航空","jys":"sz"},{"dm":"603716","mc":"塞力医疗","jys":"sh"},{"dm":"300873","mc":"海晨股份","jys":"sz"},{"dm":"002756","mc":"永兴材料","jys":"sz"},{"dm":"605566","mc":"福莱蒽特","jys":"sh"},{"dm":"603168","mc":"莎普爱思","jys":"sh"},{"dm":"301091","mc":"深城交","jys":"sz"},{"dm":"300398","mc":"飞凯材料","jys":"sz"},{"dm":"301269","mc":"华大九天","jys":"sz"},{"dm":"603939","mc":"益丰药房","jys":"sh"},{"dm":"300434","mc":"金石亚药","jys":"sz"},{"dm":"605266","mc":"健之佳","jys":"sh"},{"dm":"688235","mc":"百济神州-U","jys":"sh"},{"dm":"603208","mc":"江山欧派","jys":"sh"},{"dm":"300299","mc":"富春股份","jys":"sz"},{"dm":"600305","mc":"恒顺醋业","jys":"sh"},{"dm":"603987","mc":"康德莱","jys":"sh"},{"dm":"603767","mc":"中马传动","jys":"sh"},{"dm":"002273","mc":"水晶光电","jys":"sz"},{"dm":"002320","mc":"海峡股份","jys":"sz"},{"dm":"600749","mc":"西藏旅游","jys":"sh"},{"dm":"000541","mc":"佛山照明","jys":"sz"},{"dm":"300488","mc":"恒锋工具","jys":"sz"},{"dm":"300258","mc":"精锻科技","jys":"sz"},{"dm":"601677","mc":"明泰铝业","jys":"sh"},{"dm":"603219","mc":"富佳股份","jys":"sh"},{"dm":"600763","mc":"通策医疗","jys":"sh"},{"dm":"300737","mc":"科顺股份","jys":"sz"},{"dm":"688276","mc":"百克生物","jys":"sh"},{"dm":"301358","mc":"湖南裕能","jys":"sz"},{"dm":"300231","mc":"银信科技","jys":"sz"},{"dm":"300441","mc":"鲍斯股份","jys":"sz"},{"dm":"600482","mc":"中国动力","jys":"sh"},{"dm":"688291","mc":"金橙子","jys":"sh"},{"dm":"300767","mc":"震安科技","jys":"sz"},{"dm":"603429","mc":"集友股份","jys":"sh"},{"dm":"000695","mc":"滨海能源","jys":"sz"},{"dm":"601958","mc":"金钼股份","jys":"sh"},{"dm":"601089","mc":"福元医药","jys":"sh"},{"dm":"601665","mc":"齐鲁银行","jys":"sh"},{"dm":"600647","mc":"*ST同达","jys":"sh"},{"dm":"688562","mc":"航天软件","jys":"sh"},{"dm":"300953","mc":"震裕科技","jys":"sz"},{"dm":"000672","mc":"上峰水泥","jys":"sz"},{"dm":"300545","mc":"联得装备","jys":"sz"},{"dm":"301210","mc":"金杨股份","jys":"sz"},{"dm":"600988","mc":"赤峰黄金","jys":"sh"},{"dm":"300151","mc":"昌红科技","jys":"sz"},{"dm":"000739","mc":"普洛药业","jys":"sz"},{"dm":"002977","mc":"天箭科技","jys":"sz"},{"dm":"688358","mc":"祥生医疗","jys":"sh"},{"dm":"688221","mc":"前沿生物-U","jys":"sh"},{"dm":"600179","mc":"安通控股","jys":"sh"},{"dm":"000996","mc":"*ST中期","jys":"sz"},{"dm":"002426","mc":"胜利精密","jys":"sz"},{"dm":"300985","mc":"致远新能","jys":"sz"},{"dm":"688558","mc":"国盛智科","jys":"sh"},{"dm":"688275","mc":"万润新能","jys":"sh"},{"dm":"300418","mc":"昆仑万维","jys":"sz"},{"dm":"688510","mc":"航亚科技","jys":"sh"},{"dm":"300818","mc":"耐普矿机","jys":"sz"},{"dm":"600215","mc":"派斯林","jys":"sh"},{"dm":"688658","mc":"悦康药业","jys":"sh"},{"dm":"002341","mc":"新纶新材","jys":"sz"},{"dm":"688212","mc":"澳华内镜","jys":"sh"},{"dm":"301235","mc":"华康医疗","jys":"sz"},{"dm":"000893","mc":"亚钾国际","jys":"sz"},{"dm":"600719","mc":"大连热电","jys":"sh"},{"dm":"300661","mc":"圣邦股份","jys":"sz"},{"dm":"002375","mc":"亚厦股份","jys":"sz"},{"dm":"000888","mc":"峨眉山A","jys":"sz"},{"dm":"300318","mc":"博晖创新","jys":"sz"},{"dm":"000155","mc":"川能动力","jys":"sz"},{"dm":"600476","mc":"湘邮科技","jys":"sh"},{"dm":"688631","mc":"莱斯信息","jys":"sh"},{"dm":"688408","mc":"中信博","jys":"sh"},{"dm":"002430","mc":"杭氧股份","jys":"sz"},{"dm":"300123","mc":"亚光科技","jys":"sz"},{"dm":"601163","mc":"三角轮胎","jys":"sh"},{"dm":"605369","mc":"拱东医疗","jys":"sh"},{"dm":"300832","mc":"新产业","jys":"sz"},{"dm":"003018","mc":"金富科技","jys":"sz"},{"dm":"300375","mc":"鹏翎股份","jys":"sz"},{"dm":"300685","mc":"艾德生物","jys":"sz"},{"dm":"603901","mc":"永创智能","jys":"sh"},{"dm":"002370","mc":"亚太药业","jys":"sz"},{"dm":"605368","mc":"蓝天燃气","jys":"sh"},{"dm":"601318","mc":"中国平安","jys":"sh"},{"dm":"002470","mc":"金正大","jys":"sz"},{"dm":"600233","mc":"圆通速递","jys":"sh"},{"dm":"603196","mc":"日播时尚","jys":"sh"},{"dm":"603136","mc":"天目湖","jys":"sh"},{"dm":"002699","mc":"*ST美盛","jys":"sz"},{"dm":"688050","mc":"爱博医疗","jys":"sh"},{"dm":"688657","mc":"浩辰软件","jys":"sh"},{"dm":"002664","mc":"信质集团","jys":"sz"},{"dm":"603107","mc":"C汽配","jys":"sh"},{"dm":"601872","mc":"招商轮船","jys":"sh"},{"dm":"688556","mc":"高测股份","jys":"sh"},{"dm":"603538","mc":"美诺华","jys":"sh"},{"dm":"688238","mc":"和元生物","jys":"sh"},{"dm":"300014","mc":"亿纬锂能","jys":"sz"},{"dm":"603009","mc":"北特科技","jys":"sh"},{"dm":"600026","mc":"中远海能","jys":"sh"},{"dm":"603416","mc":"信捷电气","jys":"sh"},{"dm":"688484","mc":"南芯科技","jys":"sh"},{"dm":"603055","mc":"台华新材","jys":"sh"},{"dm":"301306","mc":"西测测试","jys":"sz"},{"dm":"002099","mc":"海翔药业","jys":"sz"},{"dm":"301488","mc":"豪恩汽电","jys":"sz"},{"dm":"605108","mc":"同庆楼","jys":"sh"},{"dm":"000697","mc":"*ST炼石","jys":"sz"},{"dm":"300645","mc":"正元智慧","jys":"sz"},{"dm":"002976","mc":"瑞玛精密","jys":"sz"},{"dm":"000049","mc":"德赛电池","jys":"sz"},{"dm":"002566","mc":"益盛药业","jys":"sz"},{"dm":"002337","mc":"赛象科技","jys":"sz"},{"dm":"002068","mc":"黑猫股份","jys":"sz"},{"dm":"300571","mc":"平治信息","jys":"sz"},{"dm":"600023","mc":"浙能电力","jys":"sh"},{"dm":"688345","mc":"博力威","jys":"sh"},{"dm":"600569","mc":"安阳钢铁","jys":"sh"},{"dm":"000676","mc":"智度股份","jys":"sz"},{"dm":"300470","mc":"中密控股","jys":"sz"},{"dm":"600362","mc":"江西铜业","jys":"sh"},{"dm":"688131","mc":"皓元医药","jys":"sh"},{"dm":"603108","mc":"润达医疗","jys":"sh"},{"dm":"688719","mc":"爱科赛博","jys":"sh"},{"dm":"300207","mc":"欣旺达","jys":"sz"},{"dm":"000680","mc":"山推股份","jys":"sz"},{"dm":"300408","mc":"三环集团","jys":"sz"},{"dm":"002788","mc":"鹭燕医药","jys":"sz"},{"dm":"601702","mc":"华峰铝业","jys":"sh"},{"dm":"002180","mc":"纳思达","jys":"sz"},{"dm":"603222","mc":"济民医疗","jys":"sh"},{"dm":"300233","mc":"金城医药","jys":"sz"},{"dm":"688088","mc":"虹软科技","jys":"sh"},{"dm":"300395","mc":"菲利华","jys":"sz"},{"dm":"601333","mc":"广深铁路","jys":"sh"},{"dm":"600587","mc":"新华医疗","jys":"sh"},{"dm":"300979","mc":"华利集团","jys":"sz"},{"dm":"600754","mc":"锦江酒店","jys":"sh"},{"dm":"688152","mc":"麒麟信安","jys":"sh"},{"dm":"002532","mc":"天山铝业","jys":"sz"},{"dm":"605277","mc":"新亚电子","jys":"sh"},{"dm":"001319","mc":"铭科精技","jys":"sz"},{"dm":"688058","mc":"宝兰德","jys":"sh"},{"dm":"689009","mc":"九号公司-WD","jys":"sh"},{"dm":"300857","mc":"协创数据","jys":"sz"},{"dm":"300391","mc":"长药控股","jys":"sz"},{"dm":"603529","mc":"爱玛科技","jys":"sh"},{"dm":"002713","mc":"东易日盛","jys":"sz"},{"dm":"688302","mc":"海创药业-U","jys":"sh"},{"dm":"603048","mc":"浙江黎明","jys":"sh"},{"dm":"002708","mc":"光洋股份","jys":"sz"},{"dm":"002541","mc":"鸿路钢构","jys":"sz"},{"dm":"600575","mc":"淮河能源","jys":"sh"},{"dm":"002870","mc":"香山股份","jys":"sz"},{"dm":"002738","mc":"中矿资源","jys":"sz"},{"dm":"300034","mc":"钢研高纳","jys":"sz"},{"dm":"688110","mc":"东芯股份","jys":"sh"},{"dm":"688150","mc":"莱特光电","jys":"sh"},{"dm":"000711","mc":"*ST京蓝","jys":"sz"},{"dm":"688261","mc":"东微半导","jys":"sh"},{"dm":"600851","mc":"海欣股份","jys":"sh"},{"dm":"301368","mc":"丰立智能","jys":"sz"},{"dm":"600771","mc":"广誉远","jys":"sh"},{"dm":"002793","mc":"罗欣药业","jys":"sz"},{"dm":"301285","mc":"鸿日达","jys":"sz"},{"dm":"688581","mc":"安杰思","jys":"sh"},{"dm":"688308","mc":"欧科亿","jys":"sh"},{"dm":"002123","mc":"梦网科技","jys":"sz"},{"dm":"002182","mc":"宝武镁业","jys":"sz"},{"dm":"688433","mc":"华曙高科","jys":"sh"},{"dm":"300083","mc":"创世纪","jys":"sz"},{"dm":"688029","mc":"南微医学","jys":"sh"},{"dm":"000488","mc":"晨鸣纸业","jys":"sz"},{"dm":"601319","mc":"中国人保","jys":"sh"},{"dm":"600333","mc":"长春燃气","jys":"sh"},{"dm":"603983","mc":"丸美股份","jys":"sh"},{"dm":"603617","mc":"君禾股份","jys":"sh"},{"dm":"001914","mc":"招商积余","jys":"sz"},{"dm":"002634","mc":"棒杰股份","jys":"sz"},{"dm":"002596","mc":"海南瑞泽","jys":"sz"},{"dm":"002007","mc":"华兰生物","jys":"sz"},{"dm":"603015","mc":"弘讯科技","jys":"sh"},{"dm":"002648","mc":"卫星化学","jys":"sz"},{"dm":"300910","mc":"瑞丰新材","jys":"sz"},{"dm":"603699","mc":"纽威股份","jys":"sh"},{"dm":"600657","mc":"信达地产","jys":"sh"},{"dm":"688173","mc":"希荻微","jys":"sh"},{"dm":"688392","mc":"骄成超声","jys":"sh"},{"dm":"688676","mc":"金盘科技","jys":"sh"},{"dm":"603098","mc":"森特股份","jys":"sh"},{"dm":"000708","mc":"中信特钢","jys":"sz"},{"dm":"688059","mc":"华锐精密","jys":"sh"},{"dm":"601336","mc":"新华保险","jys":"sh"},{"dm":"300430","mc":"诚益通","jys":"sz"},{"dm":"600258","mc":"首旅酒店","jys":"sh"},{"dm":"688153","mc":"唯捷创芯","jys":"sh"},{"dm":"301042","mc":"安联锐视","jys":"sz"},{"dm":"000528","mc":"柳 工","jys":"sz"},{"dm":"688325","mc":"赛微微电","jys":"sh"},{"dm":"603986","mc":"兆易创新","jys":"sh"},{"dm":"002277","mc":"友阿股份","jys":"sz"},{"dm":"301550","mc":"斯菱股份","jys":"sz"},{"dm":"603456","mc":"九洲药业","jys":"sh"},{"dm":"600529","mc":"山东药玻","jys":"sh"},{"dm":"000963","mc":"华东医药","jys":"sz"},{"dm":"600246","mc":"万通发展","jys":"sh"},{"dm":"002709","mc":"天赐材料","jys":"sz"},{"dm":"688052","mc":"纳芯微","jys":"sh"},{"dm":"300535","mc":"达威股份","jys":"sz"},{"dm":"300291","mc":"百纳千成","jys":"sz"},{"dm":"300266","mc":"兴源环境","jys":"sz"},{"dm":"603766","mc":"隆鑫通用","jys":"sh"},{"dm":"600563","mc":"法拉电子","jys":"sh"},{"dm":"301397","mc":"溯联股份","jys":"sz"},{"dm":"002138","mc":"顺络电子","jys":"sz"},{"dm":"000060","mc":"中金岭南","jys":"sz"},{"dm":"300196","mc":"长海股份","jys":"sz"},{"dm":"603195","mc":"公牛集团","jys":"sh"},{"dm":"603899","mc":"晨光股份","jys":"sh"},{"dm":"301230","mc":"泓博医药","jys":"sz"},{"dm":"000975","mc":"银泰黄金","jys":"sz"},{"dm":"002888","mc":"惠威科技","jys":"sz"},{"dm":"688156","mc":"路德环境","jys":"sh"},{"dm":"000543","mc":"皖能电力","jys":"sz"},{"dm":"688301","mc":"奕瑞科技","jys":"sh"},{"dm":"688596","mc":"正帆科技","jys":"sh"},{"dm":"002826","mc":"易明医药","jys":"sz"},{"dm":"000793","mc":"华闻集团","jys":"sz"},{"dm":"688679","mc":"通源环境","jys":"sh"},{"dm":"688371","mc":"菲沃泰","jys":"sh"},{"dm":"600078","mc":"ST澄星","jys":"sh"},{"dm":"001367","mc":"海森药业","jys":"sz"},{"dm":"002727","mc":"一心堂","jys":"sz"},{"dm":"601966","mc":"玲珑轮胎","jys":"sh"},{"dm":"601628","mc":"中国人寿","jys":"sh"},{"dm":"002903","mc":"宇环数控","jys":"sz"},{"dm":"688383","mc":"新益昌","jys":"sh"},{"dm":"603028","mc":"赛福天","jys":"sh"},{"dm":"000892","mc":"欢瑞世纪","jys":"sz"},{"dm":"600186","mc":"莲花健康","jys":"sh"},{"dm":"688443","mc":"智翔金泰-U","jys":"sh"},{"dm":"603383","mc":"顶点软件","jys":"sh"},{"dm":"600801","mc":"华新水泥","jys":"sh"},{"dm":"603163","mc":"圣晖集成","jys":"sh"},{"dm":"688332","mc":"中科蓝讯","jys":"sh"},{"dm":"300580","mc":"贝斯特","jys":"sz"},{"dm":"300102","mc":"乾照光电","jys":"sz"},{"dm":"688799","mc":"华纳药厂","jys":"sh"},{"dm":"688019","mc":"安集科技","jys":"sh"},{"dm":"001308","mc":"康冠科技","jys":"sz"},{"dm":"300280","mc":"紫天科技","jys":"sz"},{"dm":"300269","mc":"联建光电","jys":"sz"},{"dm":"300093","mc":"金刚光伏","jys":"sz"},{"dm":"000503","mc":"国新健康","jys":"sz"},{"dm":"688223","mc":"晶科能源","jys":"sh"},{"dm":"600285","mc":"羚锐制药","jys":"sh"},{"dm":"688595","mc":"芯海科技","jys":"sh"},{"dm":"688377","mc":"迪威尔","jys":"sh"},{"dm":"300122","mc":"智飞生物","jys":"sz"},{"dm":"300750","mc":"宁德时代","jys":"sz"},{"dm":"300606","mc":"金太阳","jys":"sz"},{"dm":"300709","mc":"精研科技","jys":"sz"},{"dm":"002151","mc":"北斗星通","jys":"sz"},{"dm":"300926","mc":"博俊科技","jys":"sz"},{"dm":"002107","mc":"沃华医药","jys":"sz"},{"dm":"688239","mc":"航宇科技","jys":"sh"},{"dm":"688356","mc":"键凯科技","jys":"sh"},{"dm":"600398","mc":"海澜之家","jys":"sh"},{"dm":"688177","mc":"百奥泰","jys":"sh"},{"dm":"002818","mc":"富森美","jys":"sz"},{"dm":"688333","mc":"铂力特","jys":"sh"},{"dm":"601799","mc":"星宇股份","jys":"sh"},{"dm":"688220","mc":"翱捷科技-U","jys":"sh"},{"dm":"600066","mc":"宇通客车","jys":"sh"},{"dm":"603786","mc":"科博达","jys":"sh"},{"dm":"301236","mc":"软通动力","jys":"sz"},{"dm":"002203","mc":"海亮股份","jys":"sz"},{"dm":"600513","mc":"联环药业","jys":"sh"},{"dm":"688318","mc":"财富趋势","jys":"sh"},{"dm":"600266","mc":"城建发展","jys":"sh"},{"dm":"603730","mc":"岱美股份","jys":"sh"},{"dm":"300539","mc":"横河精密","jys":"sz"},{"dm":"301319","mc":"唯特偶","jys":"sz"},{"dm":"002513","mc":"蓝丰生化","jys":"sz"},{"dm":"301289","mc":"国缆检测","jys":"sz"},{"dm":"002262","mc":"恩华药业","jys":"sz"},{"dm":"000603","mc":"盛达资源","jys":"sz"},{"dm":"688531","mc":"日联科技","jys":"sh"},{"dm":"603008","mc":"喜临门","jys":"sh"},{"dm":"603666","mc":"亿嘉和","jys":"sh"},{"dm":"300584","mc":"海辰药业","jys":"sz"},{"dm":"600119","mc":"长江投资","jys":"sh"},{"dm":"603885","mc":"吉祥航空","jys":"sh"},{"dm":"300833","mc":"浩洋股份","jys":"sz"},{"dm":"600649","mc":"城投控股","jys":"sh"},{"dm":"600155","mc":"华创云信","jys":"sh"},{"dm":"300683","mc":"海特生物","jys":"sz"},{"dm":"688188","mc":"柏楚电子","jys":"sh"},{"dm":"002963","mc":"豪尔赛","jys":"sz"},{"dm":"300933","mc":"中辰股份","jys":"sz"},{"dm":"601127","mc":"赛力斯","jys":"sh"},{"dm":"300169","mc":"天晟新材","jys":"sz"},{"dm":"688351","mc":"微电生理-U","jys":"sh"},{"dm":"300735","mc":"光弘科技","jys":"sz"},{"dm":"688236","mc":"春立医疗","jys":"sh"},{"dm":"688617","mc":"惠泰医疗","jys":"sh"},{"dm":"601601","mc":"中国太保","jys":"sh"},{"dm":"600593","mc":"大连圣亚","jys":"sh"},{"dm":"002572","mc":"索菲亚","jys":"sz"},{"dm":"688210","mc":"统联精密","jys":"sh"},{"dm":"688260","mc":"昀冢科技","jys":"sh"},{"dm":"688036","mc":"传音控股","jys":"sh"},{"dm":"300313","mc":"*ST天山","jys":"sz"},{"dm":"603929","mc":"亚翔集成","jys":"sh"},{"dm":"300765","mc":"新诺威","jys":"sz"},{"dm":"300049","mc":"福瑞股份","jys":"sz"},{"dm":"688028","mc":"沃尔德","jys":"sh"},{"dm":"001376","mc":"C百通","jys":"sz"}] \ No newline at end of file diff --git a/tests/chains_test.py b/tests/chains_test.py index af14804..da3f182 100644 --- a/tests/chains_test.py +++ b/tests/chains_test.py @@ -14,7 +14,7 @@ from configs.model_config import * from dev_opsgpt.connector.phase import BasePhase from dev_opsgpt.connector.agents import BaseAgent from dev_opsgpt.connector.chains import BaseChain -from dev_opsgpt.connector.connector_schema import ( +from dev_opsgpt.connector.schema import ( Message, load_role_configs, load_phase_configs, load_chain_configs ) from dev_opsgpt.connector.configs import AGETN_CONFIGS, CHAIN_CONFIGS, PHASE_CONFIGS @@ -22,8 +22,15 @@ import importlib print(src_dir) +# tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + +TOOL_SETS = [ + "StockInfo", "StockName" + ] + tools = toLangchainTools([TOOL_DICT[i] for i in TOOL_SETS if i in TOOL_DICT]) + role_configs = load_role_configs(AGETN_CONFIGS) chain_configs = load_chain_configs(CHAIN_CONFIGS) phase_configs = load_phase_configs(PHASE_CONFIGS) @@ -33,29 +40,29 @@ agent_module = importlib.import_module("dev_opsgpt.connector.agents") # agent的测试 query = Message(role_name="tool_react", role_type="human", - role_content="我有一份时序数据,[0.857, 2.345, 1.234, 4.567, 3.456, 9.876, 5.678, 7.890, 6.789, 8.901, 10.987, 12.345, 11.234, 14.567, 13.456, 19.876, 15.678, 17.890, 16.789, \ + input_query="我有一份时序数据,[0.857, 2.345, 1.234, 4.567, 3.456, 9.876, 5.678, 7.890, 6.789, 8.901, 10.987, 12.345, 11.234, 14.567, 13.456, 19.876, 15.678, 17.890, 16.789, \ 18.901, 20.987, 22.345, 21.234, 24.567, 23.456, 29.876, 25.678, 27.890, 26.789, 28.901, 30.987, 32.345, 31.234, 34.567, 33.456, 39.876, 35.678, 37.890, 36.789, 38.901, 40.987],\ 我不知道这份数据是否存在问题,请帮我判断一下", tools=tools) query = Message(role_name="tool_react", role_type="human", - role_content="帮我确认下127.0.0.1这个服务器的在10点是否存在异常,请帮我判断一下", tools=tools) + input_query="帮我确认下127.0.0.1这个服务器的在10点是否存在异常,请帮我判断一下", tools=tools) query = Message(role_name="code_react", role_type="human", - role_content="帮我确认当前目录下有哪些文件", tools=tools) + input_query="帮我确认当前目录下有哪些文件", tools=tools) # "给我一份冒泡排序的代码" -query = Message(role_name="intention_recognizer", role_type="human", - role_content="对employee_data.csv进行数据分析", tools=tools) +query = Message(role_name="user", role_type="human", + input_query="对employee_data.csv进行数据分析", tools=tools) -# role = role_configs["general_planner"] -# agent_class = getattr(agent_module, role.role.agent_type) -# agent = agent_class(role.role, -# task = None, -# memory = None, -# chat_turn=role.chat_turn, -# do_search = role.do_search, -# do_doc_retrieval = role.do_doc_retrieval, -# do_tool_retrieval = role.do_tool_retrieval,) +role = role_configs["general_planner"] +agent_class = getattr(agent_module, role.role.agent_type) +agent = agent_class(role.role, + task = None, + memory = None, + chat_turn=role.chat_turn, + do_search = role.do_search, + do_doc_retrieval = role.do_doc_retrieval, + do_tool_retrieval = role.do_tool_retrieval,) # message = agent.run(query) # print(message.role_content) @@ -65,35 +72,38 @@ query = Message(role_name="intention_recognizer", role_type="human", # query = Message(role_name="deveploer", role_type="human", role_content="编写冒泡排序,并生成测例") # query = Message(role_name="general_planner", role_type="human", role_content="对employee_data.csv进行数据分析") -# query = Message(role_name="tool_react", role_type="human", role_content="我有一份时序数据,[0.857, 2.345, 1.234, 4.567, 3.456, 9.876, 5.678, 7.890, 6.789, 8.901, 10.987, 12.345, 11.234, 14.567, 13.456, 19.876, 15.678, 17.890, 16.789, 18.901, 20.987, 22.345, 21.234, 24.567, 23.456, 29.876, 25.678, 27.890, 26.789, 28.901, 30.987, 32.345, 31.234, 34.567, 33.456, 39.876, 35.678, 37.890, 36.789, 38.901, 40.987],\我不知道这份数据是否存在问题,请帮我判断一下", tools=tools) +# query = Message(role_name="tool_react", role_type="human", role_content="我有一份时序数据,[0.857, 2.345, 1.234, 4.567, 3.456, 9.876, 5.678, 7.890, 6.789, 8.901, 10.987, 12.345, 11.234, 14.567, 13.456, 19.876 , 15.678, 17.890, 16.789, 18.901, 20.987, 22.345, 21.234, 24.567, 23.456, 29.876, 25.678, 27.890, 26.789, 28.901, 30.987, 32.345, 31.234, 34.567, 33.456, 39.876, 35.678, 37.890, 36.789, 38.901, 40.987],\我不知道这份数据是否存在问题,请帮我判断一下", tools=tools) # role = role_configs[query.role_name] -role1 = role_configs["planner"] -role2 = role_configs["code_react"] +# role1 = role_configs["general_planner"] +# role2 = role_configs["executor"] -agents = [ - getattr(agent_module, role1.role.agent_type)(role1.role, - task = None, - memory = None, - do_search = role1.do_search, - do_doc_retrieval = role1.do_doc_retrieval, - do_tool_retrieval = role1.do_tool_retrieval,), - getattr(agent_module, role2.role.agent_type)(role2.role, - task = None, - memory = None, - do_search = role2.do_search, - do_doc_retrieval = role2.do_doc_retrieval, - do_tool_retrieval = role2.do_tool_retrieval,), - ] +# agents = [ +# getattr(agent_module, role1.role.agent_type)(role1.role, +# task = None, +# memory = None, +# do_search = role1.do_search, +# do_doc_retrieval = role1.do_doc_retrieval, +# do_tool_retrieval = role1.do_tool_retrieval,), +# getattr(agent_module, role2.role.agent_type)(role2.role, +# task = None, +# memory = None, +# do_search = role2.do_search, +# do_doc_retrieval = role2.do_doc_retrieval, +# do_tool_retrieval = role2.do_tool_retrieval, +# stop = "\n**Observation:**", +# chat_turn=5, +# ), +# ] -query = Message(role_name="user", role_type="human", - role_content="确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型,分析这份数据的内容,根据这个数据预测未来走势", tools=tools) -query = Message(role_name="user", role_type="human", - role_content="确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型", tools=tools) -chain = BaseChain(chain_configs["dataAnalystChain"], agents, do_code_exec=False) +# query = Message(role_name="user", role_type="human", +# input_query="确认本地是否存在book_data.csv,并查看它有哪些列和数据类型,分析这份数据的内容,根据这个数据预测未来走势", tools=tools) +# query = Message(role_name="user", role_type="human", +# input_query="确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型;然后画柱状图", tools=tools) +# chain = BaseChain(chain_configs["executorChain"], agents, do_code_exec=False, chat_turn=1, do_checker=False) -# message = chain.step(query) -# print(message.role_content) +# output_message, local_memory = chain.step(query) +# print(output_message.role_content) # print("\n".join("\n".join([": ".join(j) for j in i]) for i in chain.get_agents_memory())) # print("\n".join(": ".join(i) for i in chain.get_memory())) @@ -105,9 +115,25 @@ chain = BaseChain(chain_configs["dataAnalystChain"], agents, do_code_exec=False) # 测试 phase phase_name = "toolReactPhase" -# phase_name = "codeReactPhase" +phase_name = "codeReactPhase" # phase_name = "chatPhase" +# phase = BasePhase(phase_name, +# task = None, +# phase_config = PHASE_CONFIGS, +# chain_config = CHAIN_CONFIGS, +# role_config = AGETN_CONFIGS, +# do_summary=False, +# do_code_retrieval=False, +# do_doc_retrieval=True, +# do_search=False, +# ) + +# query = Message(role_name="user", role_type="human", +# input_query="确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型,并选择合适的数值列画出折线图") + + +phase_name = "baseTaskPhase" phase = BasePhase(phase_name, task = None, phase_config = PHASE_CONFIGS, @@ -119,16 +145,15 @@ phase = BasePhase(phase_name, do_search=False, ) -query = Message(role_name="user", role_type="human", - role_content="确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型,并选择合适的数值列画出折线图") +query_content = "查询贵州茅台的股票代码,并查询截止到当前日期(2023年11月8日)的最近10天的每日时序数据,然后对时序数据画出折线图并分析" +query_content = "判断下127.0.0.1这个服务器的在10点的监控数据,是否存在异常" +query_content = "确认本地是否存在employee_data.csv,并查看它有哪些列和数据类型;然后画柱状图" -query = Message(role_name="user", role_type="human", - role_content="判断下127.0.0.1这个服务器的在10点的监控数据,是否存在异常", tools=tools) +query = Message(role_name="user", role_type="human", role_content=query_content, input_query=query_content, origin_query=query_content, tools=tools) -# 根据其他类似的类,新开发个 ExceptionComponent2,继承 AbstractTrafficComponent -# query = Message(role_name="human", role_type="human", role_content="langchain有什么用") +query = Message(role_name="human", role_type="human", input_query=query_content, role_content=query_content, origin_query=query_content) -# output_message = phase.step(query) +output_message = phase.step(query) # print(phase.get_chains_memory(content_key="step_content")) # print(phase.get_chains_memory_str(content_key="step_content")) @@ -150,6 +175,7 @@ from dev_opsgpt.tools import DDGSTool, CodeRetrieval # "history": [], # "doc_engine_name": "DSADSAD", # "search_engine_name": "duckduckgo", +# "code_engine_name": "", # "top_k": 3, # "score_threshold": 1.0, # "stream": False, @@ -161,8 +187,12 @@ from dev_opsgpt.tools import DDGSTool, CodeRetrieval # "custom_phase_configs": {}, # "custom_chain_configs": {}, # "custom_role_configs": {}, -# "choose_tools": list(TOOL_SETS) +# "choose_tools": list(TOOL_SETS), +# "history_node_list": [], +# "isDetailed": False, +# "upload_file": "" # } -# answer = agentChat.chat(**value) -# print(answer) \ No newline at end of file + +# for answer in agentChat.achat(**value): +# print("answer:", answer) \ No newline at end of file diff --git a/tests/docker_test.py b/tests/docker_test.py index c797dab..86c7961 100644 --- a/tests/docker_test.py +++ b/tests/docker_test.py @@ -1,14 +1,15 @@ import time import docker +from loguru import logger st = time.time() client = docker.from_env() print(time.time()-st) -st = time.time() -client.containers.run("ubuntu:latest", "echo hello world") -print(time.time()-st) +# st = time.time() +# client.containers.run("ubuntu:latest", "echo hello world") +# print(time.time()-st) import socket @@ -42,3 +43,8 @@ def get_ipv4_address(): # print(container_a_info.name, container_a_ip, [[k, v["IPAddress"]] for k,v in container1_networks.items() ]) +containers = client.containers.list(all=True) +for container in containers: + if container.name == 'devopsgpt_webui': + res = container.exec_run('''sh chatbot/dev_opsgpt/utils/nebula_cp.sh''') + logger.info(f'cp res={res}') diff --git a/tests/sandbox_test.py b/tests/sandbox_test.py index 951457b..93584ea 100644 --- a/tests/sandbox_test.py +++ b/tests/sandbox_test.py @@ -26,18 +26,18 @@ from pathlib import Path import requests -# 设置Jupyter Notebook服务器的URL -url = 'http://172.25.0.3:5050' # 或者是你自己的Jupyter服务器的URL +# # 设置Jupyter Notebook服务器的URL +# url = 'http://172.25.0.3:5050' # 或者是你自己的Jupyter服务器的URL -# 发送GET请求来获取Jupyter Notebook的登录页面 -response = requests.get(url) +# # 发送GET请求来获取Jupyter Notebook的登录页面 +# response = requests.get(url) -# 检查响应状态码 -if response.status_code == 200: - # 打印响应内容 - print('connect success') -else: - print('connect fail') +# # 检查响应状态码 +# if response.status_code == 200: +# # 打印响应内容 +# print('connect success') +# else: +# print('connect fail') # import subprocess # jupyter = subprocess.Popen( @@ -57,12 +57,12 @@ else: # import time, psutil # from loguru import logger # import asyncio -# pycodebox = PyCodeBox(remote_url="http://localhost:5050", -# remote_ip="http://localhost", -# remote_port="5050", -# token="mytoken", -# do_code_exe=True, -# do_remote=False) +pycodebox = PyCodeBox(remote_url="http://localhost:5050", + remote_ip="http://localhost", + remote_port="5050", + token="mytoken", + do_code_exe=True, + do_remote=False) # pycodebox.list_files() # file = "./torch_test.py" @@ -75,8 +75,8 @@ else: # asyncio.run(pycodebox.alist_files()) -# reuslt = pycodebox.chat("```print('hello world!')```", do_code_exe=True) -# print(reuslt) +reuslt = pycodebox.chat("```'hello world!'```", do_code_exe=True) +print(reuslt) # reuslt = pycodebox.chat("print('hello world!')", do_code_exe=False) # print(reuslt) diff --git a/tests/tool_test.py b/tests/tool_test.py index 74a5485..6ec19ad 100644 --- a/tests/tool_test.py +++ b/tests/tool_test.py @@ -83,6 +83,13 @@ agent = initialize_agent( return_intermediate_steps=True ) + +# from dev_opsgpt.utils.common_utils import read_json_file +# stock_name = read_json_file("../sources/stock.json") +from dev_opsgpt.tools.ocr_tool import BaiduOcrTool + +print(BaiduOcrTool.run("D:/chromeDownloads/devopschat-bot/ocr_figure.png")) + # agent.return_intermediate_steps = True # content = agent.run("查询北京的行政编码,同时返回北京的天气情况") # print(content)