#Vector Search

0 关注者 · 15 帖子

向量搜索是一种用于信息检索和机器学习的方法,它根据作为向量的数学表示来查找相似的条目。 在这种方法中,每个条目被表示为一个高维向量,每个维度对应于该条目的一个特征或特性。 然后,向量搜索算法比较这些向量,以找到相似条目,例如具有相似特征或在向量空间中靠得很近的条目。 点击这里阅读更多内容

文章 Lilian Huang · 十月 24, 2025 14m read

学习如何使用 LangGraph 设计结合了推理、矢量搜索和工具集成的可扩缩自主 AI 智能体。

cover

概括

  • AI 智能体是一种超越简单的聊天机器人的自主系统,它结合了记忆库、上下文,并具有自动完成任务的主动性。
  • LangGraph 是一种框架,它使我们能够利用具有内置状态管理的节点(任务)和边缘(连接),构建复杂的 AI 工作流。
  • 本指南将指导您构建 AI 赋能的客户支持智能体,该智能体可以划分优先级,识别相关主题,并确定是上报还是自动回复。

那么,AI 智能体究竟是什么?

让我们直面它吧 —“AI 智能体”听起来就像可以接管会议室的机器人。 实际上,它们是您得力的助手,可以简化复杂的工作流,消除重复性任务。 您可以把它们看作是聊天机器人的下一个进化阶段:它们不只是简单地等待提示;它们可以发起行动,协调多个步骤,并随时进行调整。

过去,打造一个“智能”系统意味着兼顾语言理解、代码生成、数据查找等各种不同的模型,然后将它们粘合在一起。 您的一半时间花在了集成上,另一半时间则花在了调试上。

智能体彻底颠覆了这一切。 它们将上下文、主动性和适应性融合在一个精心编排的流程中。 它们不仅实现了自动化,更是肩负使命的智者。 借助 LangGraph 之类的框架,我相信,组建一支自己的智能体团队实际上会很有趣。

image

LangGraph 究竟是什么?

LangGraph 是一种创新型框架,它彻底改变了我们构建涉及大语言模型 (LLM) 的复杂应用程序的方式。

想象一下,您正在指挥一支管弦乐队:每种乐器(或“节点”)都需要知道何时演奏,声音有多大,顺序如何。 这种情况下,LangGraph 就是您的指挥棒,为您提供以下内容:

  • 图结构:它采用具有节点和边缘的图结构,使开发者能够设计适应各种分支和循环的灵活非线性工作流。 它可以反映复杂的决策过程,类似于神经通路的运作方式。
  • 状态管理:LangGraph 提供了各种内置工具,可以实现状态保持和错误恢复,简化了应用程序中各个阶段的上下文数据的维护。 借助 Zep 等工具,它可以在短期记忆和长期记忆之间高效切换,提高了交互质量。
  • 工具集成:借助 LangGraph,LLM 智能体可以轻松与外部服务或数据库协作,获取真实的数据,从而改进应用程序的功能和响应性。
  • 人机协同:除了可以实现自动化外,LangGraph 还可以适应工作流中的人为干预,这对于需要分析监督或伦理考虑的决策过程至关重要。

无论您是在构建具有真实记忆的聊天机器人、交互式故事引擎,还是能够处理复杂问题的智能体团队,LangGraph 都可以将令人头疼的管道工程转变成简单明了、直观的状态机。

开始

要开始使用 LangGraph,您需要进行基本的设置,通常包括安装 langgraph 和 langchain-openai 等必要的库。 然后,您可以定义图中的节点(任务)和边缘(连接),有效地实现短期记忆的检查点,并利用 Zep 满足更持久的记忆需求。

操作 LangGraph 时,请记住以下几点:

  • 具有灵活性的设计:利用强大的图结构来解释并非严格线性的潜在工作流分支和交互。
  • 以深思熟虑的方式与工具交互:利用外部工具增强 LLM 功能,而不是取而代之。 为每个工具提供全面的描述,以实现精确使用。
  • 采用丰富的记忆解决方案:高效地使用记忆库,留意 LLM 的上下文窗口,并考虑集成外部解决方案,以实现自动事实管理。

现在,我们已经介绍 LangGraph 的基础知识,我们来看一个实例。 为此,我们将开发一个用于提供客户支持的 AI 智能体。

这个智能体将接收电子邮件请求,分析电子邮件正文中的问题描述,然后确定请求的优先级和适当的主题/类别/部门。

系好安全带,我们开始吧!

buckle up

首先,我们需要定义什么是“工具”。 可以把它看作是智能体的专属“助理”,使其能够与外部功能进行交互。

@tool 装饰器在这里必不可少。 LangChain 简化了自定义工具的创建,这意味着首先,定义一个 Python 函数,然后应用 @tool 装饰器。

tools

为了进行说明,我们来创建第一个工具。 这个工具将帮助智能体根据电子邮件内容划分 IT 支持工单的优先级:

    from langchain_core.tools import tool
    
    @tool
    def classify_priority(email_body: str) -> str:
        """Classify the priority of an IT support ticket based on email content."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and classify its priority as High, Medium, or Low.
            
            High: System outages, security breaches, critical business functions down
            Medium: Non-critical issues affecting productivity, software problems
            Low: General questions, requests, minor issues
            
            Email: {email}
            
            Respond with only: High, Medium, or Low"""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

太棒了! 现在,我们有一个提示,指示 AI 接收电子邮件正文,对其进行分析,并将其优先级分为“高”、“中”或“低”。

就是这样! 您刚刚编写了一个智能体可以调用的工具!

接下来,我们创建一个类似的工具来识别支持请求的主要主题(或类别):


    @tool
    def identify_topic(email_body: str) -> str:
        """Identify the main topic/category of the IT support request."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and identify the main topic category.
            
            Categories: password_reset, vpn, software_request, hardware, email, network, printer, other
            
            Email: {email}
            
            Respond with only the category name (lowercase with underscores)."""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

现在,我们需要创建一个状态,在 LangGraph 中,这一小部分非常重要。

把它想象成图的中枢神经系统。 这就是节点之间的通信方式,就像优等生在课堂上传递纸条一样。

文档中显示:

“状态是表示应用程序当前快照的共享数据结构。”

在实践中呢? 状态是在节点之间移动的结构化消息。 它将一个步骤的输出作为下一个步骤的输入。 基本上,它是将整个工作流粘合在一起的粘合剂。

因此,在构建图之前,我们必须先定义我们的状态结构。 本例中,我们的状态包括以下内容:

  • 用户请求(电子邮件正文)
  • 指定的优先级
  • 确定的主题(类别)

它简单明了,因此您可以像专业人士一样浏览图。

    from typing import TypedDict

    # Define the state structure
    class TicketState(TypedDict):
        email_body: str
        priority: str
        topic: str
        
    
    # Initialize state
    initial_state = TicketState(
        email_body=email_body,
        priority="",
        topic=""
    )

节点与 边缘:LangGraph 的关键组成部分

LangGraph 的基本要素包括节点边缘

  • 节点:它们是图中的操作单元,执行实际工作。 节点通常由可以执行任何逻辑(从计算到与语言模型 (LLM) 或外部集成交互)的 Python 代码组成。 从本质上讲,节点就像传统编程中的个别函数或智能体。
  • 边缘:边缘定义节点之间的执行流,决定接下来会发生什么。 它们充当连接器,允许状态根据预定义条件从一个节点转换到另一个节点。 在 LangGraph 中,边缘在协调节点之间的序列和决策流方面至关重要。

为了掌握边缘的功能,我们来看一个消息传递应用程序的简单类比:

  • 节点类似于积极参与对话的用户(或他们的设备)。
  • 边缘代表着用户之间能够促进沟通的聊天主题或连接。

当用户选择一个聊天主题来发送消息时,会有效地创建一个边缘,将他们与另一个用户连接在一起。 与 LangGraph 状态的结构化模式类似,每次交互(无论是发送文本、语音还是视频消息)都遵循预定义的顺序。 它确保了沿边缘传递的数据的一致性和可解释性。

不同于事件驱动型应用程序的动态特性,LangGraph 采用在整个执行过程中保持一致的静态模式。 它简化了节点之间的通信,使开发者可以依赖稳定的状态格式,从而确保无缝的边缘通信。

设计基本工作流

可以将 LangGraph 中的流工程理解为设计一个状态机。 在这个情境中,每个节点代表一个不同的状态或处理步骤,而边缘定义了这些状态之间的转换。 这种方式对想要在 AI 的确定性任务序列与动态决策能力之间取得平衡的开发者特别有用。 我们来使用前面定义的 TicketState 类初始化 StateGraph,开始构建流程。

    from langgraph.graph import StateGraph, START, END
    
    workflow = StateGraph(TicketState)

节点添加:节点是基本要素,用于执行划分工单优先级或识别其主题等特定任务。

每个节点函数均接收当前状态,执行其操作,并返回一个字典以更新状态:

   def classify_priority_node(state: TicketState) -> TicketState:
        """Node to classify ticket priority."""
        priority = classify_priority.invoke({"email_body": state["email_body"]})
        return {"priority": priority}

    def identify_topic_node(state: TicketState) -> TicketState:
        """Node to identify ticket topic."""
        topic = identify_topic.invoke({"email_body": state["email_body"]})
        return {"topic": topic}
        
        
    workflow.add_node("classify_priority", classify_priority_node)
    workflow.add_node("identify_topic", identify_topic_node)

classify_priority_node 和 identify_topic_node 方法将更改 TicketState 并发送参数输入。

边缘创建:定义连接节点的边缘:


    workflow.add_edge(START, "classify_priority")
    workflow.add_edge("classify_priority", "identify_topic")
    workflow.add_edge("identify_topic", END)

classify_priority 确定起点,而 identify_topic 确定到目前为止工作流的终点。

编译与执行:配置节点和边缘后,编译并执行该工作流。


    graph = workflow.compile()
    result = graph.invoke(initial_state)

太好了! 您还可以生成 LangGraph 流的可视化表示。

graph.get_graph().draw_mermaid_png(output_file_path="graph.png")

如果将代码运行到此点,您就会看到一个与下面类似的图:

first_graph.png

这幅图直观地显示了一次顺序执行:开始,然后划分优先级,接着确定主题,最后结束。

LangGraph 最强大的一个方面是它的灵活性,这让我们可以创建更复杂的流程和应用程序。 例如,我们可以修改工作流,使用以下行将 START 中的边缘添加到两个节点:

    workflow.add_edge(START, "classify_priority")
    workflow.add_edge(START, "identify_topic")

这一更改意味着智能体将同时执行 classify_priority 和 identify_topic。

LangGraph 中另一个非常有用的功能是能够使用条件边缘。 它们允许工作流根据对当前状态的评估进行分支,实现任务的动态路由。

我们来增强工作流。 我们将创建一个新工具,分析请求的内容、优先级和主题,以确定它是否为需要上报(例如,为人工团队提交工单)的高优先级问题。 如果不需要,将为用户生成一个自动响应。


    @tool
    def make_escalation_decision(email_body: str, priority: str, topic: str) -> str:
        """Decide whether to auto-respond or escalate to IT team."""
        prompt = ChatPromptTemplate.from_template(
            """Based on this IT support ticket, decide whether to:
            - "auto_respond": Send an automated response for simple/common or medium priority issues
            - "escalate": Escalate to the IT team for complex/urgent issues
            
            Email: {email}
            Priority: {priority}
            Topic: {topic}
            
            Consider: High priority items usually require escalation, while complex technical issues necessitate human review.
            
            Respond with only: auto_respond or escalate"""
        )
        chain = prompt | llm
        response = chain.invoke({
            "email": email_body,
            "priority": priority,
            "topic": topic
        })
        return response.content.strip()
        

此外,如果请求被确定为低优先级或中等优先级(导致“auto_respond”决策),我们将执行矢量搜索来检索历史回答。 然后,将使用此信息来生成适当的自动回答。 不过,这需要两个额外的工具:


    @tool
    def retrieve_examples(email_body: str) -> str:
        """Retrieve relevant examples from past responses based on email_body."""
        try:
            examples = iris.cls(__name__).Retrieve(email_body)
            return examples if examples else "No relevant examples found."
        except:
            return "No relevant examples found."

    @tool
    def generate_reply(email_body: str, topic: str, examples: str) -> str:
        """Generate a suggested reply based on the email, topic, and RAG examples."""
        prompt = ChatPromptTemplate.from_template(
            """Generate a professional IT support response based on:
            
            Original Email: {email}
            Topic Category: {topic}
            Example Response: {examples}
            
            Create a helpful, professional response that addresses the user's concern.
            Keep it concise and actionable."""
        )
        chain = prompt | llm
        response = chain.invoke({
            "email": email_body,
            "topic": topic,
            "examples": examples
        })
        return response.content.strip()

现在,我们为这些新工具定义相应的节点:

    
    def decision_node(state: TicketState) -> TicketState:
        """Node to decide on escalation or auto-response."""
        decision = make_escalation_decision.invoke({
            "email_body": state["email_body"],
            "priority": state["priority"],
            "topic": state["topic"]
        })
        return {"decision": decision}
        
    
    def rag_node(state: TicketState) -> TicketState:
        """Node to retrieve relevant examples using RAG."""
        examples = retrieve_examples.invoke({"email_body": state["email_body"]})
        return {"rag_examples": examples}

    def generate_reply_node(state: TicketState) -> TicketState:
        """Node to generate suggested reply."""
        reply = generate_reply.invoke({
            "email_body": state["email_body"],
            "topic": state["topic"],
            "examples": state["rag_examples"]
        })
        return {"suggested_reply": reply}
        
    
    def execute_action_node(state: TicketState) -> TicketState:
        """Node to execute final action based on decision."""
        if state["decision"] == "escalate":
            action = f"🚨 ESCALATED TO IT TEAM\nPriority: {state['priority']}\nTopic: {state['topic']}\nTicket created in system."
            print(f"[SYSTEM] Escalating ticket to IT team - Priority: {state['priority']}, Topic: {state['topic']}")
        else:
            action = f"✅ AUTO-RESPONSE SENT\nReply: {state['suggested_reply']}\nTicket logged for tracking."
            print(f"[SYSTEM] Auto-response sent to user - Topic: {state['topic']}")
        
        return {"final_action": action}
        
        
        
    workflow.add_node("make_decision", decision_node)
    workflow.add_node("rag", rag_node)
    workflow.add_node("generate_reply", generate_reply_node)
    workflow.add_node("execute_action", execute_action_node)

然后,条件边缘将使用 make_decision 节点的输出来定向流:

    workflow.add_conditional_edges(
        "make_decision",
        lambda x: x.get("decision"),
        {
            "auto_respond": "rag",
            "escalate": "execute_action"
        }
    )

如果 make_escalation_decision 工具(通过 decision_node)产生“auto_respond”,工作流将继续通过 RAG 节点(检索示例),然后是 generate_reply 节点(设计回答),最后是 execute_action 节点(记录 auto-response)。

相反,如果决策是“escalate”,流程将绕过 RAG 和生成步骤,直接转到 execute_action 来处理上报。 要添加剩余的标准边缘来完成图,请执行以下操作:

    workflow.add_edge("rag", "generate_reply")
    workflow.add_edge("generate_reply", "execute_action")
    workflow.add_edge("execute_action", END)

数据集注释:对于此项目,我们用于支持检索增强生成 (RAG) 的数据集来自 Hugging Face 上的 Customer Support Tickets 数据集。 对该数据集进行了筛选,以便只包含分类为技术支持并限制为英语的条目。 它确保 RAG 系统只为技术支持任务检索高度相关的特定领域的示例。

此时,我们的图应当与下图类似:

graph.png

当您使用一封电子邮件来执行此图,并导致高优先级分类和“escalate”决策时,您将看到以下响应:

image.png

同时,被分类为低优先级并导致“auto_respond”决策的请求将触发与下面类似的回复:

image.png

那么… 这一路都很顺利吗?

并不完全是。 有一些问题需要注意:

  • 数据隐私:小心敏感信息 — 这些智能体需要防护。
  • 计算成本:某些高级设置需要大量资源。
  • 幻觉:LLM 偶尔也会编造一些内容(不过仍然比大多数实习生聪明)。
  • 非确定性:相同的输入可能会返回不同的输出,这对创造力来说是好事,但对严格的流程来说却很棘手。

不过,大多数缺点都可以通过良好的规划、合适的工具和一点点思考加以控制。

LangGraph 将 AI 智能体从流行语变成实实在在的有效解决方案。 无论您是要自动执行客户支持,处理 IT 工单,还是构建自主应用程序,这个框架都让操作变得可行,甚至有趣。

您有任何问题或反馈吗? 请说出来。 AI 革命需要像您这样的建设者。

0
0 9
文章 Lilian Huang · 九月 19, 2025 5m read

image

您知道当您拿到验血结果时一切看起来都像天书的那种感觉吗? 这就是 FHIRInsight 要解决的问题。 它最初的理念是,医疗数据不应该令人恐惧或困惑 – 它应该是我们所有人都能使用的东西。 验血是健康检查中十分常见的检查,但说实话,大多数人都很难理解它们,有时甚至对不擅长实验室工作的医务人员来说也是如此。 FHIRInsight 希望整个过程能够变得更简单,信息更富有实用价值。

FHIRInsight logo

🤖我们为什么要构建 FHIRInsight

这一切都始于一个简单而有力的问题:

“为什么验血结果仍然很难读懂 — 有时甚至对医生来说也是如此?”

如果您看过化验结果,您可能会看到一大堆数字、隐晦的缩写和“参考范围”,这些可能适用于您的年龄、性别或身体状况,也可能不适用。 毫无疑问,它是一种诊断工具,但如果没有背景信息,它就变成了一个猜谜游戏。 即使是经验丰富的医疗保健专业人员有时也需要交叉参考指导方针、研究论文或专家意见才能理解所有内容。

这正是 FHIRInsight 的用武之地。

我们不只是为患者而构建,也为一线医护人员而构建。 为轮流值班的医生,为捕捉生命体征细微变化的护士,为每一位试图在有限的时间和巨大的责任下做出正确决定的医护人员而构建。 我们的目标是让他们的工作简单一点,将密集的临床 FHIR 数据转化为清晰、有用、以真正的医学科学为基础的东西, 讲人类语言的东西。

FHIRInsight 不仅仅是解释化验结果。 它还:

  • 提供化验结果是轻度、中度还是重度的情境建议
  • 根据临床症状提出潜在病因和鉴别诊断
  • 提出下一步行动建议 — 是后续检查、转诊还是紧急护理
  • 利用 RAG(检索增强生成)拉取相关科学文章,为分析提供支持

想象一下,一位年轻的医生正在查看患者的贫血检查结果。 他们不需要在 Google 上搜索每一个异常值或翻阅医学期刊,而是收到一份报告,上面不仅总结了问题,还引用了最近的研究或世界卫生组织的指导方针来支持这一推理。 这就是将 AI针对精选研究的矢量搜索相结合的力量。

那患者呢?

他们再也不用盯着满屏的数字,猜想“胆红素 2.3 mg/dL”是什么意思,或者他们是否应该担心了。 他们会得到简单、周全的解释。 感觉更像是一场对话,而不是一份临床报告。 一些他们能真正理解的东西 — 并与他们的医生进行讨论,让人感觉更有准备,不那么焦虑。

因为这就是 FHIRInsight 的真正意义**:将复杂的医疗数据转化为清晰的见解**,帮助医疗保健专业人员和患者共同制定更好、更自信的决策

🔍 表象之下

当然,所有这些表面上的简单,背后可能由一些默默运行的强大技术提供支持。

以下是 FHIRInsight 的构建基础:

  • FHIR (Fast Healthcare Interoperability Resources) — 这是健康数据的全球标准。 它是我们接收化验结果、患者病史、受众特征和诊疗等结构化信息的方式。 FHIR 是医疗系统使用的语言,我们将这种语言翻译成人们可以真正使用的东西。
  • RAG(检索增强生成)的矢量搜索:FHIRInsight 通过在使用 InterSystems IRIS 原生矢量搜索的矢量数据库中建立科学 PDF 论文和可信 URL 索引,增强其诊断推理能力。 当化验结果看起来模棱两可或差别细微时,系统会检索相关内容来支持其建议,它不是从记忆库中进行检索,而是从真实的、最新的研究中进行检索。
  • 医学推理提示工程:我们对提示进行了微调,以指导 LLM 识别各种血液相关疾病。 无论是缺铁性贫血、凝血功能障碍、激素失衡还是自身免疫触发因素,提示都会引导 LLM 了解症状、检验室模式和可能病因的变化。
  • LiteLLM 集成:自定义适配器通过统一的接口将请求路由到多个 LLM 提供程序(OpenAI、Anthropic、Ollama 等),从而轻松实现回退、流式传输和模型切换。

无论您是查看 30 个患者图表的医生,还是想要理解数字含义的患者,都可以在几秒钟内将原始的化验数据转化为可解释、富有实用价值的医学见解

🧩 创建 LiteLLM 适配器:使用一个接口管理所有模型

在后台,FHIRInsight 的 AI 赋能报告由 LiteLLM 驱动,后者是一个出色的抽象层,可以使我们通过一个 OpenAI 风格的界面调用 100 多个 LLM(OpenAI、Claude、Gemini、Ollama 等)。

但是要想将 LiteLLM 集成到 InterSystems IRIS 中,需要比隐藏在业务操作中的 Python 脚本更持久、更能重复使用的东西。 所以,我们创建了自己的 LiteLLM 适配器

认识 LiteLLMAdapter

此适配器类可以处理您希望从一个强大的 LLM 集成中获得的所有东西:

  • 接受 promptmodeltemperature 等参数
  • 动态加载环境变量(例如,API 密钥)

为了将其嵌入我们的互操作性生产中,我们将其包装在一个专门的业务操作中:

  • 通过标准的 LLMModel 设置处理生产配置
  • 与 FHIRAnalyzer 组件集成,以实时生成报告
  • 作为未来任何需要访问 LLM 的组件的中心“AI 桥”

以下是简化的核心流程:

set response = ##class(dc.LLM.LiteLLMAdapter).CallLLM("Tell me about hemoglobin.", "openai/gpt-4o", 0.7)
write response

🧭 结语

当我们开始构建 FHIRInsight 时,我们的使命很简单**:让验血结果对每个人来说都更容易理解**。 不仅仅是患者,还有医生、护士、护理人员… 任何曾经盯着化验结果苦思冥想的人,“好吧,这到底是什么意思?”

我们都有过这样的经历。

通过融合 FHIR 的结构、InterSystems IRIS 的速度、LLM 的智能,以及通过矢量搜索实现真实医学研究的深度,我们创造了一个可以将令人困惑的数字转化成有意义的叙述的工具。 帮助人们对自己的健康做出更明智的决定,甚至可能及早发现一些被忽视的疾病。

但 FHIRInsight 不仅仅与数据相关。 它还与我们查看数据时的感受相关。 我们希望它给人一种清晰、支持和赋能的感觉。 我们希望这种体验… 有点像**“氛围编程”医疗保健** — 在智能的代码、优秀的设计和人类同理心方面达到最佳平衡点。

我们希望您能尝试它,打破它,质疑它,并帮助我们改进它。

告诉我们您接下来想看到什么。 更多条件? 更具可解释性? 更加个性化?

这只是一个开端 — 我们希望您能帮助塑造它的未来。

0
0 30
InterSystems 官方 Claire Zheng · 六月 20, 2025

InterSystems 宣布 InterSystems IRIS、InterSystems IRIS for Health 和 HealthShare Health Connect 2025.1 正式发布

2025.1 版的 InterSystems IRIS® 数据平台、InterSystems IRIS® for HealthTMHealthShare® Health Connect 现已正式发布 (GA)。 这是扩展维护 (EM) 版本。

版本亮点

在这个激动人心的版本中,用户可以期待一些新功能和增强,包括:

  1. 高级向量搜索功能
  2. 增强的商业智能
    • IRIS BI 多维数据集构建和同步中的自动依赖项分析,确保复杂多维数据集依赖项中的一致性和完整性。
  3. 改进的 SQL 和数据管理
    • 引入了标准 SQL 分页语法 (LIMIT... OFFSET..., OFFSET... FETCH...)。
    • 新的 LOAD SQL 命令,可以简化 DDL 语句的批量导入。
    • 增强的 ALTER TABLE 命令,可以在行布局和列布局之间无缝转换。
  4. 优化的数据库操作
    • 日志记录大小更小,效率更高。
    • 数据库压缩速度更快,尤其是对于具有大量大字符串内容的数据库。
    • 将新数据库添加到镜像时的自动化程度更高。
    • 用于 ECP 管理任务的新命令行实用工具。
  5. 更高的安全合规性
    • 支持符合 FIPS 140-3 标准的加密库。
  6. 现代化的互操作性用户界面
    • 选择参与经过改进的生产配置和 DTL 编辑器体验,其中包含源代码控制集成、VS Code 兼容性、增强的筛选功能、分屏视图等。请参阅此开发者社区文章,详细了解如何选择参与并提供反馈。
  7. 更多的医疗保健功能
    • 高效的批量 FHIR 引入和调度,包括完整性检查和资源管理。
    • 增强的 FHIR 批量访问和经过改进的 FHIR 搜索操作。
  8. 新的开发者体验功能
  9. 通过 OpenTelemetry 提高可观测性
    • 在 IRIS 中引入了跟踪功能,有助于详细观测 Web 请求和应用程序的性能。

请通过开发者社区分享您的反馈,以便我们可以共同打造更出色的产品。

文档

访问以下链接,可以详细了解所有着重介绍的功能:

此外,请查看升级影响核对清单,轻松了解升级到此版本时需要注意的所有变更。

尤其是,请注意 InterSystems IRIS 2025.1 引入了新的日志文件格式版本,该格式与早期版本不兼容,因而给混合版本的镜像设置带来了一定的限制。 请参阅相应的文档了解更多详细信息。

抢先体验计划 (EAP)

目前提供多个 EAP。 请查看此页面并注册您感兴趣的 EAP。

下载软件

一如既往,扩展维护 (EM) 版本提供了适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。

经典安装包

安装包可以从 WRC 的 InterSystems IRIS 页面(对于 InterSystems IRIS 和 InterSystems IRIS for Health)和 WRC 的 HealthShare 页面(对于 Health Connect)获取。 您也可以在评估服务网站中找到工具包。

可用性和软件包信息

此版本提供了适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。有关完整列表,请参阅“支持的平台”文档

此扩展维护版本的内部版本号为 2025.1.0.223.0

容器镜像可以从 InterSystems 容器注册表中获取。 容器被标记为 2025.1latest-em

0
0 76
文章 Lilian Huang · 四月 10, 2025 6m read

社区朋友们好,

传统的基于关键词的搜索方式在处理具有细微差别的领域特定查询时往往力不从心。而向量搜索则通过语义理解能力,使AI智能体能够根据上下文(而非仅凭关键词)来检索信息并生成响应。

本文将通过逐步指导,带您创建一个具备代理能力的AI RAG(检索增强生成)应用程序。

实现步骤:

  1. 添加文档摄取功能
    • 自动获取并建立文档索引(例如《InterSystems IRIS 2025.1版本说明》)
    • 实现向量搜索功能
  2. 构建向量搜索智能体
  3. 移交至主智能体(分流处理)
  4. 运行智能体

1. Create Agent Tools 添加文档摄取功能

Implement Document Ingestion: Automated ingestion and indexing of documents 


1.1 - 以下是实现文档摄取工具的代码:

defingestDoc(self):#Check if document is defined, by selecting from table#If not defined then INGEST document, Otherwise back
        embeddings = OpenAIEmbeddings()	
        #Load the document based on the fle type
        loader = TextLoader("/irisdev/app/docs/IRIS2025-1-Release-Notes.txt", encoding='utf-8')      
        
        documents = loader.load()        
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=0)
        
        texts = text_splitter.split_documents(documents)
                       
        #COLLECTION_NAME = "rag_document"
        db = IRISVector.from_documents(
            embedding=embeddings,
            documents=texts,
            collection_name = self.COLLECTION_NAME,
            connection_string=self.CONNECTION_STRING,
        )

        db = IRISVector.from_documents(embedding=embeddings,documents=texts, collection_name = self.COLLECTION_NAME, connection_string=self.CONNECTION_STRING,)

向量搜索智能体(Vector Search Agent)能够自动完成文档的摄取(ingest)索引构建(index), 该新功能在InterSystems IRIS 2025.1的数据资源文件夹里) 至 IRIS 向量存储, 只有当数据尚未存在时,才执行该操作。


运行以下查询以从向量存储中获取所需数据:

SELECTid, embedding, document, metadata
FROM SQLUser.AgenticAIRAG


1.2 - 实现向量搜索功能


以下代码为智能体提供了搜索能力:

defragSearch(self,prompt):#Check if collections are defined or ingested done.# if not then call ingest method
        embeddings = OpenAIEmbeddings()	
        db2 = IRISVector (
            embedding_function=embeddings,    
            collection_name=self.COLLECTION_NAME,
            connection_string=self.CONNECTION_STRING,
        )
        docs_with_score = db2.similarity_search_with_score(prompt)
        relevant_docs = ["".join(str(doc.page_content)) + " "for doc, _ in docs_with_score]
        
        #Generate Template
        template = f"""
        Prompt: {prompt}
        Relevant Docuemnts: {relevant_docs}
        """return template


分流代理处理传入的用户查询,并将其委托给矢量搜索代理,后者执行语义搜索操作,以检索最相关的信息。

0
0 97
InterSystems 官方 Michael Lei · 一月 5, 2025

2024.3 版  InterSystems IRIS® 数据平台、InterSystems IRIS® for Health 和 HealthShare® Health Connect 现已正式发布 (GA)。

版本亮点

在此版本中,您可以期待一系列激动人心的更新,包括:

  1. 数据库和 WIJ 文件的扩展速度大大提高
  2. 能够通过 Visual Trace 重新发送消息
  3. 增强的规则管理器功能
  4. 向量搜索增强功能
  5. 等等。

请通过开发者社区分享您的反馈,与我们共同打造更好的产品。

文档

请访问以下链接了解所有亮点功能的详细信息:

此外,请查看此版本的升级信息

抢先体验计划 (EAP)

目前提供多个 EAP。 请查看此页面并注册您感兴趣的计划。

如何获取软件?

像往常一样,持续交付 (CD) 版本包含适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。

经典安装包

安装包可从 WRC 的 InterSystems IRIS、InterSystems IRIS for Health 和 Health Connect持续交付版本页面获取。此外,还可以在评估服务网站获取套件。

可用性和软件包信息

此版本提供适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。有关完整列表,请参阅“支持的平台”文档

安装包和预览版密钥可从 WRC 的预览版下载网站或通过评估服务网站获取。

此持续交付版本的版本号为:2024.3.0.217.0

容器镜像可通过 InterSystems 容器注册表获取。 容器带有 2024.3latest-cd 标签。

0
0 115
InterSystems 官方 Michael Lei · 十月 7, 2024

我们最近在向量搜索抢险体验计划中提供了新版的 InterSystems IRIS,新版本采用了新的基于分层可导航小世界 (HNSW) 索引算法的近似最近邻索引。 这一新增功能可对大型向量数据集进行高效的近似最近邻搜索,从而显著提高查询性能和可扩缩性。

HNSW 算法旨在通过构建基于图形的结构来优化高维数据的向量搜索,从而更快地在大型向量集合中找到近似邻。 无论您使用的是推荐系统、自然语言处理,还是其他机器学习应用,HNSW 都能显著缩短搜索时间,同时允许您调整准确度水平,但准确度提高的代价是查询时间变慢。

HNSW 的主要优点包括:

    •    即使数据集规模不断扩大,也能加快搜索速度
    •    减少内存占用,同时保持高准确度
    •    与现有的 IRIS 向量搜索功能无缝集成

如何开始使用

最新版本现已通过向量搜索抢先体验计划提供。 要参与,请在此处注册,下载新版本并开始测试。 我们正在持续增强向量搜索的功能,因此您的反馈至关重要!

我们鼓励您探索性能改进方法,并与社区分享您的想法。 如果您在抢先体验阶段遇到任何问题或有任何反馈,请联系我。

祝您编码愉快!

0
0 83
公告 Claire Zheng · 九月 11, 2024

HI 各位开发者们,

📅2024年9月23日🕑14:00-15:30🕞,InterSystems将举办线上研讨会,点击🔔此处🔔报名参会。

此次研讨会以“面向未来的数据平台——InterSystems IRIS五大亮点提速数据潜力挖掘与AI应用”为主题,帮助您了解InterSystems IRIS数据平台的五大亮点:

  • 使用InterSystems IRIS for Health进行FHIR开发
  • 使用Python进行互操作Production开发
  • InterSystems IRIS列存储
  • InterSystems IRIS外部表(Foreign Table)
  • InterSystems IRIS向量和基于向量检索的患者相似度匹配
2
0 115
文章 Michael Lei · 八月 1, 2024 4m read

随着 IRIS 中向量数据类型和向量搜索功能的引入,应用程序的开发正在开启一个充满各种可能性的全新世界,其中一个应用程序示例是我最近在巴伦西亚卫生局的一次公开竞赛中看到的应用程序,他们要求提供一种工具,能够使用 AI 模型协助进行 ICD-10 编码。

我们如何实现与所要求的应用程序类似的应用程序? 我们来看看需要什么:

  1. ICD-10 代码列表,我们将使用它作为 RAG 应用程序的上下文,在纯文本中搜索诊断结果。
  2. 经过训练的模型,它会将文本向量化,我们将在其中查找 ICD-10 代码中的对应项。
  3. Python 库,用于对 ICD-10 代码和文本进行摄取和向量化。
  4. 一个支持文本的友好前端,我们会在其中查找可能的诊断结果。
  5. 从前端接收的请求的编排。

IRIS 为我们提供哪些功能来满足上述需求?

  1. CSV 导入,可以使用 RecordMapper 功能,也可以直接使用嵌入式 Python。
  2. 嵌入式 Python 使我们能够实现使用所选模型生成向量所需的 Python 代码。
  3. 发布将从前端应用程序调用的 REST API。
  4. 互操作性生产,以允许在 IRIS 中跟踪信息。

我们只需要看看开发的示例:

d[IA]gnosis

在本文中,您可以访问开发的应用程序,在后续文章中,我们将详细了解如何实现每个功能,包括模型的使用、向量的存储和向量搜索的使用。

我们来看看这个应用程序:

导入 ICD-10 代码

在配置屏幕中,我们知道了 CSV 文件必须使用的格式,以符合我们要导入的 ICD-10 代码。 加载和向量化过程会占用大量时间和资源,因此,为了防止所需空间超出分配的 RAM,在部署 Docker 容器时,不仅配置了 Docker 可用的 RAM 内存,还配置了磁盘存储器:

# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./shared:/shared    environment:    -ISC_DATA_DIRECTORY=/shared/durable    command:--check-capsfalse--ISCAgentfalse    mem_limit:30G    memswap_limit:32G

包含 ICD-10 代码的文件位于项目路径 /shared/cie10/icd10.csv 中,达到 100% 后,即可使用该应用程序。

在我们的应用程序中,我们定义了两个不同的诊断结果编码功能,一个基于系统中接收到的 HL7 消息,另一个基于纯文本。

从 HL7 中捕获诊断结果

项目中包含一些准备进行测试的 HL7 消息,只需将 /shared/hl7/messagesa01_en.hl7 文件复制到 /shared/HL7In 文件夹,相关的生产就会负责从中提取诊断结果,并显示在 Web 应用程序中:

在诊断结果请求屏幕中,我们可以看到通过 HL7 消息收到的所有诊断结果。 要将它们编码为 ICD-10,我们只需点击放大镜以显示与收到的诊断结果最接近的 ICD-10 代码列表:

选择后,我们将在列表中看到诊断结果及其相关的 ICD-10 代码。 点击带有信封图标的按钮,将使用原始代码生成一条消息,并在诊断结果部分包含所选新代码:

MSH|^~\&|HIS|HULP|EMPI||||ADT^A08|592956|P|2.5.1
EVN|A01|
PID|||1556655212^^^SERMAS^SN~922210^^^HULP^PI||GARCÍA PÉREZ^JUAN^^^||20150403|M|||PASEO PEDRO ÁLVAREZ 1951 CENTRO^^LEGANÉS^MADRID^28379^SPAIN||555283055^PRN^^JUAN.GARCIA@YAHOO.COM|||||||||||||||||N|
PV1||N
DG1|1||O10.91^Unspecified pre-existing hypertension complicating pregnancy^CIE10-ES|Gestational hypertension||A||

可以在路径 /shared/HL7Out 中找到此消息

以纯文本形式显示的诊断结果的屏幕截图

在“文本分析器”选项中,用户可以包含纯文本,并对其进行分析。 该应用程序将按 3 个词形还原的词(去除冠词、代词和其他不太相关的词)构成的元组进行搜索。 分析后,系统将向我们显示相关的带下划线的文本和找到的可能的诊断结果:

执行分析后,可以随时从分析历史记录中进行查询。

分析历史记录

执行的所有分析都会记录下来,可以随时查询,并且能够查看所有可用的 ICD-10 代码:

在下一篇文章中…

我们将看到,如何借助嵌入式 Python,使用特定的 LLM 模型对 ICD-10 代码进行向量化,从而将其用作上下文和自由文本。

如果您有任何疑问或建议,请随时对本文发表评论。

0
0 79
文章 Louis Lu · 七月 1, 2024 2m read

在 OEX 最近一次编程竞赛之后,我有一些令人惊讶的发现。
几乎所有的应用程序都是基于人工智能与预制 Python 模块的结合。
但深入研究后发现,所有示例都使用了 IRIS 的相同技术组件。

从 IRIS 的角度来看,无论是搜索文本还是搜索图像或其他模式都是一样的。 其底层基本都是一样的。

          这让我想起了我家里的情况。我的妻子和女儿对家里的大量裙子、衬衫和其他衣服的信息进行了整理。
          但无论如何进行整理、分类、归档,我依然通过和我的妻子和女儿说话,来确定我的穿着。
           无论怎样包装,其结果都是如此。

回到这次竞赛比赛:
同样的 IRIS 技术内容,却有很多花哨的包装。
每个人都在同一条高速公路上奔跑。没有人提到它有什么限制。

于是我试着深入挖掘,找出新数据类型 VECTOR 的使用限制。
所有向量都有两个基本参数
  - 静态 DATATYPE:"整型integer"(或 "int")、"double"、"十进制decimal"、"字符串 "和 "时间戳"。
  - 半动态 LEN(gth): > 0 通常也称为 POSITION;纯整数。

0
0 96
文章 Louis Lu · 七月 1, 2024 4m read

迄今为止,我看到的大多数使用向量vector的示例,将它只作为 SQL 中的一种功能,尤其是围绕 VECTOR_Search 的 3 个函数。
* TO_VECTOR()
* vector_dot_product ()
* vector_cosine ()

iris-vector-search 演示包中隐藏着一个非常有用的摘要。
从那里,你可以通过多个链接找到所需的一切。

我还缺少更多的 VECTOR 方法,于是在 Idea Portal 上提出了相关请求。

接着,我想起每个 SQL 方法或存储过程都有一堆 ObjectScript 代码。
于是我开始搜索,下面就是我的研究的一些总结:

%Library.Vector 是对新数据类型的核心描述
      这是一种复杂的结构,就像对象或 %DynamicObjects 或 $Bit Expressions 一样,需要特定的方式去访问。
我们还可以看到 2 个必备参数: 
      * DATATTYPE - 一旦设置就不能更改。 可接受的类型:  "整数integer"(或 "int")、"双精度浮点double"、"十进制decimal"、"字符串 "和 "时间戳"。
      * LEN >0 时,可以增长,但绝对不能缩小

0
0 86
文章 Louis Lu · 六月 11, 2024 8m read

这篇文章介绍了使用由支持 langchain 框架的IRIS来实现问答聊天机器人,其重点介绍了检索增强生成(RAG)。

文章探讨了IRIS中的向量搜索如何在langchain-iris中完成数据的存储、检索和语义搜索,从而实现对用户查询的精确、快速的响应。通过无缝集成以及索引和检索/生成等流程,由IRIS驱动的RAG应用程序使InterSystems开发者能够利用GenAI系统的能力。

为了帮助读者巩固这些概念,文章提供了Jupyter notebook一个完整的问答聊天机器人应用程序,以供参考。

什么是RAG以及它在问答聊天机器人中的角色

RAG,即检索增强生成,是一种通过整合超出初始训练集的补充数据来丰富语言模型(LLM)知识库的技术。尽管LLM在跨不同主题进行推理方面具有能力,但它们仅限于在特定截止日期之前训练的公共数据。为了使AI应用程序能够有效处理私有或更近期的数据,RAG通过按需补充特定信息来增强模型的知识。这是一种替代微调LLM的方法,微调可能会很昂贵。

在问答聊天机器人领域,RAG在处理非结构化数据查询中发挥着关键作用,包括两个主要组成部分:索引和检索/生成。

索引从数据源摄取数据开始,然后将其分割成更小、更易于管理的块以进行高效处理。这些分割的块随后被存储和索引,通常使用嵌入模型和向量数据库,确保在运行时能够快速准确地检索。

0
0 321
文章 Michael Lei · 三月 21, 2024 2m read

这是在 IRIS 中完全运行向量搜索演示的尝试。
没有外部工具,您需要的只是终端/控制台和管理门户。
特别感谢Alvin Ryanputra作为他的软件包iris-vector-search的基础
灵感和测试数据的来源。
我的软件包基于 IRIS 2024.1 版本,需要注意您的处理器功能。

我尝试用纯 ObjectScript 编写演示。
仅描述向量的计算是在嵌入式Python中完成的
计算 2247 个记录的 384 维向量需要时间。
在我的 Docker 容器中,它正在运行 01:53:14 来完全生成它们。

然后被警告了!
所以我将这一步调整为可重入,以允许暂停向量计算。
每 50 条记录,您就会收到一次停止的提议。
该演示如下所示:

1
0 87
文章 Michael Lei · 二月 18, 2024 11m read

1. IRIS RAG Demo

IRIS RAG Demo

这是 IRIS 与 RAG(检索增强生成)示例的一个简单演示。 后端是使用 IRIS 和 IoP用 Python 编写的,LLM 模型是 orca-mini 并由 ollama 服务器提供。 前端是用 Streamlit 编写的聊天机器人。

    1. IRIS RAG 演示](#1-iris-rag-demo)
    • 1.1. 什么是 RAG](#11-what-is-rag)
    • 1.2. 如何工作?
    • 1.3. 安装演示](#13-installation-the-demo)
    • 1.4. 使用方法
    • 1.5. 演示如何运行](#15-演示如何运行)
      • [1.5.1. 前端](#151-前端)
      • 1.5.2. 后台
        • [1.5.2.1. 业务服务](#1521-业务服务)
        • [1.5.2.2. 业务流程](#1522-业务流程)
        • [1.5.2.3. LLM 操作](#1523-the-llm-operation)
        • 1.5.2.4. 矢量操作](#1524-the-vector-operation)
    • 1.6. 一般性说明](#16-一般性说明)

1.1. 什么是 RAG?

RAG 是 Retrieval Augmented Generation(检索增强生成)的缩写,它带来了使用带有知识库的 LLM 模型(GPT-3.5/4、Mistral、Orca 等)的能力。

为什么它很重要? 因为它允许使用知识库来回答问题,并使用 LLM 来生成答案。

例如,如果你直接向 LLM 询问**"grongier.pex 模块是什么?"**,它将无法回答,因为它不知道这个模块是什么(也许你也不知道🤪)。

但是,如果你向 RAG 提出同样的问题,它就能回答,因为它会使用知识库,知道 grongier.pex 模块是什么,从而找到答案。

既然你已经知道什么是 RAG,那就让我们来看看它是如何工作的。

1.2. 它是如何工作的?

首先,我们需要了解 LLMS 的工作原理。LLMS 经过训练,可以根据前一个单词预测下一个单词。因此,如果你给它一个句子,它就会尝试预测下一个词,以此类推。很简单吧?

要与 LLM 交互,通常需要给它一个提示,它就会生成句子的其余部分。例如,如果你给它一个提示 "什么是 grongier.pex 模块?

很抱歉,我对您提到的 Pex 模块并不熟悉。能否请您提供有关它的更多信息或上下文?

好的,不出所料,它不知道什么是 grongier.pex 模块。但如果我们给它一个包含答案的提示呢?例如,如果我们提示``什么是 grongier.pex 模块?它是一个可以让你做 X、Y 和 Z 的模块。`",它就会生成剩下的句子,看起来就像这样:

grongier.pex 模块是一个可以让你执行 X、Y 和 Z 的模块。

好了,现在它知道什么是 grongier.pex 模块了。

但如果我们不知道 grongier.pex 模块是什么呢?我们怎样才能给它一个包含答案的提示呢? 这就需要知识库了。

RAG

RAG 的整个思路是使用知识库找到上下文,然后使用 LLM 生成答案。

为了找到上下文,RAG 将使用一个**知识库。

1.3.安装演示

只需克隆存储库并运行“docker-compose up”命令即可。

git clone https://github.com/grongierisc/iris-rag-demo
cd iris-rag-demo
docker-compose up

⚠️ 一切都是本地的,没有任何东西发送到云端,所以请耐心等待,可能需要几分钟才能开始。

1.4.用法

演示开始后,您可以在 http://localhost:8051 访问前端。

![Frontend](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/iris_chat.png?raw=true)

你可以提出有关「综合注册资讯系统」的问题,例如:

  • 什么是grongier.pex模块?

![Question](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/without_rag.png?raw=true)

正如你所看到的,答案不是很好,因为 LLM 不知道什么是 grongier.pex 模块。

现在,让我们尝试使用 RAG:

上传“grongier.pex”模块文档,它位于“docs”文件夹中,文件“grongier.pex.md”。

并问同样的问题:

  • 什么是grongier.pex模块?

![Question](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/with_rag.png?raw=true)

正如你所看到的,答案要好得多,因为 LLM 现在知道什么是 grongier.pex 模块。

您可以在日志中看到详细信息:

转到管理门户, http://localhost:53795/csp/irisapp/EnsPortal.ProductionConfig.zen?$NAMESPACE=IRISAPP&$NAMESPACE=IRISAPP&,然后单击“消息”选项卡。

首先,您将看到发送到 RAG 进程的消息:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_query.png?raw=true)

然后是知识库(向量数据库)中的搜索查询:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_result_vector.png?raw=true)

最后,发送给 LLM 的新提示:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_new_query.png?raw=true)

1.5.这个Demo如何工作?

该演示由 3 个部分组成:

  • 前端,用 Streamlit 编写
  • 后端,用 Python 和 IRIS 编写
  • 知识库 Chroma 向量数据库
  • LLM,Orca-mini,由 Ollama 服务器提供服务

1.5.1.前端

前端是用 Streamlit 编写的,它是一个简单的聊天机器人,可让您提出问题。

这里没什么花哨的,只是一个简单的聊天机器人。

import os
import tempfile
import time
import streamlit as st
from streamlit_chat import message

from grongier.pex import Director

_service = Director.create_python_business_service("ChatService")

st.set_page_config(page_title="ChatIRIS")


def display_messages():
    st.subheader("Chat")
    for i, (msg, is_user) in enumerate(st.session_state["messages"]):
        message(msg, is_user=is_user, key=str(i))


def process_input():
    if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
        user_text = st.session_state["user_input"].strip()
        with st.spinner(f"Thinking about {user_text}"):
            rag_enabled = False
            if len(st.session_state["file_uploader"]) > 0:
                rag_enabled = True
            time.sleep(1) # help the spinner to show up
            agent_text = _service.ask(user_text, rag_enabled)

        st.session_state["messages"].append((user_text, True))
        st.session_state["messages"].append((agent_text, False))


def read_and_save_file():

    for file in st.session_state["file_uploader"]:
        with tempfile.NamedTemporaryFile(delete=False,suffix=f".{file.name.split('.')[-1]}") as tf:
            tf.write(file.getbuffer())
            file_path = tf.name

        with st.spinner(f"Ingesting {file.name}"):
            _service.ingest(file_path)
        os.remove(file_path)

    if len(st.session_state["file_uploader"]) > 0:
        st.session_state["messages"].append(
            ("File(s) successfully ingested", False)
        )

    if len(st.session_state["file_uploader"]) == 0:
        _service.clear()
        st.session_state["messages"].append(
            ("Clearing all data", False)
        )

def page():
    if len(st.session_state) == 0:
        st.session_state["messages"] = []
        _service.clear()

    st.header("ChatIRIS")

    st.subheader("Upload a document")
    st.file_uploader(
        "Upload document",
        type=["pdf", "md", "txt"],
        key="file_uploader",
        on_change=read_and_save_file,
        label_visibility="collapsed",
        accept_multiple_files=True,
    )

    display_messages()
    st.text_input("Message", key="user_input", on_change=process_input)


if __name__ == "__main__":
    page()

💡 我只是在用 :

_service = Director.create_python_business_service("ChatService")

来创建一个前后端之间的绑定.

ChatService 只是互操作性生产中的简单业务服务BS。

1.5.2.后端

后端是用 Python 和 IRIS 编写的。

它由3个部分组成:

  • 业务服务BS
    • 前端的入口点
  • 业务流程BP
    • 如果需要,在知识库中执行搜索
  • 拖曳业务运营BO
    • 一个用于知识库
      • 摄取文档
      • 搜索文档
      • 清除文件
    • 一个用于LLM大模型
      • 生成答案

1.5.2.1.业务服务BS

业务服务是一个简单的业务服务,它允许:

  • 上传文件
  • 提出问题
  • 清除向量数据库
from grongier.pex import BusinessService

from rag.msg import ChatRequest, ChatClearRequest, FileIngestionRequest

class ChatService(BusinessService):

    def on_init(self):
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatProcess"
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"

    def ingest(self, file_path: str):
        # build message
        msg = FileIngestionRequest(file_path=file_path)
        # send message
        self.send_request_sync(self.target_vector, msg)

    def ask(self, query: str, rag: bool = False):
        # build message
        msg = ChatRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response.response

    def clear(self):
        # build message
        msg = ChatClearRequest()
        # send message
        self.send_request_sync(self.target_vector, msg)

基本上,它只是操作和过程之间的传递。

1.5.2.2.业务流程

业务流程是一个简单的过程,允许在需要时搜索知识库

from grongier.pex import BusinessProcess

from rag.msg import ChatRequest, ChatResponse, VectorSearchRequest

class ChatProcess(BusinessProcess):
    """
    the aim of this process is to generate a prompt from a query
    if the vector similarity search returns a document, then we use the document's content as the prompt
    if the vector similarity search returns nothing, then we use the query as the prompt
    """
    def on_init(self):
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatOperation"

        # prompt template
        self.prompt_template = "Given the context: \n {context} \n Answer the question: {question}"


    def ask(self, request: ChatRequest):
        query = request.query
        prompt = ""
        # build message
        msg = VectorSearchRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_vector, msg)
        # if we have a response, then use the first document's content as the prompt
        if response.docs:
            # add each document's content to the context
            context = "\n".join([doc['page_content'] for doc in response.docs])
            # build the prompt
            prompt = self.prompt_template.format(context=context, question=query)
        else:
            # use the query as the prompt
            prompt = query
        # build message
        msg = ChatRequest(query=prompt)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response

这真的很简单,它只是向知识库发送一条消息来搜索文档。

如果 知识库 返回文档,则会使用文档内容作为提示,否则会使用查询作为提示。

1.5.2.3.LLM 操作

LLM 操作是一个简单的操作,可以生成答案。


class ChatOperation(BusinessOperation):

    def __init__(self):
        self.model = None

    def on_init(self):
        self.model = Ollama(base_url="http://ollama:11434",model="orca-mini")

    def ask(self, request: ChatRequest):
        return ChatResponse(response=self.model(request.query))

这一步也很简单,它只是向 LLM 发送一条消息来生成答案。

1.5.2.4.Vector 操作

向量操作是一个简单的操作,允许摄取文档、搜索文档和清除向量数据库。


class VectorOperation(BusinessOperation):

    def __init__(self):
        self.text_splitter = None
        self.vector_store = None

    def on_init(self):
        self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
        self.vector_store = Chroma(persist_directory="vector",embedding_function=FastEmbedEmbeddings())

    def ingest(self, request: FileIngestionRequest):
        file_path = request.file_path
        file_type = self._get_file_type(file_path)
        if file_type == "pdf":
            self._ingest_pdf(file_path)
        elif file_type == "markdown":
            self._ingest_markdown(file_path)
        elif file_type == "text":
            self._ingest_text(file_path)
        else:
            raise Exception(f"Unknown file type: {file_type}")

    def clear(self, request: ChatClearRequest):
        self.on_tear_down()

    def similar(self, request: VectorSearchRequest):
        # do a similarity search
        docs = self.vector_store.similarity_search(request.query)
        # return the response
        return VectorSearchResponse(docs=docs)

    def on_tear_down(self):
        docs = self.vector_store.get()
        for id in docs['ids']:
            self.vector_store.delete(id)
        
    def _get_file_type(self, file_path: str):
        if file_path.lower().endswith(".pdf"):
            return "pdf"
        elif file_path.lower().endswith(".md"):
            return "markdown"
        elif file_path.lower().endswith(".txt"):
            return "text"
        else:
            return "unknown"

    def _store_chunks(self, chunks):
        ids = [str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.page_content)) for doc in chunks]
        unique_ids = list(set(ids))
        self.vector_store.add_documents(chunks, ids = unique_ids)
        
    def _ingest_text(self, file_path: str):
        docs = TextLoader(file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)
        
    def _ingest_pdf(self, file_path: str):
        docs = PyPDFLoader(file_path=file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

    def _ingest_markdown(self, file_path: str):
        # Document loader
        docs = TextLoader(file_path).load()

        # MD splits
        headers_to_split_on = [
            ("#", "Header 1"),
            ("##", "Header 2"),
        ]

        markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
        md_header_splits = markdown_splitter.split_text(docs[0].page_content)

        # Split
        chunks = self.text_splitter.split_documents(md_header_splits)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

如果文档太大,那么向量数据库将无法存储它们,因此我们需要将它们拆分为块。

如果文档是 PDF,那么我们将使用“PyPDFLoader”来加载 PDF,否则我们将使用“TextLoader”来加载文档。

然后,我们将使用“RecursiveCharacterTextSplitter”将文档拆分为块。

最后,我们将块存储到向量数据库中。

如果文档是 Markdown,那么我们将使用“MarkdownHeaderTextSplitter”将文档拆分为块。我们还使用标题将文档拆分为块。

1.6.A. 总 论

所有这些都可以通过“langchains”来完成,但我想向你展示如何使用互操作性框架来做到这一点。并让每个人都更容易理解它是如何工作的。

1
0 587
文章 Michael Lei · 九月 18, 2023 6m read

如今,关于大语言模型、人工智能等的消息不绝于耳。向量数据库是其中的一部分,并且已经有非IRIS的技术实现了向量数据库。

为什么是向量?

  • 相似性搜索:向量可以进行高效的相似性搜索,例如在数据集中查找最相似的项目或文档。传统的关系数据库是为精确匹配搜索而设计的,不适合图像或文本相似性搜索等任务。
  • 灵活性:向量表示形式用途广泛,可以从各种数据类型派生,例如文本(通过 Word2Vec、BERT 等嵌入)、图像(通过深度学习模型)等。
  • 跨模态搜索:向量可以跨不同数据模态进行搜索。例如,给定图像的向量表示,人们可以在多模式数据库中搜索相似的图像或相关文本。

还有许多其他原因。

因此,对于这次 pyhon 竞赛,我决定尝试实现这种支持。不幸的是我没能及时完成它,下面我将解释原因。

0
0 161