首页 > 其他分享 >使用Microsoft.SemanticKernel基于本地运行的Ollama大语言模型实现Agent调用函数

使用Microsoft.SemanticKernel基于本地运行的Ollama大语言模型实现Agent调用函数

时间:2024-06-21 09:33:59浏览次数:9  
标签:kernel 模型 Agent public 调用函数 var new null Microsoft

大语言模型的发展日新月异,记得在去年这个时候,函数调用还是gpt-4的专属。到今年本地运行的大模型无论是推理能力还是文本的输出质量都已经非常接近gpt-4了。而在去年gpt-4尚未发布函数调用时,智能体框架的开发者们依赖构建精巧的提示词实现了gpt-3.5的函数调用。目前在本机运行的大模型,基于这一套逻辑也可以实现函数式调用,今天我们就是用本地运行的大模型来实现这个需求。从测试的效果来看,本地大模型对于简单的函数调用成功率已经非常高了,但是受限于本地机器的性能,调用的时间还是比较长。如果有NVIDIA显卡的CUDA环境,质量应该会好很多,今天就以大家都比较熟悉的LLAMA生态作为起点,基于阿里云开源的千问7B模型的量化版作为基座通过C#和SemanticKernel来实现函数调用的功能。

基本调用逻辑参考这张图:

首先我们需要在本机(windows系统)安装Ollama作为LLM的API后端。访问https://ollama.com/,选择Download。选择你需要的版本即可,windows用户请选择Download for Windows。下载完成后,无脑点击下一步下一步即可安装完毕。

 安装完毕后,打开我们的PowerShell即可运行大模型,第一次加载会下载模型文件到本地磁盘,会比较慢。运行起来后就可以通过控制台和模型进行简单的对话,这里我们以阿里发布的千问2:7b为例。执行以下命令即可运行起来:

ollama run qwen2:7b

 接着我们使用ctrl+D退出对话框,并执行ollama serve,看看服务器是否运行起来了,正常情况下会看到11434这个端口已经运行起来了。接下来我们就可以进入到编码阶段

 首先我们创建一个.net8.0的的控制台,接着我们引入三个必要的包

dotnet add package Microsoft.SemanticKernel --version 1.15.0
dotnet add package Newtonsoft.Json --version 13.0.3
dotnet add package OllamaSharp --version 2.0.1

SemanticKernel是我们主要的代理运行框架,OllamaSharp是一个简单的面向Ollama本地API服务的请求封装。避免我们手写httpclient来与本地服务器交互。我这里安装了Newtonsoft.Json来替代system.text.json,主要是用于后期需要一些序列化模型回调来使用,因为模型的回调json可能不是特别标准,使用system.text.json容易导致转义失败。

接下来就是编码阶段,首先我们定义一个函数,这个函数是后面LLM会用到的函数,简单的定义如下:

public class FunctionTest
{
    [KernelFunction, Description("获取城市的天气状况")]
    public object GetWeather([Description("城市名称")] string CityName, [Description("查询时段,值可以是[白天,夜晚]")] string DayPart)
    {
        return new { CityName, DayPart, CurrentCondition = "多云", LaterCondition = "阴", MinTemperature = 19, MaxTemperature = 23 };
    }
}

这里的KernelFunction和Description特性都是必要的,用于SemanticKernel查询到对应的函数并封装处对应的元数据。

接着我们需要自定义一个继承自接口IChatCompletionService的实现,因为SemanticKernel是基于openai的gpt系列设计的框架,所以要和本地模型调用,我们需要设置独立的ChatCompletionService来让SemanticKernel和本机模型API交互。这里我们主要需要实现的函数是GetChatMessageContentsAsync。因为函数调用我们需要接收到模型完整的回调用于转换json,所以流式传输这里用不上。

public class CustomChatCompletionService : IChatCompletionService
{
    public IReadOnlyDictionary<string, object?> Attributes => throw new NotImplementedException();

    public Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }

    public IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
    {
        throw new NotImplementedException();
    }
}

接下来我们需要定义一个SemanticKernel的实例,这个实例会伴随本次调用贯穿全程。SemanticKernel使用了简单的链式构建。基本代码如下:

var builder = Kernel.CreateBuilder();
//这里我们需要增加刚才我们定义的实例CustomChatCompletionService,有点类似IOC的设计
builder.Services.AddKeyedSingleton<IChatCompletionService>("ollamaChat", new CustomChatCompletionService());
//这里我们需要插入之前定义的插件
builder.Plugins.AddFromType<FunctionTest>();
var kernel = builder.Build();

可以看到基本的构建链式调用代码部分还是比较简单的,接下来就是调用的部分,这里主要的部分就是将LLM可用的函数插入到系统提示词,来引导LLM去调用特定函数:

//定义一个对话历史
ChatHistory history = [];
//获取刚才定义的插件函数的元数据,用于后续创建prompt
var plugins = kernel.Plugins.GetFunctionsMetadata();
//生成函数调用提示词,引导模型根据用户请求去调用函数
var functionsPrompt = CreateFunctionsMetaObject(plugins);
//创建系统提示词,插入刚才生成的提示词
var prompt = $"""
                  You have access to the following functions. Use them if required:
                  {functionsPrompt}
                  If function calls are used, ensure the output is in JSON format; otherwise, output should be in text format.
                  """;
//添加系统提示词
history.AddSystemMessage(prompt);
//创建一个对话服务实例
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
//添加用户的提问
history.AddUserMessage(question);
//链式执行kernel
var result = await chatCompletionService.GetChatMessageContentAsync(
    history,
    executionSettings: null,
    kernel: kernel);
//打印回调内容
Console.WriteLine($"Assistant> {result}");

在这里我们可以debug看看生成的系统提示词细节:

 当代码执行到GetChatMessageContentAsync这里时,就会跳转到我们的CustomChatCompletionService的GetChatMessageContentsAsync函数,在这里我们需要进行ollama的调用来达成目的。

这里比较核心的部分就是将LLM回调的内容使用JSON序列化来检测是否涉及到函数调用,简单来讲由于类似qwen这样没有专门针对function calling专项微调过的(glm-4-9b原生支持function calling)模型,其function calling并不是每次都能准确的回调,所以这里我们需要对回调的内容进行反序列化和信息抽取,确保模型的调用符合回调函数的格式标准。具体代码如下

public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
    GetDicSearchResult(kernel);
    var prompt = HistoryToText(chatHistory);
    var ollama = new OllamaApiClient("http://127.0.0.1:11434", "qwen2:7b");
    var chat = new Chat(ollama, _ => { });
    sw.Start();
    var history = (await chat.Send(prompt, CancellationToken.None)).ToArray();
    sw.Stop();
    Console.WriteLine($"调用耗时:{Math.Round(sw.Elapsed.TotalSeconds,2)}秒");
    var last = history.Last();
    var chatResponse = last.Content;
    try
    {
        JToken jToken = JToken.Parse(chatResponse);
        jToken = ConvertStringToJson(jToken);
        var searchs = DicSearchResult.Values.ToList();
        if (TryFindValues(jToken, ref searchs))
        {
            var firstFunc = searchs.First();
            var funcCallResult = await firstFunc.KernelFunction.InvokeAsync(kernel, firstFunc.FunctionParams);
            chatHistory.AddMessage(AuthorRole.Assistant, chatResponse);
            chatHistory.AddMessage(AuthorRole.Tool, funcCallResult.ToString());
            return await GetChatMessageContentsAsync(chatHistory, kernel: kernel);
        }
        else
        {

        }
    }
    catch(Exception e)
    {

    }
    return new List<ChatMessageContent> { new ChatMessageContent(AuthorRole.Assistant, chatResponse) };
}

这里我们首先使用SemanticKernel的kernel的函数元数据通过GetDicSearchResult构建了一个字典,这部分代码如下:

public static Dictionary<string, SearchResult> DicSearchResult = new Dictionary<string, SearchResult>();
public static void GetDicSearchResult(Kernel kernel)
{
    DicSearchResult = new Dictionary<string, SearchResult>();
    foreach (var functionMetaData in kernel.Plugins.GetFunctionsMetadata())
    {
        string functionName = functionMetaData.Name;
        if (DicSearchResult.ContainsKey(functionName))
            continue;
        var searchResult = new SearchResult
        {
            FunctionName = functionName,
            KernelFunction = kernel.Plugins.GetFunction(null, functionName)
        };
        functionMetaData.Parameters.ToList().ForEach(x => searchResult.FunctionParams.Add(x.Name, null));
        DicSearchResult.Add(functionName, searchResult);
    }
}

接着使用HistoryToText将历史对话信息组装成一个单一的prompt发送给模型,大概会组装成如下内容,其实就是系统提示词+用户提示词组合成一个单一文本:

 接着我们使用OllamaSharp的SDK提供的OllamaApiClient发送信息给模型,等待模型回调后,从模型回调的内容中抽取chatResponse,接着我们需要通过一个try catch来处理,当chatResponse可以被正确的解析成标准JToken后,说明模型的回调是一段json,否则会抛出异常,代表模型输出的是一段文本。如果是文本,我们就直接返回模型输出的内容,如果是json则继续向下处理,通过一个TryFindValues函数从模型中抽取我们所需要的回调函数名、参数,并赋值到一个临时变量中。最后通过SemanticKernel的KernelFunction的InvokeAsync进行真正的函数调用,获取到函数的回调内容,接着我们需要将模型的原始输出和回调内容一同添加到chatHistory后,再度递归发起GetChatMessageContentsAsync调用,这一次模型就会拿到前一次回调的城市天气内容来进行回答了。

 第二次回调前的prompt如下,可以看到模型的输出虽然是json,但是并没有规范的格式,不过使用我们的抽取函数还是获取到了需要的信息,从而正确的构建了底部的回调:

 通过这一轮回调再次喂给llm,llm就可以正确的输出结果了:

 以上就是整个文章的内容了,可以看到在这个过程中我们主要做的工作就是通过系统提示词诱导模型输出回调函数json,解析json获取参数,调用本地的函数后再次回调给模型,这个过程其实有点类似的RAG,只不过RAG是通过用户的提示词直接进行近似度搜索获取到近似度相关的文本组合到系统提示词,而函数调用给了模型更大的自由度,可以让模型自行决策是否调用函数,从而使本地Agent代理可以实现诸如帮你操控电脑,打印文件,编写邮件等等助手性质的功能。

下面是核心部分的代码,请大家自取

program.cs:

using ConsoleApp4;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel;
using Newtonsoft.Json.Linq;




await Ollama("我想知道西安今天晚上的天气情况");



async Task Ollama(string question)
{
    Console.WriteLine($"User> {question}");
    var builder = Kernel.CreateBuilder();
    //这里我们需要增加刚才我们定义的实例CustomChatCompletionService,有点类似IOC的设计
    builder.Services.AddKeyedSingleton<IChatCompletionService>("ollamaChat", new CustomChatCompletionService());
    //这里我们需要插入之前定义的插件
    builder.Plugins.AddFromType<FunctionTest>();
    var kernel = builder.Build();
    //定义一个对话历史
    ChatHistory history = [];
    //获取刚才定义的插件函数的元数据,用于后续创建prompt
    var plugins = kernel.Plugins.GetFunctionsMetadata();
    //生成函数调用提示词,引导模型根据用户请求去调用函数
    var functionsPrompt = CreateFunctionsMetaObject(plugins);
    //创建系统提示词,插入刚才生成的提示词
    var prompt = $"""
                      You have access to the following functions. Use them if required:
                      {functionsPrompt}
                      If function calls are used, ensure the output is in JSON format; otherwise, output should be in text format.
                      """;
    //添加系统提示词
    history.AddSystemMessage(prompt);
    //创建一个对话服务实例
    var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
    //添加用户的提问
    history.AddUserMessage(question);
    //链式执行kernel
    var result = await chatCompletionService.GetChatMessageContentAsync(
        history,
        executionSettings: null,
        kernel: kernel);
    //打印回调内容
    Console.WriteLine($"Assistant> {result}");
}
static JToken? CreateFunctionsMetaObject(IList<KernelFunctionMetadata> plugins)
{
    if (plugins.Count < 1) return null;
    if (plugins.Count == 1) return CreateFunctionMetaObject(plugins[0]);

    JArray promptFunctions = [];
    foreach (var plugin in plugins)
    {
        var pluginFunctionWrapper = CreateFunctionMetaObject(plugin);
        promptFunctions.Add(pluginFunctionWrapper);
    }

    return promptFunctions;
}
static JObject CreateFunctionMetaObject(KernelFunctionMetadata plugin)
{
    var pluginFunctionWrapper = new JObject()
        {
            { "type", "function" },
        };

    var pluginFunction = new JObject()
        {
            { "name", plugin.Name },
            { "description", plugin.Description },
        };

    var pluginFunctionParameters = new JObject()
        {
            { "type", "object" },
        };
    var pluginProperties = new JObject();
    foreach (var parameter in plugin.Parameters)
    {
        var property = new JObject()
            {
                { "type", parameter.ParameterType?.ToString() },
                { "description", parameter.Description },
            };

        pluginProperties.Add(parameter.Name, property);
    }

    pluginFunctionParameters.Add("properties", pluginProperties);
    pluginFunction.Add("parameters", pluginFunctionParameters);
    pluginFunctionWrapper.Add("function", pluginFunction);

    return pluginFunctionWrapper;
}
public class FunctionTest
{
    [KernelFunction, Description("获取城市的天气状况")]
    public object GetWeather([Description("城市名称")] string CityName, [Description("查询时段,值可以是[白天,夜晚]")] string DayPart)
    {
        return new { CityName, DayPart, CurrentCondition = "多云", LaterCondition = "阴", MinTemperature = 19, MaxTemperature = 23 };
    }
}

CustomChatCompletionService.cs:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Newtonsoft.Json.Linq;
using OllamaSharp;
using System.Diagnostics;
using System.Text;

namespace ConsoleApp4
{
    public class CustomChatCompletionService : IChatCompletionService
    {
        public static Dictionary<string, SearchResult> DicSearchResult = new Dictionary<string, SearchResult>();
        public static void GetDicSearchResult(Kernel kernel)
        {
            DicSearchResult = new Dictionary<string, SearchResult>();
            foreach (var functionMetaData in kernel.Plugins.GetFunctionsMetadata())
            {
                string functionName = functionMetaData.Name;
                if (DicSearchResult.ContainsKey(functionName))
                    continue;
                var searchResult = new SearchResult
                {
                    FunctionName = functionName,
                    KernelFunction = kernel.Plugins.GetFunction(null, functionName)
                };
                functionMetaData.Parameters.ToList().ForEach(x => searchResult.FunctionParams.Add(x.Name, null));
                DicSearchResult.Add(functionName, searchResult);
            }
        }
        public IReadOnlyDictionary<string, object?> Attributes => throw new NotImplementedException();
        static Stopwatch sw = new Stopwatch();
        public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
        {
            GetDicSearchResult(kernel);
            var prompt = HistoryToText(chatHistory);
            var ollama = new OllamaApiClient("http://127.0.0.1:11434", "qwen2:7b");
            var chat = new Chat(ollama, _ => { });
            sw.Start();
            var history = (await chat.Send(prompt, CancellationToken.None)).ToArray();
            sw.Stop();
            Console.WriteLine($"调用耗时:{Math.Round(sw.Elapsed.TotalSeconds,2)}秒");
            var last = history.Last();
            var chatResponse = last.Content;
            try
            {
                JToken jToken = JToken.Parse(chatResponse);
                jToken = ConvertStringToJson(jToken);
                var searchs = DicSearchResult.Values.ToList();
                if (TryFindValues(jToken, ref searchs))
                {
                    var firstFunc = searchs.First();
                    var funcCallResult = await firstFunc.KernelFunction.InvokeAsync(kernel, firstFunc.FunctionParams);
                    chatHistory.AddMessage(AuthorRole.Assistant, chatResponse);
                    chatHistory.AddMessage(AuthorRole.Tool, funcCallResult.ToString());
                    return await GetChatMessageContentsAsync(chatHistory, kernel: kernel);
                }
                else
                {

                }
            }
            catch(Exception e)
            {

            }
            return new List<ChatMessageContent> { new ChatMessageContent(AuthorRole.Assistant, chatResponse) };
        }
        JToken ConvertStringToJson(JToken token)
        {
            if (token.Type == JTokenType.Object)
            {
                // 遍历对象的每个属性
                JObject obj = new JObject();
                foreach (JProperty prop in token.Children<JProperty>())
                {
                    obj.Add(prop.Name, ConvertStringToJson(prop.Value));
                }
                return obj;
            }
            else if (token.Type == JTokenType.Array)
            {
                // 遍历数组的每个元素
                JArray array = new JArray();
                foreach (JToken item in token.Children())
                {
                    array.Add(ConvertStringToJson(item));
                }
                return array;
            }
            else if (token.Type == JTokenType.String)
            {
                // 尝试将字符串解析为 JSON
                string value = token.ToString();
                try
                {
                    return JToken.Parse(value);
                }
                catch (Exception)
                {
                    // 解析失败时返回原始字符串
                    return token;
                }
            }
            else
            {
                // 其他类型直接返回
                return token;
            }
        }
        bool TryFindValues(JToken token, ref List<SearchResult> searches)
        {
            if (token.Type == JTokenType.Object)
            {
                foreach (var child in token.Children<JProperty>())
                {
                    foreach (var search in searches)
                    {
                        if (child.Value.ToString().ToLower().Equals(search.FunctionName.ToLower()) && search.SearchFunctionNameSucc != true)
                            search.SearchFunctionNameSucc = true;
                        foreach (var par in search.FunctionParams)
                        {
                            if (child.Name.ToLower().Equals(par.Key.ToLower()) && par.Value == null)
                                search.FunctionParams[par.Key] = child.Value.ToString().ToLower();
                        }
                    }
                    if (searches.Any(x => x.SearchFunctionNameSucc == false || x.FunctionParams.Any(x => x.Value == null)))
                        TryFindValues(child.Value, ref searches);
                }
            }
            else if (token.Type == JTokenType.Array)
            {
                foreach (var item in token.Children())
                {
                    if (searches.Any(x => x.SearchFunctionNameSucc == false || x.FunctionParams.Any(x => x.Value == null)))
                        TryFindValues(item, ref searches);
                }
            }
            return searches.Any(x => x.SearchFunctionNameSucc && x.FunctionParams.All(x => x.Value != null));
        }
        public virtual string HistoryToText(ChatHistory history)
        {
            StringBuilder sb = new();
            foreach (var message in history)
            {
                if (message.Role == AuthorRole.User)
                {
                    sb.AppendLine($"User: {message.Content}");
                }
                else if (message.Role == AuthorRole.System)
                {
                    sb.AppendLine($"System: {message.Content}");
                }
                else if (message.Role == AuthorRole.Assistant)
                {
                    sb.AppendLine($"Assistant: {message.Content}");
                }
                else if (message.Role == AuthorRole.Tool)
                {
                    sb.AppendLine($"Tool: {message.Content}");
                }
            }
            return sb.ToString();
        }
        public IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
        {
            throw new NotImplementedException();
        }
    }
    public class SearchResult
    {
        public string FunctionName { get; set; }
        public bool SearchFunctionNameSucc { get; set; }
        public KernelArguments FunctionParams { get; set; } = new KernelArguments();
        public KernelFunction KernelFunction { get; set; }
    }
}

 

标签:kernel,模型,Agent,public,调用函数,var,new,null,Microsoft
From: https://www.cnblogs.com/gmmy/p/18259238

相关文章

  • 计算机科学:微软系统芯片 (Microsoft SoC) 探讨
    引言近年来,随着硬件技术的飞速发展,系统芯片(SystemonChip,SoC)成为了推动各类智能设备性能提升的关键技术。微软作为全球科技巨头,积极投身于SoC的研发,以期在移动设备、游戏主机和数据中心等领域取得突破。接下来将详细探讨微软在SoC方面的进展及其对行业的影响。什......
  • Agent开发+大模型微调,真的能让IT工程师薪资再起飞一次吗?
    前言短短一年间,LLaMA,Mistral,ChatGLM,Grok等等大模型纷纷开源,相关开发与微调技术也在迅速迭代,RAG,LangChain,DeepSpeed,Megatron-LLM等框架的风靡,LoRA,QLoRA,FlashAttention等微调技术的发展,模型压缩,模型蒸馏,模型部署等全工作流的优化,大模型的技术发展可以说是日新月异,几乎每天都......
  • Microsoft.AspNetCore.Identity 的使用记录
    使用Cookie,在发起请求时从浏览器附加Cookie的示例图:请求后自动生成Cookie,缓存保存于浏览器中以后每次发起请求时浏览器都会自动为请求附加其缓存的Cookies使用访问令牌bearertoken,在发起请求时附加了Authorization请求头,示例注意,如果手动传参,Head中Authorizaion的值要......
  • 智能体Agents:开启AI助手的无限可能
    1.什么是Agents?        Agents是一个具有智能功能的智能体,它使用LLM和工具来执行任务。        Agents核心思想是使用LLM来选择要采取的一系列动作。在链式结构中,一系列动作是硬编码的(在代码中)。在Agents中,使用语言模型作为推理引擎来确定要采取的......
  • 构建基于 LlamaIndex 的RAG AI Agent
    IbuiltacustomAIagentthatthinksandthenacts.Ididn'tinventitthough,theseagentsareknownasReActAgentsandI'llshowyouhowtobuildoneyourselfusingLlamaIndexinthistutorial.我构建了一个自定义的AI智能体,它能够思考然后行动。不过,这并不......
  • 揭秘大模型AI Agent:人工智能的新纪元
    什么是AIAgent(LLMAgent)“Agent”是一个跨学科的概念,涵盖了哲学、计算机科学、经济学、生物学等多个领域。尽管定义和应用范围各异,代理的核心特征在于其自主性、感知和决策能力,以及目标导向的行动能力。理解代理在不同领域中的具体应用和特征,有助于更全面地认识和利用这......
  • CogVLM/CogAgent环境搭建&推理测试-CSDN博客
    引子最近在关注多模态大模型,之前4月份的时候关注过CogVLM(https://blog.csdn.net/zzq1989_/article/details/138337071?spm=1001.2014.3001.5501)。模型整体表现还不错,不过不支持中文。智谱AI刚刚开源了GLM-4大模型,套餐里面包含了GLM-4V-9B大模型,模型基于GLM-4-9B的多模态模型GL......
  • Microsoft PPP CHAP Extensions, Version 2 rfc笔记
    之前在网上阅读过mschapv2的协议流程,并记录到博客随便中peap-mschapv2认证流程chap mschappap协议简介,mschapv1mschapv2区别8021x认证客户端都是依赖于操作系统,但是不可控,目前准备自己编写8021x客户端,所以来看看itefrfc文档了主要文档有:rfc2759 ......
  • XTuner 微调 LLM:1.8B、多模态、Agent
    InternLM2实战营第二期第四节课《XTuner微调LLM:1.8B、多模态、Agent》官网地址:书生·浦语官网课程录播视频链接地址:XTuner微调LLM:1.8B、多模态、Agent_bilibiliXtuner地址:Xtunertips:建议这节课大家仔细听,可以让你快速了解大模型预训练的一些概念和模型内部实际......
  • LLaMA Factory 实战:单卡 3 小时训练专属大模型 Agent
    节前,我们星球组织了一场算法岗技术&面试讨论会,邀请了一些互联网大厂朋友、参加社招和校招面试的同学.针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。汇总合集:《大模型面试宝典》(2024版)发......