Showing
12 changed files
with
145 additions
and
22 deletions
No preview for this file type
No preview for this file type
This file is too large to display.
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
| ... | @@ -21,6 +21,7 @@ def upload_clearance_file( | ... | @@ -21,6 +21,7 @@ def upload_clearance_file( |
| 21 | pdf_path: str, | 21 | pdf_path: str, |
| 22 | file_index: int = 0, | 22 | file_index: int = 0, |
| 23 | uid: Optional[int] = None, | 23 | uid: Optional[int] = None, |
| 24 | + Authorization: Optional[str] = None, | ||
| 24 | ) -> str: | 25 | ) -> str: |
| 25 | """根据运单ID上传清关文件(PDF)。 | 26 | """根据运单ID上传清关文件(PDF)。 |
| 26 | 27 | ||
| ... | @@ -30,6 +31,7 @@ def upload_clearance_file( | ... | @@ -30,6 +31,7 @@ def upload_clearance_file( |
| 30 | pdf_path: 本地 PDF 文件路径(必填) | 31 | pdf_path: 本地 PDF 文件路径(必填) |
| 31 | file_index: 文件索引,默认 0 | 32 | file_index: 文件索引,默认 0 |
| 32 | uid: 文件 uid,可不传,默认使用时间戳生成 | 33 | uid: 文件 uid,可不传,默认使用时间戳生成 |
| 34 | + Authorization: 授权令牌,如果提供则使用该值,否则使用 API_CONFIG 中的默认值 | ||
| 33 | 35 | ||
| 34 | Returns: | 36 | Returns: |
| 35 | 文本:成功/失败信息。 | 37 | 文本:成功/失败信息。 |
| ... | @@ -71,8 +73,11 @@ def upload_clearance_file( | ... | @@ -71,8 +73,11 @@ def upload_clearance_file( |
| 71 | "fileUpload": (os.path.basename(pdf_path), open(pdf_path, "rb"), "application/pdf"), | 73 | "fileUpload": (os.path.basename(pdf_path), open(pdf_path, "rb"), "application/pdf"), |
| 72 | } | 74 | } |
| 73 | 75 | ||
| 74 | - # 复制 headers,并移除 Content-Type(由 requests 根据 multipart 自动设置) | 76 | + # 构建 headers,优先使用传入的 Authorization |
| 75 | headers = dict(API_CONFIG.get("headers", {})) | 77 | headers = dict(API_CONFIG.get("headers", {})) |
| 78 | + if Authorization: | ||
| 79 | + headers['Authorization'] = Authorization | ||
| 80 | + # 移除 Content-Type(由 requests 根据 multipart 自动设置) | ||
| 76 | headers.pop("Content-Type", None) | 81 | headers.pop("Content-Type", None) |
| 77 | 82 | ||
| 78 | try: | 83 | try: | ... | ... |
| ... | @@ -9,11 +9,12 @@ import json | ... | @@ -9,11 +9,12 @@ import json |
| 9 | from .api_config import API_CONFIG | 9 | from .api_config import API_CONFIG |
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | -def create_waybill_d(consignmentCode: str) -> str: | 12 | +def create_waybill_d(consignmentCode: str, Authorization: str = None) -> str: |
| 13 | """根据运单号创建D类运单 | 13 | """根据运单号创建D类运单 |
| 14 | 14 | ||
| 15 | Args: | 15 | Args: |
| 16 | consignmentCode: 运单号(必填) | 16 | consignmentCode: 运单号(必填) |
| 17 | + Authorization: 授权令牌,如果提供则使用该值,否则使用 API_CONFIG 中的默认值 | ||
| 17 | 18 | ||
| 18 | Returns: | 19 | Returns: |
| 19 | 创建结果信息 | 20 | 创建结果信息 |
| ... | @@ -28,10 +29,15 @@ def create_waybill_d(consignmentCode: str) -> str: | ... | @@ -28,10 +29,15 @@ def create_waybill_d(consignmentCode: str) -> str: |
| 28 | "isAIRecognition": 0 | 29 | "isAIRecognition": 0 |
| 29 | } | 30 | } |
| 30 | 31 | ||
| 32 | + # 构建 headers,优先使用传入的 Authorization | ||
| 33 | + headers = dict(API_CONFIG['headers']) | ||
| 34 | + if Authorization: | ||
| 35 | + headers['Authorization'] = Authorization | ||
| 36 | + | ||
| 31 | # 发送POST请求 | 37 | # 发送POST请求 |
| 32 | response = requests.post( | 38 | response = requests.post( |
| 33 | url, | 39 | url, |
| 34 | - headers=API_CONFIG['headers'], | 40 | + headers=headers, |
| 35 | json=data, | 41 | json=data, |
| 36 | timeout=30 | 42 | timeout=30 |
| 37 | ) | 43 | ) |
| ... | @@ -74,7 +80,7 @@ def create_waybill_d(consignmentCode: str) -> str: | ... | @@ -74,7 +80,7 @@ def create_waybill_d(consignmentCode: str) -> str: |
| 74 | try: | 80 | try: |
| 75 | head_resp = requests.post( | 81 | head_resp = requests.post( |
| 76 | head_url, | 82 | head_url, |
| 77 | - headers=API_CONFIG["headers"], | 83 | + headers=headers, |
| 78 | json={"id": waybill_id}, | 84 | json={"id": waybill_id}, |
| 79 | timeout=30, | 85 | timeout=30, |
| 80 | ) | 86 | ) |
| ... | @@ -97,7 +103,7 @@ def create_waybill_d(consignmentCode: str) -> str: | ... | @@ -97,7 +103,7 @@ def create_waybill_d(consignmentCode: str) -> str: |
| 97 | } | 103 | } |
| 98 | detail_resp = requests.post( | 104 | detail_resp = requests.post( |
| 99 | detail_url, | 105 | detail_url, |
| 100 | - headers=API_CONFIG["headers"], | 106 | + headers=headers, |
| 101 | json=detail_payload, | 107 | json=detail_payload, |
| 102 | timeout=30, | 108 | timeout=30, |
| 103 | ) | 109 | ) |
| ... | @@ -127,12 +133,13 @@ def create_waybill_d(consignmentCode: str) -> str: | ... | @@ -127,12 +133,13 @@ def create_waybill_d(consignmentCode: str) -> str: |
| 127 | return f"❌ 创建D类运单失败: {str(e)}" | 133 | return f"❌ 创建D类运单失败: {str(e)}" |
| 128 | 134 | ||
| 129 | 135 | ||
| 130 | -def query_waybill_list(initialize: int = 1, consStatusName: str = "等待录入") -> str: | 136 | +def query_waybill_list(initialize: int = 1, consStatusName: str = "等待录入", Authorization: str = None) -> str: |
| 131 | """查询运单列表信息 | 137 | """查询运单列表信息 |
| 132 | 138 | ||
| 133 | Args: | 139 | Args: |
| 134 | initialize: 初始化标志,默认为1 | 140 | initialize: 初始化标志,默认为1 |
| 135 | consStatusName: 运单状态名称,默认为"等待录入" | 141 | consStatusName: 运单状态名称,默认为"等待录入" |
| 142 | + Authorization: 授权令牌,如果提供则使用该值,否则使用 API_CONFIG 中的默认值 | ||
| 136 | 143 | ||
| 137 | Returns: | 144 | Returns: |
| 138 | 运单查询结果 | 145 | 运单查询结果 |
| ... | @@ -154,10 +161,15 @@ def query_waybill_list(initialize: int = 1, consStatusName: str = "等待录入" | ... | @@ -154,10 +161,15 @@ def query_waybill_list(initialize: int = 1, consStatusName: str = "等待录入" |
| 154 | "total": 0 | 161 | "total": 0 |
| 155 | } | 162 | } |
| 156 | 163 | ||
| 164 | + # 构建 headers,优先使用传入的 Authorization | ||
| 165 | + headers = dict(API_CONFIG['headers']) | ||
| 166 | + if Authorization: | ||
| 167 | + headers['Authorization'] = Authorization | ||
| 168 | + | ||
| 157 | # 发送POST请求 | 169 | # 发送POST请求 |
| 158 | response = requests.post( | 170 | response = requests.post( |
| 159 | url, | 171 | url, |
| 160 | - headers=API_CONFIG['headers'], | 172 | + headers=headers, |
| 161 | json=data, | 173 | json=data, |
| 162 | timeout=30 | 174 | timeout=30 |
| 163 | ) | 175 | ) |
| ... | @@ -331,11 +343,12 @@ def get_waybill_detail_list( | ... | @@ -331,11 +343,12 @@ def get_waybill_detail_list( |
| 331 | return f"查询表体明细失败: {str(e)}" | 343 | return f"查询表体明细失败: {str(e)}" |
| 332 | 344 | ||
| 333 | 345 | ||
| 334 | -def push_waybill_for_ocr(waybill_id: int) -> str: | 346 | +def push_waybill_for_ocr(waybill_id: int, Authorization: str = None) -> str: |
| 335 | """根据运单ID推送OCR进行识别 | 347 | """根据运单ID推送OCR进行识别 |
| 336 | 348 | ||
| 337 | Args: | 349 | Args: |
| 338 | waybill_id: 运单ID(必填) | 350 | waybill_id: 运单ID(必填) |
| 351 | + Authorization: 授权令牌,如果提供则使用该值,否则使用 API_CONFIG 中的默认值 | ||
| 339 | 352 | ||
| 340 | Returns: | 353 | Returns: |
| 341 | 推送结果信息 | 354 | 推送结果信息 |
| ... | @@ -348,10 +361,15 @@ def push_waybill_for_ocr(waybill_id: int) -> str: | ... | @@ -348,10 +361,15 @@ def push_waybill_for_ocr(waybill_id: int) -> str: |
| 348 | "id": waybill_id | 361 | "id": waybill_id |
| 349 | } | 362 | } |
| 350 | 363 | ||
| 364 | + # 构建 headers,优先使用传入的 Authorization | ||
| 365 | + headers = dict(API_CONFIG['headers']) | ||
| 366 | + if Authorization: | ||
| 367 | + headers['Authorization'] = Authorization | ||
| 368 | + | ||
| 351 | # 发送POST请求 | 369 | # 发送POST请求 |
| 352 | response = requests.post( | 370 | response = requests.post( |
| 353 | url, | 371 | url, |
| 354 | - headers=API_CONFIG['headers'], | 372 | + headers=headers, |
| 355 | json=data, | 373 | json=data, |
| 356 | timeout=30 | 374 | timeout=30 |
| 357 | ) | 375 | ) | ... | ... |
No preview for this file type
| ... | @@ -3,7 +3,10 @@ from langchain_openai import ChatOpenAI | ... | @@ -3,7 +3,10 @@ from langchain_openai import ChatOpenAI |
| 3 | import os | 3 | import os |
| 4 | import sys | 4 | import sys |
| 5 | import json | 5 | import json |
| 6 | -from typing import Dict, Any | 6 | +from typing import Dict, Any, List, Optional |
| 7 | +from contextvars import ContextVar | ||
| 8 | +from langchain_core.runnables import RunnableConfig | ||
| 9 | +from langchain_core.messages import AnyMessage | ||
| 7 | 10 | ||
| 8 | # 添加项目根目录到 Python 路径 | 11 | # 添加项目根目录到 Python 路径 |
| 9 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | 12 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| ... | @@ -87,17 +90,99 @@ def pre_model_inspect_attachments(state, **kwargs): | ... | @@ -87,17 +90,99 @@ def pre_model_inspect_attachments(state, **kwargs): |
| 87 | traceback.print_exc() | 90 | traceback.print_exc() |
| 88 | return {} | 91 | return {} |
| 89 | 92 | ||
| 90 | -# 创建 ReAct 智能体 | 93 | + |
| 91 | -agent = create_react_agent( | 94 | +def extract_token(state: Dict[str, Any]) -> str: |
| 92 | - model=model, | 95 | + """ |
| 93 | - tools=[query_waybill_list, create_waybill_d, upload_clearance_file, push_waybill_for_ocr], | 96 | + 从 state 中提取 token |
| 94 | - pre_model_hook=pre_model_inspect_attachments, | 97 | + 获取最后一个类型为 HumanMessage 或 human 的消息中的 token |
| 95 | - prompt="""你是一个专业的出口物流系统智能助手,专门帮助用户处理运单相关的业务操作。 | 98 | + |
| 99 | + Args: | ||
| 100 | + state: LangGraph 状态字典,包含 messages 数组 | ||
| 101 | + | ||
| 102 | + Returns: | ||
| 103 | + token 字符串,如果未找到则返回空字符串 | ||
| 104 | + """ | ||
| 105 | + messages = state.get("messages", []) | ||
| 106 | + if not messages: | ||
| 107 | + return "" | ||
| 108 | + | ||
| 109 | + # 找到所有 is_human 类型的消息 | ||
| 110 | + human_messages = [] | ||
| 111 | + for msg in messages: | ||
| 112 | + # 兼容 dict 或 LangChain 的消息对象 | ||
| 113 | + if isinstance(msg, dict): | ||
| 114 | + msg_type = msg.get("type") | ||
| 115 | + else: | ||
| 116 | + msg_type = msg.__class__.__name__ | ||
| 117 | + | ||
| 118 | + # 检查是否是 human 类型的消息 | ||
| 119 | + is_human = (msg_type == "HumanMessage" or msg_type == "human") | ||
| 120 | + if is_human: | ||
| 121 | + human_messages.append(msg) | ||
| 122 | + | ||
| 123 | + # 如果没有 human 消息,直接返回 | ||
| 124 | + if not human_messages: | ||
| 125 | + return "" | ||
| 126 | + | ||
| 127 | + # 直接取最后一个 human 消息(不需要循环判断) | ||
| 128 | + last_human_msg = human_messages[-1] | ||
| 129 | + | ||
| 130 | + # 从 content 中提取 token | ||
| 131 | + if isinstance(last_human_msg, dict): | ||
| 132 | + content = last_human_msg.get("content") | ||
| 133 | + else: | ||
| 134 | + content = getattr(last_human_msg, "content", None) | ||
| 135 | + | ||
| 136 | + if isinstance(content, list): | ||
| 137 | + # content 是列表,遍历查找包含 token 的 part | ||
| 138 | + for part in content: | ||
| 139 | + if isinstance(part, dict) and "token" in part: | ||
| 140 | + token = part.get("token") | ||
| 141 | + if token: | ||
| 142 | + return token | ||
| 143 | + elif isinstance(content, dict): | ||
| 144 | + # content 是字典,直接获取 token | ||
| 145 | + if "token" in content: | ||
| 146 | + token = content.get("token") | ||
| 147 | + if token: | ||
| 148 | + return token | ||
| 149 | + | ||
| 150 | + return "" | ||
| 151 | + | ||
| 152 | +def _create_system_prompt(state: Dict[str, Any], config: RunnableConfig) -> List[AnyMessage]: | ||
| 153 | + """ | ||
| 154 | + 创建动态系统提示词 | ||
| 155 | + | ||
| 156 | + Args: | ||
| 157 | + state: LangGraph 状态字典 | ||
| 158 | + config: Runnable 配置 | ||
| 159 | + | ||
| 160 | + Returns: | ||
| 161 | + 包含系统消息和原始消息的列表 | ||
| 162 | + """ | ||
| 163 | + # 添加调试信息,确认函数被调用 | ||
| 164 | + # print("\n=== _create_system_prompt 被调用 ===") | ||
| 165 | + # print(f"state type: {type(state)}") | ||
| 166 | + # print(f"state keys: {list(state.keys()) if isinstance(state, dict) else 'not a dict'}") | ||
| 167 | + | ||
| 168 | + # 从 state 中提取动态参数 | ||
| 169 | + token = extract_token(state) | ||
| 170 | + | ||
| 171 | + # 如果从 state 中提取的 token 为空,则从 api_config.py 中获取 Authorization 作为备选 | ||
| 172 | + if not token: | ||
| 173 | + from API.api_config import API_CONFIG | ||
| 174 | + token = API_CONFIG.get("headers", {}).get("Authorization", "") | ||
| 175 | + # | ||
| 176 | + # print(f"提取到的 token: {token[:30] if token else 'None'}...") | ||
| 177 | + | ||
| 178 | + # 创建系统提示词(使用 f-string 以便插入 token) | ||
| 179 | + system_msg = f"""你是一个专业的出口物流系统智能助手,专门帮助用户处理运单相关的业务操作。 | ||
| 96 | 180 | ||
| 97 | ## 你的主要职责: | 181 | ## 你的主要职责: |
| 98 | 1. **运单查询**:根据用户需求查询运单列表,支持按状态、时间等条件筛选,结果以JSON形式展示,AI不用对返回数据JSON进行加工 | 182 | 1. **运单查询**:根据用户需求查询运单列表,支持按状态、时间等条件筛选,结果以JSON形式展示,AI不用对返回数据JSON进行加工 |
| 99 | 2. **运单创建**:协助用户创建D类运单,确保信息完整准确 | 183 | 2. **运单创建**:协助用户创建D类运单,确保信息完整准确 |
| 100 | -4. **业务咨询**:解答用户关于出口物流流程、运单状态、操作规范等问题 | 184 | +3. **业务咨询**:解答用户关于出口物流流程、运单状态、操作规范等问题 |
| 185 | +4. **您当前访问工具Authorization的传参为 Authorization= {token} | ||
| 101 | 186 | ||
| 102 | ## 工作原则: | 187 | ## 工作原则: |
| 103 | - 上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,不用确认文件索引,工具会自动设置索引为0,请直接调用工具接口 | 188 | - 上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,不用确认文件索引,工具会自动设置索引为0,请直接调用工具接口 |
| ... | @@ -107,13 +192,13 @@ agent = create_react_agent( | ... | @@ -107,13 +192,13 @@ agent = create_react_agent( |
| 107 | - 如遇到错误,主动分析原因并提供解决方案 | 192 | - 如遇到错误,主动分析原因并提供解决方案 |
| 108 | - 保持专业、友好的沟通态度 | 193 | - 保持专业、友好的沟通态度 |
| 109 | - 严禁改写工具函数返回的文本格式;对工具输出仅直接转述,不得增删前后缀或改写内容。 | 194 | - 严禁改写工具函数返回的文本格式;对工具输出仅直接转述,不得增删前后缀或改写内容。 |
| 110 | - - 若调用了工具并获得结果,则必须将该工具返回的文本“原样作为最终答复”输出,不允许添加任何解释、建议或额外文字。 | 195 | +- 若调用了工具并获得结果,则必须将该工具返回的文本"原样作为最终答复"输出,不允许添加任何解释、建议或额外文字。 |
| 111 | 196 | ||
| 112 | ## 可用工具: | 197 | ## 可用工具: |
| 113 | -- query_waybill_list: 查询运单列表,支持按状态筛选,结果以JSON形式展示,AI不用对返回数据JSON进行加工 | 198 | +- query_waybill_list: 查询运单列表,支持按状态筛选,需提供Authorization,结果以JSON形式展示,AI不用对返回数据JSON进行加工 |
| 114 | -- create_waybill_d: 根据运单号创建D类运单,需要提供运单号参数 | 199 | +- create_waybill_d: 根据运单号创建D类运单,需要提供参数(运单号、Authorization) |
| 115 | -- upload_clearance_file: 上传清关PDF文件,需要 code、slip_id、pdf_path,上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,不用确认文件索引,工具会自动设置索引为0,请直接调用工具接口 | 200 | +- upload_clearance_file: 上传清关PDF文件,需要 code、slip_id、pdf_path、Authorization,上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,不用确认文件索引,工具会自动设置索引为0,请直接调用工具接口 |
| 116 | -- push_waybill_for_ocr: 根据运单ID推送OCR进行识别,需要提供运单ID(waybill_id)参数 | 201 | +- push_waybill_for_ocr: 根据运单ID推送OCR进行识别,需要提供运单ID(waybill_id、Authorization)参数 |
| 117 | 202 | ||
| 118 | ## query_waybill_list数据展示说明: | 203 | ## query_waybill_list数据展示说明: |
| 119 | - 运单查询结果会自动格式化为JSON形式展示,包含:运单号、运单类型、运单状态、发件人、运单日期 | 204 | - 运单查询结果会自动格式化为JSON形式展示,包含:运单号、运单类型、运单状态、发件人、运单日期 |
| ... | @@ -122,7 +207,22 @@ agent = create_react_agent( | ... | @@ -122,7 +207,22 @@ agent = create_react_agent( |
| 122 | 207 | ||
| 123 | ## create_waybill_d数据展示说明: | 208 | ## create_waybill_d数据展示说明: |
| 124 | - 按照数据返回的原本格式进行展示,不得增删前后缀或改写内容。 | 209 | - 按照数据返回的原本格式进行展示,不得增删前后缀或改写内容。 |
| 210 | + | ||
| 125 | 请根据用户的具体需求,选择合适的工具并提供帮助。""" | 211 | 请根据用户的具体需求,选择合适的工具并提供帮助。""" |
| 212 | + | ||
| 213 | + # 返回系统消息 + 原始消息 | ||
| 214 | + result = [{"role": "system", "content": system_msg}] + state.get("messages", []) | ||
| 215 | + print(f"返回消息数量: {len(result)}") | ||
| 216 | + print("=== _create_system_prompt 执行完成 ===\n") | ||
| 217 | + return result | ||
| 218 | + | ||
| 219 | + | ||
| 220 | +# 创建 ReAct 智能体 | ||
| 221 | +agent = create_react_agent( | ||
| 222 | + model=model, | ||
| 223 | + tools=[query_waybill_list, create_waybill_d, upload_clearance_file, push_waybill_for_ocr], | ||
| 224 | + pre_model_hook=pre_model_inspect_attachments, | ||
| 225 | + prompt=_create_system_prompt, | ||
| 126 | ) | 226 | ) |
| 127 | 227 | ||
| 128 | # 如果直接运行此文件 | 228 | # 如果直接运行此文件 | ... | ... |
-
Please register or login to post a comment