ip_route_output_key函数分析(1)


http://blog.chinaunix.net/uid-24673811-id-1754229.html


上面的文章读了net/ipv4/route.c 中的ip_route_input函数,是协议站对收到报文的路由查找函数。继续阅读一下协议栈发包的时候路由查找的调用函数ip_roue_output_key。

int ip_route_output_key(struct net *net, struct rtable **rp, struct flowi *flp)
{
    return ip_route_output_flow(net, rp, flp, NULL, 0);
}
只是一个函数封装,真正的处理函数是ip_route_output_flow.
int ip_route_output_flow(struct net *net, struct rtable **rp, struct flowi *flp,
             struct sock *sk, int flags)
{
    int err;

    /*路由查找*/
    if ((err = __ip_route_output_key(net, rp, flp)) != 0)
        return err;

    /*IPSec 的处理代码*/
    if (flp->proto) {
        if (!flp->fl4_src)
            flp->fl4_src = (*rp)->rt_src;
        if (!flp->fl4_dst)
            flp->fl4_dst = (*rp)->rt_dst;
        err = __xfrm_lookup(net, (struct dst_entry **)rp, flp, sk,
                    flags ? XFRM_LOOKUP_WAIT : 0);
        if (err == -EREMOTE)
            err = ipv4_dst_blackhole(net, rp, flp);

        return err;
    }

    return 0;
}

很眼熟的__ip_route_output_key函数。
int __ip_route_output_key(struct net *net, struct rtable **rp,
              const struct flowi *flp)
{
    unsigned hash;
    struct rtable *rth;

    if (!rt_caching(net))
        goto slow_output;

    /*类似于ip_route_input,先在cache中查找路由, 找到就返回*/
    hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif, rt_genid(net));

    rcu_read_lock_bh();
    for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
        rth = rcu_dereference(rth->u.dst.rt_next)) {
        if (rth->fl.fl4_dst == flp->fl4_dst &&
            rth->fl.fl4_src == flp->fl4_src &&
            rth->fl.iif == 0 &&
            rth->fl.oif == flp->oif &&
            rth->fl.mark == flp->mark &&
            !((rth->fl.fl4_tos ^ flp->fl4_tos) &
                (IPTOS_RT_MASK | RTO_ONLINK)) &&
            net_eq(dev_net(rth->u.dst.dev), net) &&
            !rt_is_expired(rth)) {
            dst_use(&rth->u.dst, jiffies);
            RT_CACHE_STAT_INC(out_hit);
            rcu_read_unlock_bh();
            *rp = rth;
            return 0;
        }
        RT_CACHE_STAT_INC(out_hlist_search);
    }
    rcu_read_unlock_bh();

/*不支持cache 或在cache中没找到相应的路由信息,在路由表中查找*/
slow_output:
    return ip_route_output_slow(net, rp, flp);
}

继续探险
/*
 * Major route resolver routine.
 */

static int ip_route_output_slow(struct net *net, struct rtable **rp,
                const struct flowi *oldflp)
{
    u32 tos    = RT_FL_TOS(oldflp); /*获取tos和当前的RTO_ONLINK(?)标志*/
    struct flowi fl = { .nl_u = { .ip4_u =
                      { .daddr = oldflp->fl4_dst,
                    .saddr = oldflp->fl4_src,
                    .tos = tos & IPTOS_RT_MASK,
                    .scope = ((tos & RTO_ONLINK) ? /*根据这个标志,得出路由的scope*/
                          RT_SCOPE_LINK :
                          RT_SCOPE_UNIVERSE),
                      } },
                .mark = oldflp->mark,
                .iif = net->loopback_dev->ifindex, /*设备号为lo的设备号?*/
                .oif = oldflp->oif };
    struct fib_result res;
    unsigned flags = 0;
    struct net_device *dev_out = NULL;
    int free_res = 0;
    int err;


    res.fi        = NULL;
#ifdef CONFIG_IP_MULTIPLE_TABLES

 
 
    res.r        = NULL;
#endif

    /*先是对源地址, 发包接口号和目的地址进行判断分类处理。下面的每一个红色跳转就是一种情况*/ 
    if (oldflp->fl4_src) { /*源*/
        err = -EINVAL;
        if (ipv4_is_multicast(oldflp->fl4_src) ||
            ipv4_is_lbcast(oldflp->fl4_src) ||
            ipv4_is_zeronet(oldflp->fl4_src))
            goto out;
       
        /*上面是对报文源地址的合理性检查,源地址是多播,广播或0地址时,返回错误*/

        /* I removed check for oif == dev_out->oif here.
           It was wrong for two reasons:
           我在这里删去检查oif == dev_out->oif是否成立,因为有两个原因说明这个检查时错误的:
           1. ip_dev_find(net, saddr) can return wrong iface, if saddr
              is assigned to multiple interfaces
              如果源地址是一个多播接口的地址,函数ip_dev_find(net, saddr)可能返回错误的设备接口。
           2. Moreover, we are allowed to send packets with saddr  
              of another iface. --ANK
              而且可以用另外设备接口的源地址发送报文
         */

        if (oldflp->oif == 0
            && (ipv4_is_multicast(oldflp->fl4_dst) ||
            oldflp->fl4_dst == htonl(0xFFFFFFFF))) { /*发包接口为lo,目的地址是广播或多播时查找发包设备,ip_dev_find返回与所给定的源地址相等的第一个设备*/
            /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
            dev_out = ip_dev_find(net, oldflp->fl4_src);
            if (dev_out == NULL)
                goto out;

            /* Special hack: user can direct multicasts
               and limited broadcast via necessary interface
               without fiddling with IP_MULTICAST_IF or IP_PKTINFO.
               This hack is not just for fun, it allows
               vic,vat and friends to work.
               They bind socket to loopback, set ttl to zero
               and expect that it will work.
               From the viewpoint of routing cache they are broken,
               because we are not allowed to build multicast path
               with loopback source addr (look, routing cache
               cannot know, that ttl is zero, so that packet
               will not leave this host and route is valid).
               Luckily, this hack is good workaround.
             */

            /*当报文初始化的出接口为lo接口源地址不为空目的地址是多播或广播地址时,找到源地址所对应的接口重新为出接口赋值, 然后创建cache路由项*/
            fl.oif = dev_out->ifindex;
            goto make_route;
        }

        /*?????*/
        if (!(oldflp->flags & FLOWI_FLAG_ANYSRC)) {
            /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
            dev_out = ip_dev_find(net, oldflp->fl4_src);
            if (dev_out == NULL)
                goto out;
            dev_put(dev_out);
            dev_out = NULL;
        }
    }


    if (oldflp->oif) {/*发包设备不为空*/
        /*检测出接口是否存在*/
        dev_out = dev_get_by_index(net, oldflp->oif);
        err = -ENODEV;
        if (dev_out == NULL)
            goto out;

        /* RACE: Check return value of inet_select_addr instead. */
        /*看设备是否是多地址*/
        if (__in_dev_get_rtnl(dev_out) == NULL) {
            dev_put(dev_out);
            goto out;    /* Wrong error code */
        }

        /*当目的地址是本地多播地址或广播地址,并且报文源地址为空时,找出出接口设备上IP地址scope小于RT_SCOPE_LINK的地址,并赋值,然后往cache中添加路由表项*/
        if (ipv4_is_local_multicast(oldflp->fl4_dst) ||
            oldflp->fl4_dst == htonl(0xFFFFFFFF)) {
            if (!fl.fl4_src)
                fl.fl4_src = inet_select_addr(dev_out, 0,
                                  RT_SCOPE_LINK);
            goto make_route;
        }
        /*目的地址是单播地址或空,源地址为空,那就选一个小于特定scope的IP地址*/
        if (!fl.fl4_src) {
            if (ipv4_is_multicast(oldflp->fl4_dst))
                fl.fl4_src = inet_select_addr(dev_out, 0,
                                  fl.fl4_scope);
            else if (!oldflp->fl4_dst)
                fl.fl4_src = inet_select_addr(dev_out, 0,
                                  RT_SCOPE_HOST);
        }
    }

    if (!fl.fl4_dst) {/*目的地址为空*/
        fl.fl4_dst = fl.fl4_src;
        if (!fl.fl4_dst)
            fl.fl4_dst = fl.fl4_src = htonl(INADDR_LOOPBACK);/*目的和源地址都是空,则赋值为lo接口地址*/
        if (dev_out)
            dev_put(dev_out);
        dev_out = net->loopback_dev;
        dev_hold(dev_out);
        fl.oif = net->loopback_dev->ifindex;
        res.type = RTN_LOCAL;
        flags |= RTCF_LOCAL;
        /*为发给本机的报文添加cache路由*/
        goto make_route;
    }

    /*一种情况是源地址目的地址不为空,目的地址为空,出接口为lo*/
    /*还有其他几种情况,就是目的地址和出接口必须对应*/
    if (fib_lookup(net, &fl, &res)) {
        res.fi = NULL;
        if (oldflp->oif) {
            /* Apparently, routing tables are wrong. Assume,
               that the destination is on link.

               WHY? DW.
               Because we are allowed to send to iface
               even if it has NO routes and NO assigned
               addresses. When oif is specified, routing
               tables are looked up with only one purpose:
               to catch if destination is gatewayed, rather than
               direct. Moreover, if MSG_DONTROUTE is set,
               we send packet, ignoring both routing tables
               and ifaddr state. --ANK


               We could make it even if oif is unknown,
               likely IPv6, but we do not.
             */

            if (fl.fl4_src == 0)
                fl.fl4_src = inet_select_addr(dev_out, 0,
                                  RT_SCOPE_LINK);
            res.type = RTN_UNICAST;
            /*没有查到路由,并且出接口不为lo*/
            goto make_route;
        }
        if (dev_out)
            dev_put(dev_out);
        err = -ENETUNREACH;
        goto out;
    }
    /*找到路由*/
    free_res = 1;

    /*路由指向本地*/
    if (res.type == RTN_LOCAL) {
        if (!fl.fl4_src)
            fl.fl4_src = fl.fl4_dst;
        if (dev_out)
            dev_put(dev_out);
        dev_out = net->loopback_dev;
        dev_hold(dev_out);
        fl.oif = dev_out->ifindex;
        if (res.fi)
            fib_info_put(res.fi);
        res.fi = NULL;
        flags |= RTCF_LOCAL;
        goto make_route;
    }

/*是否支持多路径路由*/
#ifdef CONFIG_IP_ROUTE_MULTIPATH
    if (res.fi->fib_nhs > 1 && fl.oif == 0)
        fib_select_multipath(&fl, &res);
    else
#endif
    if (!res.prefixlen && res.type == RTN_UNICAST && !fl.oif)
        fib_select_default(net, &fl, &res);

    if (!fl.fl4_src)
        fl.fl4_src = FIB_RES_PREFSRC(res);

    if (dev_out)
        dev_put(dev_out);
    dev_out = FIB_RES_DEV(res);
    dev_hold(dev_out);
    fl.oif = dev_out->ifindex;

/*往cache中添加相应的路由项*/
make_route:
    err = ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);


    if (free_res)
        fib_res_put(&res);
    if (dev_out)
        dev_put(dev_out);
out:    return err;
}

from langchain_ollama.llms import OllamaLLM from langchain.prompts import PromptTemplate from langchain.chains import LLMChain, ConversationChain from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE from langchain.chains.router.llm_router import RouterOutputParser from langchain.chains.router import LLMRouterChain, MultiPromptChain # 专家模型(提示词),链 llm = OllamaLLM(model="qwen2:7b") # 数 math_template = """ 你是一位伟大的数学家,知道所有的数学问题,你超越了高斯和莱布尼茨,我向你提问,问题如下: {input} """ math_prompt = PromptTemplate.format_prompt(math_template) math_chain = LLMChain(llm=llm, prompt=math_prompt,output_key="text") # 理 physics_template = """ 你是一位伟大的物理学家,知道所有的物理问题,你得过诺贝尔物理学奖,我向你提问,问题如下: {input} """ physics_prompt = PromptTemplate.format_prompt(physics_template) physics_chain = LLMChain(llm=llm, prompt=physics_prompt,output_key="text") # 化 chemistry_template = """ 你是一位伟大的化学家,知道所有的化学问题,你得过诺贝尔化学奖,我向你提问,问题如下: {input} """ chemistry_prompt = PromptTemplate.format_prompt(chemistry_template) chemistry_chain = LLMChain(llm=llm, prompt=chemistry_prompt,output_key="text") # 输入问题分析清楚有哪些可能和目的,目的链 destination_chain = {"math":math_chain,"physics":physics_chain,"chemistry":chemistry_chain} # 默认链 default_chain = ConversationChain(llm=llm, output_key="text") default_template = """ math:擅长回答数学问题 physics:擅长回答物理问题 chemistry:擅长回答化学问题 """ # 路由构建 router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format( destinations=default_template ) router_promt = PromptTemplate( template=router_template, input_variables=["input"], output_parser=RouterOutputParser() ) # 路由链 router_chain = LLMRouterChain.from_llm(llm=llm,prompt=router_promt) # 最终选择(链) multi_prompt_chain = MultiPromptChain( router_chain=router_chain, default_chain=default_chain, destination_chains=destination_chain, verbose=False ) res = multi_prompt_chain.invoke({"input":"介绍一下高斯"}) print(res) 上面是源代码 KeyError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_16272
最新发布
04-03
<think>好的,用户遇到了LangChain中的KeyError问题,具体是在多提示链路由配置时出现的。我需要先分析可能的原因,然后给出解决方案。 首先,根据用户提到的错误类型KeyError,通常是因为访问了字典中不存在的键。在LangChain的多提示链配置中,路由配置可能涉及到选择不同的提示模板,这时候如果路由逻辑返回的键不在现有的提示链名称中,就会导致KeyError。例如,用户可能配置了多个提示链,但路由函数返回了一个未被定义的链名称,或者链的名称拼写不一致。 接下来,查看用户提供的引用内容。引用[2]提到如果没有配置Token,会报错ValidationError,说明环境变量或参数配置的重要性。这可能提示用户检查是否正确定义了路由所需的参数或配置项。引用[3]讨论了数据结构和API协议的一致性,这可能对应到路由配置中的Schema是否正确,确保各个提示链的名称和路由逻辑中的预期一致。 可能的原因有: 1. 提示链名称拼写错误,导致路由返回的键无法匹配。 2. 路由函数逻辑错误,返回了不在可用链列表中的键。 3. 配置文件中链的名称与实际代码中的名称不一致。 解决方案方面,首先需要检查路由配置中的链名称是否正确,确保所有可能的返回值都在可用的提示链名称列表中。其次,验证路由函数的逻辑是否正确处理了所有情况,并返回有效的键。此外,检查配置文件或代码中是否存在拼写错误,比如大小写不一致或拼写错误。 最后,建议用户使用调试工具或打印中间变量来跟踪路由函数的输出,确认返回的键是否正确,并确保所有提示链都已正确注册和命名。如果问题依旧存在,可能需要审查整个多提示链的配置流程,确保每个步骤都符合LangChain的文档要求。</think>### LangChain KeyError 多提示链路由配置错误分析及解决方案 #### 错误原因分析 1. **路由键值不匹配** 当使用多提示链路由功能时,若`prompt_router`返回的`destination`值未在`destination_chains`定义的键名列表中找到匹配项,会触发`KeyError`错误[^3]。例如: ```python destination_chains = {"math": math_chain, "history": history_chain} # 若路由函数返回"science",但该键未在destination_chains中定义 ``` 2. **Schema定义不一致** 输入数据的字段名称与路由判断逻辑中使用的字段名称不一致。例如路由逻辑使用`query_type`字段进行判断,但输入数据中该字段名为`question_type`。 3. **默认链缺失** 未配置`default_chain`时,当所有路由条件均不满足,系统无法选择默认处理链。 #### 分步解决方案 1. **检查路由键值映射** 验证`destination_chains`的键名是否包含路由函数所有可能的返回值: ```python print("可用链名称:", list(destination_chains.keys())) # 输出示例: ['math', 'history'] ``` 2. **标准化Schema定义** 确保输入数据字段与路由判断逻辑字段完全一致: ```python class RouterInput(BaseModel): query: str = Field(description="用户问题") query_type: str = Field(description="问题类型") # 必须与路由判断字段名相同 ``` 3. **添加默认处理链** 配置默认链处理未匹配情况: ```python from langchain.chains import LLMChain default_chain = LLMChain(llm=llm, prompt=default_prompt) ``` 4. **增强路由可靠性** 在路由函数中添加异常捕获和日志记录: ```python def route_query(info): try: if "方程" in info["query"]: return "math" elif "历史事件" in info["query"]: return "history" else: return "default" except KeyError as e: print(f"缺少必要字段: {e}") return "default" ``` 5. **验证配置完整性** 使用单元测试验证配置: ```python test_cases = [ {"query": "解方程x²-5x+6=0", "query_type": "数学问题"}, {"query": "明朝建立时间", "query_type": "历史问题"}, {"query": "天气预报", "query_type": "其他"} ] for case in test_cases: result = router_chain.run(case) print(f"输入: {case['query']} => 路由结果: {result}") ``` #### 典型错误示例修正 **错误配置:** ```python destination_chains = { "math_problem": math_chain, # 键名包含下划线 "history_qa": history_chain } def route_query(info): if "数学" in info["query"]: return "math-problem" # 返回带连字符的键名 ``` **修正方案:** ```python destination_chains = { "math-problem": math_chain, # 统一使用连字符命名 "history-qa": history_chain } ``` #### 调试建议 1. 启用LangChain调试模式: ```python import langchain langchain.debug = True ``` 2. 检查路由中间结果: ```python print("路由输入:", router_input) print("路由输出:", destination) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值