Toggle navigation
Toggle navigation
This project
Loading...
Sign in
zhouhui.jiang
/
Test_LangGraph
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
zhouhui.jiang
2025-11-15 13:28:23 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
c315e53ac3d79f1a98ac6c13fe06af1c6b7cd700
c315e53a
1 parent
5a8f3de7
update
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
221 additions
and
29 deletions
API/api_config.py
API/paperless_api.py
API/waybill_api.py
langgraph_examples/api_agent.py
API/api_config.py
View file @
c315e53
...
...
@@ -13,7 +13,7 @@ API_CONFIG = {
"headers"
:
{
"accept"
:
"*/*"
,
"Content-Type"
:
"application/json"
,
"Authorization"
:
os
.
getenv
(
"API_AUTHORIZATION"
,
"Bearer 2.
21e3deecca904c7697ab891b8276cf3
6"
),
"Authorization"
:
os
.
getenv
(
"API_AUTHORIZATION"
,
"Bearer 2.
bd593d8f36c4468fbf02ee95c473cbb
6"
),
"Ver"
:
os
.
getenv
(
"API_VER"
,
"033BD94B1168D7E4F0D644C3C95E35BF.D73E33B659AD1D6B7D181D1DF8D05760"
),
"Referer"
:
os
.
getenv
(
"API_REFERER"
,
"http://192.168.1.251/"
)
}
...
...
API/paperless_api.py
View file @
c315e53
...
...
@@ -103,3 +103,79 @@ def upload_clearance_file(
return
f
"上传清关文件失败: {str(e)}"
def
upload_file_for_ocr
(
pdf_path
:
str
,
Authorization
:
Optional
[
str
]
=
None
,
)
->
str
:
"""上传文件进行OCR识别
Args:
pdf_path: 本地 PDF 文件路径(必填)
Authorization: 授权令牌,如果提供则使用该值,否则使用 API_CONFIG 中的默认值
Returns:
文本:成功/失败信息
"""
if
not
os
.
path
.
isfile
(
pdf_path
):
return
f
"参数错误: 文件不存在 - {pdf_path}"
# 从文件路径提取文件名
file_name
=
os
.
path
.
basename
(
pdf_path
)
# 构建URL(不包含查询参数)
url
=
f
"{API_CONFIG['base_url']}/API/ocrDemo/pushDemo"
# 封装参数到 params_payload
params_payload
=
{
"fileName01"
:
file_name
,
"fileNumber"
:
1
}
# form-data: params 是 JSON 字符串,fileUpload 是文件
data
=
{
"params"
:
json
.
dumps
(
params_payload
,
ensure_ascii
=
False
),
}
files
=
{
"file"
:
(
file_name
,
open
(
pdf_path
,
"rb"
),
"application/pdf"
),
}
# 构建 headers,优先使用传入的 Authorization
headers
=
dict
(
API_CONFIG
.
get
(
"headers"
,
{}))
if
Authorization
:
headers
[
'Authorization'
]
=
Authorization
# 移除 Content-Type(由 requests 根据 multipart 自动设置)
headers
.
pop
(
"Content-Type"
,
None
)
try
:
resp
=
requests
.
post
(
url
,
headers
=
headers
,
data
=
data
,
files
=
files
,
timeout
=
60
)
# 确保文件句柄尽快关闭
try
:
files
[
"file"
][
1
]
.
close
()
except
Exception
:
pass
if
resp
.
status_code
==
200
:
# 解析响应JSON
result
=
resp
.
json
()
# 提取指定字段,只返回 status、message、chmCode、chmId
filtered_result
=
{}
if
"status"
in
result
:
filtered_result
[
"status"
]
=
result
[
"status"
]
if
"message"
in
result
:
filtered_result
[
"message"
]
=
result
[
"message"
]
if
"chmCode"
in
result
:
filtered_result
[
"chmCode"
]
=
result
[
"chmCode"
]
if
"chmId"
in
result
:
filtered_result
[
"chmId"
]
=
result
[
"chmId"
]
# 返回过滤后的JSON字符串
return
json
.
dumps
(
filtered_result
,
ensure_ascii
=
False
)
return
f
"上传失败: HTTP {resp.status_code}, 错误信息: {resp.text}"
except
requests
.
exceptions
.
RequestException
as
e
:
return
f
"网络请求失败: {str(e)}"
except
Exception
as
e
:
return
f
"上传文件进行OCR识别失败: {str(e)}"
...
...
API/waybill_api.py
View file @
c315e53
...
...
@@ -133,6 +133,70 @@ def create_waybill_d(consignmentCode: str, Authorization: str = None) -> str:
return
f
"❌ 创建D类运单失败: {str(e)}"
def
create_waybill_d_with_id
(
consignmentCode
:
str
,
consignmentId
:
str
,
loginName
:
str
,
userId
:
int
,
Authorization
:
str
=
None
,
)
->
str
:
"""根据运单ID及运单号创建D类运单
Args:
consignmentCode: 运单号(必填)
consignmentId: 运单ID(必填)
loginName: 登录名(必填)
userId: 用户ID(必填)
Authorization: 授权令牌,如果提供则使用该值,否则使用 API_CONFIG 中的默认值
Returns:
创建结果,成功时返回序列化的JSON,失败时返回错误信息
"""
try
:
url
=
f
"{API_CONFIG['base_url']}/Exp/bus-customer/consignment/createChmConsignmentD/demo"
# 构建请求数据
data
=
{
"consignmentCode"
:
consignmentCode
,
"consignmentId"
:
consignmentId
,
"isAIRecognition"
:
1
,
# 写死为1
"loginName"
:
loginName
,
"userId"
:
userId
}
# 构建 headers,优先使用传入的 Authorization
headers
=
dict
(
API_CONFIG
[
'headers'
])
if
Authorization
:
headers
[
'Authorization'
]
=
Authorization
# 发送POST请求
response
=
requests
.
post
(
url
,
headers
=
headers
,
json
=
data
,
timeout
=
30
)
# 检查响应状态
if
response
.
status_code
==
200
:
result
=
response
.
json
()
# 提取运单ID
data
=
result
.
get
(
'data'
)
or
{}
extra
=
data
.
get
(
'extra'
)
or
{}
waybill_id
=
extra
.
get
(
'id'
)
if
waybill_id
is
not
None
:
return
waybill_id
# 返回数字格式的运单ID
return
f
"❌ 创建失败: 响应中未找到运单ID"
return
f
"❌ 创建失败: HTTP {response.status_code}, 错误信息: {response.text}"
except
requests
.
exceptions
.
RequestException
as
e
:
return
f
"❌ 网络请求失败: {str(e)}"
except
json
.
JSONDecodeError
as
e
:
return
f
"❌ 响应解析失败: {str(e)}"
except
Exception
as
e
:
return
f
"❌ 创建D类运单失败: {str(e)}"
def
query_waybill_info
(
consignmentCode
:
str
,
Authorization
:
str
=
None
)
->
str
:
"""根据运单编号查询运单信息
...
...
langgraph_examples/api_agent.py
View file @
c315e53
...
...
@@ -12,8 +12,8 @@ from langchain_core.messages import AnyMessage
sys
.
path
.
append
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))))
# 导入 API 模块
from
API.waybill_api
import
query_waybill_list
,
create_waybill_d
,
push_waybill_for_ocr
,
query_waybill_info
from
API.paperless_api
import
upload_clearance_file
from
API.waybill_api
import
query_waybill_list
,
create_waybill_d
,
push_waybill_for_ocr
,
query_waybill_info
,
create_waybill_d_with_id
from
API.paperless_api
import
upload_clearance_file
,
upload_file_for_ocr
# 导入工具类
from
langgraph_examples.utils.message_processor
import
MessageProcessor
...
...
@@ -91,20 +91,28 @@ def pre_model_inspect_attachments(state, **kwargs):
return
{}
def
extract_token
(
state
:
Dict
[
str
,
Any
])
->
str
:
def
extract_token
(
state
:
Dict
[
str
,
Any
])
->
Dict
[
str
,
Any
]
:
"""
从 state 中提取
token
获取最后一个类型为 HumanMessage 或 human 的消息中的
token
从 state 中提取
参数
获取最后一个类型为 HumanMessage 或 human 的消息中的
参数(token、consignmentCode、consignmentId、loginName、userId)
Args:
state: LangGraph 状态字典,包含 messages 数组
Returns:
token 字符串,如果未找到则返回空字符串
包含所有参数的字典,如果未找到则返回空字符串或None
"""
result
=
{
"token"
:
""
,
"consignmentCode"
:
None
,
"consignmentId"
:
None
,
"loginName"
:
None
,
"userId"
:
None
}
messages
=
state
.
get
(
"messages"
,
[])
if
not
messages
:
return
""
return
result
# 找到所有 is_human 类型的消息
human_messages
=
[]
...
...
@@ -122,32 +130,45 @@ def extract_token(state: Dict[str, Any]) -> str:
# 如果没有 human 消息,直接返回
if
not
human_messages
:
return
""
return
result
# 直接取最后一个 human 消息(不需要循环判断)
last_human_msg
=
human_messages
[
-
1
]
# 从 content 中提取
token
# 从 content 中提取
参数
if
isinstance
(
last_human_msg
,
dict
):
content
=
last_human_msg
.
get
(
"content"
)
else
:
content
=
getattr
(
last_human_msg
,
"content"
,
None
)
if
isinstance
(
content
,
list
):
# content 是列表,遍历查找包含
token
的 part
# content 是列表,遍历查找包含
参数
的 part
for
part
in
content
:
if
isinstance
(
part
,
dict
)
and
"token"
in
part
:
token
=
part
.
get
(
"token"
)
if
token
:
return
token
if
isinstance
(
part
,
dict
):
if
"token"
in
part
and
part
.
get
(
"token"
):
result
[
"token"
]
=
part
.
get
(
"token"
)
if
"consignmentCode"
in
part
:
result
[
"consignmentCode"
]
=
part
.
get
(
"consignmentCode"
)
if
"consignmentId"
in
part
:
result
[
"consignmentId"
]
=
part
.
get
(
"consignmentId"
)
if
"loginName"
in
part
:
result
[
"loginName"
]
=
part
.
get
(
"loginName"
)
if
"userId"
in
part
:
result
[
"userId"
]
=
part
.
get
(
"userId"
)
elif
isinstance
(
content
,
dict
):
# content 是字典,直接获取 token
if
"token"
in
content
:
token
=
content
.
get
(
"token"
)
if
token
:
return
token
# content 是字典,直接获取参数
if
"token"
in
content
and
content
.
get
(
"token"
):
result
[
"token"
]
=
content
.
get
(
"token"
)
if
"consignmentCode"
in
content
:
result
[
"consignmentCode"
]
=
content
.
get
(
"consignmentCode"
)
if
"consignmentId"
in
content
:
result
[
"consignmentId"
]
=
content
.
get
(
"consignmentId"
)
if
"loginName"
in
content
:
result
[
"loginName"
]
=
content
.
get
(
"loginName"
)
if
"userId"
in
content
:
result
[
"userId"
]
=
content
.
get
(
"userId"
)
return
""
return
result
def
_create_system_prompt
(
state
:
Dict
[
str
,
Any
],
config
:
RunnableConfig
)
->
List
[
AnyMessage
]:
"""
...
...
@@ -166,14 +187,23 @@ def _create_system_prompt(state: Dict[str, Any], config: RunnableConfig) -> List
# print(f"state keys: {list(state.keys()) if isinstance(state, dict) else 'not a dict'}")
# 从 state 中提取动态参数
token
=
extract_token
(
state
)
params
=
extract_token
(
state
)
token
=
params
.
get
(
"token"
,
""
)
consignmentCode
=
params
.
get
(
"consignmentCode"
)
consignmentId
=
params
.
get
(
"consignmentId"
)
loginName
=
params
.
get
(
"loginName"
)
userId
=
params
.
get
(
"userId"
)
# 如果从 state 中提取的 token 为空,则从 api_config.py 中获取 Authorization 作为备选
if
not
token
:
from
API.api_config
import
API_CONFIG
token
=
API_CONFIG
.
get
(
"headers"
,
{})
.
get
(
"Authorization"
,
""
)
#
# print(f"提取到的 token: {token[:30] if token else 'None'}...")
# 格式化参数值用于提示词显示
consignmentCode_str
=
str
(
consignmentCode
)
if
consignmentCode
is
not
None
else
"无"
consignmentId_str
=
str
(
consignmentId
)
if
consignmentId
is
not
None
else
"无"
loginName_str
=
str
(
loginName
)
if
loginName
is
not
None
else
"无"
userId_str
=
str
(
userId
)
if
userId
is
not
None
else
"无"
# 创建系统提示词(使用 f-string 以便插入 token)
system_msg
=
f
"""你是一个专业的出口物流系统智能助手,专门帮助用户处理运单相关的业务操作。
...
...
@@ -183,9 +213,16 @@ def _create_system_prompt(state: Dict[str, Any], config: RunnableConfig) -> List
- **查询运单列表**:当用户查询运单但不提供运单号时,使用 query_waybill_list 查询运单列表,支持按状态、时间等条件筛选,结果以JSON形式展示,AI不用对返回数据JSON进行加工
- **查询单个运单详情**:当用户提供运单号查询时,使用 query_waybill_info 查询该运单的详细信息(包括运单基本信息、公司信息、商品明细等),返回格式与 create_waybill_d 相同
2. **运单创建**:协助用户创建D类运单,确保信息完整准确
- **重要说明**:当用户输入"创建运单"或"申报"时,都理解为"创建运单"操作,应调用 create_waybill_d 工具
3. **业务咨询**:解答用户关于出口物流流程、运单状态、操作规范等问题
4. **您当前访问工具Authorization的传参为 Authorization= {token}
- **优先级规则**:当用户输入"创建运单"或"申报"时,首先检查提示词中是否已有 consignmentCode、consignmentId、loginName、userId 且都不为None
- **如果提示词中有这些参数且都不为None**:必须使用 create_waybill_d_with_id 工具创建运单,参数从提示词中获取
- **如果提示词中没有这些参数或为None**:使用 create_waybill_d 工具,运单编号需要从用户输入中获取
- **create_waybill_d**:根据运单号创建D类运单,运单编号需要从用户输入中获取,不是从提示词中获取。仅在提示词中没有 consignmentCode、consignmentId、loginName、userId 或这些参数为None时使用
- **create_waybill_d_with_id**:根据运单ID及运单号创建D类运单,参数从提示词中获取(consignmentCode、consignmentId、loginName、userId),不需要用户再次输入。当提示词中这些参数齐全且都不为None时,必须使用此工具
3. **文件上传**:根据用户需求上传文件
- **上传清关文件**:当用户需要上传清关文件并关联运单信息(code、slip_id)时,使用 upload_clearance_file 工具
- **上传文件进行OCR识别**:当用户只有一个上传文件地址,需要直接进行OCR识别时,使用 upload_file_for_ocr 工具
4. **业务咨询**:解答用户关于出口物流流程、运单状态、操作规范等问题
5. **您当前访问工具Authorization的传参为 Authorization= {token}
## 工作原则:
- 上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,不用确认文件索引,工具会自动设置索引为0,请直接调用工具接口
...
...
@@ -203,8 +240,17 @@ def _create_system_prompt(state: Dict[str, Any], config: RunnableConfig) -> List
- query_waybill_info: 根据运单编号查询单个运单的详细信息(包括运单基本信息、公司信息、商品明细等),需要提供参数(运单编号、Authorization),返回格式与 create_waybill_d 相同
- **使用场景**:当用户提供运单号查询运单详情时使用此工具
- create_waybill_d: 根据运单号创建D类运单,需要提供参数(运单号、Authorization)
- **注意**:当用户说"创建运单"或"申报"时,都应调用此工具
- **使用场景**:当用户输入"创建运单"或"申报"时,仅在提示词中没有 consignmentCode、consignmentId、loginName、userId 或这些参数为None时使用此工具
- **重要说明**:运单编号需要从用户输入中获取,不是从提示词中获取
- **优先级**:如果提示词中已有 consignmentCode、consignmentId、loginName、userId 且都不为None,则不应使用此工具,应使用 create_waybill_d_with_id
- create_waybill_d_with_id: 根据运单ID及运单号创建D类运单,需要提供参数(运单编号、运单ID、登录名、用户ID、Authorization)
- **使用场景**:当用户输入"创建运单"或"申报"时,如果提示词中已经有了 consignmentCode、consignmentId、loginName、userId,并且这几个参数都不为None,那么必须调用此工具创建运单
- **重要说明**:此工具的参数从提示词中获取,不需要用户再次输入。当提示词中这些参数齐全且都不为None时,必须优先使用此工具,而不是 create_waybill_d
- **可用参数**:您当前访问工具 create_waybill_d_with_id 的传参为 consignmentCode={consignmentCode_str}、consignmentId={consignmentId_str}、loginName={loginName_str}、userId={userId_str},如果参数有值则使用,没有值则不使用
- upload_clearance_file: 上传清关PDF文件,需要 code、slip_id、pdf_path、Authorization,上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,不用确认文件索引,工具会自动设置索引为0,请直接调用工具接口
- **使用场景**:当用户需要上传清关文件并关联运单信息(code、slip_id)时使用此工具
- upload_file_for_ocr: 上传文件进行OCR识别,只需要提供文件路径(pdf_path、Authorization),上传时文件路径不用确认,路径肯定是完整的,文件肯定是存在的,请直接调用工具接口
- **使用场景**:当用户只有一个上传文件地址,需要直接进行OCR识别时,使用此工具
- push_waybill_for_ocr: 根据运单ID推送OCR进行识别,需要提供运单ID(waybill_id、Authorization)参数
## query_waybill_list数据展示说明:
...
...
@@ -219,6 +265,12 @@ def _create_system_prompt(state: Dict[str, Any], config: RunnableConfig) -> List
## create_waybill_d数据展示说明:
- 按照数据返回的原本格式进行展示,不得增删前后缀或改写内容。
## create_waybill_d_with_id数据展示说明:
- 按照数据返回的原本格式进行展示,不得增删前后缀或改写内容。
## upload_file_for_ocr数据展示说明:
- 按照数据返回的原本格式进行展示,不得增删前后缀或改写内容。
请根据用户的具体需求,选择合适的工具并提供帮助。"""
# 返回系统消息 + 原始消息
...
...
@@ -231,7 +283,7 @@ def _create_system_prompt(state: Dict[str, Any], config: RunnableConfig) -> List
# 创建 ReAct 智能体
agent
=
create_react_agent
(
model
=
model
,
tools
=
[
query_waybill_list
,
query_waybill_info
,
create_waybill_d
,
upload_clearance_file
,
push_waybill_for_ocr
],
tools
=
[
query_waybill_list
,
query_waybill_info
,
create_waybill_d
,
create_waybill_d_with_id
,
upload_clearance_file
,
upload_file_for_ocr
,
push_waybill_for_ocr
],
pre_model_hook
=
pre_model_inspect_attachments
,
prompt
=
_create_system_prompt
,
)
...
...
Please
register
or
login
to post a comment