|
@@ -1,212 +0,0 @@
|
|
|
-from qwen_agent.actions.base import Action
|
|
|
-from qwen_agent.memory.SqlMemory import SqlRetriever
|
|
|
-from qwen_agent.tools.tools import tools_list, modify_sql, async_db
|
|
|
-# from qwen_agent.agents.plugin import call_plugin
|
|
|
-from qwen_agent.tools.tools import call_plugin
|
|
|
-import copy
|
|
|
-import re
|
|
|
-from pydantic import BaseModel, Field
|
|
|
-import time
|
|
|
-from typing import Dict, List, Literal, Optional, Union
|
|
|
-import json
|
|
|
-import openai
|
|
|
-from qwen_agent.sub_agent.BaseSubAgent import BaseSubAgent
|
|
|
-from qwen_agent.messages.context_message import ChatResponseChoice, ChatResponseStreamChoice
|
|
|
-from tabulate import tabulate
|
|
|
-import traceback
|
|
|
-
|
|
|
-
|
|
|
-class LandSupplySqlAgent(BaseSubAgent):
|
|
|
- def __init__(self, llm=None, llm_name=None, stream=False, name='LandSupplySqlAgent'):
|
|
|
- super(LandSupplySqlAgent, self).__init__(llm, llm_name, stream, name=name)
|
|
|
- # self.llm_name = 'Code'
|
|
|
- # self.llm_name = llm.model
|
|
|
- self.llm = llm
|
|
|
- self.tool_list = [{
|
|
|
- 'name_for_human': '查询土地供应信息数据库',
|
|
|
- 'name_for_model': 'TenderResultSqlAgent',
|
|
|
- 'description_for_model': """
|
|
|
- 当需要连接PostgreSQL数据库并执行一段sql时,请使用此功能。
|
|
|
- """
|
|
|
- + ' Format the arguments as a JSON object.',
|
|
|
- 'parameters': [{'name': 'sql_code', 'type': 'string',
|
|
|
- 'description': '合法的PostgreSQL查询语言。不接受【select *】,必须使用【select xxx,yyy】'}]
|
|
|
- }]
|
|
|
- self.SubAgent_Summary_Prompt = "通过查询数据库,检索数据库得到的信息为:\n{obs}\n"
|
|
|
- self.SubAgent_PROMPT = """你是一个PostgreSQL专家,当前需要根据用户问题和上下文,生成语法正确的PostgreSQL查询语句。'
|
|
|
- #数据库表的表名和表结构如下:
|
|
|
- `nrllm.land_supply`(
|
|
|
- `land_id` COMMENTS '地块id',
|
|
|
- `region_code` COMMENTS '行政区代码',
|
|
|
- `region_name` COMMENTS '地块id',
|
|
|
- `dkbh` COMMENTS '行政区名称',
|
|
|
- `province` COMMENTS '所属省 用来指定省份',
|
|
|
- `city` COMMENTS '所属地级市 用来指定地级市',
|
|
|
- `county` COMMENTS '所属区县 用来指定区县',
|
|
|
- `land_name` COMMENTS '地块名称',
|
|
|
- `amount` COMMENTS '出让金额(万元)',
|
|
|
- `land_area` COMMENTS '地块出让面积',
|
|
|
- `sell_date` COMMENTS '出让日期',
|
|
|
- `land_use_type` COMMENTS '土地用途 05开头的代表商业用地,06开头的代表工业用地,07开头的代表住宅用地,12开头的代表其他用地',
|
|
|
- `is_sampleplot` COMMENTS '是否标准地',
|
|
|
- `industry` COMMENTS '所属行业',
|
|
|
- `land_supply_type` COMMENTS '土地供应类型, 2 为招拍挂出让,3 为协议出让'
|
|
|
- )
|
|
|
-
|
|
|
- `nrllm.land_supply_structure`(
|
|
|
- `land_id` COMMENTS '地块id',
|
|
|
- `region_code` COMMENTS '行政区代码',
|
|
|
- `region_name` COMMENTS '地块id',
|
|
|
- `dkbh` COMMENTS '行政区名称',
|
|
|
- `province` COMMENTS '所属省 用来指定省份',
|
|
|
- `city` COMMENTS '所属地级市 用来指定地级市',
|
|
|
- `county` COMMENTS '所属区县 用来指定区县',
|
|
|
- `land_name` COMMENTS '地块名称',
|
|
|
- `amount` COMMENTS '出让金额(万元)',
|
|
|
- `land_area` COMMENTS '地块出让面积',
|
|
|
- `sell_date` COMMENTS '出让日期',
|
|
|
- `land_use_type` COMMENTS '土地用途 05开头的代表商业用地,06开头的代表工业用地,07开头的代表住宅用地,12开头的代表其他用地',
|
|
|
- `land_use_type_alias_cn` COMMENTS '土地用途中文名称,如:商业用地, 工业用地, 住宅用地,其他用地等',
|
|
|
- `is_sampleplot` COMMENTS '是否标准地',
|
|
|
- `industry` COMMENTS '所属行业',
|
|
|
- `land_supply_type` COMMENTS '土地供应类型, 2 为招拍挂出让,3 为协议出让'
|
|
|
- )
|
|
|
-
|
|
|
- 有几个注意事项:
|
|
|
- 注意0:请仔细区分"去年","今年","N年前","近三年"等时间关键词,必须使用to_char(sell_date,'yyyy') as nf,具体去年、今年等使用to_char(CURRENT_DATE - INTERVAL '1 year', 'YYYY')、to_char(CURRENT_DATE, 'YYYY'),如果提到了近三年、近五年时候查询是请使用to_char(sell_date,'yyyy') > to_char(CURRENT_DATE - INTERVAL '3 year', 'YYYY')、to_char(sell_date,'yyyy') > to_char(CURRENT_DATE - INTERVAL '5 year', 'YYYY'),并且要使用group by to_char(sell_date,'yyyy') 来区分不同年份的数据,所用的group by字段必须出现在查询返回字段中,且必须用order by to_char(sell_date,'yyyy');
|
|
|
- 注意1: 当土地用途为工业用地时,需要指定是否标准地 为 是
|
|
|
- 注意2: 统计出让面积的时候,需要统计的是 土地供应类型为招拍挂出让和协议出让的土地
|
|
|
- 注意3: 查询地区条件时,查询**省时请使用 province 字段,查询**市时请使用 city 字段, 查询**区县时请使用 county 字段,区县为**时请使用 county 字段
|
|
|
- 注意4: 统计土地用途分类时,只取land_use_type前两位并将转换为对应的中文含义,05开头的代表商业用地,06开头的代表工业用地,07开头的代表住宅用地,12开头的代表其他用地
|
|
|
- 注意5: 环比是指指定月份的数据比较指定月份上一个月份的数据,并统计他的增量百分比,同比是指指定月份的数据与去年同一个月份的数据进行比较,并统计他的增量百分比
|
|
|
- 注意6: 统计面积时候,无指定单位的情况下,请默认转换为亩并四舍五入成整数,平方米转换为亩请乘以0.0015
|
|
|
- 注意7: 住宅用地 为 land_use_type 字段中值为07开头的数据,细分的类型分为
|
|
|
- 用于安置的商品住房用地 为 0710,
|
|
|
- 保障性租赁住房出让金 为 0706,
|
|
|
- 公共租赁住房 为 0705、077和07010101三个值中的任意一个,
|
|
|
- 经济适用住房 为 0704、074和07010103三个值中的任意一个
|
|
|
- 普通商品住房 为 0703、07010201、071和072四个值中的任意一个
|
|
|
- 租赁型商品住房 为 0708和07010202两个值中的任意一个
|
|
|
- 共有产权住房 为 0709和07010203两个值中的任意一个
|
|
|
- 其他类型 为不在上面的类型的数据
|
|
|
- 注意8: 当查询土地供应结构时、 工业用地出让情况、住宅用地出让情况、商业用地出让情况或者标准地供应情况的时候,请使用 nrllm.land_supply_structure 表,只需要统计商服用地,工矿仓储用地 和住宅用地,其他的类型请归类为基础设施用地, 其他情况下请使用 nrllm.land_supply 表
|
|
|
- 注意9: 计算同比或者环比数据指的计算按月统计面积时,需要将sell_date 转换为 to_char(sell_date, 'yyyy-MM')
|
|
|
- 注意10: 获取近几年浙江省供地同比和环比增长率,使用limit 12 返回近12个月的同比和环比增长率
|
|
|
-
|
|
|
- ```
|
|
|
-
|
|
|
- 以下是可供参考的SQL写法(仅供参考,也可自由发挥):
|
|
|
- ```
|
|
|
- {refs}
|
|
|
- ```
|
|
|
- """
|
|
|
- self.retriever = SqlRetriever(query_type='land_supply')
|
|
|
-
|
|
|
- async def run(self, plan_context, messages=[]):
|
|
|
- if not plan_context.has_sql_data:
|
|
|
- query = plan_context.get_context(add_plan_msg=False)
|
|
|
- else:
|
|
|
- query = plan_context.get_context(add_plan_msg=False, add_sql_code=True)
|
|
|
- print(f'query for sql: {query}')
|
|
|
- if self.retriever:
|
|
|
- self.SubAgent_PROMPT = self.SubAgent_PROMPT.format(refs='\n'.join(
|
|
|
- [f"{i}\t{n}:{q}" for i, (n, q) in enumerate(self.retriever.get_relevant_documents(query, top_k=2))]))
|
|
|
- # print('Examples:','\n'.join([f"{i}\t{n}:{q}" for i,(n,q) in enumerate(self.retriever.get_relevant_documents(plan_context.user_request,top_k=5))]))
|
|
|
- # local_message, is_success = self._core(query,messages)
|
|
|
- async for msg in self._core(query, messages):
|
|
|
- yield msg
|
|
|
- if isinstance(msg, ChatResponseChoice):
|
|
|
- self.exec_res = "\n" + msg.content + "\n"
|
|
|
-
|
|
|
- self.sql_code = None
|
|
|
- plan_context.has_search_db = True
|
|
|
- if self.is_success:
|
|
|
-
|
|
|
- sql_json_res = json.loads(self.exec_res.replace('\n', '').lstrip('```json').rstrip('```'))
|
|
|
-
|
|
|
- # print("sql_json_res:",sql_json_res)
|
|
|
- # headers = sql_json_res.keys()
|
|
|
- # result = zip(*sql_json_res.values())
|
|
|
- headers = list(sql_json_res[0].keys())
|
|
|
- exec_res = copy.deepcopy(sql_json_res)
|
|
|
- if 'bid' in headers:
|
|
|
- headers.remove('bid')
|
|
|
- for row in exec_res:
|
|
|
- row.pop('bid')
|
|
|
- self.exec_res = f"\n```json\n{json.dumps(exec_res, ensure_ascii=False)}\n```\n"
|
|
|
- print("sql exec res:", self.exec_res)
|
|
|
-
|
|
|
- result = []
|
|
|
- for row in sql_json_res:
|
|
|
- result.append([])
|
|
|
- for head, value in row.items():
|
|
|
- if head in ('中标单位', '招标单位', '代理单位'):
|
|
|
- value = ", ".join(
|
|
|
- f"[{name}](/package2/pages/companyDetail/index?companyName={name})" for name in
|
|
|
- value.split(' '))
|
|
|
- if head == '标题':
|
|
|
- value = f"[{value}](/package2/pages/bidDetail/index?md5Id={row.get('bid', '')})"
|
|
|
- if head != 'bid':
|
|
|
- result[-1].append(value)
|
|
|
-
|
|
|
- markdown_table = tabulate(result, headers, tablefmt="pipe")
|
|
|
- print(markdown_table)
|
|
|
- yield ChatResponseChoice(role='info', content=self.SubAgent_Summary_Prompt.format(obs=markdown_table))
|
|
|
-
|
|
|
- plan_context.has_sql_data = True
|
|
|
- # if self.plugin_args:
|
|
|
- # self.sql_code = json.loads(self.plugin_args)['sql_code']
|
|
|
- print('sql plugin args:', self.plugin_args)
|
|
|
- else:
|
|
|
- self.empty_data = True
|
|
|
- self.exec_res = "在数据库中,未查询到相关信息。"
|
|
|
- yield ChatResponseChoice(role='info', content="在数据库中,未查询到相关信息。")
|
|
|
-
|
|
|
- def modify_sql(self, sql_query):
|
|
|
- sql_query = sql_query.replace('`', '')
|
|
|
- # 使用正则表达式寻找 GROUP BY 和其后面的内容,直到遇到 HAVING, ORDER BY 或者字符串结束
|
|
|
- match = re.search(r'(GROUP BY|group by)(.*?)(order by|having|HAVING|ORDER BY|;|$)', sql_query, re.IGNORECASE)
|
|
|
- target_table = "agent_bidding_history_detail_all"
|
|
|
- # 如果找到匹配项
|
|
|
- if match:
|
|
|
- # 提取 GROUP BY 后面的字段,去掉多余的空格,并分割成列表
|
|
|
- fields = match.group(2).strip().split(',')
|
|
|
- for field in fields:
|
|
|
- if field.strip() == '招标单位':
|
|
|
- target_table = 'agent_bidding_history_detail_by_ifb_new'
|
|
|
- break
|
|
|
- elif field.strip() == '中标单位':
|
|
|
- target_table = 'agent_bidding_history_detail_by_wtb_new'
|
|
|
- break
|
|
|
- elif field.strip() == '招标产品':
|
|
|
- target_table = 'agent_bidding_history_detail_by_prod_new'
|
|
|
- break
|
|
|
- sql_query = sql_query.replace('agent_bidding_history_detail_all', target_table)
|
|
|
- match = re.search(r'(SELECT|select)(.*?)(from|FROM)', sql_query, re.IGNORECASE)
|
|
|
- if match:
|
|
|
- if '标题' in match.group(2) and 'bid' not in match.group(2):
|
|
|
- sql_query = sql_query.replace('标题', 'bid, 标题')
|
|
|
- return sql_query
|
|
|
-
|
|
|
- async def run_function(self, plugin_args):
|
|
|
- plugin_args = self.parse_parameter(plugin_args)
|
|
|
- try:
|
|
|
- sql_to_execute = plugin_args.strip()
|
|
|
- if not sql_to_execute.lower().startswith('select'):
|
|
|
- sql_to_execute = sql_to_execute.replace('json', '')
|
|
|
- sql_to_execute = json.loads(plugin_args)['sql_code']
|
|
|
- sql_to_execute = self.modify_sql(sql_to_execute)
|
|
|
- print(f"sql_to_execute:{sql_to_execute}")
|
|
|
- self.sql_code = sql_to_execute
|
|
|
- a = time.time()
|
|
|
- res_tuples = await async_db.run(sql_to_execute)
|
|
|
- print('SQL Time Cost:', time.time() - a)
|
|
|
- result, success = res_tuples
|
|
|
- if success and len(result) == 0:
|
|
|
- return '暂未查询到相关信息,可能是字段错误,请尝试调整查询条件,例如(1)where条件中修改‘招标类型’;(2)where条件中修改‘招标单位’或者“中标单位”;(3)注意在查询省市的时候是否使用了LIKE查询;', plugin_args, False
|
|
|
- return f"```json\n{result}\n```", plugin_args, success
|
|
|
- except:
|
|
|
- traceback.print_exc()
|
|
|
- return f"ERROR:{traceback.format_exc()}", plugin_args, False
|
|
|
-
|
|
|
-
|