This commit is contained in:
parent
6624427528
commit
f289955974
|
|
@ -294,10 +294,12 @@ def use_guidance() -> rx.Component:
|
|||
font_weight="var(--prismui-font-weight-3)",
|
||||
),
|
||||
rx.hstack(
|
||||
# 预定航班按钮
|
||||
# 工作流按钮:预定航班
|
||||
workflow_button(WorkflowType.BOOK_FLIGHT),
|
||||
# 银行客服按钮
|
||||
# 工作流按钮:银行客户服务
|
||||
workflow_button(WorkflowType.BANK_CUSTOMER_SERVICE),
|
||||
# 工作流按钮:研究
|
||||
workflow_button(WorkflowType.RESEARCH),
|
||||
flex_wrap="wrap",
|
||||
align_items="center",
|
||||
gap="12px",
|
||||
|
|
@ -408,7 +410,6 @@ def thinking(item: MessageHistoryItem) -> rx.Component:
|
|||
rx.markdown(
|
||||
item.content,
|
||||
width="100%",
|
||||
margin="4px 0",
|
||||
line_height="1.6",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-3)",
|
||||
|
|
|
|||
|
|
@ -109,10 +109,11 @@ class ConversationState(rx.State):
|
|||
# 当前会话
|
||||
conversation = self.conversations[self.conversation_id]
|
||||
# 最后一个消息
|
||||
message = next(reversed(conversation.messages.values()))
|
||||
message = next(iter(reversed(conversation.messages.values())), None)
|
||||
# 若最后一个消息包含审批且审批结果为 None 则将会话正在运行设置为是、消息正在运行设置为是
|
||||
if (
|
||||
isinstance(approval := message.approval, Approval)
|
||||
message is not None
|
||||
and isinstance(approval := message.approval, Approval)
|
||||
and approval.result is None
|
||||
):
|
||||
conversation.is_running = True
|
||||
|
|
@ -396,8 +397,21 @@ class ConversationState(rx.State):
|
|||
usage=usage,
|
||||
)
|
||||
|
||||
# 消息列表
|
||||
messages: list[Message] = []
|
||||
# 研究
|
||||
case WorkflowType.RESEARCH:
|
||||
from application.workshop.research import (
|
||||
run_stream_events,
|
||||
)
|
||||
|
||||
# 运行并流式输出事件
|
||||
stream_events = run_stream_events(
|
||||
user_prompt=user_prompt,
|
||||
message_history=message_history,
|
||||
usage=usage,
|
||||
)
|
||||
|
||||
messages: list[Message] = [] # 消息列表
|
||||
previous_provider_details = {} # 上一个供应商详情
|
||||
# 获取运行流式输出事件
|
||||
async for event in stream_events:
|
||||
# 若等待流式输出则将等待流式输出设置为否
|
||||
|
|
@ -459,26 +473,47 @@ class ConversationState(rx.State):
|
|||
|
||||
# ========== 增量事件 ==========
|
||||
case PartDeltaEvent(index=index, delta=delta):
|
||||
# 获取消息
|
||||
match delta:
|
||||
# 思考分片增量
|
||||
case ThinkingPartDelta(
|
||||
content_delta=content_delta,
|
||||
provider_details=provider_details,
|
||||
):
|
||||
# 若内容增量不为空则增量更新
|
||||
if content_delta:
|
||||
conversation.messages[
|
||||
message = conversation.messages[
|
||||
index_map_to_message_id[index]
|
||||
].content += content_delta
|
||||
]
|
||||
# 若内容增量非空则将内容追加至消息内容,否则若供应商详情可调用更新消息内容
|
||||
if content_delta:
|
||||
message.content += content_delta
|
||||
yield # 通知前端更新渲染
|
||||
|
||||
else:
|
||||
if callable(provider_details):
|
||||
# 获取原始内容
|
||||
raw_content = (
|
||||
previous_provider_details := provider_details(
|
||||
previous_provider_details
|
||||
)
|
||||
).get("raw_content")
|
||||
if isinstance(raw_content, str):
|
||||
...
|
||||
elif isinstance(raw_content, list):
|
||||
raw_content = "".join(raw_content)
|
||||
else:
|
||||
continue
|
||||
message.content = raw_content
|
||||
yield # 通知前端更新渲染
|
||||
|
||||
# 文本分片增量
|
||||
case TextPartDelta(
|
||||
content_delta=content_delta,
|
||||
):
|
||||
# 增量更新
|
||||
conversation.messages[
|
||||
message = conversation.messages[
|
||||
index_map_to_message_id[index]
|
||||
].content += content_delta
|
||||
]
|
||||
# 增量更新
|
||||
message.content += content_delta
|
||||
yield # 通知前端更新渲染
|
||||
|
||||
# ========== 结束事件 ==========
|
||||
|
|
@ -677,6 +712,12 @@ class ConversationState(rx.State):
|
|||
# 设置用户提示词
|
||||
self.set_user_prompt(f"帮我查询账户余额")
|
||||
|
||||
# 研究
|
||||
case WorkflowType.RESEARCH:
|
||||
# 初始化研究工作流
|
||||
workflow = Workflow(type=type)
|
||||
|
||||
# 其他
|
||||
case _:
|
||||
# 初始化对话工作流
|
||||
workflow = Workflow()
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class WorkflowType(StrEnum):
|
|||
TALK = "对话"
|
||||
BOOK_FLIGHT = "预定航班"
|
||||
BANK_CUSTOMER_SERVICE = "银行客户服务"
|
||||
RESEARCH = "研究"
|
||||
|
||||
|
||||
class Workflow(BaseModel):
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ from pydantic_ai.messages import AgentStreamEvent
|
|||
from pydantic_ai.run import AgentRunResultEvent
|
||||
|
||||
from application.workshop.models import (
|
||||
DEEPSEEK_V4_FLASH_MODEL,
|
||||
DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||
DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -75,8 +75,8 @@ async def query_account_balance(ctx: RunContext[Deps]) -> str:
|
|||
|
||||
# 智能体
|
||||
agent = Agent[Deps, str](
|
||||
model=DEEPSEEK_V4_FLASH_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||
model=DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS,
|
||||
deps_type=Deps,
|
||||
output_type=str, # 若输出类型为 str,则智能体输出结果时不进行输出类型校验。若定义输出类型,需考虑处理相应事件
|
||||
instructions="你是我方银行的客服智能体,为客户提供客服应答。", # 优先使用 instructions
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ from pydantic_ai.run import AgentRunResultEvent, AgentRunResult
|
|||
|
||||
from application.workshop.events import AgentRuntimeEvent
|
||||
from application.workshop.models import (
|
||||
DEEPSEEK_V4_FLASH_MODEL,
|
||||
DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||
DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ class Deps(BaseModel):
|
|||
|
||||
# 主智能体
|
||||
agent = Agent[Deps, Flight | DeferredToolRequests](
|
||||
model=DEEPSEEK_V4_FLASH_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||
model=DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS,
|
||||
deps_type=Deps,
|
||||
output_type=Flight | DeferredToolRequests,
|
||||
system_prompt=(
|
||||
|
|
@ -87,8 +87,8 @@ agent = Agent[Deps, Flight | DeferredToolRequests](
|
|||
|
||||
# 提取所有航班信息智能体
|
||||
extraction_flights_agent = Agent[Deps, list[Flight]](
|
||||
model=DEEPSEEK_V4_FLASH_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||
model=DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS,
|
||||
deps_type=Deps,
|
||||
output_type=list[Flight],
|
||||
system_prompt=(
|
||||
|
|
@ -101,8 +101,8 @@ extraction_flights_agent = Agent[Deps, list[Flight]](
|
|||
|
||||
# 提取航班号智能体
|
||||
extraction_flight_number_agent = Agent[Deps, str | None](
|
||||
model=DEEPSEEK_V4_FLASH_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||
model=DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
model_settings=DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS,
|
||||
deps_type=Deps,
|
||||
output_type=str | None,
|
||||
system_prompt=(
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
智能体模型模块
|
||||
"""
|
||||
from pydantic_ai import ModelSettings
|
||||
from pydantic_ai.models.openai import OpenAIChatModel
|
||||
from pydantic_ai.models.openai import OpenAIChatModel, OpenAIResponsesModel
|
||||
from pydantic_ai.providers.openai import OpenAIProvider
|
||||
|
||||
DEEPSEEK_V4_FLASH_MODEL = OpenAIChatModel(
|
||||
DEEPSEEK_V4_FLASH_CHAT_MODEL = OpenAIChatModel(
|
||||
model_name="deepseek-v4-flash",
|
||||
provider=OpenAIProvider(
|
||||
base_url="https://tokenhub.tencentmaas.com/v1",
|
||||
|
|
@ -14,6 +14,16 @@ DEEPSEEK_V4_FLASH_MODEL = OpenAIChatModel(
|
|||
),
|
||||
)
|
||||
|
||||
DEEPSEEK_V4_FLASH_MODEL_SETTINGS = ModelSettings(
|
||||
DEEPSEEK_V4_FLASH_CHAT_MODEL_SETTINGS = ModelSettings(
|
||||
temperature=0, extra_body={"thinking": {"type": "disabled"}} # 温度控制
|
||||
) # 不启用思考模式
|
||||
|
||||
DEEPSEEK_V4_FLASH_RESPONSE_MODEL = OpenAIResponsesModel(
|
||||
model_name="deepseek/deepseek-flash",
|
||||
provider=OpenAIProvider(
|
||||
base_url="https://tokenhub.tencentmaas.com/v1",
|
||||
api_key="sk-D9Y1mCe8VlvNqLuSC4mAjqEwxJ2nW4C0h8a7EPn8kg9RLsHq",
|
||||
),
|
||||
)
|
||||
|
||||
"sk-4a79ec5c0af74680997ef56758604850"
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
研究
|
||||
"""
|
||||
from typing import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_ai import Agent, ModelMessage, RunContext, RunUsage
|
||||
from pydantic_ai.messages import AgentStreamEvent
|
||||
from pydantic_ai.run import AgentRunResultEvent
|
||||
from pydantic_ai.toolsets import FunctionToolset
|
||||
|
||||
from application.workshop.models import DEEPSEEK_V4_FLASH_RESPONSE_MODEL
|
||||
|
||||
|
||||
class Args(BaseModel):
|
||||
query: str = Field(description="查询语句")
|
||||
|
||||
|
||||
# 实例化工具集
|
||||
bocha_web_search = FunctionToolset()
|
||||
|
||||
|
||||
@bocha_web_search.tool_plain() # 普通工具:不使用运行上下文
|
||||
async def web_search(args: Args) -> str:
|
||||
"""
|
||||
联网搜索工具:
|
||||
用于查询互联网实时信息、新闻、政策、行业报告、企业公告、最新数据。
|
||||
适合查询模型静态知识库不存在的外部网页信息。
|
||||
回答时必须标注引用来源URL,禁止编造链接。
|
||||
不要用于简单常识、数学计算、代码编写等无需联网的任务。
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=60) as client:
|
||||
response = await client.post(
|
||||
"https://api.bocha.cn/v1/web-search",
|
||||
headers={
|
||||
"Authorization": f"Bearer sk-a9ed73d8b26745799cf970f84d70f512",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={
|
||||
"query": args.query,
|
||||
"summary": True, # 显示文本摘要
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
# 解析响应载荷
|
||||
response_payload = response.json()
|
||||
|
||||
except httpx.HTTPStatusError as exception:
|
||||
return f"请求错误 {exception.response.status_code}: {exception.response.text}"
|
||||
|
||||
except Exception as exception:
|
||||
return f"请求异常: {str(exception)}"
|
||||
|
||||
if (code := response_payload.get("code")) != 200:
|
||||
return f"业务错误 {code}: {response_payload.get("msg", "未知业务错误")}"
|
||||
|
||||
web_pages = response_payload.get("data", {}).get("webPages", {}).get("value", [])
|
||||
if not web_pages:
|
||||
return "未搜索到相关网页"
|
||||
|
||||
result = [] # 搜索结果
|
||||
for idx, web_page in enumerate(web_pages, 1):
|
||||
# 解析网页的标题
|
||||
name = web_page.get("name", "")
|
||||
# 解析网页的URL
|
||||
url = web_page.get("url", "")
|
||||
# 解析网页的网站名称
|
||||
site_name = web_page.get("siteName", "")
|
||||
# 解析网页的发布时间
|
||||
date_published = web_page.get("datePublished") or web_page.get(
|
||||
"dateLastCrawled", ""
|
||||
)
|
||||
# 优先解析网页内容的文本摘要,若文本摘要为空则解析简短描述
|
||||
summary = web_page.get("summary") or web_page.get("snippet", "")
|
||||
|
||||
result.append(
|
||||
f"### 搜索结果 {idx}\n"
|
||||
f"- 标题:{name}\n"
|
||||
f"- URL:{url}\n"
|
||||
f"- 来源网站:{site_name}\n"
|
||||
f"- 发布时间:{date_published}\n"
|
||||
f"- 摘要:{summary}"
|
||||
)
|
||||
return "\n\n".join(result)
|
||||
|
||||
|
||||
instruction = """
|
||||
你是研究助手,可调用联网搜索工具。
|
||||
1. 事实、时效、外部资料类问题必须搜索;静态常识可直接回答,支持多次检索、改写query。
|
||||
2. 优先采用一手资料、官方权威信源,慎用自媒体与转载内容。
|
||||
3. 所有事实描述必须标注来源链接,引用格式:`[参考{序号}] {URL}`,序号匹配搜索结果编号。
|
||||
4. 严格区分:【事实】来自网页原文;【推论】基于事实推导;资料不足时标注【推测】,不可当作事实。
|
||||
5. 严禁编造链接和事实;证据不足直接说明,不要强行下结论。
|
||||
6. 组织回答:先陈列证据,再总结结论,结论不能超出资料支撑范围。
|
||||
"""
|
||||
|
||||
# 智能体
|
||||
agent = Agent(
|
||||
model=DEEPSEEK_V4_FLASH_RESPONSE_MODEL,
|
||||
output_type=str, # 若输出类型为 str,则智能体输出结果时不进行输出类型校验。若定义输出类型,需考虑处理相应事件
|
||||
instructions=instruction,
|
||||
toolsets=[bocha_web_search],
|
||||
)
|
||||
|
||||
|
||||
async def run_stream_events(
|
||||
user_prompt: str | None,
|
||||
message_history: list[ModelMessage],
|
||||
usage: RunUsage,
|
||||
) -> AsyncGenerator[AgentStreamEvent | AgentRunResultEvent, None]:
|
||||
"""
|
||||
运行并流式输出事件
|
||||
:param user_prompt: 用户提示词
|
||||
:param message_history: 消息历史
|
||||
:param usage: 使用量
|
||||
:return: AsyncGenerator
|
||||
"""
|
||||
async with agent.run_stream_events(
|
||||
user_prompt=user_prompt,
|
||||
message_history=message_history,
|
||||
usage=usage,
|
||||
) as events:
|
||||
async for event in events:
|
||||
print(event)
|
||||
yield event
|
||||
|
|
@ -8,7 +8,7 @@ from pydantic_ai import Agent, ModelMessage, RunUsage
|
|||
from pydantic_ai.messages import AgentStreamEvent
|
||||
from pydantic_ai.run import AgentRunResultEvent
|
||||
|
||||
from application.workshop.models import DEEPSEEK_V4_FLASH_MODEL
|
||||
from application.workshop.models import DEEPSEEK_V4_FLASH_CHAT_MODEL
|
||||
|
||||
|
||||
instruction = """
|
||||
|
|
@ -28,7 +28,7 @@ instruction = """
|
|||
"""
|
||||
|
||||
agent = Agent(
|
||||
model=DEEPSEEK_V4_FLASH_MODEL,
|
||||
model=DEEPSEEK_V4_FLASH_CHAT_MODEL,
|
||||
instructions=instruction,
|
||||
)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue