zhouhui.jiang

update 动态TOKEN

...@@ -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 )
......
...@@ -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 # 如果直接运行此文件
......