☰
AI遇到两个同名联系人,业务表单该选哪一个?
2026/10/30 4:54:10 网站建设 项目流程

AI把“借给同名人员”填进业务表单,页面上的姓名看起来正确,保存的却可能是另一个ID。可靠做法是先在当前允许的数据范围里检索,再按零个、一个、多个候选分别处理;用户确认后仍按ID重新校验,不能让模型替你猜一个身份。

名称是输入,ID才是业务引用

用户说的是名称,系统保存的是稳定对象引用。名称可能重复、改名或停用,房间也可能在不同楼层同名。模型从句子提取“姓名”和“房间”可以作为检索条件,但不应凭自然语言生成一个看似合理的内部ID。

候选情况表单处理
零个保留未确定状态,提示换关键词或人工选择
一个可提出这一对象供核对;写入前仍校验有效性
多个展示可区分的必要信息,让用户确认

展示信息要够消歧,又不要顺带公开无关手机号等个人资料。独立示例使用合成ID、姓名与租户标识,不包含真实人员。

先限制可见范围,再做名称匹配

示例里t1有两个同名对象,t2还有一个同名对象。先按租户和启用状态过滤,t1的候选只能有两个;若先把所有租户的候选交给模型,再要求它别选错,已经越过了数据可见边界。

EF Core全局查询过滤文档说明了用当前租户上下文过滤查询的方式。实际业务还可能有对象级权限;单个TenantId条件不能自动证明所有权限均已处理。

用户确认之后,再查一次对象是否仍可用

候选展示到用户点击之间,对象可能被停用或权限发生变化。独立示例返回明确的三态结果:NotFound不产生候选票据,Ambiguous返回多个候选,Resolved表示唯一命中。后两种结果都带本次内存候选票据,绑定允许范围、候选ID集合和过期时间。名称不直接落成业务ID,确认通过才返回最终ID。

确认时先核票据范围与过期,再核选中的ID是否在原候选集合里,最后重查对象是否仍启用。伪造一个未提供过的ID会返回NOT_OFFERED;切换范围返回SCOPE_CHANGED;停用返回CANDIDATE_UNAVAILABLE;超期返回EXPIRED;成功后再次确认返回TICKET_CONSUMED。示例在进程内锁内消费票据,没有实现真实会话、签名或数据库事务;生产还需把票据、当前权限与业务写入放入可验证的提交边界。

AssetaGuard现有名称解析代码可见唯一命中、多候选和无候选分支,以及按候选编码或用户名重新查找的路径。本文另加的确认时重查是独立示例设计,不宣称现有所有表单都已有候选过期处理或完整对象级权限。

用输入变化检查它有没有真的消歧

本次运行检查三态结果、排除另一租户、五类拒绝码,以及确认成功后返回原候选的合成ID。程序没有调用真实模型,所以结果只验证解析与确认规则,不能证明某个模型的自然语言准确率。

先给自己的表单放入两个合成同名对象,看界面是否要求选择,再测试确认前停用或撤权的情况。

完整独立示例

以下程序使用合成数据,不调用产品接口,不需要账号、数据库或模型服务。准备 .NET 8或兼容SDK,将项目文件与程序放入同一目录。运行命令为dotnet run --project . -- example-output,输出文件写入指定目录。

<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup></Project>
usingSystem.Text.Json;usingSystem.Globalization;varroot=args.Length>0?Path.GetFullPath(args[0]):Path.Combine(Environment.CurrentDirectory,"example-output");Directory.CreateDirectory(root);varresults=newDictionary<string,object>();CheckResolution();File.WriteAllText(Path.Combine(root,"results.json"),JsonSerializer.Serialize(results,newJsonSerializerOptions{WriteIndented=true}));voidRequire(boolvalue,stringcode){if(!value)thrownewInvalidOperationException(code);}voidCheckResolution(){varpeople=newList<Person>{new("u1","同名人员","t1",true),new("u2","同名人员","t1",true),new("u3","同名人员","t2",true)};vartickets=newDictionary<string,CandidateTicket>();varnow=DateTimeOffset.Parse("2026-01-01T00:00:00Z",CultureInfo.InvariantCulture);varcounter=0;varmissing=Resolve("不存在","t1");Require(missing.Status==ResolveStatus.NotFound&&missing.TicketId==null,"ZERO_MATCH");varambiguous=Resolve("同名人员","t1");Require(ambiguous.Status==ResolveStatus.Ambiguous&&ambiguous.CandidateIds.SequenceEqual(new[]{"u1","u2"}),"SCOPE_BEFORE_MATCH");Require(Confirm(ambiguous.TicketId!,"u3","t1",now).Code=="NOT_OFFERED","FORGED_ID_ACCEPTED");Require(Confirm(ambiguous.TicketId!,"u1","t2",now).Code=="SCOPE_CHANGED","CROSS_SCOPE");people[0]=people[0]with{Active=false};Require(Confirm(ambiguous.TicketId!,"u1","t1",now).Code=="CANDIDATE_UNAVAILABLE","STALE_CANDIDATE");Require(Confirm(ambiguous.TicketId!,"u2","t1",now.AddMinutes(6)).Code=="EXPIRED","EXPIRED_ACCEPTED");varunique=Resolve("同名人员","t1");Require(unique.Status==ResolveStatus.Resolved&&unique.CandidateIds.Single()=="u2","ONE_MATCH");varconfirmed=Confirm(unique.TicketId!,"u2","t1",now);Require(confirmed.Code=="CONFIRMED"&&confirmed.EntityId=="u2","CONFIRM_FAILED");Require(Confirm(unique.TicketId!,"u2","t1",now).Code=="TICKET_CONSUMED","REPLAY_ACCEPTED");results["A4"]=new{passed=true,resultContract=new[]{"NotFound","Ambiguous","Resolved"},errors=new[]{"NOT_OFFERED","SCOPE_CHANGED","CANDIDATE_UNAVAILABLE","EXPIRED","TICKET_CONSUMED"},confirmedEntity=confirmed.EntityId,modelCalled=false};Console.WriteLine("PASS A4 resultStates=3 rejectionCodes=5 confirmed=u2 modelCalled=false");NameResolutionResolve(stringname,stringscope){varids=people.Where(x=>x.Tenant==scope&&x.Active&&x.Name==name).Select(x=>x.Id).ToArray();if(ids.Length==0)returnnew(ResolveStatus.NotFound,ids,null);varticketId="ticket-"+++counter;tickets[ticketId]=new(scope,ids,now.AddMinutes(5),false);returnnew(ids.Length==1?ResolveStatus.Resolved:ResolveStatus.Ambiguous,ids,ticketId);}ConfirmationConfirm(stringticketId,stringid,stringscope,DateTimeOffsetat){// 此票据只在本进程内存保存;真实服务须绑定会话/权限,并原子消费。lock(tickets){if(!tickets.TryGetValue(ticketId,outvarticket))returnnew("TICKET_NOT_FOUND",null);if(ticket.Scope!=scope)returnnew("SCOPE_CHANGED",null);if(ticket.Consumed)returnnew("TICKET_CONSUMED",null);if(at>=ticket.ExpiresAt)returnnew("EXPIRED",null);if(!ticket.CandidateIds.Contains(id))returnnew("NOT_OFFERED",null);if(!people.Any(x=>x.Id==id&&x.Tenant==scope&&x.Active))returnnew("CANDIDATE_UNAVAILABLE",null);tickets[ticketId]=ticketwith{Consumed=true};returnnew("CONFIRMED",id);}}}publicrecordPerson(stringId,stringName,stringTenant,boolActive);publicenumResolveStatus{NotFound,Resolved,Ambiguous}publicrecordNameResolution(ResolveStatusStatus,string[]CandidateIds,string?TicketId);publicrecordCandidateTicket(stringScope,string[]CandidateIds,DateTimeOffsetExpiresAt,boolConsumed);publicrecordConfirmation(stringCode,string?EntityId);

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询